From 852f2a6343518919e5ca8d3c1bbcab9f493e3cd8 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Wed, 17 Jan 2024 16:26:06 +0100 Subject: [PATCH] Squashed 'external/sdl/SDL/' changes from 399bc709b7..0d7df16812 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0d7df16812 Timers are a required platform feature 518b070aa9 Fixed drop event coordinate conversion 3ca8cee874 Sync SDL3 wiki -> header 8bf74280e0 docs: Note SDL_UpdateWindowSurfaceRects can update beyond specified areas. 9408299bad Set the number of audio devices to 0 if audio hasn't been initialized be0ba78c56 Convert the coordinates in drop events as well (thanks @Dragon-Baroque!) e6c8872fdc Fixed bug #7614: Segmentation Fault in SDL_BlitSurface ce0e0675de Fixed bug #8301 - Software renderer draws long lines incorrectly / SDL_RENDERLINEMETHOD_GEOMETRY 6e48d4532c Added raw input device handle for WM_INPUT mouse button handling cf5e0637b5 Touchpads already have their right/left buttons swapped in raw input fb2d7ed83e Restore window minimum and maximum size if it is recreated 915afae514 Remove force using one thread 64a3e2d17d Remove ps2_driver from workflow 464a41411e Sync SDL3 wiki -> header 4f3d4bd110 wayland: Add the ability to import and wrap external surfaces 99f6bcf504 Sync SDL3 wiki -> header 3a4ac15a27 Make Xbox GDK code public (and fix some GDK code rot) (#8844) 649556befa win32: Let windows manage the floating state unless explicitly overridden d4a9748740 win32: Fix test failures 101f903bb1 testgeometry: allow to use arrows to move the triangle 4033a0a83b Prevent ASAN warning: like SDL_triangle.c:305:30: runtime error: left shift of negative value -672 (even if the value was correctly computed) f0b9c7f0f0 Let Windows track floating window state 98be55894e Moved window state change handling from WM_WINDOWPOSCHANGING to WM_WINDOWPOSCHANGED 7efeb36131 Pass the frame DPI to WIN_AdjustWindowRectForHWND() e4ee1cade7 Revert "SDL_windowsevents.c: fixed -Werror=unused-variable" f8cce4ead4 SDL_windowsevents.c: fixed -Werror=unused-variable 277fded7ba Refactor AdjustWindowRectEx() into WIN_AdjustWindowRectForHWND() 312160935d Removed deprecated use of __IPHONEOS__ (thanks @Dragon-Baroque!) 483155bbf9 Fixed borderless window client area calculation cf0d1d7234 Fixed ASAN warning, runtime error: left shift of 160 by 24 places cannot be represented in type 'int' 0c6b070761 Capture debug macros off by default b5bc64aa55 Fixed pause key on Windows (thanks Mikhail!) d766f68cb3 Fix compile error with XInputOnGameInput.h ec2159d492 tests: Clean up the window creation properties in the Wayland custom surface example 9a77813df0 WinMain is specified by the Win32 API as having extern "C" linkage 6407e0cc37 Added attribution for GIP protocol handling (thanks @medusalix!) 3a219caf3d wayland: Restore accidentally removed line of code ccae9c1ef6 Only initialize audio drivers that have been requested 590d0bec6f Revert "Make sure new windows have mouse and keyboard focus if there's no windowing system" 5948ea997f Make sure new windows have mouse and keyboard focus if there's no windowing system 05d18aab1c Sync SDL3 wiki -> header 4b6df89238 wayland: Add a property to allow creation of a wl_egl_window object even if OpenGL is not enabled f7dd0f9491 wayland: Allow the creation of roleless window surfaces for custom application use 4417250d0d wayland: Remove the registry from the window properties 392796e49c wayland: Eliminate redundant protocol checks 82f2c4d581 render: Renamed SDL_GetTextureRenderer to SDL_GetRendererFromTexture. 95066ce2a0 Sync SDL3 wiki -> header df438a3170 opengl: OpenGL renderers need to support texture target in properties. 8e7c0b34d7 test: If SDL_CreateRenderer() fails, say why 022ff075b9 test: When listing test-cases, say which ones are disabled 44adab7dfd Sync SDL3 wiki -> header 059fb560ba gamepad: Clarify range and direction of axes 4942027117 Sync SDL3 wiki -> header e056f52f7d include: Remove string literals from properties documentation. e8c595af5c Sync SDL3 wiki -> header adef35b9ec include: Attempt to make new properties documentation wiki bridge friendly. 4ffec098b7 Sync SDL3 wiki -> header 9bc7cfc755 render: Added SDL_GetTextureRenderer(). 7eae08cfc4 Removed SDL_GL_BindTexture() and SDL_GL_UnbindTexture() 4d5bffc323 Sync SDL3 wiki -> header 1a13dae219 Added constant definitions for SDL properties 3deefa6b43 Updated documentation for SDL_HINT_SHUTDOWN_DBUS_ON_QUIT 2348e8b6a2 Add hint to make SDL handle dbus_shutdown() d3daba791a Don't try to send the PS third-party feature request to the Logitech G815 keyboard 014a63b4b5 Renamed ShowTextInput/HideTextInput to ShowScreenKeyboard/HideScreenKeyboard on Android 3a9a52fe6c updateKeyboard should use the SDL window's screen instead of the view window's screen, which may be nil. e100992c17 Added mapping for the RX Gamepad, e.g. Pandora Box 7 139a0931a3 Fix memory barriers on ARMv5 21c80ac843 Added a practical example of confirm/cancel gamepad button handling 787a15f760 Fix Mac child windows that are created hidden showing if their parent window is shown d6b1fc9576 Add SDL_MAC_REGISTER_ACTIVATION_HANDLERS hint to allow disabling the normal application startup/activation/deactivation handlers 7c5e694022 Ignore a new warning in Visual Studio 2022 73d02184d7 docs: Add Wayland to the SysWM migration example c03c01e9b2 Make sure we get mouse events as soon as possible 5cbdeab799 Rename SDL_mslibc_x64.asm -> SDL_mslibc_x64.masm ed62d6e7de cocoa: Set the titled flag on fullscreen space windows b4b5dbd92f testcontroller.c: fixed warnings. 31851a50d2 Fixed dropping raw input because of mixing GetRawInputBuffer() and WM_INPUT handling 987744aae8 Fix Duplicated includes 2b369a14ab Fixed allocation and alignment of raw input buffers bec1b8f849 Add basic rumble support to Steam Deck 8fe4a45edf Use GetRawInputBuffer() instead processing raw input one at a time 87b8f09657 Fixed warning: no previous prototype for function 'SDL_PrivateGetGamepadButtonFromString' [-Wmissing-prototypes] c2951655ff Fixed warning: missing field 'window' initializer [-Wmissing-field-initializers] 5b3ee51c6c Updated copyright for 2024 a7b79c483c Remove unused 'window' variable from -[Cocoa_WindowListener windowWillExitFullScreen] dd2d809407 AndroidShowToast: make OneShotTask members private final 44c2f344d6 Fixed build 2faae8457d The C standard defines a boolean expression as a signed integer value. e3d50619f8 Fixed fatal error: SDL_pen.h: No such file or directory dfe1a37bab Fixed error: 'static' is not at beginning of declaration [-Werror=old-style-declaration] 423b1fafcd Fixed warning C4047: 'function': '__x_ABI_CWindows_CGaming_CInput_CIRawGameController **' differs in levels of indirection from '__x_ABI_CWindows_CGaming_CInput_CIRawGameController *' 7681695875 Revert "Fixed signed/unsigned warnings with Visual Studio when comparing SDL_bool with boolean expressions" 8f94102b04 tests: Use unsigned format specifiers for printing flags 5d0c893723 wayland: Remove bitfield specifiers from boolean values 530b41d531 Fixed warnings in SDL_pen.c 9906d6d3bc Fixed warning C4244: '=': conversion from 'SDL_bool' to 'Uint8', possible loss of data ebd7f9adbd Fixed warning C4245: 'initializing': conversion from 'int' to 'Uint32', signed/unsigned mismatch in SDL_video.c dc1c27885e Fixed warning C4389: '!=': signed/unsigned mismatch in SDL_blit.c e813c72b3c Fixed warning C4245: 'return': conversion from 'int' to 'SDL_JoystickID', signed/unsigned mismatch dce626f469 Fixed warning C4244: 'function': conversion from 'int' to 'Uint16', possible loss of data 7f376277e5 Fixed warning C4244: 'initializing': conversion from 'SDL_bool' to 'Uint8', possible loss of data 61db102da9 Fixed signed/unsigned warnings with Visual Studio when comparing SDL_bool with boolean expressions d71454da17 Store the surface properties in the reserved pointer of a surface b6a92c113f wayland: Don't apply old libdecor window dimensions 39e24e52c8 Fixed example of creating a window with properties dc450ba908 Added an example of creating a window with position ce4fe32ce3 Added documentation for getting the X11 display from an SDL window 327d31a5d9 Added documentation for getting the NSWindow from an SDL window 3976bbef2a Added documentation for getting the X11 window from an SDL window ffb8515c21 Use the Valve code name for the Steam Deck controller 43c40d30a2 Added comment for the BDA Pro Ex controller 61704b5862 Removed an assertion it's possible to hit c24b33d8d9 Fixed building with older Windows SDK 70ba3f2830 Report the D-Pad for HIDAPI gamepads as a hat ce329d60e4 Added support for alpha blending using palette alpha 9c3e831e33 uikit: Send fullscreen enter/leave events 5df3eac925 Sync SDL3 wiki -> header 0dfdf1f3f2 Fixed crash if joystick functions are passed a NULL joystick 4ce935b910 Fix static build with libdecor 0.2.0 5d6d149862 Allow passing in `extrainfo` value to `GetMouseMessageSource()` e0df963ef0 Fix wrong bit count in comment c2a55cd2c5 Add missing `(void)` in functions params a3c8f2f6cb Consolidate mouse-touch event checking logic d747daf03d Use correct touch check flag a961066d0b Add basic touch/finger support to `testpen.c` a3b5eb07b2 Removed extern "C" linkage from main() declaration 50e309bb17 Include SDL_events.h in SDL_main.h cae657140c Add Access Controller 74418e1aa8 Made the cursor list check a compile time assert instead of a runtime one d6fb0d91d8 Added testpen to the Visual Studio solution bbdd41f287 Fix windows touch using wrong axis for normalisation a28ac29aa0 Add missing cursor types 6daf2e943f Try SDL_UDEV_deviceclass to detect joysticks even if in a container 1bf78ed544 We get a resize event when the view enters fullscreen mode on iOS e3b5999bb4 Use the application requested size to determine automatic orientation on iOS 278e3f9184 Whoops, fixed setting fullscreen flag 69e60e0f1b Fixed setting fullscreen mode on iOS 0e5ea3cc4b Fixed infinite recursion when adding an accelerometer as joystick on iOS c3d84c3342 Record the initial input report mode and only restore simple mode if that's what we started with 10a8b750a0 Use common generic syscond for platforms with no cond implementation 4914e5bb78 PS2 use WaitSemaEx for waiting for semaphore with timeout bb0e0ae080 Added a runtime check for BLUETOOTH_CONNECT in addition to BLUETOOTH (thanks @perepujal!) 312f98c2a1 Make sure the string is terminated, fixed invalid read in SDL_PrivateParseGamepadConfigString() 199f7cc3b1 x11: Ignore border extents when the border hint is unset 07e9603398 Sync SDL3 wiki -> header ffd82fb7c4 Add scaleMode to SDL_SoftStretch(), remove SDL_SoftStretchLinear(). 5dba04b29b Remove SDL_{Set,Get}SurfaceScale(). Add Scale parameter to SDL_BlitSurfaceScaled() and SDL_BlitSurfaceScaledUnchecked() (see #8732) e66896406d cocoa: Set appropriate flags on fullscreen spaces windows 57fcb9044c video: Remove more assumptions about window state in the video layer cb90653695 win32: use USER_DEFAULT_SCREEN_DPI instead of explicit 96 value a2e05480d6 Use crc16 return value when calculating GUID 2ad50e9675 Make the SDL3 surface ABI compatible with SDL2 f72d6a7fd9 Use more verbose names for properties, to match upcoming public property names 1f1ee6f77c Use the original manufacturer and product strings for the joystick CRC 4bb5e1f0f9 Added migration notes for migrating Steam Input support from SDL2 to SDL3 56f111dffc Fix compilation / same as sdl2-compat 72c366bf3d Fixed whitespace 3152b98e87 win32: minor fixup in WIN_UpdateKeymap() 2c4360ce8f Sync SDL3 wiki -> header c981a597dc Added Steam Input API support for game controllers a8f4f40d08 Sync more Steam Controller header definitions for the Steam Deck e6e54b22c8 Ignore all surface comparison output files in the testautomation directory 9d13be743b Make sure we're rendering whenever the activity might be visible, even if we don't have focus. 69ec0322d3 win32: Make leaving fullscreen when the window already isn't fullscreen a no-op 8f79e0b7f8 win32: Fix high-DPI debug logging a4496f7dcf Update doc: SDL_SoftStretch() and SDL_SoftStretchLinear() #8667 43309d38ed joystick: Extract 0x02a9 and 0x0291 PIDs into separate defines. 84a0d5f623 Added SDL_SetSurfaceScaleMode() and SDL_GetSurfaceScaleMode() to control scale mode using SDL_BlitSurfaceScaled() 4d5949dcf6 Added a controller name for the Steam virtual gamepad 695846f2ed Pass through the name of the controller for the XInput mapping 1745289b1b x11: Don't move the window when restoring and ensure that resize and position events are sent when entering or leaving fullscreen 08a7ca4d53 XInput: Use XInputGetCapabilitiesEx instead of fragile GuessXInputDevice 7f75178908 Verify that the %p format specifier works for 64-bit pointers 72b7acfe8a Don't create a TLS error buffer if we're just clearing the error 240e7747c8 Fix #8702: Add SDL_hidapi_steamdeck.c to Xcode target b937c54b66 win32: Set all size and position variables for external windows 8c285828e5 Fixed return value for SDL_UDEV_AddCallback() cbf9012c74 Fixed build 5547007915 Added test for inverted cursor 0ab13f2498 joystick: fixup for Wireless Xbox 360 Controller VID/PID detection in WGI backend. 627d134b9e Add support for monochrome cursors with inverted pixels under Windows. 21879faf48 wayland: Handle mouse focus when receiving touch events 58a5f5cbe8 Allow sendCommand() to be overridden by derived classes e6d8ef1a5b Revert "Back out Steam virtual gamepad changes" b0e7b7db6f Don't unload graphics libraries until after the window has been destroyed. 69288038ed Refactor away some additional integer types. f3048e3cd2 Add new file to vcxproj files. 5a21febecb Add new steam deck HIDAPI controller to controller database. 67d44c1017 Disable lizard mode while steam deck HID device is opened. 6dd6827343 Translate steam deck HID reports to SDL events. 94f621e030 Implement steam deck HIDAPI initialization. c1a7d0f96e Add steam deck detection and HIDAPI driver scaffold. 0baee3e676 Reversed test to be easier to read, more efficient, and match other code bddbd1e317 cocoa: Check the resizable flag along with zoomed status when resizing 835c733f4d video: Only sync when programmatically entering/exiting fullscreen c790572674 Use existing XUSB driver software PID 0x02a1 instead of PID 0x02fe 581d1cab25 You should call present when using a software renderer as well. 5173b0c2cc Make built-in joystick device lists extendable by using hints 34eb24ebc4 Back out Steam virtual gamepad changes 7529d25b2b Use the Steam virtual gamepad slot as the gamepad player index 445f08a0fb Print the gamepad player index when opening a gamepad 17723381da Sort Steam virtual gamepads by Steam controller slot f3d8a2def5 audio: Fixed resource leak in unlikely failure case during device add. 63ae84e140 x11: Improve sync algorithm 7e5511d3cd x11: Move unrelated variables out of XFixes #ifdef 3c5e9e6112 We can wait up to 500ms in onNativeSurfaceDestroyed(), so wait at least that long in onDestroy() 53cda988f4 Clear any errors from a previous run a197efe3a7 pen: fix leak caused by pen subsystem fbb0914b78 wayland+x11: free system cursors when quiting video 7484d02a2e testshape: use SDL_test to create multiple windows 20250aecc5 Sync SDL3 wiki -> header 4fd778119b video: Implement asynchronous windowing ace385a134 Revert "Fixed warning C33010: Unchecked lower bound for enum scancode used as index." e482f00a17 SDL_string.c (SDL_vsscanf): fix gcc build f00ecf5f19 Fixed building with older Windows SDK 7ca43995a1 Fixed warning C4028: formal parameter 1 different from declaration aab7432f5f Fixed analyze warnings for SDL_dynapi_procs.h c484140f56 Fixed warning C33010: Unchecked lower bound for enum scancode used as index. 02a116217d Fixed Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '-' to avoid overflow (io.2). 91da942b33 Fixed warning C28251: Inconsistent annotation for 'SDL_RWvprintf_REAL': this instance has no annotations. b8840801cc Fixed analyze warnings in SDL_xinputhaptic.c 8e0d728c67 Fixed warning C26451: Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '-' to avoid overflow (io.2). 6a736d7766 Fixed warning C6340: Mismatch on sign: 'unsigned char' passed as _Param_(2) when some signed type is required in call to 'SDL_SetError_REAL'. 1fcc75ba81 Fixed warning C6340: Mismatch on sign: 'unsigned char' passed as _Param_(4) when some signed type is required in call to 'SDL_snprintf_REAL'. 7f2e16db8b Fixed warning C6340: Mismatch on sign: 'const unsigned short' passed as _Param_(2) when some signed type is required in call to 'SDL_SetError_REAL'. 163de8e697 Fixed warning C6001: Using uninitialized memory 'rdi'. e29393e407 Fixed warning C6001: Using uninitialized memory 'devName'. 0f34ca2e71 Fixed analyze warnings in SDL_xinputjoystick.c 2b5c7db645 Fixed analyze warnings in SDL_render_d3d12.c a28769759b Fixed warning C26052: Potentially unconstrained access using expression '(signed char *)info' 6ee34380f4 Fixed warning C6011: Dereferencing NULL pointer 'viewport'. 230581f4a8 Fixed warning C26451: Arithmetic overflow: Using operator '+' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '+' to avoid overflow (io.2). 3775d9be4b Fixed warning C28251: Inconsistent annotation for 'SDL_LogMessageV_REAL': this instance has no annotations. b512182222 Fixed warning C6340: Mismatch on sign: 'unsigned int' passed as _Param_(2) when some signed type is required in call to 'SDL_SetError_REAL'. 8a82f7e837 Fixed warning C33005: VARIANT '&valueX' was provided as an _In_ or _InOut_ parameter but was not initialized fda039e6f8 Fixed analyzer warnings for SDL_string.c 22f44aefe7 Fixed warning C6340: Mismatch on sign: 'int' passed as _Param_(3) when some unsigned type is required in call to 'SDL_sscanf_REAL'. eab2d97d07 Fixed warning C26451: Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2). 41bfcad5d7 Fixed warning C6340: Mismatch on sign: 'unsigned short' passed as _Param_(2) when some signed type is required in call to 'SDL_SetError_REAL'. 54dc73aa88 Fixed warning C6386: Buffer overrun while writing to 'palette_saved_alpha': the writable size is 'sizeof(Uint8)*((palette_saved_alpha_ncolors))' bytes, but '2' bytes might be written. 0dad56354c Fixed warning C6326: Potential comparison of a constant with another constant. c9b243fb56 Fixed warning C6263: Using _alloca in a loop: this can quickly overflow stack. 4ccc53edfe Fixed warning C6011: Dereferencing NULL pointer 'display'. 3db4695ac7 warning C6340: Mismatch on sign: 'unsigned int' passed as _Param_(3) when some signed type is required in call to 'SDL_LogDebug_REAL'. c7d81d936a Fixed warning C6031: Return value ignored: 'GetKeyboardState'. 69b9d44bdc Fixed warning C26451: Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '-' to avoid overflow (io.2). a9b87ee201 Fixed warning C28159: Consider using 'GetTickCount64' instead of 'GetTickCount'. Reason: GetTickCount overflows roughly every 49 days. Code that does not take that into account can loop indefinitely. GetTickCount64 operates on 64 bit values and does not have that problem 21f273ecc7 Fixed warning C6255: _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead. 0c4cb3d153 Fixed warning C26451: Arithmetic overflow: Using operator '<<' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '<<' to avoid overflow (io.2). 06f8f9a891 Fixed warning C6326: Potential comparison of a constant with another constant. 3e54061fa8 Fixed warning C6011: Dereferencing NULL pointer 'SDL_disabled_events[hi]'. 226f8fde09 Fixed warning C28182: Dereferencing NULL pointer. 'streams[j]' contains the same NULL value as 'stream' did. fe6b3ab0b0 Fixed warning C6031: Return value ignored: 'CLSIDFromString'. f3b0149756 Fixed warning C26451: Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2). 6cfce101fb Don't call the property cleanup function if setting the property fails 14380ec48a Fixed signed/unsigned comparison warning ac0751a652 Added SDL_strnstr() 7c71e72193 SDL_render: Call InvalidateCachedState to initialise some of driverdata values (eg '*_dirty' to 1). At the earliest place, immediatly after driverdata is set. (Doing it in SDL_render.c, after creation, would be too late, because there're renderers that already use/change those values in the CreateRender() function). 058213366b Make sure we use alpha of 0 when clearing a transparent window with no content ecd56bb8f0 Removed SDL_GetErrorMsg(), trivially implemented in application code 66e532fa61 Handle out of memory errors without any allocation 579681a372 fixed SDL_SCANCODE_LEFT array position in scancodes_windows.h 675423f096 Sync SDL3 wiki -> header 744a29b88f WGI: Cleanup code a bit (#8647) e4582e6edc Sync SDL3 wiki -> header df0fd55891 wikiheaders.pl: README files are no longer in Windows endline format. 1072b6e86e docs: fixed typo f184dea16c uikit: Patched to compile. 447b508a77 error: SDL's allocators now call SDL_OutOfMemory on error. 70b65d4170 cocoa: Resync modifier keypressed on NSEventTypeFlagsChanged event. d1b831e232 include: Clarified documentation for two functions. daa38dc793 touch: Replace GetNumTouchDevices/GetTouchDevice with a single function. dd47da8a5c gamepad: Replace GetNumMappings/GetMappingByIndex with a single function. dfee3f9e92 render: Replaced SDL_RenderFlush with SDL_FlushRenderer. eef5c53668 docs: Moved CREDITS and INSTALL to markdown format. f32575dfab docs: Convert everything to Unix-style endlines. c5daf8adb1 testcontroller: Don't query the mapping list until after they are available. df05d5eff4 Fix scroll wheel handling in testmouse 63d4bd4e57 SDL_SendKeyboardText: remove workaround from 1e12d7c, fix use of iscntrl fea6e7afb1 cmake: copy sources to binary directory in separate target 9faa7977bd UWP: Add support for supplementary Unicode characters input 7b628ea4d5 Win32: Simplify Unicode text input code 411c70abb1 Fix the target membership of SDL_pen.h (thanks kanjitalk755!) 3e6513c773 document the purpose of SDL_SetTextInputRect 1e12d7cfb6 Make sure we don't pass UTF-8 to SDL_iscntrl() 9a206adbee UWP: Use Windows.UI.Core.CoreDispatcher.AcceleratorKeyActivated event for keyboard 309ea2d5f9 UWP: Simplify Win32 scan code to SDL scan code mapping acc5bb89f8 [Win32] Better keyboard button mapping to SDL scan codes 6b28065e9e Reformat Win32 scan code table 7e86b6aef2 Win32: Fix keymap for keyboard layouts that can print UTF-16 surrogates and ligatures 08c6ac1b16 test: SDLTest_PrintEvent now reports key event mod state. 571e9796b9 Fixed the GameCube HIDAPI controller mapping dd984dcd9f Removed HIDAPI controller mappings 3817f5126e cocoa: Use `-[NSApplicationDelegate applicationSupportsSecureRestorableState]`. c5b0ff77d7 test: testwm2 now displays keyboard mod state. 5772e00c3f cmake: disable oss by default on OpenBSD, not FreeBSD ceac93ade2 Set framebuffer_srgb_capable to the actual value obtained (#8634) ed3fad1880 cmake: disable oss by default on Linux, NetBSD and FreeBSD 6bb40f1d8d SDL_VideoCapture: allow add/remove device at runtime on linux f0e47f8ee0 Added support for the NACON Revolution 5 Pro controller 2f806c89b5 initial import of hidapi netbsd uhid native backend from mainstream 1b284cd415 X11 pen detection: fix misclassification due to improper init c4ca64deaf ci: do 'brew update' & don't run dependent checks on installed things d486de6349 cmake: fixed iconv detection test program 42a8139fd6 render: Clip bresenham lines against a real viewport thing. 4a40a272bd render: Patched to compile. 983f178b7d render: Clip lines before Bresenham algorithm generates points. db7f6425d0 rect: Avoid numeric overflow on massive lines in SDL_IntersectRectAndLine. e548044a82 ci: add NetBSD to test matrix dd5b8db3a6 SDL_hidapi requires libusb >= 1.0.16 6ba90f7775 render: Batching is always enabled now! b24d6bd59a opengl: Creating a texture trashes the cached `texturing` state, fix it. dcf04559db render: GL-based renderers should treat adaptive vsync as vsync being enabled. 74a2542564 x11: Deal with difference in GLX_EXT_swap_control_tear behavior. 08fac5b1b2 SDL_PenModifyForWacomID: return zero as axis_flags upon failure. aaba01aee4 hidapi: syncing with mainstream: 5730eb67f0 add HAVE_GCC_DIAGNOSTIC_PRAGMA to SDL_internal.h, use it 42c8366fdc revise iconv detection: a45b371de0 cmake: create and install SDL3::Jar target for Android 53544cabaa psp: fix copy/paste error from SDL2 in PSP_VideoQuit(). 6cde96f9a0 psp: Hook up event subsystem init/quit. e9659f5738 Removed some function declarations that don't actually exist. 3264e64738 SDL_RunApp: Make sure argc/argv are stable if the caller didn't define them. eca79e38db Removed test shape images 81fc7ded78 Removed the window shape API for SDL 3.0 45938bbfa5 Corrected comment 059e550e98 Fix 3DS Analog Values (#8581) dbf14df80f Fix joysticks returning invalid device IDs d2db3f3993 ci: cache android ndk archive d6291d4d42 alloca: use alloca from on NetBSD f26a93211f SDL_bsdjoystick: fix -Wundef warning on FreeBSD d1def7f033 cmake: add openbsd wscons sources to build, if supported 86d77bbcc5 kmsdrm: restrict KMSDRM_ReleaseVT/KMSDRM_AcquireVT to SDL_INPUT_LINUXEV 94ad1a4ae4 SDL_bsdjoystick.c: fix -Wundef warning. e761770c24 No, this wasn't right b7d7e54895 Fixed uninitialized variable 119e02f314 Always use 'm' for controllers provided by the MFI joystick driver 75df4cc5c2 Don't tickle PS4 Bluetooth controllers in simple mode 0d431015bf Extract BMP magic into separate define 0413f6fc49 Use SDL_iscntrl() call instead of manual code that is doing the same (#8593) fd91178d7f Make size cursors be double arrows on Wayland 5e9b0820f3 Add cursors for X11/Wayland window resizing 91e122316c Use SDL_small_alloc() instead of SDL_stack_alloc() in CreateMaskBitmap() b76f8de298 Hit testing tweaks for X11 and Wayland (#8582) 5b1c68c2f3 testshader: Don't make local variables with the same name as GL entry points. 30a2291d59 Fixed compatibility with sdl2-compat (thanks @sezero!) 49d58bc73a Cleanup WIN_CreateCursor() code a bit 4722269fb6 tests: Print window occluded log events 5db781cc3d Use the correct pixel formats for OpenGL ES on big endian 05e7dcf8f8 Support returning <8bpp surfaces in SDL_LoadBMP_RW 773ec1cfcb Extend blitting support for all <8bpp formats 753bbd199e Add SDL_PIXELFORMAT_INDEX2LSB and SDL_PIXELFORMAT_INDEX2MSB fda69e5e79 Test both MSB and LSB indexed surfaces in testautomation 39870031d1 use format string attributes for functions accepting va_list params, too 666301f9f9 Fixed build with older macOS SDK 238987df3b Always use physicalInputProfile on OS versions that support it e424dcca4b More fixing the build with older macOS SDKs 4ebb0c6dac Revert "Fixed build" 7abacc9f9f Fixed build ac1f896f89 Fixed building with older macOS SDK 8043dad369 Fixed build warning 5c8c3931f2 Removed outdated information about SDL_GetWindowWMInfo() e0d0d140b2 Fixed SDL_GetWindowWMInfo() code example f61c0f3dc1 SDL_HINT_TV_REMOTE_AS_JOYSTICK should only affect Siri Remotes on Apple TV 51fc134cad Added support for the second generation Siri Remote on Apple TV bfba9de43a Revert "Map Siri Remote touchpad to D-Pad for consistency with physicalInputProfile mapping" f2c12fe5f3 Removed TODO.txt d4448fe3d2 Update virtual joystick test now that we're just using abxy for mappings, for compatibility f40f272107 Fixed build 34bdd321bf Improved navigation while setting up controller mapping 57e5c7f6ee We'll just use the legacy names for face buttons in the mappings 051ed397d1 Removed testautomation_syswm.c from the Xcode project fb08c22abf Disable controller state debug messages 2999634f29 Map Siri Remote touchpad to D-Pad for consistency with physicalInputProfile mapping 2ff9255f29 Print the GUID for controllers that don't have a mapping 64939d3586 Switch the menu button on the Apple TV remote to the B button to match UI guidelines 0fe5713964 Improved GCController handling on Apple platforms aaf54b09a1 Revert "Cleanup WIN_CreateCursor() code a bit" e923a458ea audio: Protect against race conditions when closing a physical device. 8fa0746d4a audio: Fix postmix state when migrating to new default devices. 078995bbe0 x11: Set the skip taskbar and skip pager atoms on utility windows a58af6d0d1 wasapi: Reference devices when proxying to management thread. 34392db9c3 Fixed IOS_SupportedHIDDevice() returning SDL_FALSE before initialization 708f18d49e Added SDL_HINT_JOYSTICK_IOKIT and SDL_HINT_JOYSTICK_MFI to control whether the IOKit and GCController drivers should be used for joystick support. bd4966999b Replacing SDL_SCANCODE_AUDIOMUTE by SDL_SCANCODE_MUTE on Windows 15504da0b8 Fixed mouse wheel scrolling direction on iOS f5600fd9f4 Fall back to using the physical profile for Apple controllers if they don't match a standard profile 924de4df48 Enable transparent windows when using the D3D11 renderer 1c64366b80 Added SDL_CreateRendererWithProperties() and SDL_CreateTextureWithProperties() 7203641597 Note that the SDL window properties are read-only e0c45c6c98 Renamed SDL_WINDOW_FOREIGN to SDL_WINDOW_EXTERNAL 229b7b9d50 SDL_CreateWindowWithPosition() and SDL_CreateWindowFrom() have been replaced with SDL_CreateWindowWithProperties() 2c1fbe1967 Revert "Check to make sure the Windows joystick device has buttons and axes" edd044e901 Fixed the ROG PUGIO II showing up as a game controller 861b1ebd12 properties: Use a mutex instead of an RWLock to guard the hash table dd8ab67bd9 Sync wiki -> headers. 91460fc13d include: Fixed up documentation in SDL_pen.h 876c97454a Cleanup WIN_CreateCursor() code a bit 8766aa39d6 Sync wiki -> headers. e5ffd6d8eb include: Removed `\link` and `\endlink` Doxygen tags. 1c4723ac66 SDL_CreateWindowFrom() now takes a set of properties that describe the native window and options. 6afae6681b Allow casting properties of different types bd269b0f41 Added SDL_SetBooleanProperty() and SDL_GetBooleanProperty() c47ac5b2df include: Fixed copy/paste error 0efb3d90e0 audio: removed a fixed FIXME comment. 69cae07cc1 cpuinfo: Fix detection of physical memory above 2GB on NetBSD 45fc828c95 move SDL_EVENT_WINDOW_PEN_ENTER and SDL_EVENT_WINDOW_PEN_LEAVE down 0907f345cb Added property types: pointer, string, number, float 7c80ac6df7 API for pressure-sensitive pens + XInput2/Wayland d3e43668d0 Revert "Sync SDL3 wiki -> header" 43571769f1 Sync SDL3 wiki -> header 843873626c Handle window resizing on the render thread in D3D11 and D3D12 f66f61de01 Fixed missing "0" in the button list 04b6b2979f Re-add SDL_assert() with non boolean ptr syntax (#8530) b374105975 Replaced SDL_GetTextureDXGIResource() with texture properties 09d1e9defb Only update the battery status for Bluetooth Switch Pro controllers 87794d03ad Added shortened name for "Nintendo Co., Ltd." fbb6934905 Added support for the Dragonrise GameCube adapter with VID 0x1843 d98e1bdfe1 Use the standard gamepad type for Switch Pro controllers using the GameCube form factor a5a47d3bee Fixed crash if there is no controller mapping 312faf9305 Updated documentation for the controller face buttons 2991b9f6ac SDL now represents gamepad buttons as positional elements with a separate label 8708ba7393 Don't leak if realloc fails 36b2d2e463 Fix memory leak in SDL_SendDrop() dfb87e1099 Fix uninitialised variable 'properties' e54c5e0204 Fix condition in SDLTest_TrackAllocations() 89408a9705 wasapi: ResetWasapiDevice no longer blocks on management thread. aa7baf63aa Sync wiki -> headers. 74f3643bfa wayland: Add missing break to switch statement 2d6bae70b4 Older gcc does not support #pragma GCC diagnostic inside functions d8600f717e Pointer as bool (libsdl-org#7214) 23db971681 x11: Ignore deprecated declaration of XKeycodeToKeysym 61c39ce848 ci: re-enable Intel compilers on ci 4ac3f5c07e Updated Xcode project with the video capture API 59f93e20a7 Add SDL Video Capture, with back-end for linux/macos/ios/android 3ab98a3572 Removed debug print statements (thanks @stsp!) f4b61fff30 Implemented VT switching for KMSDRM on Linux 391a3d23d0 cmake: the compile-time pdb does not have a suffix, if set a6541166bc cmake: also install pdb files of static libraries 2e3f574f8f cmake: don't add the C runtime library to the .obj file (when using MSVC) bea34c5380 Fixed a memory leak at window creation. b5347c3364 Fixed emscripten and iOS builds 02f356439d Allow the application to draw while Windows is in a modal move/resize loop 1934417b4d Show the existing mapping when a controller is connected 407a3cb4e0 Fixed infinite recursion initializing properties 979214363f Added SDL_GetGlobalProperties() 151cdfa99f Added the "SDL.window.wayland.registry" property 6c91b28e71 Added the "SDL.renderer.d3d12.command_queue" property 4e8d1ec983 Fixed crash trying to create a metal view with the dummy driver fd4a2cce9e SDL_syswm.h has been removed and replaced with window properties aea6e6de6f Simplified SDL_SetProperty() and added SDL_SetPropertyWithCleanup() a1941fad6c Replaced SDL_RenderGetD3D11Device(), SDL_RenderGetD3D12Device(), and SDL_RenderGetD3D9Device() with renderer properties. 0cd4b7d3e3 Added display properties a02afbaea5 Clean up window properties when the window is destroyed 8668943746 Standardized property names for internal SDL properties a4c6b38fef Fixed FreeBSD build d9e6dcc650 Fixed FreeBSD build 7cc3e94eb2 Store the requested muted state ce9e1bd324 Don't mute the console input if we can't read the keyboard 0a1b6b270f sdlchecks.cmake: Clarified the reason why shared X11 mode doesn't work 80b2bbad21 Removed useless branch test 15bc12165a Actually we need to enumerate the 8BitDo Xbox SKUs 8049af3355 Assume all 8BitDo Xbox controllers have a share button ed1e0c1530 Make sure joysticks are locked when adding and removing them 415283ef38 Fixed checking for linux/input.h fea2504a37 Prioritize the pipewire audio driver over ALSA 04e98d2236 Added missing calls to SDL_InvalidParamError("surface") 521bbcc15e Destroy the window surface if we've created it for the software renderer 28e623c504 Added a mapping for the Atari VCS controller connected over Bluetooth (thanks @WizzardSK!) 4106697774 Make sure we include the null terminator in XLookupStringAsUTF8() b5057edf29 Remove unused SDL_TextureModulate enum 9458cbf75e Removed unused SDL_OSEvent 1a57f6bb29 wayland: Remove QtWayland extensions 5f920d6639 fix emscripten build after commit 07cb7c10 22016b4eae Enable the 5th player LED on the DualSense controller 66cf30c2de Removed misleading comment dbcd390cdf Log drag and drop position updates in SDL test programs 07cb7c10a1 Fixed connecting and disconnecting real-joysticks closing virtual joysticks in Emscripten (thanks David!) 869257a5c1 SDL_migration.cocci: Added a thing for SDL_Vulkan_CreateSurface. c6d9fb1ad7 hidapi: Avoid memcpy'ing to NULL. 4d1aecc225 vulkan: Patched to compile on iOS. fccec65afe Sync SDL3 wiki -> header 2f92807087 vulkan: SDL_Vulkan_CreateSurface now accepts an app-provided allocator. c53843a961 docs: Remove Doxygen `\brief` tags. c132295ad7 SDL_FlushEventMemory is not a public procedure. 7ac281f800 Sync wiki -> headers. f7d40b7594 Added 10-bit pixel formats in addition to SDL_PIXELFORMAT_ARGB2101010 3e4d7e48b0 Fixed memory leak in XInput code bc3d9e99f3 Only save ibus_addr_file after we've successfully read an address from it. 04dfca958a Added a note to events indicating that memory is owned by SDL 20cd789bab Improved migration documentation for the event memory change. 459f17257c cmake: fix MSVC unrecognized option link warning 761390b62f cmake: detect linker id, and assume MSVC does not support version scripts d2e005ee13 dynapi: remove duplicated SDL_LoadWAV entries 21ff699251 test: Fix popup test crash on exit 70c149c88f Automatically clean up memory associated with events 1a83bf2399 fix a possible memory leak in SDL_vasprintf() 59b37d0e5b cmake: fix Windows unrecognized option link warning 9302d7732d Fixed touch normalized coordinates ff3c20a799 Sync SDL3 wiki -> header 17a0fe3a0c Sync SDL3 wiki -> header 14d2471a8f Sync SDL3 wiki -> header 2ad22eeeb5 Sync SDL3 wiki -> header 930438dfb7 Added note that the #ifdef is for !__IOS__ c56583fe45 Fix duplicate symbol on iOS/tvOS 2b62f25a6f Add SDL_sysmain_callbacks.c to the Xcode project 4ab31ca678 Fix dropping file event 5dce4bc716 Makes SDLInputConnection and DummyEdit public classes (thanks Cole!) d3f2eb2aba Use XINPUT_STATE instead of XINPUT_STATE_EX (thanks Andrew!) 3a482ebae0 Add createSDLMainRunnable() to SDLActivity (thanks Cole!) b9784feb24 Fixed potential uninitialized memory access (thanks Mathieu!) 75ea3a8d32 Dynamically allocate long text for SDL_EVENT_TEXT_INPUT events 2a1660ab51 Additional cleanup for SDL_RWprintf() (thanks @sezero!) e5f2cea234 Sync wiki -> headers ad842dd5ad Fixed a typo in SDL_log.h f9d11807c0 Added SDL_RWprintf() and SDL_RWvprintf() to do formatted printing to an SDL_rwops stream 52c4e3eab3 events: Update self-referential pointers when copying event objects 91f0456391 Add the source application for drag and drop events (thanks Nathan!) 1a8bf31a69 include: Fixing whitespace on SDL_MixAudioFormat. d07a264a9b Use the default UCS2/UCS4 conversion rather than non-portable INTERNAL encoding 780b6612a9 wayland: Wayland_Vulkan_GetInstanceExtensions didn't set the count variable. 46b940d571 Updated documentation to note that the event callback is called on the same thread as the main iteration callback 4481754359 Make sure we only dispatch events on the main thread when using application callbacks 274da8561c Updated the migration guide to note that you can check the return value of SDL_AddEventWatch() ad9dcdbbce Clarify that you should use the other field when reading the event a19029e3c1 docs: Updated README-main-functions.md based on feedback. 019468dc59 main: Check for SDL_AddEventWatch failure, now that it can report it. 7e445da569 Added SDL_CleanupEvent() c4bf05fd9d Added subsystem refcount tests to testautomation 7f65ed6461 Handle subsystem dependencies recursively a6b85c81cc Fixed build 3ab6670cb1 Sync SDL3 wiki -> header f439ccfc1a Updated return values for SDL event functions e0379c3b37 Grab events in large chunks in SDL_IterateMainCallbacks() ad5264e54f Don't run SDL_IterateMainCallbacks() if the init call returns an exit code dad1a84be4 Fixed building Vivante video driver fe175d025f Fixed building Vivante video driver 0b460f34ba The HP HyperX controllers have a share button f3261fedcc Code cleanup now that SDL_bool is equivalent to a C boolean expression a76d8e39aa Changed SDL_bool from an enum to unsigned int cf7e5bd0e8 Sync SDL3 wiki -> header 853c28e624 docs: Added first draft of README-main-functions.md 70d75b4a23 Sync wiki -> headers ea02630143 More audio migration clarification 14980b25a8 Clarify documentation for audio callback migration 6cf84e2c5b cmake: fold HAVE_INPUT_EVENTS into HAVE_LINUX_INPUT_H 5e869d1b35 fix Cocoa_Vulkan_GetInstanceExtensions prototype for Mac 07a776f255 include: Fixed documentation for SDL_Vulkan_GetInstanceExtensions. 5b3a2c6df6 docs: Updated SDL_Vulkan_GetInstanceExtensions info in README-migration.md. 9224a0a2d8 Fix emscripten, android, uikit and windows d0d8b28df1 Change SDL_Vulkan_GetInstanceExtensions 338974bb29 SDL_test_memory.c: fix build against older windows SDKs. 618d15bce6 Fixed typo ad0af48883 Check to make sure the Windows joystick device has buttons and axes ac6b32bb02 gendynapi.py: Discard SDLMAIN_DECLSPEC functions. 9c664b0062 main: Added _optional_ callback entry points. 9323417e9c Fixed gendyapi.py parsing of SDL_RELEASE_GENERIC 759cdf6159 audio: Fixed GetFirstAudioDeviceAdded(). 0e614d9179 audio: Massive reworking on thread locking. 40fb76196c audio: Don't let simplified audio streams bind to new devices. 24e3328cca audio: Don't reset device ID counter on subsystem init/quit. 5d95cbde37 cmake: reset check state before testing -fobjc-arc f18120c83c cmake: check -fobjc-arc compiler flag on Apple platforms 4aacc4b92e cmake: file(RELATIVE_PATH) needs 2 absolute paths dcc8805c21 testaudio: Fixed compiler warning on Visual Studio. 9cb259e865 audio: Never SDL_PushEvent from anywhere but SDL_UpdateAudio(). 875e45e70b wayland: Sanity check pointers and protocols before confining 0e87b71d08 wayland: Check the relative pointer handle before destroying 6127ac0871 Use SDL_DISABLE_ALLOCA instead of HAVE_ALLOCA in SDL_stdinc.h 552bee47cb Clear any previous errors if we successfully show a message box 343da852a6 Don't try to use the Wayland messagebox if we're not in Wayland f63e9a8a3f wasapi: Handle disconnected devices that get reconnected. 5fa7b291d4 wasapi: Fixed memory leak if new audio devices fail to add. 468c386686 wasapi: Handle disconnect notifications from the management thread, too. ce3be02b48 wasapi: If device is marked as a zombie, don't try to resuscitate it. 85923049a6 wasapi: Patched to compile. 9bec57309c wasapi: Proxy default device change handling to management thread. c45b5121ce audio: Fixed potential race condition. 8b6da3c701 Fixed making the EGL context current when resuming on Android 2e9eb1073d Sync SDL3 wiki -> header e6116d399a mutex: Removed SDL_MUTEX_MAXWAIT. 82f48be3ef Sync SDL3 wiki -> header 899eb0d042 thread: Locking mutexes and rwlocks are now void functions. 082ef41566 alsa: Fix crash from invalid handle pointer a9aa15c792 CI: change FreeBSD CI runner to cross-platform-actions. 23ceae94c9 Fixed Xbox 360 Controller support using libusb on Linux ace0c2c297 mutex: Fixed bug where generic SDL_TryLockMutex would incorrectly block. f52b330ed8 Added support for the HP HyperX Clutch Gladiate controller b61706373c n3ds: Check that audio thread name starts with "SDLAudioP" 6827b3331d n3ds systhread - use 80kb thread stack size as default, remove hard cap e4cd1d4059 n3ds systhread - prefer to put audio thread on system core 1023d8ec84 SDL_n3dsaudio.c - don't risk leaving current_priority uninitialized 07171be596 SDL_n3dsaudio.h: use triple buffering 6efe957159 SDL_n3dsaudio.c: separate mixer locks from audio device locks 39a961ba41 Added support for "%[]" sscanf syntax 124a0050b6 Fixed warning: no previous prototype for function 'SDL_UpdateAudio' b16165a33f rwlock: SDL_UnlockRWLock was incorrectly tagged with SDL_RELEASE_SHARED. 865dd04068 pulseaudio: Don't use a hash for device change detection. b8cc51875a Fixed build 0413e21e54 Fixed audio device removed events for ALSA 5ba03d377a Revert "Fixed audio device removed events for ALSA" a774694be0 pulseaudio: Simplified default device change detection code. e57fef8f0b Fixed audio device removed events for ALSA 4280d4b359 Fixed warning C4210: nonstandard extension used: function given file scope 182cfc3265 pulseaudio: Rework how we manage default devices and hotplug. b2ae1e835f pulseaudio: Change debug printf calls to use SDL_Log instead. 38afd48daf Added a single source of SDL object IDs e07f6c0a17 SDL_IsJoystickProductWheel() returns SDL_TRUE for Asetek wheelbases (thanks @IOBYTE!) c98a14fdeb Renamed display added/removed events for consistency with the rest of the API c2a3112b6f Added "--substring" to the help for rename_symbols.py a844d90942 Add missing error reporting in Android_JNI_FileOpen() 4ac38d13dd alsa: Don't touch free'd memory in hotplug thread. 43d41c9dcb audio: Another attempt to make device add/remove work vs event watchers. 9abc692156 audio: Another attempt to deal with device destruction from device thread. 33c9eeec7c Revert "audio: Device threads don't increment physical device refcounts." e5a15f94e2 Revert "Check to make sure the Windows joystick device has buttons and axes" 70fd8e2ba2 Lock joysticks when adding gamepad mappings 76f81797b7 audio: Device threads don't increment physical device refcounts. 594fda4120 Sync SDL3 wiki -> header 0d7c5a2c56 Updated Android API documentation 0df888c584 Moved Android low latency audio behind a hint "SDL_ANDROID_LOW_LATENCY_AUDIO" 142366c837 Sync SDL3 wiki -> header 3a4c9d6990 Fixed build error when API logging is enabled 1f8f82b379 Removed redundant thread-safety information a6edc75fe7 Sync SDL3 wiki -> header 3c8edeb79b Clarified SDL property thread-safety information 4fa821cb3e Sync SDL3 wiki -> header 1c70760c0b Added thread-safety information for the new SDL properties API 15533dce05 Cleaned up warnings from check_stdlib_usage.py bf269571fc jack: Removed FIXME comment that has since been fixed. 797b70877d audio: Remove stub header SDL_audio_c.h. 9d7c57234a audio: Cleaned out most remaining `/* */` comments for `//` style. 0ff67dc21b video: Fix compiler warning about SDL_ReadSurfacePixel not being declared. 81c77396af opensles: Patched to compile. 442e84916a opensles: Fixed capitalization to match other SDL backends. 34914bfb49 alsa: Clean up device handles, now that hotplug thread cleanup is in place. 48d80efb51 Fixed warning C4701: potentially uninitialized local variable 'props' used f7dc63bcc3 audio: another windows wasapi build fix. dd98330076 audio: fix windows wasapi build. 7a52f7b3fd audio: Split Deinitialize into two stages. e55e556f32 alsa: Fixed minor memory leak. b45a0d9016 Updated the documentation for SDL_LoadWAV_RW() 435e7ce663 Check for device disconnection in HIDAPI_JoystickOpen() b733adb503 audio: Fix device refcounting vs ProvidesOwnCallbackThread backends. c6f08c2553 testaudio: Removed debugging code. d5dac0ad27 testaudio: Deal with a texture being unexpectedly NULL when scaling. b19e68c8ec testaudio: Properly display playback progress, regardless of data source. 8c39269279 audio: Fix audio stream format when binding to a capture device. f26b838a3e jack: Check for sample rate and buffer size changes from the server. 063cb0df6b audio: Fixed comment typo. "deref" should be "unref" 354611a0c6 testaudio: Fixed some bugs Valgrind pointed out. a17f3ba916 audio: Reworked audio device disconnect management. 6ddd96a4d0 Fix some wrong gamepad/controller event enums 8df68b4120 hashtable: Moved over to single-line comments. 1c6d996108 testaudio: if the SDL_Renderer is already gone, don't destroy SDL_Textures. b22ffb9797 audio: Fix some logic errors in the new device hashtable code. e526dc64bd Don't set unused variable 6664437748 hashtable: Don't rearrange bucket elements during SDL_FindInHashTable. 8ac5c84ad1 audio: device thread shouldn't touch `thread_alive` after object is free'd. b17151eb16 testaudio: Don't crash if renderer is NULL (happens during shutdown). 7f408e57ee audio: Keep all available devices in a hashtable instead of linked lists. 0aba2c97db hashtable: SDL_IterateHashTable might as well provide both key and value. 95a9271dbf audio: Never lock a device while holding the device_list_lock. 9aeabb0b05 Fix macOS build error by #8269 382751c4b5 testffmpeg: print usage of options to change audio/video codec f91bde64d5 testffmpeg: Only enable blending if we're using a texture format that supports it 516d6f9efc testffmpeg: added support for YUVA formats using swscale ac71831350 Sync wiki -> headers d18f910248 testffmpeg: added the ability to specify audio and video codecs 72034b9a07 wayland: Fix primary selection handling when the protocol isn't supported e152129787 Fixes #8190. From #7249, reverted the hunks other than #7239. b79db0a6ea Fixed potential wraparound issue with property IDs c9ccf0670c Add unsupported functions to dynapi 25ce87979d Always provide an implementation for all SDL3 symbols 3a36433a3c cmake: test -Wl,--version-script with minimal version script 0efa196989 dynapi: implement SDL_DYNAPI_entry even when building SDL without dynapi support bf64fecf19 testffmpeg: allow resizing of the video window efa9a45048 Clarified that testffmpeg will resize the window to the video size 4368f70ff9 Added properties to various SDL objects 973c8b3273 Added SDL properties API 2bca4671a6 audio: Allow audio streams to be created when the subsystem isn't initialized. 1ae33f6751 cmake: optionally install pdb's 0d5cad91b1 We need audio converters initialized in SDL_InitAudio() 1c3a0ade74 audio: Whoops, this stream format change is only for capture devices. 10fab3a39e pulseaudio: Stop the threaded mainloop before destroying the context. 0b71898cb1 Make it clear that the string comparison isn't a boolean check 6c8ad975c7 Like mutexes, operations on NULL rwlocks are no-ops c552cc6847 We don't require the audio system to be initialized for audio format conversion 9a5f7b17c6 Use SDL wrapped getenv function 044046bc50 audio: Fixed assertions when capture devices have wrong audio formats. bb2f767f5d testaudio: Make program usable without a 3-button mouse. 321fc18417 README-migration.md: Added note about SDL_HasRDTSC removal. 82f54af617 x11: Properly check for XInput2 support before using it. b654427537 Added support for the PowerA Nintendo Switch Nano Wired Controller dc2a5f6ab2 Fixed error C2054: expected '(' to follow 'inline' a7ae1de9a6 Fixed warning C4028: formal parameter different from declaration 3a47fb7208 The sensor and joystick instance ID generator isn't guarded by a lock. aee4862958 ci: stop FreeBSD job after 30 minutes 99fa0d6cae Disable low latency audio by default when using AAudio on Android ebfbd7327b testffmpeg: use SDL_test to parse arguments and track memory ee53e4d319 cmake: check ffmpeg capability instead of version 2d62c65a75 Fixed build warning bf72704bfd audio: Disable NEON sample conversion until test failures are fixed 0fe95cfba3 Sync wiki -> header adcace6f95 Added a "--software" option to testffmpeg 86ada8a9f0 fix testffmpeg.c build. 303f4e965c testffmpeg works with ffmpeg 5.1.3 and newer 2bd478ae65 Added SDL_GetTextureDXGIResource() to get the DXGI resource associated with a render texture. a842446f62 Added support for 0-copy decode and display using D3D11 d830cd140b Added support for 0-copy decode and display using Apple VideoToolbox 1bf913b29a Added support for 0-copy decode and display using VAAPI and EGL ce8161e0cf Make sure we're building with ffmpeg 6.0 or newer ed6381b68d Allow setting any number of sprites over the video ebf5e08fa1 cmake: use *_STATIC_* variables when linking to a static ffmpeg 88f2fb9dcf Added an example of video decoding with ffmpeg d88bf687a8 surface: Document the in-memory layout of the pixels 3698630bbc pixels: Document the naming convention 04edb38cdf shape: Use SDL[Test]_ReadSurfacePixel f5745c3a67 surface: Add a private SDL_ReadSurfacePixel 0d68f45879 test: Extract SDLTest_ReadSurfacePixel 55a1458ed0 audio: Changes to one logical device must update all sibling logical devices. 8e03ea4383 hashtable: Use Create/Destroy naming, in the SDL3 style. 568902b64e hashtable: Added src/SDL_hashtable.[ch]. 8745a9949b add-source-to-projects.pl: Fix adding files in the base src dir. 836927edf8 wayland: Try to skip the Wayland driver if not connecting to or in a Wayland session 2a9480c815 wayland: Add null check for zenity version string f30392da5b Fix assertion in LINUX_JoystickSetSensorsEnabled() 4e59bf6cb9 SDLTest_CompareSurfaces: Output RGBA values of first differing pixel b2ddfbbec3 SDLTest_CompareSurfaces: If surfaces differ, log their formats b028fd9604 SDLTest_CompareSurfaces: Log better messages if NULL or different sizes 183606d3d4 testdrawchessboard: clean up renderer and window 4f0642bf47 triangle: don't read destination pixel when you're going to discard it anyways 4cd0c13823 blit_slow: don't read destination pixel when you're going to discard it anyways 49abb9c1fa aaudio: Fixed a comment. 0eb8651d5e Do not report gyro/accelerometer if we can't read axes info ff57867516 audio: Fixed copy/paste error that was checking wrong variable. d2d4914ac3 audio: WaitDevice/WaitCaptureDevice now returns a result. a0820ed833 directsound: Cleaned up WaitDevice. 6c33a05bdb audio: Removed unused AllowsArbitraryDeviceNames variable. f1fc198278 audio: Destroy the logical audio device before sending DEVICE_REMOVED event. 64ec208479 Fix log message spelling 9111c5e178 tests: Disable mouse warp test under Wayland de5068f4e4 audio: Commented out a currently-incorrect assert. 3abb464f10 ci: disable Intel compilers 251f8fa272 Revert "Do a better job of finding default ALSA devices" 8857b0f13a Use the device audio format for the lowest latency 806e11ac00 Update sample processing bookkeeping when recovering the AAudio audio device 482c238953 aaudio: Deal with device recovery. a8813b58a6 aaudio: Change an int to an SDL_bool. 8923305f34 We don't need to wait a full 10 ms, just delay a bit 6a152676bb Wait a bit when snd_pcm_avail() returns 0 b4372de186 alsa: Cleaned up remaining debug logging. a063c943dc pulseaudio: Use pa_stream_begin_write to avoid an extra buffer copy. 0471a93706 alsa: Simplified PlayDevice and CaptureFromDevice. 776d9d0ee3 alsa: Convert `/* */` comments to `//` comments. 64fee85c69 alsa: More efficient audio thread iteration. 47cba08259 VisualC/pkg-support/cmake: remove subdir from public header include path 61e9a9dd56 pulseaudio: Just feed the device whenever it asks for any amount of data. 4f76f9b0a7 pulseaudio: Use correct buffer size of stream, wait less between fills. d95d2d7051 SDLTest_CompareSurfaces: Decode pixels correctly on big-endian platforms d65861f049 Do a better job of finding default ALSA devices ba65ef5ce7 Recover from -EPIPE in snd_pcm_avail() 5be5000fa1 cmake: make HEADERS_DIR a required argument of SDL_generate_manpages 5c1a91a4e1 ci: make sure perl is installed + build docs with MSVC toolchain 6248472c0c test: Accept small numerical differences in more mathematical tests a2c5dc6507 pulseaudio: Added typedef needed for compat with ancient Pulse installs. f24551f6d1 pulseaudio: More workarounds for extremely old Pulse installs. 441a5b707b audio: Adjusted const/static fields on some variables. 280c2c1d7d pulseaudio: Revert "pulseaudio: Require PulseAudio 5.0 or later for SDL3." 4db2b968af audio: simple-copy path should check if device is paused. 505dc4c39c wasapi: Deal with device failures when we aren't holding the device lock. ea5f59c234 Removed unused code a6854098f7 Fixed stuttering on Android when using the AAudio driver a5175e5ed0 audio: Fixed bug when setting up mixing formats. 9667aa18e6 wayland: Check that the data device supports the release method before calling it 4454dc400b wayland: Null all Wayland manager objects after freeing e1789b320e video: Streamline a little deinit code. 2a1058713c Bump libdecor feature check to look for 0.2.0 f5886f11d0 cmake: let every test depends on pretest f45761908a Move check for SDL_Delay upper bounds to testtimer a84389f6bb libm: use union for infinity 474c8d0073 testautomation: don't do float equality tests a6bc6b882c ci: always upload the artifacts 85e3099ba4 testautomation: only require accelated renderer with non-dummy video driver 26fd231151 ci: run tests on msvc workflow 0e955a9127 cmake: run testautomation with CTest 1375d2049d SDL_iconv_string() defaults to UTF-8 70a1bc6973 Check for NULL before dereferencing newly allocated memory 752f14e5a6 wayland: Convert some memcpy calls to copyp 9284a03053 wayland: Remove some unnecessary helper functions git-subtree-dir: external/sdl/SDL git-subtree-split: 0d7df16812c75c4a587d7d2673e3d1a5f2c2879b --- .github/workflows/android.yml | 1 + .github/workflows/cpactions.yml | 56 + .github/workflows/main.yml | 20 +- .github/workflows/msvc.yml | 33 +- .github/workflows/ps2.yml | 15 +- .github/workflows/vita.yml | 5 + .github/workflows/vmactions.yml | 70 - .gitignore | 4 +- Android.mk | 1 + CMakeLists.txt | 237 +- CREDITS.md | 34 + CREDITS.txt | 53 - INSTALL.md | 64 + INSTALL.txt | 43 - LICENSE.txt | 2 +- README.md | 36 +- TODO.txt | 10 - VisualC-GDK/SDL.sln | 3 + VisualC-GDK/SDL/SDL.vcxproj | 72 +- VisualC-GDK/SDL/SDL.vcxproj.filters | 1814 +++-------- VisualC-GDK/clean.sh | 1 + .../shaders/D3D12_PixelShader_Colors.hlsl | 19 + .../shaders/D3D12_PixelShader_NV12_BT601.hlsl | 43 + .../shaders/D3D12_PixelShader_NV12_BT709.hlsl | 43 + .../shaders/D3D12_PixelShader_NV12_JPEG.hlsl | 43 + .../shaders/D3D12_PixelShader_NV21_BT601.hlsl | 43 + .../shaders/D3D12_PixelShader_NV21_BT709.hlsl | 43 + .../shaders/D3D12_PixelShader_NV21_JPEG.hlsl | 43 + .../shaders/D3D12_PixelShader_Textures.hlsl | 24 + .../shaders/D3D12_PixelShader_YUV_BT601.hlsl | 46 + .../shaders/D3D12_PixelShader_YUV_BT709.hlsl | 46 + .../shaders/D3D12_PixelShader_YUV_JPEG.hlsl | 46 + VisualC-GDK/shaders/D3D12_VertexShader.hlsl | 95 + VisualC-GDK/shaders/buildshaders.bat | 35 + .../testcontroller/testcontroller.vcxproj | 14 +- .../testcontroller.vcxproj.filters | 8 +- VisualC-GDK/tests/testgdk/src/testgdk.cpp | 79 +- VisualC-GDK/tests/testgdk/testgdk.vcxproj | 6 +- .../tests/testgdk/testgdk.vcxproj.filters | 6 +- .../tests/testsprite/testsprite.vcxproj | 6 +- .../testsprite/testsprite.vcxproj.filters | 6 +- VisualC-WinRT/SDL-UWP.vcxproj | 28 +- VisualC-WinRT/SDL-UWP.vcxproj.filters | 85 +- VisualC/SDL.sln | 11 + VisualC/SDL/SDL.vcxproj | 37 +- VisualC/SDL/SDL.vcxproj.filters | 81 +- VisualC/pkg-support/cmake/sdl3-config.cmake | 2 +- .../testautomation/testautomation.vcxproj | 9 +- VisualC/tests/testpen/testpen.vcxproj | 204 ++ Xcode/SDL/SDL.xcodeproj/project.pbxproj | 190 +- Xcode/SDL/pkg-support/resources/License.txt | 2 +- .../SDLTest/SDLTest.xcodeproj/project.pbxproj | 6 +- .../java/org/libsdl/app/HIDDeviceManager.java | 7 + .../main/java/org/libsdl/app/SDLActivity.java | 277 +- .../java/org/libsdl/app/SDLDummyEdit.java | 62 + .../org/libsdl/app/SDLInputConnection.java | 136 + .../main/java/org/libsdl/app/SDLSurface.java | 37 +- build-scripts/SDL_migration.cocci | 61 +- build-scripts/add-source-to-projects.pl | 1 + build-scripts/check_stdlib_usage.py | 2 +- build-scripts/gen_audio_channel_conversion.c | 8 +- build-scripts/gen_audio_resampler_filter.c | 8 +- build-scripts/rename_api.py | 8 +- build-scripts/rename_symbols.py | 2 +- build-scripts/wikiheaders.pl | 2 +- cmake/FindFFmpeg.cmake | 138 + cmake/PkgConfigHelper.cmake | 5 - cmake/SDL3Config.cmake.in | 5 + cmake/SDL3jarTargets.cmake.in | 10 + cmake/macros.cmake | 54 +- cmake/sdlchecks.cmake | 21 +- cmake/sdlcompilers.cmake | 26 +- cmake/sdlmanpages.cmake | 2 +- cmake/sdltargets.cmake | 3 + cmake/test/CMakeLists.txt | 4 + cmake/test/main_gui.c | 2 +- docs/README-android.md | 1126 +++---- docs/README-cmake.md | 656 ++-- docs/README-contributing.md | 194 +- docs/README-dynapi.md | 276 +- docs/README-emscripten.md | 730 ++--- docs/README-gdk.md | 333 +- docs/README-git.md | 38 +- docs/README-hg.md | 14 +- docs/README-highdpi.md | 16 +- docs/README-ios.md | 564 ++-- docs/README-kmsbsd.md | 54 +- docs/README-linux.md | 176 +- docs/README-macos.md | 504 +-- docs/README-main-functions.md | 194 ++ docs/README-migration.md | 2809 +++++++++-------- docs/README-n3ds.md | 56 +- docs/README-ngage.md | 88 +- docs/README-platforms.md | 16 +- docs/README-porting.md | 130 +- docs/README-ps2.md | 102 +- docs/README-psp.md | 72 +- docs/README-raspberrypi.md | 360 +-- docs/README-riscos.md | 70 +- docs/README-touch.md | 163 +- docs/README-versions.md | 120 +- docs/README-visualc.md | 226 +- docs/README-vita.md | 66 +- docs/README-wayland.md | 268 +- docs/README-windows.md | 132 +- docs/README-winrt.md | 1046 +++--- docs/README.md | 114 +- include/SDL3/SDL.h | 6 +- include/SDL3/SDL_assert.h | 4 +- include/SDL3/SDL_atomic.h | 15 +- include/SDL3/SDL_audio.h | 35 +- include/SDL3/SDL_begin_code.h | 2 +- include/SDL3/SDL_bits.h | 4 +- include/SDL3/SDL_blendmode.h | 10 +- include/SDL3/SDL_clipboard.h | 4 +- include/SDL3/SDL_close_code.h | 2 +- include/SDL3/SDL_copying.h | 4 +- include/SDL3/SDL_cpuinfo.h | 4 +- include/SDL3/SDL_egl.h | 4 +- include/SDL3/SDL_endian.h | 4 +- include/SDL3/SDL_error.h | 19 +- include/SDL3/SDL_events.h | 312 +- include/SDL3/SDL_filesystem.h | 8 +- include/SDL3/SDL_gamepad.h | 155 +- include/SDL3/SDL_guid.h | 4 +- include/SDL3/SDL_haptic.h | 73 +- include/SDL3/SDL_hidapi.h | 10 +- include/SDL3/SDL_hints.h | 744 +++-- include/SDL3/SDL_init.h | 6 +- include/SDL3/SDL_intrin.h | 4 +- include/SDL3/SDL_joystick.h | 23 +- include/SDL3/SDL_keyboard.h | 9 +- include/SDL3/SDL_keycode.h | 8 +- include/SDL3/SDL_loadso.h | 4 +- include/SDL3/SDL_locale.h | 4 +- include/SDL3/SDL_log.h | 15 +- include/SDL3/SDL_main.h | 237 +- include/SDL3/SDL_main_impl.h | 59 +- include/SDL3/SDL_messagebox.h | 2 +- include/SDL3/SDL_metal.h | 6 +- include/SDL3/SDL_misc.h | 4 +- include/SDL3/SDL_mouse.h | 16 +- include/SDL3/SDL_mutex.h | 73 +- include/SDL3/SDL_oldnames.h | 66 +- include/SDL3/SDL_opengl.h | 4 +- include/SDL3/SDL_opengles.h | 4 +- include/SDL3/SDL_opengles2.h | 4 +- include/SDL3/SDL_pen.h | 285 ++ include/SDL3/SDL_pixels.h | 64 +- include/SDL3/SDL_platform.h | 4 +- include/SDL3/SDL_platform_defines.h | 4 +- include/SDL3/SDL_power.h | 4 +- include/SDL3/SDL_properties.h | 411 +++ include/SDL3/SDL_quit.h | 4 +- include/SDL3/SDL_rect.h | 4 +- include/SDL3/SDL_render.h | 414 ++- include/SDL3/SDL_revision.h | 4 +- include/SDL3/SDL_rwops.h | 66 +- include/SDL3/SDL_scancode.h | 6 +- include/SDL3/SDL_sensor.h | 21 +- include/SDL3/SDL_shape.h | 150 - include/SDL3/SDL_stdinc.h | 96 +- include/SDL3/SDL_surface.h | 118 +- include/SDL3/SDL_system.h | 175 +- include/SDL3/SDL_syswm.h | 387 --- include/SDL3/SDL_test.h | 4 +- include/SDL3/SDL_test_assert.h | 20 +- include/SDL3/SDL_test_common.h | 36 +- include/SDL3/SDL_test_compare.h | 27 +- include/SDL3/SDL_test_crc32.h | 10 +- include/SDL3/SDL_test_font.h | 24 +- include/SDL3/SDL_test_fuzzer.h | 4 +- include/SDL3/SDL_test_harness.h | 8 +- include/SDL3/SDL_test_log.h | 8 +- include/SDL3/SDL_test_md5.h | 10 +- include/SDL3/SDL_test_memory.h | 10 +- include/SDL3/SDL_test_random.h | 10 +- include/SDL3/SDL_thread.h | 4 +- include/SDL3/SDL_timer.h | 4 +- include/SDL3/SDL_touch.h | 44 +- include/SDL3/SDL_version.h | 4 +- include/SDL3/SDL_video.h | 691 ++-- include/SDL3/SDL_video_capture.h | 377 +++ include/SDL3/SDL_vulkan.h | 38 +- include/build_config/SDL_build_config.h | 4 +- include/build_config/SDL_build_config.h.cmake | 80 +- .../build_config/SDL_build_config_android.h | 3 +- .../SDL_build_config_emscripten.h | 3 +- include/build_config/SDL_build_config_ios.h | 5 +- include/build_config/SDL_build_config_macos.h | 5 +- .../build_config/SDL_build_config_minimal.h | 5 +- include/build_config/SDL_build_config_ngage.h | 2 +- .../build_config/SDL_build_config_windows.h | 3 +- .../build_config/SDL_build_config_wingdk.h | 3 +- include/build_config/SDL_build_config_winrt.h | 3 +- include/build_config/SDL_build_config_xbox.h | 3 +- include/build_config/SDL_revision.h.cmake | 4 +- src/SDL.c | 113 +- src/SDL_assert.c | 18 +- src/SDL_assert_c.h | 2 +- src/SDL_error.c | 46 +- src/SDL_error_c.h | 13 +- src/SDL_guid.c | 4 +- src/SDL_hashtable.c | 273 ++ src/SDL_hashtable.h | 57 + src/SDL_hints.c | 42 +- src/SDL_hints_c.h | 2 +- src/SDL_internal.h | 16 +- src/SDL_list.c | 8 +- src/SDL_list.h | 2 +- src/SDL_log.c | 20 +- src/SDL_log_c.h | 2 +- src/SDL_properties.c | 722 +++++ src/SDL_properties_c.h | 23 + src/SDL_utils.c | 2 +- src/SDL_utils_c.h | 2 +- src/atomic/SDL_atomic.c | 18 +- src/atomic/SDL_spinlock.c | 8 +- src/audio/SDL_audio.c | 1604 ++++++---- src/audio/SDL_audio_c.h | 6 +- src/audio/SDL_audio_channel_converters.h | 60 +- src/audio/SDL_audio_resampler_filter.h | 4 +- src/audio/SDL_audiocvt.c | 98 +- src/audio/SDL_audiodev.c | 18 +- src/audio/SDL_audiodev_c.h | 8 +- src/audio/SDL_audioqueue.c | 38 +- src/audio/SDL_audioqueue.h | 2 +- src/audio/SDL_audioresample.c | 10 +- src/audio/SDL_audioresample.h | 4 +- src/audio/SDL_audiotypecvt.c | 181 +- src/audio/SDL_mixer.c | 14 +- src/audio/SDL_sysaudio.h | 63 +- src/audio/SDL_wave.c | 72 +- src/audio/SDL_wave.h | 2 +- src/audio/aaudio/SDL_aaudio.c | 330 +- src/audio/aaudio/SDL_aaudio.h | 6 +- src/audio/aaudio/SDL_aaudiofuncs.h | 28 +- src/audio/alsa/SDL_alsa_audio.c | 397 +-- src/audio/alsa/SDL_alsa_audio.h | 10 +- src/audio/android/SDL_androidaudio.c | 17 +- src/audio/android/SDL_androidaudio.h | 4 +- src/audio/coreaudio/SDL_coreaudio.h | 6 +- src/audio/coreaudio/SDL_coreaudio.m | 60 +- src/audio/directsound/SDL_directsound.c | 107 +- src/audio/directsound/SDL_directsound.h | 2 +- src/audio/disk/SDL_diskaudio.c | 24 +- src/audio/disk/SDL_diskaudio.h | 6 +- src/audio/dsp/SDL_dspaudio.c | 44 +- src/audio/dsp/SDL_dspaudio.h | 8 +- src/audio/dummy/SDL_dummyaudio.c | 11 +- src/audio/dummy/SDL_dummyaudio.h | 2 +- src/audio/emscripten/SDL_emscriptenaudio.c | 20 +- src/audio/emscripten/SDL_emscriptenaudio.h | 4 +- src/audio/haiku/SDL_haikuaudio.cc | 3 +- src/audio/haiku/SDL_haikuaudio.h | 4 +- src/audio/jack/SDL_jackaudio.c | 106 +- src/audio/jack/SDL_jackaudio.h | 4 +- src/audio/n3ds/SDL_n3dsaudio.c | 18 +- src/audio/n3ds/SDL_n3dsaudio.h | 8 +- src/audio/netbsd/SDL_netbsdaudio.c | 21 +- src/audio/netbsd/SDL_netbsdaudio.h | 12 +- src/audio/openslES/SDL_openslES.c | 109 +- src/audio/openslES/SDL_openslES.h | 12 +- src/audio/pipewire/SDL_pipewire.c | 190 +- src/audio/pipewire/SDL_pipewire.h | 6 +- src/audio/ps2/SDL_ps2audio.c | 15 +- src/audio/ps2/SDL_ps2audio.h | 12 +- src/audio/psp/SDL_pspaudio.c | 15 +- src/audio/psp/SDL_pspaudio.h | 12 +- src/audio/pulseaudio/SDL_pulseaudio.c | 505 +-- src/audio/pulseaudio/SDL_pulseaudio.h | 12 +- src/audio/qnx/SDL_qsa_audio.c | 26 +- src/audio/qnx/SDL_qsa_audio.h | 5 +- src/audio/sndio/SDL_sndioaudio.c | 48 +- src/audio/sndio/SDL_sndioaudio.h | 4 +- src/audio/vita/SDL_vitaaudio.c | 22 +- src/audio/vita/SDL_vitaaudio.h | 12 +- src/audio/wasapi/SDL_wasapi.c | 182 +- src/audio/wasapi/SDL_wasapi.h | 12 +- src/audio/wasapi/SDL_wasapi_win32.c | 91 +- src/audio/wasapi/SDL_wasapi_winrt.cpp | 18 +- src/core/SDL_core_unsupported.c | 235 ++ src/core/SDL_runapp.c | 11 +- src/core/android/SDL_android.c | 72 +- src/core/android/SDL_android.h | 9 +- src/core/freebsd/SDL_evdev_kbd_freebsd.c | 24 +- src/core/gdk/SDL_gdk.cpp | 2 +- src/core/gdk/SDL_gdk.h | 2 +- src/core/haiku/SDL_BApp.h | 2 +- src/core/haiku/SDL_BeApp.cc | 10 +- src/core/haiku/SDL_BeApp.h | 2 +- src/core/linux/SDL_dbus.c | 37 +- src/core/linux/SDL_dbus.h | 2 +- src/core/linux/SDL_evdev.c | 90 +- src/core/linux/SDL_evdev.h | 5 +- src/core/linux/SDL_evdev_capabilities.c | 2 +- src/core/linux/SDL_evdev_capabilities.h | 3 +- src/core/linux/SDL_evdev_kbd.c | 225 +- src/core/linux/SDL_evdev_kbd.h | 5 +- .../linux/SDL_evdev_kbd_default_accents.h | 2 +- src/core/linux/SDL_evdev_kbd_default_keymap.h | 2 +- src/core/linux/SDL_fcitx.c | 46 +- src/core/linux/SDL_fcitx.h | 2 +- src/core/linux/SDL_ibus.c | 102 +- src/core/linux/SDL_ibus.h | 2 +- src/core/linux/SDL_ime.c | 8 +- src/core/linux/SDL_ime.h | 2 +- src/core/linux/SDL_sandbox.c | 2 +- src/core/linux/SDL_sandbox.h | 2 +- src/core/linux/SDL_system_theme.c | 6 +- src/core/linux/SDL_system_theme.h | 2 +- src/core/linux/SDL_threadprio.c | 12 +- src/core/linux/SDL_udev.c | 148 +- src/core/linux/SDL_udev.h | 6 +- src/core/n3ds/SDL_n3ds.c | 2 +- src/core/ngage/SDL_ngage_runapp.cpp | 2 +- src/core/openbsd/SDL_wscons.h | 2 +- src/core/openbsd/SDL_wscons_kbd.c | 10 +- src/core/openbsd/SDL_wscons_mouse.c | 6 +- src/core/ps2/SDL_ps2.c | 2 +- src/core/psp/SDL_psp.c | 2 +- src/core/unix/SDL_appid.c | 2 +- src/core/unix/SDL_appid.h | 2 +- src/core/unix/SDL_poll.c | 2 +- src/core/unix/SDL_poll.h | 2 +- src/core/windows/SDL_directx.h | 2 +- src/core/windows/SDL_hid.c | 8 +- src/core/windows/SDL_hid.h | 2 +- src/core/windows/SDL_immdevice.c | 45 +- src/core/windows/SDL_immdevice.h | 10 +- src/core/windows/SDL_windows.c | 10 +- src/core/windows/SDL_windows.h | 2 +- src/core/windows/SDL_xinput.c | 9 +- src/core/windows/SDL_xinput.h | 57 +- src/core/windows/pch.c | 2 +- src/core/windows/pch_cpp.cpp | 2 +- src/core/windows/version.rc | 2 +- src/core/winrt/SDL_winrtapp_common.cpp | 2 +- src/core/winrt/SDL_winrtapp_common.h | 2 +- src/core/winrt/SDL_winrtapp_direct3d.cpp | 22 +- src/core/winrt/SDL_winrtapp_direct3d.h | 5 +- src/core/winrt/SDL_winrtapp_xaml.cpp | 2 +- src/core/winrt/SDL_winrtapp_xaml.h | 2 +- src/cpuinfo/SDL_cpuinfo.c | 21 +- src/dynapi/SDL_dynapi.c | 53 +- src/dynapi/SDL_dynapi.h | 2 +- src/dynapi/SDL_dynapi.sym | 105 +- src/dynapi/SDL_dynapi_overrides.h | 107 +- src/dynapi/SDL_dynapi_procs.h | 156 +- src/dynapi/SDL_dynapi_unsupported.h | 48 + src/dynapi/gendynapi.py | 17 +- src/events/SDL_clipboardevents.c | 2 +- src/events/SDL_clipboardevents_c.h | 2 +- src/events/SDL_displayevents.c | 8 +- src/events/SDL_displayevents_c.h | 2 +- src/events/SDL_dropevents.c | 30 +- src/events/SDL_dropevents_c.h | 6 +- src/events/SDL_events.c | 408 ++- src/events/SDL_events_c.h | 3 +- src/events/SDL_keyboard.c | 55 +- src/events/SDL_keyboard_c.h | 2 +- src/events/SDL_keysym_to_scancode.c | 2 +- src/events/SDL_keysym_to_scancode_c.h | 2 +- src/events/SDL_mouse.c | 80 +- src/events/SDL_mouse_c.h | 5 +- src/events/SDL_pen.c | 1099 +++++++ src/events/SDL_pen_c.h | 341 ++ src/events/SDL_quit.c | 2 +- src/events/SDL_scancode_tables.c | 2 +- src/events/SDL_scancode_tables_c.h | 2 +- src/events/SDL_touch.c | 68 +- src/events/SDL_touch_c.h | 5 +- src/events/SDL_windowevents.c | 32 +- src/events/SDL_windowevents_c.h | 2 +- src/events/blank_cursor.h | 2 +- src/events/default_cursor.h | 2 +- src/events/scancodes_ascii.h | 2 +- src/events/scancodes_darwin.h | 2 +- src/events/scancodes_linux.h | 2 +- src/events/scancodes_windows.h | 291 +- src/events/scancodes_xfree86.h | 2 +- src/file/SDL_rwops.c | 96 +- src/file/cocoa/SDL_rwopsbundlesupport.h | 2 +- src/file/cocoa/SDL_rwopsbundlesupport.m | 2 +- src/file/n3ds/SDL_rwopsromfs.c | 5 +- src/file/n3ds/SDL_rwopsromfs.h | 2 +- src/filesystem/android/SDL_sysfilesystem.c | 5 +- src/filesystem/cocoa/SDL_sysfilesystem.m | 19 +- src/filesystem/dummy/SDL_sysfilesystem.c | 2 +- src/filesystem/emscripten/SDL_sysfilesystem.c | 18 +- src/filesystem/gdk/SDL_sysfilesystem.cpp | 17 +- src/filesystem/haiku/SDL_sysfilesystem.cc | 29 +- src/filesystem/n3ds/SDL_sysfilesystem.c | 7 +- src/filesystem/ps2/SDL_sysfilesystem.c | 6 +- src/filesystem/psp/SDL_sysfilesystem.c | 6 +- src/filesystem/riscos/SDL_sysfilesystem.c | 29 +- src/filesystem/unix/SDL_sysfilesystem.c | 81 +- src/filesystem/vita/SDL_sysfilesystem.c | 9 +- src/filesystem/windows/SDL_sysfilesystem.c | 15 +- src/filesystem/winrt/SDL_sysfilesystem.cpp | 21 +- src/haptic/SDL_haptic.c | 12 +- src/haptic/SDL_haptic_c.h | 2 +- src/haptic/SDL_syshaptic.h | 2 +- src/haptic/android/SDL_syshaptic.c | 25 +- src/haptic/android/SDL_syshaptic_c.h | 2 +- src/haptic/darwin/SDL_syshaptic.c | 83 +- src/haptic/darwin/SDL_syshaptic_c.h | 2 +- src/haptic/dummy/SDL_syshaptic.c | 2 +- src/haptic/linux/SDL_syshaptic.c | 50 +- src/haptic/windows/SDL_dinputhaptic.c | 80 +- src/haptic/windows/SDL_dinputhaptic_c.h | 2 +- src/haptic/windows/SDL_windowshaptic.c | 15 +- src/haptic/windows/SDL_windowshaptic_c.h | 2 +- src/haptic/windows/SDL_xinputhaptic.c | 37 +- src/haptic/windows/SDL_xinputhaptic_c.h | 2 +- src/hidapi/BUILD.cmake.md | 2 +- src/hidapi/CMakeLists.txt | 3 + src/hidapi/SDL_hidapi.c | 4 +- src/hidapi/SDL_hidapi_android.h | 2 +- src/hidapi/SDL_hidapi_c.h | 2 +- src/hidapi/SDL_hidapi_ios.h | 2 +- src/hidapi/SDL_hidapi_libusb.h | 2 +- src/hidapi/SDL_hidapi_linux.h | 2 +- src/hidapi/SDL_hidapi_mac.h | 2 +- src/hidapi/SDL_hidapi_netbsd.h | 25 + src/hidapi/SDL_hidapi_steamxbox.h | 2 +- src/hidapi/SDL_hidapi_windows.h | 2 +- src/hidapi/doxygen/Doxyfile | 2 +- src/hidapi/hidtest/test.c | 2 +- src/hidapi/libusb/hid.c | 7 +- src/hidapi/libusb/hidapi_thread_sdl.h | 2 +- src/hidapi/mac/hid.c | 24 +- src/hidapi/netbsd/CMakeLists.txt | 35 + src/hidapi/netbsd/README.md | 29 + src/hidapi/netbsd/hid.c | 1173 +++++++ src/hidapi/pc/hidapi-netbsd.pc.in | 11 + src/hidapi/src/CMakeLists.txt | 17 +- src/hidapi/windows/hid.c | 12 +- .../windows/hidapi_descriptor_reconstruct.h | 15 +- .../windows/pp_data_dump/pp_data_dump.c | 130 +- .../test/hid_report_reconstructor_test.c | 144 +- src/joystick/SDL_gamepad.c | 731 +++-- src/joystick/SDL_gamepad_c.h | 2 +- src/joystick/SDL_gamepad_db.h | 128 +- src/joystick/SDL_joystick.c | 1041 +++--- src/joystick/SDL_joystick_c.h | 55 +- src/joystick/SDL_steam_virtual_gamepad.c | 248 ++ .../SDL_steam_virtual_gamepad.h} | 30 +- src/joystick/SDL_sysjoystick.h | 8 +- src/joystick/android/SDL_sysjoystick.c | 50 +- src/joystick/android/SDL_sysjoystick_c.h | 2 +- src/joystick/apple/SDL_mfijoystick.m | 978 ++++-- src/joystick/apple/SDL_mfijoystick_c.h | 25 +- src/joystick/bsd/SDL_bsdjoystick.c | 51 +- src/joystick/controller_list.h | 60 +- src/joystick/controller_type.h | 1 + src/joystick/darwin/SDL_iokitjoystick.c | 64 +- src/joystick/darwin/SDL_iokitjoystick_c.h | 3 +- src/joystick/dummy/SDL_sysjoystick.c | 8 +- src/joystick/emscripten/SDL_sysjoystick.c | 33 +- src/joystick/emscripten/SDL_sysjoystick_c.h | 2 +- src/joystick/haiku/SDL_haikujoystick.cc | 24 +- src/joystick/hidapi/SDL_hidapi_combined.c | 2 +- src/joystick/hidapi/SDL_hidapi_gamecube.c | 60 +- src/joystick/hidapi/SDL_hidapi_luna.c | 100 +- src/joystick/hidapi/SDL_hidapi_nintendo.h | 2 +- src/joystick/hidapi/SDL_hidapi_ps3.c | 208 +- src/joystick/hidapi/SDL_hidapi_ps4.c | 94 +- src/joystick/hidapi/SDL_hidapi_ps5.c | 176 +- src/joystick/hidapi/SDL_hidapi_rumble.c | 6 +- src/joystick/hidapi/SDL_hidapi_rumble.h | 2 +- src/joystick/hidapi/SDL_hidapi_shield.c | 114 +- src/joystick/hidapi/SDL_hidapi_stadia.c | 59 +- src/joystick/hidapi/SDL_hidapi_steam.c | 67 +- src/joystick/hidapi/SDL_hidapi_steamdeck.c | 443 +++ src/joystick/hidapi/SDL_hidapi_switch.c | 516 +-- src/joystick/hidapi/SDL_hidapi_wii.c | 134 +- src/joystick/hidapi/SDL_hidapi_xbox360.c | 44 +- src/joystick/hidapi/SDL_hidapi_xbox360w.c | 37 +- src/joystick/hidapi/SDL_hidapi_xboxone.c | 122 +- src/joystick/hidapi/SDL_hidapijoystick.c | 77 +- src/joystick/hidapi/SDL_hidapijoystick_c.h | 6 +- .../hidapi/steam/controller_constants.h | 89 +- .../hidapi/steam/controller_structs.h | 166 +- src/joystick/linux/SDL_sysjoystick.c | 355 ++- src/joystick/linux/SDL_sysjoystick_c.h | 2 +- src/joystick/n3ds/SDL_sysjoystick.c | 32 +- src/joystick/ps2/SDL_sysjoystick.c | 11 +- src/joystick/psp/SDL_sysjoystick.c | 10 +- src/joystick/steam/SDL_steamcontroller.c | 2 +- src/joystick/steam/SDL_steamcontroller.h | 2 +- src/joystick/usb_ids.h | 24 +- src/joystick/virtual/SDL_virtualjoystick.c | 54 +- src/joystick/virtual/SDL_virtualjoystick_c.h | 2 +- src/joystick/vita/SDL_sysjoystick.c | 10 +- src/joystick/windows/SDL_dinputjoystick.c | 49 +- src/joystick/windows/SDL_dinputjoystick_c.h | 2 +- src/joystick/windows/SDL_rawinputjoystick.c | 101 +- src/joystick/windows/SDL_rawinputjoystick_c.h | 2 +- .../windows/SDL_windows_gaming_input.c | 448 +-- src/joystick/windows/SDL_windowsjoystick.c | 47 +- src/joystick/windows/SDL_windowsjoystick_c.h | 3 +- src/joystick/windows/SDL_xinputjoystick.c | 196 +- src/joystick/windows/SDL_xinputjoystick_c.h | 3 +- src/libm/e_exp.c | 9 + src/libm/math_libm.h | 2 +- src/loadso/dlopen/SDL_sysloadso.c | 10 +- src/loadso/dummy/SDL_sysloadso.c | 2 +- src/loadso/windows/SDL_sysloadso.c | 10 +- src/locale/SDL_locale.c | 7 +- src/locale/SDL_syslocale.h | 2 +- src/locale/android/SDL_syslocale.c | 2 +- src/locale/dummy/SDL_syslocale.c | 2 +- src/locale/emscripten/SDL_syslocale.c | 2 +- src/locale/haiku/SDL_syslocale.cc | 2 +- src/locale/macos/SDL_syslocale.m | 2 +- src/locale/n3ds/SDL_syslocale.c | 2 +- src/locale/unix/SDL_syslocale.c | 10 +- src/locale/vita/SDL_syslocale.c | 2 +- src/locale/windows/SDL_syslocale.c | 8 +- src/locale/winrt/SDL_syslocale.c | 2 +- src/main/SDL_main_callbacks.c | 142 + .../SDL_main_callbacks.h} | 21 +- src/main/emscripten/SDL_sysmain_callbacks.c | 47 + src/main/generic/SDL_sysmain_callbacks.c | 83 + src/main/ios/SDL_sysmain_callbacks.m | 82 + src/misc/SDL_sysurl.h | 2 +- src/misc/SDL_url.c | 4 +- src/misc/android/SDL_sysurl.c | 2 +- src/misc/dummy/SDL_sysurl.c | 2 +- src/misc/emscripten/SDL_sysurl.c | 2 +- src/misc/haiku/SDL_sysurl.cc | 2 +- src/misc/ios/SDL_sysurl.m | 2 +- src/misc/macos/SDL_sysurl.m | 2 +- src/misc/riscos/SDL_sysurl.c | 2 +- src/misc/unix/SDL_sysurl.c | 2 +- src/misc/vita/SDL_sysurl.c | 2 +- src/misc/windows/SDL_sysurl.c | 6 +- src/misc/winrt/SDL_sysurl.cpp | 4 +- src/power/SDL_power.c | 6 +- src/power/SDL_syspower.h | 2 +- src/power/android/SDL_syspower.c | 2 +- src/power/emscripten/SDL_syspower.c | 2 +- src/power/haiku/SDL_syspower.c | 2 +- src/power/linux/SDL_syspower.c | 12 +- src/power/macos/SDL_syspower.c | 2 +- src/power/n3ds/SDL_syspower.c | 2 +- src/power/psp/SDL_syspower.c | 2 +- src/power/uikit/SDL_syspower.h | 2 +- src/power/uikit/SDL_syspower.m | 2 +- src/power/vita/SDL_syspower.c | 2 +- src/power/windows/SDL_syspower.c | 2 +- src/power/winrt/SDL_syspower.cpp | 2 +- src/render/SDL_d3dmath.c | 2 +- src/render/SDL_d3dmath.h | 2 +- src/render/SDL_render.c | 391 +-- .../SDL_render_unsupported.c} | 21 +- src/render/SDL_sysrender.h | 23 +- src/render/SDL_yuv_sw.c | 9 +- src/render/SDL_yuv_sw_c.h | 2 +- src/render/direct3d/SDL_render_d3d.c | 125 +- src/render/direct3d/SDL_shaders_d3d.c | 2 +- src/render/direct3d/SDL_shaders_d3d.h | 2 +- src/render/direct3d11/SDL_render_d3d11.c | 372 ++- src/render/direct3d11/SDL_render_winrt.cpp | 27 +- src/render/direct3d11/SDL_render_winrt.h | 2 +- src/render/direct3d11/SDL_shaders_d3d11.c | 2 +- src/render/direct3d11/SDL_shaders_d3d11.h | 2 +- src/render/direct3d12/SDL_render_d3d12.c | 354 ++- .../direct3d12/SDL_render_d3d12_xbox.cpp | 155 +- src/render/direct3d12/SDL_render_d3d12_xbox.h | 31 +- src/render/direct3d12/SDL_shaders_d3d12.c | 2 +- src/render/direct3d12/SDL_shaders_d3d12.h | 2 +- .../direct3d12/SDL_shaders_d3d12_xboxone.cpp | 125 +- .../SDL_shaders_d3d12_xboxseries.cpp | 125 +- src/render/metal/SDL_render_metal.m | 89 +- src/render/opengl/SDL_glfuncs.h | 2 +- src/render/opengl/SDL_render_gl.c | 262 +- src/render/opengl/SDL_shaders_gl.c | 4 +- src/render/opengl/SDL_shaders_gl.h | 2 +- src/render/opengles2/SDL_gles2funcs.h | 2 +- src/render/opengles2/SDL_render_gles2.c | 308 +- src/render/opengles2/SDL_shaders_gles2.c | 2 +- src/render/opengles2/SDL_shaders_gles2.h | 2 +- src/render/ps2/SDL_render_ps2.c | 49 +- src/render/psp/SDL_render_psp.c | 73 +- src/render/software/SDL_blendfillrect.c | 8 +- src/render/software/SDL_blendfillrect.h | 2 +- src/render/software/SDL_blendline.c | 10 +- src/render/software/SDL_blendline.h | 2 +- src/render/software/SDL_blendpoint.c | 8 +- src/render/software/SDL_blendpoint.h | 2 +- src/render/software/SDL_draw.h | 2 +- src/render/software/SDL_drawline.c | 10 +- src/render/software/SDL_drawline.h | 2 +- src/render/software/SDL_drawpoint.c | 6 +- src/render/software/SDL_drawpoint.h | 2 +- src/render/software/SDL_render_sw.c | 104 +- src/render/software/SDL_render_sw_c.h | 2 +- src/render/software/SDL_rotate.c | 34 +- src/render/software/SDL_rotate.h | 2 +- src/render/software/SDL_triangle.c | 98 +- src/render/software/SDL_triangle.h | 2 +- src/render/vitagxm/SDL_render_vita_gxm.c | 52 +- .../vitagxm/SDL_render_vita_gxm_memory.c | 10 +- .../vitagxm/SDL_render_vita_gxm_memory.h | 2 +- .../vitagxm/SDL_render_vita_gxm_shaders.h | 2 +- .../vitagxm/SDL_render_vita_gxm_tools.c | 8 +- .../vitagxm/SDL_render_vita_gxm_tools.h | 2 +- .../vitagxm/SDL_render_vita_gxm_types.h | 2 +- src/sensor/SDL_sensor.c | 44 +- src/sensor/SDL_sensor_c.h | 5 +- src/sensor/SDL_syssensor.h | 4 +- src/sensor/android/SDL_androidsensor.c | 10 +- src/sensor/android/SDL_androidsensor.h | 2 +- src/sensor/coremotion/SDL_coremotionsensor.h | 2 +- src/sensor/coremotion/SDL_coremotionsensor.m | 10 +- src/sensor/dummy/SDL_dummysensor.c | 2 +- src/sensor/dummy/SDL_dummysensor.h | 2 +- src/sensor/n3ds/SDL_n3dssensor.c | 6 +- src/sensor/vita/SDL_vitasensor.c | 14 +- src/sensor/vita/SDL_vitasensor.h | 2 +- src/sensor/windows/SDL_windowssensor.c | 18 +- src/sensor/windows/SDL_windowssensor.h | 2 +- src/stdlib/SDL_crc16.c | 2 +- src/stdlib/SDL_crc32.c | 2 +- src/stdlib/SDL_getenv.c | 24 +- src/stdlib/SDL_iconv.c | 44 +- src/stdlib/SDL_malloc.c | 11 +- src/stdlib/SDL_mslibc.c | 74 +- src/stdlib/SDL_mslibc_x64.masm | 29 + src/stdlib/SDL_qsort.c | 2 +- src/stdlib/SDL_stdlib.c | 2 +- src/stdlib/SDL_string.c | 146 +- src/stdlib/SDL_strtokr.c | 2 +- src/stdlib/SDL_vacopy.h | 2 +- src/test/SDL_test_assert.c | 6 +- src/test/SDL_test_common.c | 212 +- src/test/SDL_test_compare.c | 65 +- src/test/SDL_test_crc32.c | 14 +- src/test/SDL_test_font.c | 12 +- src/test/SDL_test_fuzzer.c | 4 +- src/test/SDL_test_harness.c | 48 +- src/test/SDL_test_log.c | 2 +- src/test/SDL_test_md5.c | 10 +- src/test/SDL_test_memory.c | 14 +- src/test/SDL_test_random.c | 8 +- src/thread/SDL_systhread.h | 2 +- src/thread/SDL_thread.c | 57 +- src/thread/SDL_thread_c.h | 2 +- src/thread/generic/SDL_syscond.c | 10 +- src/thread/generic/SDL_syscond_c.h | 2 +- src/thread/generic/SDL_sysmutex.c | 151 +- src/thread/generic/SDL_sysmutex_c.h | 2 +- src/thread/generic/SDL_sysrwlock.c | 125 +- src/thread/generic/SDL_sysrwlock_c.h | 12 +- src/thread/generic/SDL_syssem.c | 9 +- src/thread/generic/SDL_systhread.c | 2 +- src/thread/generic/SDL_systhread_c.h | 2 +- src/thread/generic/SDL_systls.c | 2 +- src/thread/n3ds/SDL_syscond.c | 12 +- src/thread/n3ds/SDL_sysmutex.c | 44 +- src/thread/n3ds/SDL_sysmutex_c.h | 2 +- src/thread/n3ds/SDL_syssem.c | 13 +- src/thread/n3ds/SDL_systhread.c | 28 +- src/thread/n3ds/SDL_systhread_c.h | 2 +- src/thread/ngage/SDL_sysmutex.cpp | 30 +- src/thread/ngage/SDL_syssem.cpp | 12 +- src/thread/ngage/SDL_systhread.cpp | 2 +- src/thread/ngage/SDL_systhread_c.h | 2 +- src/thread/ps2/SDL_syssem.c | 35 +- src/thread/ps2/SDL_systhread.c | 2 +- src/thread/ps2/SDL_systhread_c.h | 2 +- src/thread/psp/SDL_syscond.c | 204 -- src/thread/psp/SDL_sysmutex.c | 105 +- src/thread/psp/SDL_sysmutex_c.h | 2 +- src/thread/psp/SDL_syssem.c | 16 +- src/thread/psp/SDL_systhread.c | 2 +- src/thread/psp/SDL_systhread_c.h | 2 +- src/thread/pthread/SDL_syscond.c | 8 +- src/thread/pthread/SDL_sysmutex.c | 154 +- src/thread/pthread/SDL_sysmutex_c.h | 2 +- src/thread/pthread/SDL_sysrwlock.c | 57 +- src/thread/pthread/SDL_syssem.c | 14 +- src/thread/pthread/SDL_systhread.c | 6 +- src/thread/pthread/SDL_systhread_c.h | 2 +- src/thread/pthread/SDL_systls.c | 2 +- src/thread/stdcpp/SDL_syscond.cpp | 8 +- src/thread/stdcpp/SDL_sysmutex.cpp | 63 +- src/thread/stdcpp/SDL_sysmutex_c.h | 2 +- src/thread/stdcpp/SDL_sysrwlock.cpp | 94 +- src/thread/stdcpp/SDL_systhread.cpp | 2 +- src/thread/stdcpp/SDL_systhread_c.h | 2 +- src/thread/vita/SDL_syscond.c | 204 -- src/thread/vita/SDL_sysmutex.c | 99 +- src/thread/vita/SDL_sysmutex_c.h | 2 +- src/thread/vita/SDL_syssem.c | 16 +- src/thread/vita/SDL_systhread.c | 2 +- src/thread/vita/SDL_systhread_c.h | 2 +- src/thread/windows/SDL_syscond_cv.c | 29 +- src/thread/windows/SDL_sysmutex.c | 100 +- src/thread/windows/SDL_sysmutex_c.h | 6 +- src/thread/windows/SDL_sysrwlock_srw.c | 87 +- src/thread/windows/SDL_syssem.c | 31 +- src/thread/windows/SDL_systhread.c | 10 +- src/thread/windows/SDL_systhread_c.h | 2 +- src/thread/windows/SDL_systls.c | 2 +- src/timer/SDL_timer.c | 15 +- src/timer/SDL_timer_c.h | 2 +- src/timer/haiku/SDL_systimer.c | 2 +- src/timer/n3ds/SDL_systimer.c | 2 +- src/timer/ngage/SDL_systimer.cpp | 2 +- src/timer/ps2/SDL_systimer.c | 2 +- src/timer/psp/SDL_systimer.c | 2 +- src/timer/unix/SDL_systimer.c | 2 +- src/timer/vita/SDL_systimer.c | 2 +- src/timer/windows/SDL_systimer.c | 2 +- src/video/SDL_RLEaccel.c | 20 +- src/video/SDL_RLEaccel_c.h | 2 +- src/video/SDL_blit.c | 10 +- src/video/SDL_blit.h | 19 +- src/video/SDL_blit_0.c | 572 ++-- src/video/SDL_blit_1.c | 16 +- src/video/SDL_blit_A.c | 10 +- src/video/SDL_blit_N.c | 34 +- src/video/SDL_blit_auto.c | 2 +- src/video/SDL_blit_auto.h | 2 +- src/video/SDL_blit_copy.c | 2 +- src/video/SDL_blit_copy.h | 2 +- src/video/SDL_blit_slow.c | 67 +- src/video/SDL_blit_slow.h | 2 +- src/video/SDL_bmp.c | 119 +- src/video/SDL_clipboard.c | 28 +- src/video/SDL_clipboard_c.h | 2 +- src/video/SDL_egl.c | 54 +- src/video/SDL_egl_c.h | 2 +- src/video/SDL_fillrect.c | 16 +- src/video/SDL_pixels.c | 83 +- src/video/SDL_pixels_c.h | 2 +- src/video/SDL_rect.c | 8 +- src/video/SDL_rect_c.h | 2 +- src/video/SDL_rect_impl.h | 51 +- src/video/SDL_shape.c | 304 -- src/video/SDL_shape_internals.h | 59 - src/video/SDL_stretch.c | 27 +- src/video/SDL_surface.c | 197 +- src/video/SDL_surface_pixel_impl.h | 80 + src/video/SDL_sysvideo.h | 104 +- src/video/SDL_sysvideocapture.h | 92 + src/video/SDL_video.c | 1232 ++++---- src/video/SDL_video_c.h | 4 +- src/video/SDL_video_capture.c | 968 ++++++ src/video/SDL_video_capture_apple.m | 659 ++++ ...L_windowsshape.h => SDL_video_capture_c.h} | 25 +- src/video/SDL_video_capture_v4l2.c | 1217 +++++++ src/video/SDL_video_unsupported.c | 91 + src/video/SDL_vulkan_internal.h | 10 +- src/video/SDL_vulkan_utils.c | 48 +- src/video/SDL_yuv.c | 26 +- src/video/SDL_yuv_c.h | 2 +- src/video/android/SDL_android_video_capture.c | 713 +++++ src/video/android/SDL_androidclipboard.c | 2 +- src/video/android/SDL_androidclipboard.h | 2 +- src/video/android/SDL_androidevents.c | 31 +- src/video/android/SDL_androidevents.h | 2 +- src/video/android/SDL_androidgl.c | 2 +- src/video/android/SDL_androidgl.h | 2 +- src/video/android/SDL_androidkeyboard.c | 24 +- src/video/android/SDL_androidkeyboard.h | 7 +- src/video/android/SDL_androidmessagebox.c | 2 +- src/video/android/SDL_androidmessagebox.h | 2 +- src/video/android/SDL_androidmouse.c | 13 +- src/video/android/SDL_androidmouse.h | 2 +- src/video/android/SDL_androidtouch.c | 4 +- src/video/android/SDL_androidtouch.h | 2 +- src/video/android/SDL_androidvideo.c | 15 +- src/video/android/SDL_androidvideo.h | 2 +- src/video/android/SDL_androidvulkan.c | 27 +- src/video/android/SDL_androidvulkan.h | 8 +- src/video/android/SDL_androidwindow.c | 31 +- src/video/android/SDL_androidwindow.h | 7 +- src/video/cocoa/SDL_cocoaclipboard.h | 2 +- src/video/cocoa/SDL_cocoaclipboard.m | 4 +- src/video/cocoa/SDL_cocoaevents.h | 2 +- src/video/cocoa/SDL_cocoaevents.m | 45 +- src/video/cocoa/SDL_cocoakeyboard.h | 2 +- src/video/cocoa/SDL_cocoakeyboard.m | 16 +- src/video/cocoa/SDL_cocoamessagebox.h | 2 +- src/video/cocoa/SDL_cocoamessagebox.m | 2 +- src/video/cocoa/SDL_cocoametalview.h | 2 +- src/video/cocoa/SDL_cocoametalview.m | 4 +- src/video/cocoa/SDL_cocoamodes.h | 2 +- src/video/cocoa/SDL_cocoamodes.m | 6 +- src/video/cocoa/SDL_cocoamouse.h | 2 +- src/video/cocoa/SDL_cocoamouse.m | 42 +- src/video/cocoa/SDL_cocoaopengl.h | 2 +- src/video/cocoa/SDL_cocoaopengl.m | 2 +- src/video/cocoa/SDL_cocoaopengles.h | 2 +- src/video/cocoa/SDL_cocoaopengles.m | 2 +- src/video/cocoa/SDL_cocoashape.m | 115 - src/video/cocoa/SDL_cocoavideo.h | 2 +- src/video/cocoa/SDL_cocoavideo.m | 14 +- src/video/cocoa/SDL_cocoavulkan.h | 8 +- src/video/cocoa/SDL_cocoavulkan.m | 30 +- src/video/cocoa/SDL_cocoawindow.h | 30 +- src/video/cocoa/SDL_cocoawindow.m | 844 +++-- src/video/dummy/SDL_nullevents.c | 2 +- src/video/dummy/SDL_nullevents_c.h | 2 +- src/video/dummy/SDL_nullframebuffer.c | 29 +- src/video/dummy/SDL_nullframebuffer_c.h | 2 +- src/video/dummy/SDL_nullvideo.c | 18 +- src/video/dummy/SDL_nullvideo.h | 2 +- src/video/emscripten/SDL_emscriptenevents.c | 33 +- src/video/emscripten/SDL_emscriptenevents.h | 2 +- .../emscripten/SDL_emscriptenframebuffer.c | 6 +- .../emscripten/SDL_emscriptenframebuffer.h | 2 +- src/video/emscripten/SDL_emscriptenmouse.c | 41 +- src/video/emscripten/SDL_emscriptenmouse.h | 2 +- src/video/emscripten/SDL_emscriptenopengles.c | 4 +- src/video/emscripten/SDL_emscriptenopengles.h | 2 +- src/video/emscripten/SDL_emscriptenvideo.c | 45 +- src/video/emscripten/SDL_emscriptenvideo.h | 2 +- src/video/gdk/SDL_gdktextinput.cpp | 28 +- src/video/gdk/SDL_gdktextinput.h | 2 +- src/video/haiku/SDL_BApp.h | 2 +- src/video/haiku/SDL_BWin.h | 4 +- src/video/haiku/SDL_bclipboard.cc | 6 +- src/video/haiku/SDL_bclipboard.h | 2 +- src/video/haiku/SDL_bevents.cc | 2 +- src/video/haiku/SDL_bevents.h | 2 +- src/video/haiku/SDL_bframebuffer.cc | 4 +- src/video/haiku/SDL_bframebuffer.h | 2 +- src/video/haiku/SDL_bkeyboard.cc | 2 +- src/video/haiku/SDL_bkeyboard.h | 2 +- src/video/haiku/SDL_bmessagebox.cc | 8 +- src/video/haiku/SDL_bmessagebox.h | 2 +- src/video/haiku/SDL_bmodes.cc | 2 +- src/video/haiku/SDL_bmodes.h | 2 +- src/video/haiku/SDL_bopengl.cc | 10 +- src/video/haiku/SDL_bopengl.h | 2 +- src/video/haiku/SDL_bvideo.cc | 22 +- src/video/haiku/SDL_bvideo.h | 2 +- src/video/haiku/SDL_bwindow.cc | 63 +- src/video/haiku/SDL_bwindow.h | 8 +- src/video/kmsdrm/SDL_kmsdrmdyn.c | 14 +- src/video/kmsdrm/SDL_kmsdrmdyn.h | 2 +- src/video/kmsdrm/SDL_kmsdrmevents.c | 2 +- src/video/kmsdrm/SDL_kmsdrmevents.h | 4 +- src/video/kmsdrm/SDL_kmsdrmmouse.c | 21 +- src/video/kmsdrm/SDL_kmsdrmmouse.h | 2 +- src/video/kmsdrm/SDL_kmsdrmopengles.c | 13 +- src/video/kmsdrm/SDL_kmsdrmopengles.h | 2 +- src/video/kmsdrm/SDL_kmsdrmsym.h | 3 +- src/video/kmsdrm/SDL_kmsdrmvideo.c | 127 +- src/video/kmsdrm/SDL_kmsdrmvideo.h | 10 +- src/video/kmsdrm/SDL_kmsdrmvulkan.c | 27 +- src/video/kmsdrm/SDL_kmsdrmvulkan.h | 8 +- src/video/n3ds/SDL_n3dsevents.c | 2 +- src/video/n3ds/SDL_n3dsevents_c.h | 2 +- src/video/n3ds/SDL_n3dsframebuffer.c | 45 +- src/video/n3ds/SDL_n3dsframebuffer_c.h | 2 +- src/video/n3ds/SDL_n3dsswkb.c | 2 +- src/video/n3ds/SDL_n3dsswkb.h | 2 +- src/video/n3ds/SDL_n3dstouch.c | 6 +- src/video/n3ds/SDL_n3dstouch.h | 2 +- src/video/n3ds/SDL_n3dsvideo.c | 21 +- src/video/n3ds/SDL_n3dsvideo.h | 2 +- src/video/ngage/SDL_ngageevents.cpp | 2 +- src/video/ngage/SDL_ngageevents_c.h | 2 +- src/video/ngage/SDL_ngageframebuffer.cpp | 6 +- src/video/ngage/SDL_ngageframebuffer_c.h | 2 +- src/video/ngage/SDL_ngagevideo.cpp | 8 +- src/video/ngage/SDL_ngagevideo.h | 2 +- src/video/ngage/SDL_ngagewindow.cpp | 8 +- src/video/ngage/SDL_ngagewindow.h | 11 +- src/video/offscreen/SDL_offscreenevents.c | 2 +- src/video/offscreen/SDL_offscreenevents_c.h | 2 +- .../offscreen/SDL_offscreenframebuffer.c | 29 +- .../offscreen/SDL_offscreenframebuffer_c.h | 2 +- src/video/offscreen/SDL_offscreenopengles.c | 2 +- src/video/offscreen/SDL_offscreenopengles.h | 2 +- src/video/offscreen/SDL_offscreenvideo.c | 6 +- src/video/offscreen/SDL_offscreenvideo.h | 2 +- src/video/offscreen/SDL_offscreenwindow.c | 13 +- src/video/offscreen/SDL_offscreenwindow.h | 5 +- src/video/ps2/SDL_ps2video.c | 7 +- src/video/ps2/SDL_ps2video.h | 8 +- src/video/psp/SDL_pspevents.c | 2 +- src/video/psp/SDL_pspevents_c.h | 4 +- src/video/psp/SDL_pspgl.c | 2 +- src/video/psp/SDL_pspgl_c.h | 2 +- src/video/psp/SDL_pspmouse.c | 2 +- src/video/psp/SDL_pspmouse_c.h | 2 +- src/video/psp/SDL_pspvideo.c | 30 +- src/video/psp/SDL_pspvideo.h | 5 +- src/video/qnx/SDL_qnxgl.c | 2 +- src/video/qnx/SDL_qnxvideo.c | 11 +- src/video/raspberry/SDL_rpievents.c | 2 +- src/video/raspberry/SDL_rpievents_c.h | 4 +- src/video/raspberry/SDL_rpimouse.c | 28 +- src/video/raspberry/SDL_rpimouse.h | 2 +- src/video/raspberry/SDL_rpiopengles.c | 2 +- src/video/raspberry/SDL_rpiopengles.h | 2 +- src/video/raspberry/SDL_rpivideo.c | 24 +- src/video/raspberry/SDL_rpivideo.h | 5 +- src/video/riscos/SDL_riscosdefs.h | 2 +- src/video/riscos/SDL_riscosevents.c | 2 +- src/video/riscos/SDL_riscosevents_c.h | 2 +- src/video/riscos/SDL_riscosframebuffer.c | 8 +- src/video/riscos/SDL_riscosframebuffer_c.h | 2 +- src/video/riscos/SDL_riscosmessagebox.c | 2 +- src/video/riscos/SDL_riscosmessagebox.h | 2 +- src/video/riscos/SDL_riscosmodes.c | 20 +- src/video/riscos/SDL_riscosmodes.h | 2 +- src/video/riscos/SDL_riscosmouse.c | 8 +- src/video/riscos/SDL_riscosmouse.h | 2 +- src/video/riscos/SDL_riscosvideo.c | 9 +- src/video/riscos/SDL_riscosvideo.h | 2 +- src/video/riscos/SDL_riscoswindow.c | 18 +- src/video/riscos/SDL_riscoswindow.h | 5 +- src/video/riscos/scancodes_riscos.h | 2 +- src/video/sdlgenblit.pl | 2 +- src/video/uikit/SDL_uikitappdelegate.h | 2 +- src/video/uikit/SDL_uikitappdelegate.m | 17 +- src/video/uikit/SDL_uikitclipboard.h | 2 +- src/video/uikit/SDL_uikitclipboard.m | 2 +- src/video/uikit/SDL_uikitevents.h | 2 +- src/video/uikit/SDL_uikitevents.m | 42 +- src/video/uikit/SDL_uikitmessagebox.h | 2 +- src/video/uikit/SDL_uikitmessagebox.m | 2 +- src/video/uikit/SDL_uikitmetalview.h | 2 +- src/video/uikit/SDL_uikitmetalview.m | 4 +- src/video/uikit/SDL_uikitmodes.h | 2 +- src/video/uikit/SDL_uikitmodes.m | 2 +- src/video/uikit/SDL_uikitopengles.h | 2 +- src/video/uikit/SDL_uikitopengles.m | 2 +- src/video/uikit/SDL_uikitopenglview.h | 2 +- src/video/uikit/SDL_uikitopenglview.m | 2 +- src/video/uikit/SDL_uikitvideo.h | 2 +- src/video/uikit/SDL_uikitvideo.m | 15 +- src/video/uikit/SDL_uikitview.h | 2 +- src/video/uikit/SDL_uikitview.m | 2 +- src/video/uikit/SDL_uikitviewcontroller.h | 2 +- src/video/uikit/SDL_uikitviewcontroller.m | 6 +- src/video/uikit/SDL_uikitvulkan.h | 8 +- src/video/uikit/SDL_uikitvulkan.m | 24 +- src/video/uikit/SDL_uikitwindow.h | 9 +- src/video/uikit/SDL_uikitwindow.m | 55 +- src/video/vita/SDL_vitaframebuffer.c | 4 +- src/video/vita/SDL_vitaframebuffer.h | 2 +- src/video/vita/SDL_vitagl_pvr.c | 6 +- src/video/vita/SDL_vitagl_pvr_c.h | 2 +- src/video/vita/SDL_vitagles.c | 2 +- src/video/vita/SDL_vitagles_c.h | 2 +- src/video/vita/SDL_vitagles_pvr.c | 6 +- src/video/vita/SDL_vitagles_pvr_c.h | 2 +- src/video/vita/SDL_vitakeyboard.c | 4 +- src/video/vita/SDL_vitakeyboard.h | 2 +- src/video/vita/SDL_vitamessagebox.c | 2 +- src/video/vita/SDL_vitamessagebox.h | 2 +- src/video/vita/SDL_vitamouse.c | 4 +- src/video/vita/SDL_vitamouse_c.h | 2 +- src/video/vita/SDL_vitatouch.c | 18 +- src/video/vita/SDL_vitatouch.h | 2 +- src/video/vita/SDL_vitavideo.c | 27 +- src/video/vita/SDL_vitavideo.h | 5 +- src/video/vivante/SDL_vivanteopengles.c | 2 +- src/video/vivante/SDL_vivanteopengles.h | 2 +- src/video/vivante/SDL_vivanteplatform.c | 2 +- src/video/vivante/SDL_vivanteplatform.h | 2 +- src/video/vivante/SDL_vivantevideo.c | 40 +- src/video/vivante/SDL_vivantevideo.h | 7 +- src/video/vivante/SDL_vivantevulkan.c | 26 +- src/video/vivante/SDL_vivantevulkan.h | 8 +- src/video/wayland/SDL_waylandclipboard.c | 24 +- src/video/wayland/SDL_waylandclipboard.h | 2 +- src/video/wayland/SDL_waylanddatamanager.c | 94 +- src/video/wayland/SDL_waylanddatamanager.h | 2 +- src/video/wayland/SDL_waylanddyn.c | 14 +- src/video/wayland/SDL_waylanddyn.h | 2 +- src/video/wayland/SDL_waylandevents.c | 836 +++-- src/video/wayland/SDL_waylandevents_c.h | 46 +- src/video/wayland/SDL_waylandkeyboard.c | 12 +- src/video/wayland/SDL_waylandkeyboard.h | 2 +- src/video/wayland/SDL_waylandmessagebox.c | 56 +- src/video/wayland/SDL_waylandmessagebox.h | 2 +- src/video/wayland/SDL_waylandmouse.c | 141 +- src/video/wayland/SDL_waylandmouse.h | 3 +- src/video/wayland/SDL_waylandopengles.c | 2 +- src/video/wayland/SDL_waylandopengles.h | 2 +- src/video/wayland/SDL_waylandsym.h | 8 +- src/video/wayland/SDL_waylandtouch.c | 283 -- src/video/wayland/SDL_waylandtouch.h | 334 -- src/video/wayland/SDL_waylandvideo.c | 159 +- src/video/wayland/SDL_waylandvideo.h | 19 +- src/video/wayland/SDL_waylandvulkan.c | 31 +- src/video/wayland/SDL_waylandvulkan.h | 8 +- src/video/wayland/SDL_waylandwindow.c | 900 +++--- src/video/wayland/SDL_waylandwindow.h | 35 +- src/video/windows/SDL_msctf.h | 2 +- src/video/windows/SDL_vkeys.h | 74 - src/video/windows/SDL_windowsclipboard.c | 13 +- src/video/windows/SDL_windowsclipboard.h | 2 +- src/video/windows/SDL_windowsevents.c | 913 +++--- src/video/windows/SDL_windowsevents.h | 3 +- src/video/windows/SDL_windowsframebuffer.c | 6 +- src/video/windows/SDL_windowsframebuffer.h | 2 +- src/video/windows/SDL_windowskeyboard.c | 90 +- src/video/windows/SDL_windowskeyboard.h | 2 +- src/video/windows/SDL_windowsmessagebox.c | 35 +- src/video/windows/SDL_windowsmessagebox.h | 2 +- src/video/windows/SDL_windowsmodes.c | 20 +- src/video/windows/SDL_windowsmodes.h | 2 +- src/video/windows/SDL_windowsmouse.c | 367 ++- src/video/windows/SDL_windowsmouse.h | 2 +- src/video/windows/SDL_windowsopengl.c | 39 +- src/video/windows/SDL_windowsopengl.h | 3 +- src/video/windows/SDL_windowsopengles.c | 6 +- src/video/windows/SDL_windowsopengles.h | 2 +- src/video/windows/SDL_windowsshape.c | 90 - src/video/windows/SDL_windowsvideo.c | 25 +- src/video/windows/SDL_windowsvideo.h | 6 +- src/video/windows/SDL_windowsvulkan.c | 28 +- src/video/windows/SDL_windowsvulkan.h | 8 +- src/video/windows/SDL_windowswindow.c | 527 ++-- src/video/windows/SDL_windowswindow.h | 28 +- src/video/windows/wmmsg.h | 2 +- src/video/winrt/SDL_winrtevents.cpp | 2 +- src/video/winrt/SDL_winrtevents_c.h | 7 +- src/video/winrt/SDL_winrtgamebar.cpp | 8 +- src/video/winrt/SDL_winrtgamebar_cpp.h | 2 +- src/video/winrt/SDL_winrtkeyboard.cpp | 410 +-- src/video/winrt/SDL_winrtmessagebox.cpp | 2 +- src/video/winrt/SDL_winrtmessagebox.h | 2 +- src/video/winrt/SDL_winrtmouse.cpp | 28 +- src/video/winrt/SDL_winrtmouse_c.h | 2 +- src/video/winrt/SDL_winrtopengles.cpp | 2 +- src/video/winrt/SDL_winrtopengles.h | 2 +- src/video/winrt/SDL_winrtpointerinput.cpp | 2 +- src/video/winrt/SDL_winrtvideo.cpp | 46 +- src/video/winrt/SDL_winrtvideo_cpp.h | 3 +- src/video/x11/SDL_x11clipboard.c | 10 +- src/video/x11/SDL_x11clipboard.h | 2 +- src/video/x11/SDL_x11dyn.c | 14 +- src/video/x11/SDL_x11dyn.h | 2 +- src/video/x11/SDL_x11events.c | 471 ++- src/video/x11/SDL_x11events.h | 7 +- src/video/x11/SDL_x11framebuffer.c | 8 +- src/video/x11/SDL_x11framebuffer.h | 2 +- src/video/x11/SDL_x11keyboard.c | 2 +- src/video/x11/SDL_x11keyboard.h | 2 +- src/video/x11/SDL_x11messagebox.c | 26 +- src/video/x11/SDL_x11messagebox.h | 2 +- src/video/x11/SDL_x11modes.c | 51 +- src/video/x11/SDL_x11modes.h | 4 +- src/video/x11/SDL_x11mouse.c | 113 +- src/video/x11/SDL_x11mouse.h | 3 +- src/video/x11/SDL_x11opengl.c | 72 +- src/video/x11/SDL_x11opengl.h | 12 +- src/video/x11/SDL_x11opengles.c | 2 +- src/video/x11/SDL_x11opengles.h | 2 +- src/video/x11/SDL_x11pen.c | 696 ++++ src/video/x11/SDL_x11pen.h | 54 + src/video/x11/SDL_x11shape.c | 98 - src/video/x11/SDL_x11sym.h | 11 +- src/video/x11/SDL_x11touch.c | 2 +- src/video/x11/SDL_x11touch.h | 2 +- src/video/x11/SDL_x11video.c | 41 +- src/video/x11/SDL_x11video.h | 2 +- src/video/x11/SDL_x11vulkan.c | 38 +- src/video/x11/SDL_x11vulkan.h | 8 +- src/video/x11/SDL_x11window.c | 755 +++-- src/video/x11/SDL_x11window.h | 31 +- src/video/x11/SDL_x11xfixes.c | 2 +- src/video/x11/SDL_x11xfixes.h | 2 +- src/video/x11/SDL_x11xinput2.c | 199 +- src/video/x11/SDL_x11xinput2.h | 5 +- src/video/yuv2rgb/yuv_rgb.c | 9 + src/video/yuv2rgb/yuv_rgb_std_func.h | 63 +- test/CMakeLists.txt | 144 +- test/checkkeys.c | 13 +- test/checkkeysthreads.c | 8 +- test/gamepadutils.c | 248 +- test/gamepadutils.h | 14 +- test/loopwave.c | 124 +- test/{testfilesystem_pre.c => pretest.c} | 7 +- test/shapes/p01_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p01_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p01_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p02_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p02_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p02_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p03_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p03_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p04_shape1.bmp | Bin 51346 -> 0 bytes test/shapes/p04_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p04_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p04_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p05_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p06_shape1alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p06_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p06_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p06_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p07_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p07_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p07_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p08_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p08_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p08_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p09_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p09_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p09_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p10_shape1.bmp | Bin 51346 -> 0 bytes test/shapes/p10_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p10_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p10_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p11_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p11_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p11_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p12_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p12_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p13_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p13_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p13_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p14_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p14_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p15_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p15_shape32alpha.bmp | Bin 1638538 -> 0 bytes test/shapes/p15_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/p16_shape1.bmp | Bin 51346 -> 0 bytes test/shapes/p16_shape24.bmp | Bin 1228938 -> 0 bytes test/shapes/p16_shape8.bmp | Bin 410678 -> 0 bytes test/shapes/trollface_24.bmp | Bin 196662 -> 0 bytes test/shapes/trollface_32alpha.bmp | Bin 262198 -> 0 bytes test/testatomic.c | 20 +- test/testaudio.c | 589 ++-- test/testaudiocapture.c | 187 +- test/testaudiohotplug.c | 10 +- test/testaudioinfo.c | 8 +- test/testaudiostreamdynamicresample.c | 10 +- test/testautomation.c | 10 +- test/testautomation_audio.c | 58 +- test/testautomation_clipboard.c | 6 +- test/testautomation_events.c | 6 +- test/testautomation_guid.c | 6 +- test/testautomation_hints.c | 6 +- test/testautomation_images.c | 24 +- test/testautomation_images.h | 2 +- test/testautomation_intrinsics.c | 8 +- test/testautomation_joystick.c | 63 +- test/testautomation_keyboard.c | 28 +- test/testautomation_main.c | 2 +- test/testautomation_math.c | 46 +- test/testautomation_mouse.c | 74 +- test/testautomation_pen.c | 1909 +++++++++++ test/testautomation_pixels.c | 16 +- test/testautomation_platform.c | 26 +- test/testautomation_properties.c | 350 ++ test/testautomation_rect.c | 78 +- test/testautomation_render.c | 57 +- test/testautomation_rwops.c | 51 +- test/testautomation_sdltest.c | 30 +- test/testautomation_stdlib.c | 178 +- test/testautomation_subsystems.c | 239 ++ test/testautomation_suites.h | 4 +- test/testautomation_surface.c | 65 +- test/testautomation_syswm.c | 60 - test/testautomation_timer.c | 11 +- test/testautomation_video.c | 664 +++- test/testbounds.c | 4 +- test/testcontroller.c | 243 +- test/testcustomcursor.c | 142 +- test/testdisplayinfo.c | 6 +- test/testdraw.c | 4 +- test/testdrawchessboard.c | 12 +- test/testdropfile.c | 14 +- test/testerror.c | 6 +- test/testevdev.c | 4 +- test/testffmpeg.c | 1139 +++++++ test/testffmpeg_videotoolbox.h | 15 + test/testffmpeg_videotoolbox.m | 147 + test/testfile.c | 22 +- test/testfilesystem.c | 10 +- test/testgeometry.c | 23 +- test/testgl.c | 25 +- test/testgles.c | 8 +- test/testgles2.c | 8 +- test/testgles2_sdf.c | 14 +- test/testhaptic.c | 10 +- test/testhittesting.c | 6 +- test/testhotplug.c | 6 +- test/testiconv.c | 6 +- test/testime.c | 26 +- test/testintersections.c | 4 +- test/testkeys.c | 4 +- test/testloadso.c | 8 +- test/testlocale.c | 6 +- test/testlock.c | 17 +- test/testmessage.c | 4 +- test/testmouse.c | 18 +- test/testmultiaudio.c | 6 +- test/testnative.c | 32 +- test/testnative.h | 7 +- test/testnativew32.c | 4 +- test/testnativewayland.c | 226 ++ test/testnativex11.c | 2 +- test/testoffscreen.c | 8 +- test/testoverlay.c | 85 +- test/testpen.c | 530 ++++ test/testplatform.c | 6 +- test/testpopup.c | 10 +- test/testpower.c | 4 +- test/testqsort.c | 4 +- test/testrelative.c | 4 +- test/testrendercopyex.c | 4 +- test/testrendertarget.c | 6 +- test/testresample.c | 6 +- test/testrumble.c | 12 +- test/testrwlock.c | 29 +- test/testscale.c | 4 +- test/testsem.c | 4 +- test/testsensor.c | 8 +- test/testshader.c | 134 +- test/testshape.c | 212 +- test/testsprite.c | 71 +- test/testspriteminimal.c | 4 +- test/teststreaming.c | 14 +- test/testsurround.c | 6 +- test/testthread.c | 8 +- test/testtimer.c | 47 +- test/testurl.c | 2 +- test/testutils.c | 43 +- test/testutils.h | 2 +- test/testver.c | 2 +- test/testvideocapture.c | 770 +++++ test/testvideocaptureminimal.c | 206 ++ test/testviewport.c | 6 +- test/testvulkan.c | 44 +- test/testwaylandcustom.c | 334 ++ test/testwm.c | 23 +- test/testyuv.c | 16 +- test/testyuv_cvt.c | 2 +- test/testyuv_cvt.h | 2 +- test/torturethread.c | 4 +- 1244 files changed, 49212 insertions(+), 27224 deletions(-) create mode 100644 .github/workflows/cpactions.yml delete mode 100644 .github/workflows/vmactions.yml create mode 100644 CREDITS.md delete mode 100644 CREDITS.txt create mode 100644 INSTALL.md delete mode 100644 INSTALL.txt delete mode 100644 TODO.txt create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_Colors.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_Textures.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG.hlsl create mode 100644 VisualC-GDK/shaders/D3D12_VertexShader.hlsl create mode 100644 VisualC-GDK/shaders/buildshaders.bat create mode 100644 VisualC/tests/testpen/testpen.vcxproj create mode 100644 android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java create mode 100644 android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java create mode 100644 cmake/FindFFmpeg.cmake create mode 100644 cmake/SDL3jarTargets.cmake.in create mode 100644 docs/README-main-functions.md create mode 100644 include/SDL3/SDL_pen.h create mode 100644 include/SDL3/SDL_properties.h delete mode 100644 include/SDL3/SDL_shape.h delete mode 100644 include/SDL3/SDL_syswm.h create mode 100644 include/SDL3/SDL_video_capture.h create mode 100644 src/SDL_hashtable.c create mode 100644 src/SDL_hashtable.h create mode 100644 src/SDL_properties.c create mode 100644 src/SDL_properties_c.h create mode 100644 src/core/SDL_core_unsupported.c create mode 100644 src/dynapi/SDL_dynapi_unsupported.h create mode 100644 src/events/SDL_pen.c create mode 100644 src/events/SDL_pen_c.h create mode 100644 src/hidapi/SDL_hidapi_netbsd.h create mode 100644 src/hidapi/netbsd/CMakeLists.txt create mode 100644 src/hidapi/netbsd/README.md create mode 100644 src/hidapi/netbsd/hid.c create mode 100644 src/hidapi/pc/hidapi-netbsd.pc.in create mode 100644 src/joystick/SDL_steam_virtual_gamepad.c rename src/{video/cocoa/SDL_cocoashape.h => joystick/SDL_steam_virtual_gamepad.h} (63%) create mode 100644 src/joystick/hidapi/SDL_hidapi_steamdeck.c create mode 100644 src/main/SDL_main_callbacks.c rename src/{video/x11/SDL_x11shape.h => main/SDL_main_callbacks.h} (66%) create mode 100644 src/main/emscripten/SDL_sysmain_callbacks.c create mode 100644 src/main/generic/SDL_sysmain_callbacks.c create mode 100644 src/main/ios/SDL_sysmain_callbacks.m rename src/{timer/dummy/SDL_systimer.c => render/SDL_render_unsupported.c} (70%) create mode 100644 src/stdlib/SDL_mslibc_x64.masm delete mode 100644 src/thread/psp/SDL_syscond.c delete mode 100644 src/thread/vita/SDL_syscond.c delete mode 100644 src/video/SDL_shape.c delete mode 100644 src/video/SDL_shape_internals.h create mode 100644 src/video/SDL_surface_pixel_impl.h create mode 100644 src/video/SDL_sysvideocapture.h create mode 100644 src/video/SDL_video_capture.c create mode 100644 src/video/SDL_video_capture_apple.m rename src/video/{windows/SDL_windowsshape.h => SDL_video_capture_c.h} (64%) create mode 100644 src/video/SDL_video_capture_v4l2.c create mode 100644 src/video/SDL_video_unsupported.c create mode 100644 src/video/android/SDL_android_video_capture.c delete mode 100644 src/video/cocoa/SDL_cocoashape.m delete mode 100644 src/video/wayland/SDL_waylandtouch.c delete mode 100644 src/video/wayland/SDL_waylandtouch.h delete mode 100644 src/video/windows/SDL_vkeys.h delete mode 100644 src/video/windows/SDL_windowsshape.c create mode 100644 src/video/x11/SDL_x11pen.c create mode 100644 src/video/x11/SDL_x11pen.h delete mode 100644 src/video/x11/SDL_x11shape.c rename test/{testfilesystem_pre.c => pretest.c} (76%) delete mode 100644 test/shapes/p01_shape24.bmp delete mode 100644 test/shapes/p01_shape32alpha.bmp delete mode 100644 test/shapes/p01_shape8.bmp delete mode 100644 test/shapes/p02_shape24.bmp delete mode 100644 test/shapes/p02_shape32alpha.bmp delete mode 100644 test/shapes/p02_shape8.bmp delete mode 100644 test/shapes/p03_shape24.bmp delete mode 100644 test/shapes/p03_shape8.bmp delete mode 100644 test/shapes/p04_shape1.bmp delete mode 100644 test/shapes/p04_shape24.bmp delete mode 100644 test/shapes/p04_shape32alpha.bmp delete mode 100644 test/shapes/p04_shape8.bmp delete mode 100644 test/shapes/p05_shape8.bmp delete mode 100644 test/shapes/p06_shape1alpha.bmp delete mode 100644 test/shapes/p06_shape24.bmp delete mode 100644 test/shapes/p06_shape32alpha.bmp delete mode 100644 test/shapes/p06_shape8.bmp delete mode 100644 test/shapes/p07_shape24.bmp delete mode 100644 test/shapes/p07_shape32alpha.bmp delete mode 100644 test/shapes/p07_shape8.bmp delete mode 100644 test/shapes/p08_shape24.bmp delete mode 100644 test/shapes/p08_shape32alpha.bmp delete mode 100644 test/shapes/p08_shape8.bmp delete mode 100644 test/shapes/p09_shape24.bmp delete mode 100644 test/shapes/p09_shape32alpha.bmp delete mode 100644 test/shapes/p09_shape8.bmp delete mode 100644 test/shapes/p10_shape1.bmp delete mode 100644 test/shapes/p10_shape24.bmp delete mode 100644 test/shapes/p10_shape32alpha.bmp delete mode 100644 test/shapes/p10_shape8.bmp delete mode 100644 test/shapes/p11_shape24.bmp delete mode 100644 test/shapes/p11_shape32alpha.bmp delete mode 100644 test/shapes/p11_shape8.bmp delete mode 100644 test/shapes/p12_shape24.bmp delete mode 100644 test/shapes/p12_shape8.bmp delete mode 100644 test/shapes/p13_shape24.bmp delete mode 100644 test/shapes/p13_shape32alpha.bmp delete mode 100644 test/shapes/p13_shape8.bmp delete mode 100644 test/shapes/p14_shape24.bmp delete mode 100644 test/shapes/p14_shape8.bmp delete mode 100644 test/shapes/p15_shape24.bmp delete mode 100644 test/shapes/p15_shape32alpha.bmp delete mode 100644 test/shapes/p15_shape8.bmp delete mode 100644 test/shapes/p16_shape1.bmp delete mode 100644 test/shapes/p16_shape24.bmp delete mode 100644 test/shapes/p16_shape8.bmp delete mode 100644 test/shapes/trollface_24.bmp delete mode 100644 test/shapes/trollface_32alpha.bmp create mode 100644 test/testautomation_pen.c create mode 100644 test/testautomation_properties.c create mode 100644 test/testautomation_subsystems.c delete mode 100644 test/testautomation_syswm.c create mode 100644 test/testffmpeg.c create mode 100644 test/testffmpeg_videotoolbox.h create mode 100644 test/testffmpeg_videotoolbox.m create mode 100644 test/testnativewayland.c create mode 100644 test/testpen.c create mode 100644 test/testvideocapture.c create mode 100644 test/testvideocaptureminimal.c create mode 100644 test/testwaylandcustom.c diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 20a09fe4..3ae775af 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,6 +23,7 @@ jobs: - uses: nttld/setup-ndk@v1 id: setup_ndk with: + local-cache: true ndk-version: r21e - name: Build (Android.mk) if: ${{ matrix.platform.name == 'Android.mk' }} diff --git a/.github/workflows/cpactions.yml b/.github/workflows/cpactions.yml new file mode 100644 index 00000000..7591c0dc --- /dev/null +++ b/.github/workflows/cpactions.yml @@ -0,0 +1,56 @@ +name: Build (C/P Actions) + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + freebsd: + runs-on: ubuntu-latest + name: '${{ matrix.platform.name }} ${{ matrix.platform.os-version }}' + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + platform: + - { name: FreeBSD, os: freebsd, os-version: 13.2, os-arch: x86-64, artifact: SDL-freebsd-x64, + sdl-cmake-configure-arguments: '-DSDL_CHECK_REQUIRED_INCLUDES="/usr/local/include" -DSDL_CHECK_REQUIRED_LINK_OPTIONS="-L/usr/local/lib"', + setup-cmd: 'sudo pkg update', + install-cmd: 'sudo pkg install -y cmake ninja pkgconf libXcursor libXext libXinerama libXi libXfixes libXrandr libXScrnSaver libXxf86vm wayland wayland-protocols libxkbcommon mesa-libs libglvnd evdev-proto libinotify alsa-lib jackit pipewire pulseaudio sndio dbus zh-fcitx ibus libudev-devd', + } + - { name: NetBSD, os: netbsd, os-version: 9.3, os-arch: x86-64, artifact: SDL-netbsd-x64, + sdl-cmake-configure-arguments: '', + 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', + install-cmd: 'sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1', + } + steps: + - uses: actions/checkout@v3 + - name: Build + uses: cross-platform-actions/action@v0.21.1 + with: + operating_system: ${{ matrix.platform.os }} + architecture: ${{ matrix.platform.os-arch }} + version: ${{ matrix.platform.os-version }} + run: | + ${{ matrix.platform.setup-cmd }} + ${{ matrix.platform.install-cmd }} + cmake -S . -B build -GNinja \ + -Wdeprecated -Wdev -Werror \ + -DCMAKE_BUILD_TYPE=Release \ + -DSDL_WERROR=ON \ + ${{ matrix.platform.sdl-cmake-configure-arguments }} + cmake --build build/ --config Release --verbose + cmake --build build/ --config Release --target package + + cmake --build build/ --config Release --target clean + rm -rf build/dist/_CPack_Packages + rm -rf build/CMakeFiles + rm -rf build/docs + + - uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: ${{ matrix.platform.artifact }} + path: build/dist/SDL3* diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a0a8e47..9cfb6a84 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,6 +43,7 @@ jobs: ${{ matrix.platform.msys-env }}-cc ${{ matrix.platform.msys-env }}-cmake ${{ matrix.platform.msys-env }}-ninja + ${{ matrix.platform.msys-env }}-perl ${{ matrix.platform.msys-env }}-pkg-config ${{ matrix.platform.msys-env }}-clang-tools-extra @@ -65,6 +66,8 @@ jobs: - name: Setup Macos dependencies if: runner.os == 'macOS' run: | + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + brew update brew install \ ninja \ pkg-config \ @@ -73,12 +76,16 @@ jobs: - name: Setup Intel oneAPI if: matrix.platform.intel run: | - # Setup oneAPI repo - wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - sudo echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + # Download the key to system keyring + wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ + | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + + # Add signed entry to apt sources and configure the APT client to use Intel repository: + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + + # Update package list sudo apt-get update -y - + # Install oneAPI sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic @@ -103,6 +110,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ ${{ matrix.platform.cmake }} - name: Build (CMake) + id: build run: | ${{ matrix.platform.source_cmd }} cmake --build build/ --config Release --verbose --parallel @@ -123,6 +131,7 @@ jobs: cmake --install build/ --config Release ( cd cmake_prefix; find . ) | LC_ALL=C sort -u - name: Package (CPack) + if: ${{ always() && steps.build.outcome == 'success' }} run: | cmake --build build/ --config Release --target package - name: Verify CMake configuration files @@ -141,6 +150,7 @@ jobs: export PKG_CONFIG_PATH=$(echo "${{ github.workspace }}/cmake_prefix/lib/pkgconfig" | sed -e 's#\\#/#g') cmake/test/test_pkgconfig.sh - uses: actions/upload-artifact@v3 + if: ${{ always() && steps.build.outcome == 'success' }} with: if-no-files-found: error name: ${{ matrix.platform.artifact }} diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index d6968cd8..9195a94e 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -21,9 +21,9 @@ jobs: - { name: Windows static VCRT (x86), flags: -A Win32 -DSDL_FORCE_STATIC_VCRT=ON, artifact: 'SDL-VC-static-VCRT-x86' } - { name: Windows (clang-cl x64), flags: -T ClangCL -A x64, artifact: 'SDL-clang-cl-x64' } - { name: Windows (clang-cl x86), flags: -T ClangCL -A Win32, artifact: 'SDL-clang-cl-x86' } - - { name: Windows (ARM), flags: -A ARM, artifact: 'SDL-VC-arm32' } - - { name: Windows (ARM64), flags: -A ARM64, artifact: 'SDL-VC-arm64' } - - { name: UWP (x64), flags: -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0", nowerror: true, + - { name: Windows (ARM), flags: -A ARM, artifact: 'SDL-VC-arm32', notests: true } + - { name: Windows (ARM64), flags: -A ARM64, artifact: 'SDL-VC-arm64', notests: true } + - { name: UWP (x64), flags: -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0", nowerror: true, notests: true, project: VisualC-WinRT/SDL-UWP.sln, projectflags: '/p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0.17763.0', artifact: 'SDL-VC-UWP' } steps: @@ -36,12 +36,20 @@ jobs: srcdir = r"${{ github.workspace }}".replace("\\", "/") builddir = f"{ srcdir }/build" os.makedirs(builddir) + cmakelists_txt = textwrap.dedent(f"""\ + # Always build .PDB symbol file + set(CMAKE_POLICY_DEFAULT_CMP0141 "NEW" CACHE STRING "MSVC debug information format flags are selected by an abstraction") + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "ProgramDatabase" CACHE STRING "MSVC debug information format") + set(CMAKE_EXE_LINKER_FLAGS "-DEBUG" CACHE STRING "Linker flags for executables") + set(CMAKE_SHARED_LINKER_FLAGS "-DEBUG" CACHE STRING "Linker flag for shared libraries") + cmake_minimum_required(VERSION 3.0...3.25) + project(sdl_user) + enable_testing() + add_subdirectory("{ srcdir }" SDL) + """) + print(cmakelists_txt) with open(f"{ builddir }/CMakeLists.txt", "w") as f: - f.write(textwrap.dedent(f"""\ - cmake_minimum_required(VERSION 3.0) - project(sdl_user) - add_subdirectory("{ srcdir }" SDL) - """)) + f.write(cmakelists_txt) - name: Configure (CMake) run: cmake -S build -B build ` -Wdeprecated -Wdev -Werror ` @@ -53,12 +61,15 @@ jobs: -DSDL_VENDOR_INFO="Github Workflow" ` -DSDL_DISABLE_INSTALL=OFF ` -DSDL_DISABLE_INSTALL_CPACK=OFF ` + -DSDL_DISABLE_INSTALL_DOCS=OFF ` ${{ matrix.platform.flags }} ` -DCMAKE_INSTALL_PREFIX=prefix - name: Build (CMake) - run: cmake --build build/ --config Release --parallel + id: build + run: | + cmake --build build/ --config Release --parallel - name: Run build-time tests - if: "! contains(matrix.platform.name, 'ARM')" + if: ${{ !matrix.platform.notests }} run: | $env:SDL_TESTS_QUICK=1 ctest -VV --test-dir build/ -C Release -j2 @@ -67,6 +78,7 @@ jobs: echo "SDL3_DIR=$Env:GITHUB_WORKSPACE/prefix" >> $Env:GITHUB_ENV cmake --install build/ - name: Package (CPack) + if: ${{ always() && steps.build.outcome == 'success' }} run: | cmake --build build/ --config Release --target PACKAGE - name: Verify CMake configuration files @@ -83,6 +95,7 @@ jobs: if: ${{ matrix.platform.project != '' }} run: msbuild ${{ matrix.platform.project }} /m /p:BuildInParallel=true /p:Configuration=Release ${{ matrix.platform.projectflags }} - uses: actions/upload-artifact@v3 + if: ${{ always() && steps.build.outcome == 'success' }} with: if-no-files-found: error name: ${{ matrix.platform.artifact }} diff --git a/.github/workflows/ps2.yml b/.github/workflows/ps2.yml index f1c349b5..834ab99e 100644 --- a/.github/workflows/ps2.yml +++ b/.github/workflows/ps2.yml @@ -17,15 +17,6 @@ jobs: apk update apk add cmake gmp mpc1 mpfr4 ninja pkgconf make git - # To be removed once ps2_drivers is part of PS2DEV - - name: Install ps2_drivers lib - run: | - git clone https://github.com/fjtrujy/ps2_drivers.git - cd ps2_drivers - make -j $(getconf _NPROCESSORS_ONLN) clean - make -j $(getconf _NPROCESSORS_ONLN) - make -j $(getconf _NPROCESSORS_ONLN) install - - name: Configure (CMake) run: | cmake -S . -B build -G Ninja \ @@ -36,7 +27,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=cmake_prefix \ -DCMAKE_BUILD_TYPE=Release - name: Build (CMake) - run: cmake --build build --config Release --verbose -- -j 1 + run: cmake --build build --config Release --verbose - name: Install (CMake) run: | set -eu @@ -45,7 +36,7 @@ jobs: ( cd cmake_prefix; find ) | LC_ALL=C sort -u - name: Package (CPack) run: | - cmake --build build/ --config Release --target package -- -j 1 + cmake --build build/ --config Release --target package - name: Verify CMake configuration files run: | @@ -55,7 +46,7 @@ jobs: -DTEST_SHARED=FALSE \ -DCMAKE_PREFIX_PATH=${{ env.SDL3_DIR }} \ -DCMAKE_BUILD_TYPE=Release - cmake --build cmake_config_build --verbose -- -j 1 + cmake --build cmake_config_build --verbose - name: Verify sdl3.pc run: | export CC=mips64r5900el-ps2-elf-gcc diff --git a/.github/workflows/vita.yml b/.github/workflows/vita.yml index 32d7f4d5..6c8f14a5 100644 --- a/.github/workflows/vita.yml +++ b/.github/workflows/vita.yml @@ -87,6 +87,11 @@ jobs: run: | cp -rv /vita/dependencies/* ${VITASDK}/arm-vita-eabi + - name: Fix vita.toolchain.cmake + run: | + # cache PKG_CONFIG_PATH + sed -i -E 's/set\( PKG_CONFIG_EXECUTABLE "\$\{VITASDK}\/bin\/arm-vita-eabi-pkg-config" )/set( PKG_CONFIG_EXECUTABLE "${VITASDK}\/bin\/arm-vita-eabi-pkg-config" CACHE PATH "Path of pkg-config executable" )/' ${VITASDK}/share/vita.toolchain.cmake + - name: Configure (CMake) run: | cmake -S . -B build -G Ninja \ diff --git a/.github/workflows/vmactions.yml b/.github/workflows/vmactions.yml deleted file mode 100644 index e7868341..00000000 --- a/.github/workflows/vmactions.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build (VM Actions) - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -jobs: - freebsd: - runs-on: macos-12 - name: FreeBSD - steps: - - uses: actions/checkout@v3 - - name: Build - uses: vmactions/freebsd-vm@v0 - with: - mem: 8192 - usesh: true - prepare: | - pkg install -y \ - cmake \ - ninja \ - pkgconf \ - libXcursor \ - libXext \ - libXinerama \ - libXi \ - libXfixes \ - libXrandr \ - libXScrnSaver \ - libXxf86vm \ - wayland \ - wayland-protocols \ - libxkbcommon \ - mesa-libs \ - libglvnd \ - evdev-proto \ - libinotify \ - alsa-lib \ - jackit \ - pipewire \ - pulseaudio \ - sndio \ - dbus \ - zh-fcitx \ - ibus \ - libsamplerate \ - libudev-devd - - run: | - cmake -S . -B build -GNinja \ - -Wdeprecated -Wdev -Werror \ - -DCMAKE_BUILD_TYPE=Release \ - -DSDL_HIDAPI_LIBUSB=OFF \ - -DSDL_CHECK_REQUIRED_INCLUDES="/usr/local/include" \ - -DSDL_CHECK_REQUIRED_LINK_OPTIONS="-L/usr/local/lib" - cmake --build build/ --config Release --verbose -- -j`sysctl -n hw.ncpu` - cmake --build build/ --config Release --target package - - cmake --build build/ --config Release --target clean - rm -rf build/dist/_CPack_Packages - rm -rf build/CMakeFiles - rm -rf build/docs - - - uses: actions/upload-artifact@v3 - with: - if-no-files-found: error - name: SDL-freebsd - path: build/dist/SDL3* diff --git a/.gitignore b/.gitignore index 93ee540a..9e577f49 100644 --- a/.gitignore +++ b/.gitignore @@ -66,8 +66,7 @@ VisualC/tests/gamepadmap/button.bmp VisualC/tests/gamepadmap/gamepadmap.bmp VisualC/tests/gamepadmap/gamepadmap_back.bmp VisualC/tests/loopwave/sample.wav -VisualC/tests/testautomation/CompareSurfaces0001_Reference.bmp -VisualC/tests/testautomation/CompareSurfaces0001_TestOutput.bmp +VisualC/tests/testautomation/*.bmp VisualC/tests/testgamepad/axis.bmp VisualC/tests/testgamepad/button.bmp VisualC/tests/testgamepad/gamepadmap.bmp @@ -80,6 +79,7 @@ VisualC/tests/testscale/sample.bmp VisualC/tests/testsprite/icon.bmp VisualC/tests/testyuv/testyuv.bmp VisualC-GDK/**/Layout +VisualC-GDK/shaders/*.h # for Android android-project/local.properties diff --git a/Android.mk b/Android.mk index 4c26d3c9..3779eb53 100644 --- a/Android.mk +++ b/Android.mk @@ -24,6 +24,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/audio/openslES/*.c) \ $(LOCAL_PATH)/src/atomic/SDL_atomic.c.arm \ $(LOCAL_PATH)/src/atomic/SDL_spinlock.c.arm \ + $(wildcard $(LOCAL_PATH)/src/core/*.c) \ $(wildcard $(LOCAL_PATH)/src/core/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \ $(wildcard $(LOCAL_PATH)/src/dynapi/*.c) \ diff --git a/CMakeLists.txt b/CMakeLists.txt index c244bc09..d19ad993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,6 +220,12 @@ if(VITA OR PSP OR PS2 OR N3DS OR RISCOS) set(SDL_LOADSO_DEFAULT OFF) endif() +if((RISCOS OR UNIX_SYS) AND NOT (LINUX OR NETBSD OR OPENBSD)) + set(SDL_OSS_DEFAULT ON) +else() + set(SDL_OSS_DEFAULT OFF) +endif() + if(SDL_SHARED_DEFAULT AND SDL_STATIC_DEFAULT AND SDL_SHARED_AVAILABLE) if(DEFINED BUILD_SHARED_LIBS) # When defined, use BUILD_SHARED_LIBS as default @@ -295,6 +301,7 @@ dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY 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) set_option(SDL_GCC_ATOMICS "Use gcc builtin atomics" ${SDL_GCC_ATOMICS_DEFAULT}) dep_option(SDL_DBUS "Enable D-Bus support" ON ${UNIX_SYS} OFF) set_option(SDL_DISKAUDIO "Support the disk writer audio driver" ON) @@ -305,7 +312,7 @@ dep_option(SDL_OPENGL "Include OpenGL support" ON "NOT VISIONOS" OF dep_option(SDL_OPENGLES "Include OpenGL ES support" ON "NOT VISIONOS" OFF) set_option(SDL_PTHREADS "Use POSIX threads for multi-threading" ${SDL_PTHREADS_DEFAULT}) dep_option(SDL_PTHREADS_SEM "Use pthread semaphores" ON "SDL_PTHREADS" OFF) -dep_option(SDL_OSS "Support the OSS audio API" ON "UNIX_SYS OR RISCOS" OFF) +dep_option(SDL_OSS "Support the OSS audio API" ${SDL_OSS_DEFAULT} "UNIX_SYS OR RISCOS" OFF) set_option(SDL_ALSA "Support the ALSA audio API" ${UNIX_SYS}) dep_option(SDL_ALSA_SHARED "Dynamically load ALSA audio support" ON "SDL_ALSA" OFF) set_option(SDL_JACK "Support the JACK audio API" ${UNIX_SYS}) @@ -329,7 +336,6 @@ set_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS}) dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) dep_option(SDL_WAYLAND_LIBDECOR_SHARED "Dynamically load libdecor support" ON "SDL_WAYLAND_LIBDECOR;SDL_WAYLAND_SHARED" OFF) -dep_option(SDL_WAYLAND_QT_TOUCH "QtWayland server support for Wayland video driver" ON "SDL_WAYLAND" OFF) dep_option(SDL_RPI "Use Raspberry Pi video driver" ON "UNIX_SYS;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF) dep_option(SDL_ROCKCHIP "Use ROCKCHIP Hardware Acceleration video driver" ON "UNIX_SYS;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF) set_option(SDL_COCOA "Use Cocoa video driver" ${APPLE}) @@ -344,6 +350,7 @@ set_option(SDL_METAL "Enable Metal support" ${APPLE}) set_option(SDL_KMSDRM "Use KMS DRM video driver" ${UNIX_SYS}) dep_option(SDL_KMSDRM_SHARED "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF) set_option(SDL_OFFSCREEN "Use offscreen video driver" ON) +dep_option(SDL_VIDEO_CAPTURE "Enable video capturing" ON SDL_VIDEO OFF) option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF) option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF) dep_option(SDL_HIDAPI "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF) @@ -423,7 +430,7 @@ if(WINDOWS_STORE) sdl_compile_options(PRIVATE "-ZW") endif() -check_linker_flag(C "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym" HAVE_WL_VERSION_SCRIPT) +check_linker_supports_version_file(HAVE_WL_VERSION_SCRIPT) if(HAVE_WL_VERSION_SCRIPT) sdl_shared_link_options("-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym") else() @@ -475,6 +482,7 @@ sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/hidapi/*.c" "${SDL3_SOURCE_DIR}/src/libm/*.c" "${SDL3_SOURCE_DIR}/src/locale/*.c" + "${SDL3_SOURCE_DIR}/src/main/*.c" "${SDL3_SOURCE_DIR}/src/misc/*.c" "${SDL3_SOURCE_DIR}/src/power/*.c" "${SDL3_SOURCE_DIR}/src/render/*.c" @@ -486,6 +494,18 @@ sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/video/*.c" "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.c" ) +if(MSVC AND TARGET SDL3-shared) + if(SDL_CPU_X64) + enable_language(ASM_MASM) + set(asm_src "${SDL3_SOURCE_DIR}/src/stdlib/SDL_mslibc_x64.masm") + target_compile_options(SDL3-shared PRIVATE "$<$:/nologo>") + set_property(SOURCE "${asm_src}" PROPERTY LANGUAGE "ASM_MASM") + target_sources(SDL3-shared PRIVATE "${asm_src}") + elseif(SDL_CPU_ARM32 OR SDL_CPU_ARM64) + # FIXME: ARM assembler (armasm.exe/armasm64.exe) is NOT ASM_MASM, and does currently not work with CMake + # (https://gitlab.kitware.com/cmake/cmake/-/issues/18912) + endif() +endif() if(USE_INTELCC) # warning #39: division by zero @@ -985,12 +1005,21 @@ if(NOT HAVE_ARMNEON) set(SDL_DISABLE_NEON 1) endif() +set(SDL_DISABLE_ALLOCA 0) +check_include_file("alloca.h" "HAVE_ALLOCA_H") +if(MSVC) + check_include_file("malloc.h" "HAVE_MALLOC") + check_symbol_exists("_alloca" "malloc.h" _ALLOCA_IN_MALLOC_H) + if(NOT HAVE_ALLOCA_H AND NOT _ALLOCA_IN_MALLOC_H) + set(SDL_DISABLE_ALLOCA 1) + endif() +endif() + # TODO: Can't deactivate on FreeBSD? w/o LIBC, SDL_stdinc.h can't define anything. if(SDL_LIBC) set(available_headers) set(HAVE_LIBC TRUE) set(headers_to_check - alloca.h ctype.h float.h iconv.h @@ -1022,7 +1051,7 @@ if(SDL_LIBC) endforeach() set(symbols_to_check - abs acos acosf alloca asin asinf atan atan2 atan2f atanf atof atoi + abs acos acosf asin asinf atan atan2 atan2f atanf atof atoi bcopy bsearch calloc ceil ceilf copysign copysignf cos cosf _Exit exp expf @@ -1036,7 +1065,7 @@ if(SDL_LIBC) realloc rindex round roundf scalbn scalbnf setenv sin sinf sqr sqrt sqrtf sscanf strchr strcmp strlcat strlcpy strlen strncmp strnlen - strrchr strstr strtod strtok_r strtol strtoll strtoul strtoull + strrchr strstr strnstr strtod strtok_r strtol strtoll strtoul strtoull tan tanf trunc truncf unsetenv vsnprintf vsscanf @@ -1066,8 +1095,6 @@ if(SDL_LIBC) cmake_pop_check_state() if(NOT WINDOWS) - check_include_file(linux/input.h HAVE_LINUX_INPUT_H) - check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE) check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION) check_symbol_exists(setjmp "setjmp.h" HAVE_SETJMP) @@ -1079,24 +1106,31 @@ if(SDL_LIBC) check_symbol_exists(poll "poll.h" HAVE_POLL) if(SDL_SYSTEM_ICONV) - check_library_exists(iconv iconv_open "" HAVE_LIBICONV) - if(HAVE_LIBICONV) - find_package(Iconv) - if(Iconv_FOUND AND NOT Iconv_IS_BUILT_IN) - set(HAVE_ICONV 1) - set(HAVE_SYSTEM_ICONV TRUE) - pkg_check_modules(PC_ICONV iconv) - if(PC_ICONV_FOUND) - sdl_link_dependency(iconv LIBS Iconv::Iconv CMAKE_MODULE Iconv PKG_CONFIG_SPECS iconv) - else() - sdl_link_dependency(iconv LIBS Iconv::Iconv CMAKE_MODULE Iconv PKG_CONFIG_LIBS iconv) - endif() - endif() - else() - check_library_exists(c iconv_open "" HAVE_BUILTIN_ICONV) - if(HAVE_BUILTIN_ICONV) - set(HAVE_ICONV 1) - set(HAVE_SYSTEM_ICONV TRUE) + check_c_source_compiles(" + #define LIBICONV_PLUG 1 /* in case libiconv header is in include path */ + #include + #include + int main(int argc, char **argv) { + return !iconv_open(NULL,NULL); + }" ICONV_IN_LIBC) + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES iconv) + check_c_source_compiles(" + #include + #include + int main(int argc, char **argv) { + return !iconv_open(NULL,NULL); + }" ICONV_IN_LIBICONV) + cmake_pop_check_state() + + if(ICONV_IN_LIBC OR ICONV_IN_LIBICONV) + set(HAVE_ICONV 1) + set(HAVE_SYSTEM_ICONV TRUE) + if(ICONV_IN_LIBICONV AND (SDL_LIBICONV OR (NOT ICONV_IN_LIBC))) + sdl_link_dependency(iconv LIBS iconv) + set(SDL_USE_LIBICONV 1) + set(HAVE_LIBICONV TRUE) endif() endif() endif() @@ -1330,6 +1364,10 @@ if(ANDROID) VERSION "${SDL3_VERSION}" ) set_property(TARGET SDL3-jar PROPERTY OUTPUT "${SDL3_BINARY_DIR}/SDL3-${SDL3_VERSION}.jar") + add_library(SDL3__Jar INTERFACE) + add_library(SDL3::Jar ALIAS SDL3__Jar) + get_property(sdl3_jar_location TARGET SDL3-jar PROPERTY JAR_FILE) + set_property(TARGET SDL3__Jar PROPERTY JAR_FILE "${sdl3_jar_location}") set(javasourcesjar "${SDL3_BINARY_DIR}/SDL3-${SDL3_VERSION}-sources.jar") string(REGEX REPLACE "${android_java_sources_root}/" "" sdl_relative_java_sources "${SDL_JAVA_SOURCES}") add_custom_command( @@ -1362,6 +1400,9 @@ elseif(EMSCRIPTEN) # project. Uncomment at will for verbose cross-compiling -I/../ path info. sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/emscripten/*.c") + set(HAVE_SDL_MAIN_CALLBACKS TRUE) + if(SDL_MISC) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c") set(HAVE_SDL_MISC TRUE) @@ -1457,14 +1498,12 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(UNIX) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/unix/*.c") - if (HAVE_LINUX_INPUT_H) - check_c_source_compiles(" - #include - #ifndef EVIOCGNAME - #error EVIOCGNAME() ioctl not available - #endif - int main(int argc, char** argv) { return 0; }" HAVE_INPUT_EVENTS) - endif() + check_c_source_compiles(" + #include + #ifndef EVIOCGNAME + #error EVIOCGNAME() ioctl not available + #endif + int main(int argc, char** argv) { return 0; }" HAVE_LINUX_INPUT_H) if(LINUX) check_c_source_compiles(" @@ -1500,11 +1539,11 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) }" HAVE_INPUT_WSCONS) endif() - if(HAVE_INPUT_EVENTS) + if(HAVE_LINUX_INPUT_H) set(SDL_INPUT_LINUXEV 1) endif() - if(SDL_HAPTIC AND HAVE_INPUT_EVENTS) + if(SDL_HAPTIC AND HAVE_LINUX_INPUT_H) set(SDL_HAPTIC_LINUX 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/linux/*.c") set(HAVE_SDL_HAPTIC TRUE) @@ -1557,7 +1596,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(SDL_USE_IME 1) endif() - if(FREEBSD AND NOT HAVE_INOTIFY) + if((FREEBSD OR NETBSD) AND NOT HAVE_INOTIFY) set(LibInotify_PKG_CONFIG_SPEC libinotify) pkg_check_modules(PC_LIBINOTIFY IMPORTED_TARGET ${LibInotify_PKG_CONFIG_SPEC}) if(PC_LIBINOTIFY_FOUND) @@ -1592,7 +1631,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources("${SDL3_SOURCE_DIR}/src/core/linux/SDL_udev.c") endif() - if(HAVE_INPUT_EVENTS) + if(HAVE_LINUX_INPUT_H) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_kbd.c" @@ -1603,6 +1642,13 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources("${SDL3_SOURCE_DIR}/src/core/freebsd/SDL_evdev_kbd_freebsd.c") endif() + if(HAVE_INPUT_WSCONS) + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_kbd.c" + "${SDL3_SOURCE_DIR}/src/core/openbsd/SDL_wscons_mouse.c" + ) + endif() + # Always compiled for Linux, unconditionally: sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.c" @@ -1717,7 +1763,7 @@ elseif(WINDOWS) if(TARGET SDL3-shared AND MSVC AND NOT SDL_LIBC) # Prevent codegen that would use the VC runtime libraries. - target_compile_options(SDL3-shared PRIVATE "/GS-" "/Gs1048576") + target_compile_options(SDL3-shared PRIVATE $<$:/GS-> $<$:/Gs1048576>) if(SDL_CPU_X86) target_compile_options(SDL3-shared PRIVATE "/arch:SSE") endif() @@ -1783,16 +1829,6 @@ elseif(WINDOWS) #include #include int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_H) - check_c_source_compiles(" - #include - #include - XINPUT_GAMEPAD_EX x1; - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_GAMEPAD_EX) - check_c_source_compiles(" - #include - #include - XINPUT_STATE_EX s1; - int main(int argc, char **argv) { return 0; }" HAVE_XINPUT_STATE_EX) check_c_source_compiles(" #define COBJMACROS #include @@ -1922,8 +1958,8 @@ elseif(WINDOWS) vccorlib$<$:d>.lib msvcrt$<$:d>.lib LINK_OPTIONS - -nodefaultlib:vccorlib$<$:d> - -nodefaultlib:msvcrt$<$:d> + /nodefaultlib:vccorlib$<$:d> + /nodefaultlib:msvcrt$<$:d> ) endif() @@ -2012,6 +2048,8 @@ elseif(WINDOWS) elseif(APPLE) # TODO: rework this all for proper macOS, iOS and Darwin support + # !!! FIXME: all the `if(IOS OR TVOS OR VISIONOS)` checks should get merged into one variable, so we're ready for the next platform (or just WatchOS). + # We always need these libs on macOS at the moment. # !!! FIXME: we need Carbon for some very old API calls in # !!! FIXME: src/video/cocoa/SDL_cocoakeyboard.c, but we should figure out @@ -2023,12 +2061,22 @@ elseif(APPLE) set(SDL_FRAMEWORK_FOUNDATION 1) set(SDL_FRAMEWORK_COREVIDEO 1) + # iOS can use a CADisplayLink for main callbacks. macOS just uses the generic one atm. + if(IOS OR TVOS OR VISIONOS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ios/*.m") + set(HAVE_SDL_MAIN_CALLBACKS TRUE) + endif() + # Requires the darwin file implementation if(SDL_FILE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/file/cocoa/*.m") set(HAVE_SDL_FILE TRUE) endif() + if(IOS OR TVOS OR MACOSX OR DARWIN) + sdl_sources("${SDL3_SOURCE_DIR}/src/video/SDL_video_capture_apple.m") + endif() + if(SDL_MISC) if(IOS OR TVOS OR VISIONOS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/ios/*.m") @@ -2131,7 +2179,7 @@ elseif(APPLE) set(SDL_TIMER_UNIX 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") set(HAVE_SDL_TIMERS TRUE) - endif(SDL_TIMERS) + endif() if(SDL_FILESYSTEM) set(SDL_FILESYSTEM_COCOA 1) @@ -2212,6 +2260,10 @@ elseif(APPLE) # Actually load the frameworks at the end so we don't duplicate include. if(SDL_FRAMEWORK_COREVIDEO) + find_library(COREMEDIA CoreMedia) + if(COREMEDIA) + sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreMedia") + endif() sdl_link_dependency(corevideo LINK_OPTIONS "-Wl,-framework,CoreVideo") endif() if(SDL_FRAMEWORK_COCOA) @@ -2418,7 +2470,7 @@ elseif(VITA) "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_sysmutex.c" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c" "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c" - "${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syscond.c" + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" ) @@ -2549,6 +2601,7 @@ elseif(PSP) if(SDL_THREADS) set(SDL_THREAD_PSP 1) sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c" "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c" "${SDL3_SOURCE_DIR}/src/thread/psp/*.c" @@ -2778,8 +2831,12 @@ if(NOT HAVE_SDL_THREADS) endif() endif() if(NOT HAVE_SDL_TIMERS) - set(SDL_TIMER_DUMMY 1) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/dummy/*.c") + message(FATAL_ERROR "Timers are needed by many SDL subsystems and may not be disabled") +endif() + +# Most platforms use this. +if(NOT HAVE_SDL_MAIN_CALLBACKS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/generic/*.c") endif() # config variables may contain generator expression, so we need to generate SDL_build_config.h in 2 steps: @@ -2804,7 +2861,7 @@ set(SDL_REVISION "" CACHE STRING "Custom SDL revision (overrides SDL_REVISION_SU if(NOT SDL_REVISION) set(SDL_REVISION_SUFFIX "" CACHE STRING "Suffix for the SDL revision") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt") - # If VERSION exists, it contains the SDL version + # If VERSION.txt exists, it contains the SDL version file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt" SDL_REVISION_CENTER) string(STRIP "${SDL_REVISION_CENTER}" SDL_REVISION_CENTER) else() @@ -2952,6 +3009,12 @@ if(ANDROID) endif() if(APPLE) + cmake_push_check_state(RESET) + check_c_compiler_flag(-fobjc-arc COMPILER_SUPPORTS_FOBJC_ARC) + cmake_pop_check_state() + if(NOT COMPILER_SUPPORTS_FOBJC_ARC) + message(FATAL_ERROR "Compiler does not support -fobjc-arc: this is required on Apple platforms") + endif() sdl_compile_options(PRIVATE "-fobjc-arc") endif() @@ -2959,6 +3022,24 @@ if(PS2) sdl_compile_options(PRIVATE "-Wno-error=declaration-after-statement") endif() +if(NOT SDL_LIBC) + if(MSVC) + set(saved_CMAKE_TRY_COMPILE_TARGET_TYPE "${CMAKE_TRY_COMPILE_TARGET_TYPE}") + cmake_push_check_state(RESET) + set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + check_c_compiler_flag("/Zl" COMPILER_SUPPORTS_Zl) + cmake_pop_check_state() + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${saved_CMAKE_TRY_COMPILE_TARGET_TYPE}") + if(COMPILER_SUPPORTS_Zl) + # /Zl omits the default C runtime library name from the .obj file. + sdl_compile_options(PRIVATE "$<$,$>:/Zl>") + if(TARGET SDL3_test) + target_compile_options(SDL3_test PRIVATE "/Zl") + endif() + endif() + endif() +endif() + if(APPLE) get_property(sources TARGET SDL3-collector PROPERTY INTERFACE_SOURCES) foreach(SOURCE_FILE IN LISTS sources) @@ -3029,9 +3110,15 @@ if(SDL_SHARED) ) endif() if(NOT SDL_LIBC) - if(MSVC AND SDL_CPU_X86) - # FIXME: should be added for all architectures (missing symbols for ARM) - target_link_libraries(SDL3-shared PRIVATE "-nodefaultlib:MSVCRT") + if(MSVC AND (NOT MSVC_CLANG AND NOT WINDOWS_STORE)) + # Don't try to link with the default set of libraries. + # Note: The clang toolset for Visual Studio does not support /NODEFAULTLIB. + target_link_options(SDL3-shared PRIVATE "/NODEFAULTLIB") + if(SDL_CPU_ARM32) + # linking to msvcrt.lib avoid unresolved external symbols + # (__rt_sdiv, __rt_udiv, __rt_sdiv64, _rt_udiv64, __dtou64, __u64tod, __i64tos) + target_link_libraries(SDL3-shared PRIVATE msvcrt.lib) + endif() endif() if(HAS_Q_NO_USE_LIBIRC) target_compile_options(SDL3-shared PRIVATE /Q_no-use-libirc) @@ -3066,14 +3153,6 @@ if(SDL_SHARED) ) endif() endif() - # Note: The clang toolset for Visual Studio does not support /NODEFAULTLIB. - if(MSVC AND NOT SDL_LIBC AND NOT MSVC_CLANG AND NOT SDL_CPU_ARM32) - # Don't try to link with the default set of libraries. - if(NOT WINDOWS_STORE) - # FIXME: is this needed? "-nodefaultlib:MSVCRT" ia already added when SDL_LIBC is false - target_link_options(SDL3-shared PRIVATE "/NODEFAULTLIB") - endif() - endif() target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS}) target_include_directories(SDL3-shared PRIVATE @@ -3231,6 +3310,7 @@ sdl_cmake_config_find_pkg_config_commands(SDL_TEST_FIND_PKG_CONFIG_COMMANDS include(CMakePackageConfigHelpers) configure_package_config_file(cmake/SDL3Config.cmake.in SDL3Config.cmake + NO_SET_AND_CHECK_MACRO PATH_VARS CMAKE_INSTALL_PREFIX INSTALL_DESTINATION "${SDL_SDL_INSTALL_CMAKEDIR}" ) @@ -3264,6 +3344,9 @@ if(NOT SDL_DISABLE_INSTALL) FRAMEWORK DESTINATION "." RESOURCE DESTINATION "${SDL_SDL_INSTALL_RESOURCEDIR}" ) + if(MSVC) + SDL_install_pdb(SDL3-shared "${CMAKE_INSTALL_BINDIR}") + endif() endif() if(SDL_STATIC) @@ -3272,6 +3355,9 @@ if(NOT SDL_DISABLE_INSTALL) FRAMEWORK DESTINATION "." RESOURCE DESTINATION "${SDL_SDLstatic_INSTALL_RESOURCEDIR}" ) + if(MSVC) + SDL_install_pdb(SDL3-static "${CMAKE_INSTALL_LIBDIR}") + endif() endif() if(SDL_TEST_LIBRARY) @@ -3280,6 +3366,9 @@ if(NOT SDL_DISABLE_INSTALL) FRAMEWORK DESTINATION "." RESOURCE DESTINATION "${SDL_SDLtest_INSTALL_RESOURCEDIR}" ) + if(MSVC) + SDL_install_pdb(SDL3_test "${CMAKE_INSTALL_LIBDIR}") + endif() endif() ##### Install CMake Targets ##### @@ -3351,10 +3440,19 @@ if(NOT SDL_DISABLE_INSTALL) endif() if(ANDROID) - set(SDL_INSTALL_JAVADIR "${CMAKE_INSTALL_DATAROOTDIR}/java" CACHE PATH "Path where to install java clases + java sources") if(TARGET SDL3-jar) - install(FILES "${SDL3_BINARY_DIR}/SDL3.jar" "${SDL3_BINARY_DIR}/SDL3-${SDL3_VERSION}.jar" + set(SDL_INSTALL_JAVADIR "${CMAKE_INSTALL_DATAROOTDIR}/java" CACHE PATH "Path where to install java clases + java sources") + install(FILES $ DESTINATION "${SDL_INSTALL_JAVADIR}/SDL3") + configure_package_config_file(cmake/SDL3jarTargets.cmake.in SDL3jarTargets.cmake + INSTALL_DESTINATION "${SDL_SDL_INSTALL_CMAKEDIR}" + PATH_VARS SDL_INSTALL_JAVADIR + NO_CHECK_REQUIRED_COMPONENTS_MACRO + INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" + ) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/SDL3jarTargets.cmake" + DESTINATION "${SDL_SDL_INSTALL_CMAKEDIR}" + ) endif() if(TARGET SDL3-javasources) install(FILES "${SDL3_BINARY_DIR}/SDL3-${SDL3_VERSION}-sources.jar" @@ -3364,6 +3462,7 @@ if(NOT SDL_DISABLE_INSTALL) if(NOT SDL_DISABLE_INSTALL_DOCS) SDL_generate_manpages( + HEADERS_DIR "${PROJECT_SOURCE_DIR}/include/SDL3" SYMBOL "SDL_Init" WIKIHEADERS_PL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/wikiheaders.pl" REVISION "${SDL_REVISION}" diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 00000000..370bcec0 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,34 @@ +# Simple DirectMedia Layer CREDITS + +Thanks to everyone who made this possible, including: + +- Cliff Matthews, for giving me a reason to start this project. :) -- Executor rocks! *grin* +- Ryan Gordon for helping everybody out and keeping the dream alive. :) +- Gabriel Jacobo for his work on the Android port and generally helping out all around. +- Philipp Wiesemann for his attention to detail reviewing the entire SDL code base and proposes patches. +- Andreas Schiffler for his dedication to unit tests, Visual Studio projects, and managing the Google Summer of Code. +- Mike Sartain for incorporating SDL into Team Fortress 2 and cheering me on at Valve. +- Alfred Reynolds for the game controller API and general (in)sanity +- Jørgen Tjernø¸ for numerous magical macOS fixes. +- Pierre-Loup Griffais for his deep knowledge of OpenGL drivers. +- Julian Winter for the SDL 2.0 website. +- Sheena Smith for many months of great work on the SDL wiki creating the API documentation and style guides. +- Paul Hunkin for his port of SDL to Android during the Google Summer of Code 2010. +- Eli Gottlieb for his work on shaped windows during the Google Summer of Code 2010. +- Jim Grandpre for his work on multi-touch and gesture recognition during + the Google Summer of Code 2010. +- Edgar "bobbens" Simo for his force feedback API development during the + Google Summer of Code 2008. +- Aaron Wishnick for his work on audio resampling and pitch shifting during + the Google Summer of Code 2008. +- Holmes Futrell for his port of SDL to the iPhone and iPod Touch during the + Google Summer of Code 2008. +- Jon Atkins for SDL_image, SDL_mixer and SDL_net documentation. +- Everybody at Loki Software, Inc. for their great contributions! + + And a big hand to everyone else who has contributed over the years. + +THANKS! :) + + -- Sam Lantinga + diff --git a/CREDITS.txt b/CREDITS.txt deleted file mode 100644 index 3243318b..00000000 --- a/CREDITS.txt +++ /dev/null @@ -1,53 +0,0 @@ - -Simple DirectMedia Layer CREDITS -Thanks to everyone who made this possible, including: - -* Cliff Matthews, for giving me a reason to start this project. :) - -- Executor rocks! *grin* - -* Ryan Gordon for helping everybody out and keeping the dream alive. :) - -* Gabriel Jacobo for his work on the Android port and generally helping out all around. - -* Philipp Wiesemann for his attention to detail reviewing the entire SDL code base and proposes patches. - -* Andreas Schiffler for his dedication to unit tests, Visual Studio projects, and managing the Google Summer of Code. - -* Mike Sartain for incorporating SDL into Team Fortress 2 and cheering me on at Valve. - -* Alfred Reynolds for the game controller API and general (in)sanity - -* Jørgen Tjernø for numerous magical macOS fixes. - -* Pierre-Loup Griffais for his deep knowledge of OpenGL drivers. - -* Julian Winter for the SDL 2.0 website. - -* Sheena Smith for many months of great work on the SDL wiki creating the API documentation and style guides. - -* Paul Hunkin for his port of SDL to Android during the Google Summer of Code 2010. - -* Eli Gottlieb for his work on shaped windows during the Google Summer of Code 2010. - -* Jim Grandpre for his work on multi-touch and gesture recognition during - the Google Summer of Code 2010. - -* Edgar "bobbens" Simo for his force feedback API development during the - Google Summer of Code 2008. - -* Aaron Wishnick for his work on audio resampling and pitch shifting during - the Google Summer of Code 2008. - -* Holmes Futrell for his port of SDL to the iPhone and iPod Touch during the - Google Summer of Code 2008. - -* Jon Atkins for SDL_image, SDL_mixer and SDL_net documentation. - -* Everybody at Loki Software, Inc. for their great contributions! - - And a big hand to everyone else who has contributed over the years. - -THANKS! :) - - -- Sam Lantinga - diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..f071eab0 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,64 @@ +# To compile and install SDL: + +## Windows with Visual Studio: + +Read ./docs/README-visualc.md + +## Windows building with mingw-w64 for x86: + +Run: `cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-i686.cmake && cmake --build build && cmake --install build` + +## Windows building with mingw-w64 for x64: + +Run: `cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-x86_64.cmake && cmake --build build && cmake --install build` + +## macOS with Xcode: + +Read docs/README-macos.md + +## macOS from the command line: + +Run: `cmake -S . -B build && cmake --build build && cmake --install build` + +## Linux and other UNIX systems: + +Run: `cmake -S . -B build && cmake --build build && cmake --install build` + +## Android: + +Read docs/README-android.md + +## iOS: + +Read docs/README-ios.md + +## Using CMake: + +Read docs/README-cmake.md + +# Example code + +Look at the example programs in ./test, and check out the online +documentation at https://wiki.libsdl.org/SDL3/ + +# Discussion + +## Forums/mailing lists + +Join the SDL developer discussions, sign up on + +https://discourse.libsdl.org/ + +and go to the development forum + +https://discourse.libsdl.org/c/sdl-development/6 + +Once you sign up, you can use the forum through the website, or as a mailing +list from your email client. + +## Announcement list + +Sign up for the announcement list through the web interface: + +https://www.libsdl.org/mailing-list.php + diff --git a/INSTALL.txt b/INSTALL.txt deleted file mode 100644 index 9faadeb0..00000000 --- a/INSTALL.txt +++ /dev/null @@ -1,43 +0,0 @@ - -To compile and install SDL: - - 1. Windows with Visual Studio: - * Read ./docs/README-visualc.md - - Windows building with mingw-w64 for x86: - * Run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-i686.cmake && cmake --build build && cmake --install build - - Windows building with mingw-w64 for x64: - * Run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-x86_64.cmake && cmake --build build && cmake --install build - - macOS with Xcode: - * Read docs/README-macos.md - - macOS from the command line: - * Run: cmake -S . -B build && cmake --build build && cmake --install build - - Linux and other UNIX systems: - * Run: cmake -S . -B build && cmake --build build && cmake --install build - - Android: - * Read docs/README-android.md - - iOS: - * Read docs/README-ios.md - - Using Cmake: - * Read docs/README-cmake.md - - 2. Look at the example programs in ./test, and check out the online - documentation at https://wiki.libsdl.org/ - - 3. Join the SDL developer discussions, sign up on - https://discourse.libsdl.org/ - and go to the development forum - https://discourse.libsdl.org/c/sdl-development/6 - - 4. Sign up for the announcement list through the web interface: - https://www.libsdl.org/mailing-list.php - -That's it! -Sam Lantinga diff --git a/LICENSE.txt b/LICENSE.txt index 83d8937e..74bb56cd 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 1997-2023 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 diff --git a/README.md b/README.md index ce500fa5..4d4741b0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ - -# Simple DirectMedia Layer (SDL) Version 3.0 - -https://www.libsdl.org/ - -Simple DirectMedia Layer is a cross-platform development library designed -to provide low level access to audio, keyboard, mouse, joystick, and graphics -hardware via OpenGL and Direct3D. It is used by video playback software, -emulators, and popular games including Valve's award winning catalog -and many Humble Bundle games. - -More extensive documentation is available in the docs directory, starting -with [README.md](docs/README.md). If you are migrating to SDL 3.0 from SDL 2.0, -the changes are extensively documented in [README-migration.md](docs/README-migration.md). - -Enjoy! - -Sam Lantinga (slouken@libsdl.org) + +# Simple DirectMedia Layer (SDL) Version 3.0 + +https://www.libsdl.org/ + +Simple DirectMedia Layer is a cross-platform development library designed +to provide low level access to audio, keyboard, mouse, joystick, and graphics +hardware via OpenGL and Direct3D. It is used by video playback software, +emulators, and popular games including Valve's award winning catalog +and many Humble Bundle games. + +More extensive documentation is available in the docs directory, starting +with [README.md](docs/README.md). If you are migrating to SDL 3.0 from SDL 2.0, +the changes are extensively documented in [README-migration.md](docs/README-migration.md). + +Enjoy! + +Sam Lantinga (slouken@libsdl.org) diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 20b205f0..00000000 --- a/TODO.txt +++ /dev/null @@ -1,10 +0,0 @@ -Future work roadmap: - * http://wiki.libsdl.org/Roadmap - - * Check 1.2 revisions: - 3554 - Need to resolve semantics for locking keys on different platforms - 4874 - Do we want screen rotation? At what level? - 4974 - Windows file code needs to convert UTF-8 to Unicode, but we don't need to tap dance for Windows 95/98 - 4865 - See if this is still needed (mouse coordinate clamping) - 4866 - See if this is still needed (blocking window repositioning) - diff --git a/VisualC-GDK/SDL.sln b/VisualC-GDK/SDL.sln index 2cdfa0bd..14289c28 100644 --- a/VisualC-GDK/SDL.sln +++ b/VisualC-GDK/SDL.sln @@ -11,6 +11,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL3_test", "SDL_test\SDL_test.vcxproj", "{DA956FD3-E143-46F2-9FE5-C77BEBC56B1A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testcontroller", "tests\testcontroller\testcontroller.vcxproj", "{55812185-D13C-4022-9C81-32E0F4A08305}" + ProjectSection(ProjectDependencies) = postProject + {DA956FD3-E143-46F2-9FE5-C77BEBC56B1A} = {DA956FD3-E143-46F2-9FE5-C77BEBC56B1A} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testgdk", "tests\testgdk\testgdk.vcxproj", "{1C9A3F71-35A5-4C56-B292-F4375B3C3649}" EndProject diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 0545d3c4..c7fa0a28 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -59,6 +59,7 @@ + @@ -129,7 +130,7 @@ OldStyle OnlyExplicitInline true - Use + NotUsing SDL_internal.h @@ -169,6 +170,12 @@ Windows true + + $(SolutionDir)\shaders\buildshaders.bat $(SolutionDir) + + + Building shader blobs (Xbox Series) + @@ -197,6 +204,12 @@ Windows true + + $(SolutionDir)\shaders\buildshaders.bat $(SolutionDir) one + + + Building shader blobs (Xbox One) + @@ -214,7 +227,7 @@ ProgramDatabase OnlyExplicitInline true - Use + NotUsing SDL_internal.h @@ -257,6 +270,12 @@ true true + + $(SolutionDir)\shaders\buildshaders.bat $(SolutionDir) + + + Building shader blobs (Xbox Series) + @@ -286,6 +305,12 @@ true true + + $(SolutionDir)\shaders\buildshaders.bat $(SolutionDir) one + + + Building shader blobs (Xbox One) + @@ -329,6 +354,7 @@ + @@ -340,11 +366,9 @@ - - @@ -407,6 +431,7 @@ + @@ -417,6 +442,7 @@ + @@ -455,6 +481,12 @@ Create $(IntDir)$(TargetName)_cpp.pch + + true + true + true + true + $(IntDir)$(TargetName)_cpp.pch $(IntDir)$(TargetName)_cpp.pch @@ -467,11 +499,15 @@ $(IntDir)$(TargetName)_cpp.pch $(IntDir)$(TargetName)_cpp.pch + + + + @@ -517,12 +553,10 @@ - - @@ -532,7 +566,6 @@ - @@ -540,8 +573,6 @@ - - @@ -557,6 +588,8 @@ + + @@ -581,11 +614,15 @@ + - + + true + true + @@ -623,6 +660,7 @@ + @@ -630,6 +668,7 @@ + @@ -699,6 +738,7 @@ + @@ -712,8 +752,10 @@ + + @@ -724,6 +766,9 @@ + + NotUsing + @@ -761,10 +806,11 @@ - + + @@ -776,7 +822,6 @@ - @@ -787,5 +832,6 @@ + - + \ No newline at end of file diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 62f79c6b..4692d468 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -1,1402 +1,436 @@  - - {395b3af0-33d0-411b-b153-de1676bf1ef8} - - - {5a3e3167-75be-414f-8947-a5306df372b2} - - - {546d9ed1-988e-49d3-b1a5-e5b3d19de6c1} - - - {a56247ff-5108-4960-ba6a-6814fd1554ec} - - - {8880dfad-2a06-4e84-ab6e-6583641ad2d1} - - - {2b996a7f-f3e9-4300-a97f-2c907bcd89a9} - - - {5713d682-2bc7-4da4-bcf0-262a98f142eb} - - - {5e27e19f-b3f8-4e2d-b323-b00b2040ec86} - - - {a3ab9cff-8495-4a5c-8af6-27e43199a712} - - - {377061e4-3856-4f05-b916-0d3b360df0f6} - - - {226a6643-1c65-4c7f-92aa-861313d974bb} - - - {ef859522-a7fe-4a00-a511-d6a9896adf5b} - - - {01fd2642-4493-4316-b548-fb829f4c9125} - - - {cce7558f-590a-4f0a-ac0d-e579f76e588e} - - - {7a53c9e4-d4bd-40ed-9265-1625df685121} - - - {4c7a051c-ce7c-426c-bf8c-9187827f9052} - - - {97e2f79f-311b-42ea-81b2-e801649fdd93} - - - {baf97c8c-7e90-41e5-bff8-14051b8d3956} - - - {45e50d3a-56c9-4352-b811-0c60c49a2431} - - - {9d86e0ef-d6f6-4db2-bfc5-b3529406fa8d} - - - {b35fa13c-6ed2-4680-8c56-c7d71b76ceab} - - - {61b61b31-9e26-4171-a3bb-b969f1889726} - - - {f63aa216-6ee7-4143-90d3-32be3787f276} - - - {90bee923-89df-417f-a6c3-3e260a7dd54d} - - - {4c8ad943-c2fb-4014-9ca3-041e0ad08426} - - - {3d68ae70-a9ff-46cf-be69-069f0b02aca0} - - - {ebc2fca3-3c26-45e3-815e-3e0581d5e226} - - - {47c445a2-7014-4e15-9660-7c89a27dddcf} - - - {d008487d-6ed0-4251-848b-79a68e3c1459} - - - {c9e8273e-13ae-47dc-bef8-8ad8e64c9a3d} - - - {0b8e136d-56ae-47e7-9981-e863a57ac616} - - - {bf3febd3-9328-43e8-b196-0fd3be8177dd} - - - {1a62dc68-52d2-4c07-9d81-d94dfe1d0d12} - - - {e9f01b22-34b3-4380-ade6-0e96c74e9c90} - - - {f674f22f-7841-4f3a-974e-c36b2d4823fc} - - - {d7ad92de-4e55-4202-9b2b-1bd9a35fe4dc} - - - {8311d79d-9ad5-4369-99fe-b2fb2659d402} - - - {6c4dfb80-fdf9-497c-a6ff-3cd8f22efde9} - - - {4810e35c-33cb-4da2-bfaf-452da20d3c9a} - - - {2cf93f1d-81fd-4bdc-998c-5e2fa43988bc} - - - {5752b7ab-2344-4f38-95ab-b5d3bc150315} - - - {7a0eae3d-f113-4914-b926-6816d1929250} - - - {ee602cbf-96a2-4b0b-92a9-51d38a727411} - - - {a812185b-9060-4a1c-8431-be4f66894626} - - - {31c16cdf-adc4-4950-8293-28ba530f3882} - - - {add61b53-8144-47d6-bd67-3420a87c4905} - - - {e7cdcf36-b462-49c7-98b7-07ea7b3687f4} - - - {82588eef-dcaa-4f69-b2a9-e675940ce54c} - - - {560239c3-8fa1-4d23-a81a-b8408b2f7d3f} - - - {81711059-7575-4ece-9e68-333b63e992c4} - - - {1e44970f-7535-4bfb-b8a5-ea0cea0349e0} - - - {1dd91224-1176-492b-a2cb-e26153394db0} - - - {e3ecfe50-cf22-41d3-8983-2fead5164b47} - - - {5521d22f-1e52-47a6-8c52-06a3b6bdefd7} - - - {4755f3a6-49ac-46d6-86be-21f5c21f2197} - - - {f48c2b17-1bee-4fec-a7c8-24cf619abe08} - - - {3ab60a46-e18e-450a-a916-328fb8547e59} - - - {3ad16a8a-0ed8-439c-a771-383af2e2867f} - - - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - - - API Headers - - - API Headers - - - API Headers - - - audio - - - audio - - - audio - - - audio - - - audio - - - audio - - - core\windows - - - core\windows - - - core\windows - - - core\windows - - - core\windows - - - dynapi - - - dynapi - - - dynapi - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - haptic - - - haptic - - - joystick - - - joystick - - - joystick - - - joystick - - - joystick - - - joystick - - - libm - - - libm - - - hidapi\hidapi - - - locale - - - misc - - - audio\directsound - - - audio\disk - - - audio\dummy - - - audio\wasapi - - - haptic\windows - - - haptic\windows - - - haptic\windows - - - joystick\hidapi - - - joystick\hidapi - - - joystick\windows - - - joystick\windows - - - joystick\windows - - - joystick\windows - - - joystick\virtual - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video\dummy - - - video\dummy - - - video\dummy - - - video\yuv2rgb - - - video\yuv2rgb - - - video\yuv2rgb - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - timer - - - thread - - - thread - - - thread\windows - - - thread\windows - - - thread\generic - - - sensor - - - sensor - - - sensor\dummy - - - sensor\windows - - - render - - - render - - - render - - - render\direct3d - - - render\direct3d11 - - - render\opengl - - - render\opengl - - - render\opengles2 - - - render\opengles2 - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - power - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - video\khronos\vulkan - - - - - - - render\direct3d12 - - - - core\gdk - - - render\direct3d12 - - - video\gdk - - - thread\generic - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - audio - - - audio - - - audio - - - audio - - - audio - - - audio - - - audio - - - audio - - - atomic - - - atomic - - - core\windows - - - core\windows - - - core\windows - - - core\windows - - - cpuinfo - - - dynapi - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - events - - - file - - - filesystem\gdk - - - haptic - - - hidapi - - - joystick - - - joystick - - - joystick - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - libm - - - loadso\windows - - - misc - - - misc\windows - - - locale\windows - - - locale - - - audio\directsound - - - audio\disk - - - audio\dummy - - - audio\wasapi - - - audio\wasapi - - - haptic\windows - - - haptic\windows - - - haptic\windows - - - haptic\dummy - - - joystick\dummy - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\hidapi - - - joystick\windows - - - joystick\windows - - - joystick\windows - - - joystick\windows - - - joystick\windows - - - joystick\virtual - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video - - - video\dummy - - - video\dummy - - - video\dummy - - - video\yuv2rgb - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - video\windows - - - timer - - - timer\windows - - - thread - - - thread\windows - - - thread\windows - - - thread\windows - - - thread\windows - - - thread\windows - - - thread\generic - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - stdlib - - - sensor - - - sensor\dummy - - - sensor\windows - - - render - - - render - - - render - - - render\direct3d - - - render\direct3d - - - render\direct3d11 - - - render\direct3d11 - - - render\opengl - - - render\opengl - - - render\opengles2 - - - render\opengles2 - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - render\software - - - power - + + + - - power\windows - - - render\direct3d12 - - - render\direct3d12 - - - stdlib - - - core\gdk - - - render\direct3d12 - - - render\direct3d12 - - - render\direct3d12 - - - core\windows - - - core\windows - - - video\gdk - - - thread\windows - - - thread\generic - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/VisualC-GDK/clean.sh b/VisualC-GDK/clean.sh index a026b71a..235b79c4 100755 --- a/VisualC-GDK/clean.sh +++ b/VisualC-GDK/clean.sh @@ -4,3 +4,4 @@ find . -type f \( -name '*.bmp' -o -name '*.wav' -o -name '*.dat' \) -print -del find . -depth -type d \( -name Gaming.Desktop.x64 \) -exec rm -rv {} \; find . -depth -type d \( -name Gaming.Xbox.Scarlett.x64 \) -exec rm -rv {} \; find . -depth -type d \( -name Gaming.Xbox.XboxOne.x64 \) -exec rm -rv {} \; +rm shaders/*.h diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_Colors.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_Colors.hlsl new file mode 100644 index 00000000..47eff4cc --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_Colors.hlsl @@ -0,0 +1,19 @@ +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define ColorRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + "DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + "DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + "DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0)" + +[RootSignature(ColorRS)] +float4 main(PixelShaderInput input) : SV_TARGET0 +{ + return input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601.hlsl new file mode 100644 index 00000000..cffbc226 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.5960}; + const float3 Gcoeff = {1.1644, -0.3918, -0.8130}; + const float3 Bcoeff = {1.1644, 2.0172, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).rg; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709.hlsl new file mode 100644 index 00000000..81d409c9 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.7927}; + const float3 Gcoeff = {1.1644, -0.2132, -0.5329}; + const float3 Bcoeff = {1.1644, 2.1124, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).rg; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG.hlsl new file mode 100644 index 00000000..494bce51 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {0.0, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.0000, 0.0000, 1.4020}; + const float3 Gcoeff = {1.0000, -0.3441, -0.7141}; + const float3 Bcoeff = {1.0000, 1.7720, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).rg; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601.hlsl new file mode 100644 index 00000000..794c7637 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.5960}; + const float3 Gcoeff = {1.1644, -0.3918, -0.8130}; + const float3 Bcoeff = {1.1644, 2.0172, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).gr; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709.hlsl new file mode 100644 index 00000000..f5b9522c --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.7927}; + const float3 Gcoeff = {1.1644, -0.2132, -0.5329}; + const float3 Bcoeff = {1.1644, 2.1124, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).gr; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG.hlsl new file mode 100644 index 00000000..1b467b48 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG.hlsl @@ -0,0 +1,43 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureUV : register(t1); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(NVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {0.0, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.0000, 0.0000, 1.4020}; + const float3 Gcoeff = {1.0000, -0.3441, -0.7141}; + const float3 Bcoeff = {1.0000, 1.7720, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.yz = theTextureUV.Sample(theSampler, input.tex).gr; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_Textures.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_Textures.hlsl new file mode 100644 index 00000000..0dcdf89c --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_Textures.hlsl @@ -0,0 +1,24 @@ +Texture2D theTexture : register(t0); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define TextureRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(TextureRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + return theTexture.Sample(theSampler, input.tex) * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601.hlsl new file mode 100644 index 00000000..09e58943 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601.hlsl @@ -0,0 +1,46 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureU : register(t1); +Texture2D theTextureV : register(t2); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define YUVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t2), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(YUVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.5960}; + const float3 Gcoeff = {1.1644, -0.3918, -0.8130}; + const float3 Bcoeff = {1.1644, 2.0172, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.y = theTextureU.Sample(theSampler, input.tex).r; + yuv.z = theTextureV.Sample(theSampler, input.tex).r; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709.hlsl new file mode 100644 index 00000000..f5aa0cd7 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709.hlsl @@ -0,0 +1,46 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureU : register(t1); +Texture2D theTextureV : register(t2); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define YUVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t2), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(YUVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {-0.0627451017, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.1644, 0.0000, 1.7927}; + const float3 Gcoeff = {1.1644, -0.2132, -0.5329}; + const float3 Bcoeff = {1.1644, 2.1124, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.y = theTextureU.Sample(theSampler, input.tex).r; + yuv.z = theTextureV.Sample(theSampler, input.tex).r; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG.hlsl b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG.hlsl new file mode 100644 index 00000000..84d09b8b --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG.hlsl @@ -0,0 +1,46 @@ +Texture2D theTextureY : register(t0); +Texture2D theTextureU : register(t1); +Texture2D theTextureV : register(t2); +SamplerState theSampler : register(s0); + +struct PixelShaderInput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define YUVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t2), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(YUVRS)] +float4 main(PixelShaderInput input) : SV_TARGET +{ + const float3 offset = {0.0, -0.501960814, -0.501960814}; + const float3 Rcoeff = {1.0000, 0.0000, 1.4020}; + const float3 Gcoeff = {1.0000, -0.3441, -0.7141}; + const float3 Bcoeff = {1.0000, 1.7720, 0.0000}; + + float4 Output; + + float3 yuv; + yuv.x = theTextureY.Sample(theSampler, input.tex).r; + yuv.y = theTextureU.Sample(theSampler, input.tex).r; + yuv.z = theTextureV.Sample(theSampler, input.tex).r; + + yuv += offset; + Output.r = dot(yuv, Rcoeff); + Output.g = dot(yuv, Gcoeff); + Output.b = dot(yuv, Bcoeff); + Output.a = 1.0f; + + return Output * input.color; +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/D3D12_VertexShader.hlsl b/VisualC-GDK/shaders/D3D12_VertexShader.hlsl new file mode 100644 index 00000000..e10b4889 --- /dev/null +++ b/VisualC-GDK/shaders/D3D12_VertexShader.hlsl @@ -0,0 +1,95 @@ +#pragma pack_matrix( row_major ) + +struct VertexShaderConstants +{ + matrix model; + matrix projectionAndView; +}; +ConstantBuffer Constants : register(b0); + +struct VertexShaderInput +{ + float3 pos : POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +struct VertexShaderOutput +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; + float4 color : COLOR0; +}; + +#define ColorRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + "DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + "DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + "DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0)" + +#define TextureRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +#define YUVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t2), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +#define NVRS \ + "RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \ + " DENY_DOMAIN_SHADER_ROOT_ACCESS |" \ + " DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \ + " DENY_HULL_SHADER_ROOT_ACCESS )," \ + "RootConstants(num32BitConstants=32, b0),"\ + "DescriptorTable ( SRV(t0), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( SRV(t1), visibility = SHADER_VISIBILITY_PIXEL ),"\ + "DescriptorTable ( Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL )" + +[RootSignature(ColorRS)] +VertexShaderOutput mainColor(VertexShaderInput input) +{ + VertexShaderOutput output; + float4 pos = float4(input.pos, 1.0f); + + // Transform the vertex position into projected space. + pos = mul(pos, Constants.model); + pos = mul(pos, Constants.projectionAndView); + output.pos = pos; + + // Pass through texture coordinates and color values without transformation + output.tex = input.tex; + output.color = input.color; + + return output; +} + +[RootSignature(TextureRS)] +VertexShaderOutput mainTexture(VertexShaderInput input) +{ + return mainColor(input); +} + +[RootSignature(YUVRS)] +VertexShaderOutput mainYUV(VertexShaderInput input) +{ + return mainColor(input); +} + +[RootSignature(NVRS)] +VertexShaderOutput mainNV(VertexShaderInput input) +{ + return mainColor(input); +} \ No newline at end of file diff --git a/VisualC-GDK/shaders/buildshaders.bat b/VisualC-GDK/shaders/buildshaders.bat new file mode 100644 index 00000000..4447b5e2 --- /dev/null +++ b/VisualC-GDK/shaders/buildshaders.bat @@ -0,0 +1,35 @@ +if %2.==one. goto setxboxone +rem Xbox Series compile +set XBOXDXC="%GameDKLatest%\GXDK\bin\Scarlett\DXC.exe" +set SUFFIX=_Series.h +goto startbuild + +:setxboxone +set XBOXDXC="%GameDKLatest%\GXDK\bin\XboxOne\DXC.exe" +set SUFFIX=_One.h + +:startbuild +echo Building with %XBOXDXC% +cd "%1\shaders" +rem Root Signatures +%XBOXDXC% -E ColorRS -T rootsig_1_1 -rootsig-define ColorRS -Fh D3D12_RootSig_Color%SUFFIX% -Vn D3D12_RootSig_Color D3D12_VertexShader.hlsl +%XBOXDXC% -E TextureRS -T rootsig_1_1 -rootsig-define TextureRS -Fh D3D12_RootSig_Texture%SUFFIX% -Vn D3D12_RootSig_Texture D3D12_VertexShader.hlsl +%XBOXDXC% -E YUVRS -T rootsig_1_1 -rootsig-define YUVRS -Fh D3D12_RootSig_YUV%SUFFIX% -Vn D3D12_RootSig_YUV D3D12_VertexShader.hlsl +%XBOXDXC% -E NVRS -T rootsig_1_1 -rootsig-define NVRS -Fh D3D12_RootSig_NV%SUFFIX% -Vn D3D12_RootSig_NV D3D12_VertexShader.hlsl +rem Vertex Shaders +%XBOXDXC% -E mainColor -T vs_6_0 -Fh D3D12_VertexShader_Color%SUFFIX% -Vn D3D12_VertexShader_Color D3D12_VertexShader.hlsl +%XBOXDXC% -E mainTexture -T vs_6_0 -Fh D3D12_VertexShader_Texture%SUFFIX% -Vn D3D12_VertexShader_Texture D3D12_VertexShader.hlsl +%XBOXDXC% -E mainNV -T vs_6_0 -Fh D3D12_VertexShader_NV%SUFFIX% -Vn D3D12_VertexShader_NV D3D12_VertexShader.hlsl +%XBOXDXC% -E mainYUV -T vs_6_0 -Fh D3D12_VertexShader_YUV%SUFFIX% -Vn D3D12_VertexShader_YUV D3D12_VertexShader.hlsl +rem Pixel Shaders +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_Colors%SUFFIX% -Vn D3D12_PixelShader_Colors D3D12_PixelShader_Colors.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV12_BT601%SUFFIX% -Vn D3D12_PixelShader_NV12_BT601 D3D12_PixelShader_NV12_BT601.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV12_BT709%SUFFIX% -Vn D3D12_PixelShader_NV12_BT709 D3D12_PixelShader_NV12_BT709.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV12_JPEG%SUFFIX% -Vn D3D12_PixelShader_NV12_JPEG D3D12_PixelShader_NV12_JPEG.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV21_BT601%SUFFIX% -Vn D3D12_PixelShader_NV21_BT601 D3D12_PixelShader_NV21_BT601.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV21_BT709%SUFFIX% -Vn D3D12_PixelShader_NV21_BT709 D3D12_PixelShader_NV21_BT709.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_NV21_JPEG%SUFFIX% -Vn D3D12_PixelShader_NV21_JPEG D3D12_PixelShader_NV21_JPEG.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_Textures%SUFFIX% -Vn D3D12_PixelShader_Textures D3D12_PixelShader_Textures.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_YUV_BT601%SUFFIX% -Vn D3D12_PixelShader_YUV_BT601 D3D12_PixelShader_YUV_BT601.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_YUV_BT709%SUFFIX% -Vn D3D12_PixelShader_YUV_BT709 D3D12_PixelShader_YUV_BT709.hlsl +%XBOXDXC% -E main -T ps_6_0 -Fh D3D12_PixelShader_YUV_JPEG%SUFFIX% -Vn D3D12_PixelShader_YUV_JPEG D3D12_PixelShader_YUV_JPEG.hlsl \ No newline at end of file diff --git a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj index 3f015616..5680fe31 100644 --- a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj +++ b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj @@ -139,7 +139,7 @@ Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -211,7 +211,7 @@ true Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -273,6 +273,12 @@ false true + + {da956fd3-e143-46f2-9fe5-c77bebc56b1a} + false + false + true + @@ -298,7 +304,7 @@ - + Document true true @@ -330,4 +336,4 @@ - + \ No newline at end of file diff --git a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj.filters b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj.filters index e4e2413e..90aec1b6 100644 --- a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj.filters +++ b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj.filters @@ -18,9 +18,6 @@ logos - - wingdk - wingdk @@ -34,6 +31,9 @@ logos + + wingdk + @@ -49,4 +49,4 @@ {e704dcb9-c83c-4c94-a139-b0f3e3f428f2} - + \ No newline at end of file diff --git a/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/VisualC-GDK/tests/testgdk/src/testgdk.cpp index 1a962a4c..bc4895bd 100644 --- a/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -53,23 +53,14 @@ static struct int soundpos; /* Current play position */ } wave; -static SDL_AudioDeviceID device; - -static void -close_audio() -{ - if (device != 0) { - SDL_CloseAudioDevice(device); - device = 0; - } -} +static SDL_AudioStream *stream; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { SDL_free(sprites); - close_audio(); + SDL_DestroyAudioStream(stream); SDL_free(wave.sound); SDLTest_CommonQuit(state); /* If rc is 0, just let main return normally rather than calling exit. @@ -80,49 +71,13 @@ quit(int rc) } } -static void -open_audio() +static int fillerup(void) { - /* Initialize fillerup() variables */ - device = SDL_OpenAudioDevice(NULL, SDL_FALSE, &wave.spec, NULL, 0); - if (!device) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); - SDL_free(wave.sound); - quit(2); + const int minimum = (wave.soundlen / SDL_AUDIO_FRAMESIZE(wave.spec)) / 2; + if (SDL_GetAudioStreamQueued(stream) < minimum) { + SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); } - - /* Let the audio run */ - SDL_PlayAudioDevice(device); -} - -static void -reopen_audio() -{ - close_audio(); - open_audio(); -} - -void SDLCALL -fillerup(void *unused, Uint8 *stream, int len) -{ - Uint8 *waveptr; - int waveleft; - - /* Set up the pointers */ - waveptr = wave.sound + wave.soundpos; - waveleft = wave.soundlen - wave.soundpos; - - /* Go! */ - while (waveleft <= len) { - SDL_memcpy(stream, waveptr, waveleft); - stream += waveleft; - len -= waveleft; - waveptr = wave.sound; - waveleft = wave.soundlen; - wave.soundpos = 0; - } - SDL_memcpy(stream, waveptr, len); - wave.soundpos += len; + return 0; } void @@ -371,6 +326,7 @@ loop() } DrawSprites(state->renderers[i], sprites[i]); } + fillerup(); } int @@ -385,7 +341,7 @@ main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); - if (state == NULL) { + if (!state) { return 1; } @@ -448,7 +404,7 @@ main(int argc, char *argv[]) /* Create the windows, initialize the renderers, and load the textures */ sprites = (SDL_Texture **) SDL_malloc(state->num_windows * sizeof(*sprites)); - if (sprites == NULL) { + if (!sprites) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); quit(2); } @@ -463,19 +419,17 @@ main(int argc, char *argv[]) soundname = GetResourceFilename(argc > 1 ? argv[1] : NULL, "sample.wav"); - if (soundname == NULL) { + if (!soundname) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); quit(1); } /* Load the wave file into memory */ - if (SDL_LoadWAV(soundname, &wave.spec, &wave.sound, &wave.soundlen) == NULL) { + if (SDL_LoadWAV(soundname, &wave.spec, &wave.sound, &wave.soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", soundname, SDL_GetError()); quit(1); } - wave.spec.callback = fillerup; - /* Show the list of available drivers */ SDL_Log("Available audio drivers:"); for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) { @@ -484,7 +438,12 @@ main(int argc, char *argv[]) SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver()); - open_audio(); + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); + return -1; + } + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); /* Main render loop */ done = 0; diff --git a/VisualC-GDK/tests/testgdk/testgdk.vcxproj b/VisualC-GDK/tests/testgdk/testgdk.vcxproj index c0e74e22..c43e5f4d 100644 --- a/VisualC-GDK/tests/testgdk/testgdk.vcxproj +++ b/VisualC-GDK/tests/testgdk/testgdk.vcxproj @@ -139,7 +139,7 @@ Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -223,7 +223,7 @@ true Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -346,7 +346,7 @@ copy "%(FullPath)" "$(OutDir)\" - + Document true true diff --git a/VisualC-GDK/tests/testgdk/testgdk.vcxproj.filters b/VisualC-GDK/tests/testgdk/testgdk.vcxproj.filters index b82a9898..1cbae864 100644 --- a/VisualC-GDK/tests/testgdk/testgdk.vcxproj.filters +++ b/VisualC-GDK/tests/testgdk/testgdk.vcxproj.filters @@ -18,9 +18,6 @@ logos - - wingdk - wingdk @@ -35,6 +32,9 @@ logos + + wingdk + diff --git a/VisualC-GDK/tests/testsprite/testsprite.vcxproj b/VisualC-GDK/tests/testsprite/testsprite.vcxproj index 211f14cb..9534f5b0 100644 --- a/VisualC-GDK/tests/testsprite/testsprite.vcxproj +++ b/VisualC-GDK/tests/testsprite/testsprite.vcxproj @@ -139,7 +139,7 @@ Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -223,7 +223,7 @@ true Windows - xgameruntime.lib;../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib;%(AdditionalDependencies) + xgameruntime.lib;../Microsoft.Xbox.Services.GDK.C.Thunks.lib;%(AdditionalDependencies) @@ -346,7 +346,7 @@ copy "%(FullPath)" "$(OutDir)\" - + Document true true diff --git a/VisualC-GDK/tests/testsprite/testsprite.vcxproj.filters b/VisualC-GDK/tests/testsprite/testsprite.vcxproj.filters index 3e0399e1..ac1cda63 100644 --- a/VisualC-GDK/tests/testsprite/testsprite.vcxproj.filters +++ b/VisualC-GDK/tests/testsprite/testsprite.vcxproj.filters @@ -18,9 +18,6 @@ logos - - wingdk - xboxseries @@ -34,6 +31,9 @@ logos + + wingdk + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index f45e8240..715c6020 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -67,10 +67,12 @@ + + @@ -78,17 +80,16 @@ - - + @@ -126,12 +127,14 @@ + + @@ -151,10 +154,12 @@ + + @@ -175,8 +180,8 @@ - + @@ -247,6 +252,8 @@ true true + + @@ -299,6 +306,7 @@ + @@ -324,6 +332,7 @@ + @@ -332,6 +341,8 @@ + + true @@ -380,6 +391,7 @@ + @@ -391,11 +403,13 @@ + + @@ -405,6 +419,9 @@ + + NotUsing + @@ -503,10 +520,11 @@ - + + true @@ -659,6 +677,7 @@ + @@ -858,5 +877,6 @@ + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index 8edd716f..728fdbbc 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -7,6 +7,12 @@ {68e1b30b-19ed-4612-93e4-6260c5a979e5} + + {00004a2523fc69c7128c60648c860000} + + + {0000318d975e0a2867ab1d5727bf0000} + @@ -99,6 +105,9 @@ Header Files + + Header Files + Header Files @@ -129,9 +138,6 @@ Header Files - - Header Files - Header Files @@ -141,9 +147,6 @@ Header Files - - Header Files - Header Files @@ -162,6 +165,9 @@ Header Files + + Header Files + Header Files @@ -255,6 +261,9 @@ Source Files + + Source Files + Source Files @@ -270,6 +279,9 @@ Source Files + + main + Source Files @@ -324,6 +336,9 @@ Source Files + + Source Files + Source Files @@ -333,6 +348,9 @@ Source Files + + Source Files + Source Files @@ -387,10 +405,10 @@ Source Files - + Source Files - + Source Files @@ -492,6 +510,12 @@ Source Files + + Source Files + + + Source Files + Source Files @@ -528,6 +552,9 @@ Source Files + + Source Files + Source Files @@ -561,6 +588,9 @@ Source Files + + Source Files + Source Files @@ -576,6 +606,12 @@ Source Files + + main\generic + + + main + Source Files @@ -600,6 +636,9 @@ Source Files + + Source Files + Source Files @@ -639,12 +678,24 @@ Source Files + + Source Files + Source Files + + Source Files + Source Files + + Source Files + + + Source Files + Source Files @@ -756,9 +807,6 @@ Source Files - - Source Files - Source Files @@ -768,6 +816,12 @@ Source Files + + Source Files + + + Source Files + Source Files @@ -795,12 +849,6 @@ Source Files - - Source Files - - - Source Files - Source Files @@ -851,6 +899,9 @@ Source Files + + stdlib + Source Files diff --git a/VisualC/SDL.sln b/VisualC/SDL.sln index ff6a2b46..67484805 100644 --- a/VisualC/SDL.sln +++ b/VisualC/SDL.sln @@ -50,6 +50,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsensor", "tests\testsen EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsurround", "tests\testsurround\testsurround.vcxproj", "{70B894A9-E306-49E8-ABC2-932A952A5E5F}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testpen", "tests\testpen\testpen.vcxproj", "{C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -250,6 +252,14 @@ Global {70B894A9-E306-49E8-ABC2-932A952A5E5F}.Release|Win32.Build.0 = Release|Win32 {70B894A9-E306-49E8-ABC2-932A952A5E5F}.Release|x64.ActiveCfg = Release|x64 {70B894A9-E306-49E8-ABC2-932A952A5E5F}.Release|x64.Build.0 = Release|x64 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Debug|Win32.ActiveCfg = Debug|Win32 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Debug|Win32.Build.0 = Debug|Win32 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Debug|x64.ActiveCfg = Debug|x64 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Debug|x64.Build.0 = Debug|x64 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Release|Win32.ActiveCfg = Release|Win32 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Release|Win32.Build.0 = Release|Win32 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Release|x64.ActiveCfg = Release|x64 + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -277,6 +287,7 @@ Global {40FB7794-D3C3-4CFE-BCF4-A80C97635682} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} {C4E04D18-EF76-4B42-B4C2-16A1BACDC0A4} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} {70B894A9-E306-49E8-ABC2-932A952A5E5F} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3} = {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 e3554040..0b534578 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -43,6 +43,7 @@ + @@ -279,10 +280,12 @@ + + @@ -290,11 +293,9 @@ - - @@ -331,6 +332,7 @@ + @@ -355,6 +357,7 @@ + @@ -365,6 +368,7 @@ + @@ -394,11 +398,15 @@ Create Create + + + + @@ -443,12 +451,10 @@ - - @@ -458,7 +464,6 @@ - @@ -466,8 +471,6 @@ - - @@ -483,6 +486,8 @@ + + @@ -500,6 +505,7 @@ + @@ -524,6 +530,7 @@ + @@ -531,6 +538,7 @@ + @@ -577,6 +585,7 @@ + @@ -588,10 +597,12 @@ - + + + @@ -602,6 +613,9 @@ + + NotUsing + @@ -635,10 +649,11 @@ - + + @@ -650,7 +665,6 @@ - @@ -661,5 +675,6 @@ + - \ No newline at end of file + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index dbf4e543..e53a8eb4 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -169,6 +169,12 @@ {f48c2b17-1bee-4fec-a7c8-24cf619abe08} + + {00001967ea2801028a046a722a070000} + + + {0000ddc7911820dbe64274d3654f0000} + @@ -288,6 +294,9 @@ API Headers + + API Headers + API Headers @@ -300,6 +309,9 @@ API Headers + + API Headers + API Headers @@ -321,9 +333,6 @@ API Headers - - API Headers - API Headers @@ -333,9 +342,6 @@ API Headers - - API Headers - API Headers @@ -387,7 +393,11 @@ API Headers + + main + + API Headers @@ -415,7 +425,7 @@ audio - + core\windows @@ -440,6 +450,9 @@ dynapi + + dynapi + events @@ -488,6 +501,9 @@ joystick + + joystick + joystick @@ -578,9 +594,6 @@ video - - video - video @@ -611,9 +624,6 @@ video\yuv2rgb - - video\windows - video\windows @@ -638,9 +648,6 @@ video\windows - - video\windows - video\windows @@ -813,6 +820,7 @@ + render\direct3d12 @@ -820,12 +828,20 @@ + + main\generic + + + main + + + audio @@ -857,6 +873,12 @@ atomic + + core + + + core + core\windows @@ -893,6 +915,9 @@ events + + events + events @@ -923,6 +948,9 @@ joystick + + joystick + libm @@ -1061,6 +1089,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1142,9 +1173,6 @@ video - - video - video @@ -1154,6 +1182,12 @@ video + + video + + + video + video @@ -1199,9 +1233,6 @@ video\windows - - video\windows - video\windows @@ -1283,6 +1314,9 @@ render + + render + render @@ -1353,6 +1387,9 @@ stdlib + + stdlib + diff --git a/VisualC/pkg-support/cmake/sdl3-config.cmake b/VisualC/pkg-support/cmake/sdl3-config.cmake index b687aa3a..4a9b4ac2 100644 --- a/VisualC/pkg-support/cmake/sdl3-config.cmake +++ b/VisualC/pkg-support/cmake/sdl3-config.cmake @@ -40,7 +40,7 @@ else() endif() set_and_check(_sdl3_prefix "${CMAKE_CURRENT_LIST_DIR}/..") -set(_sdl3_include_dirs "${_sdl3_prefix}/include;${_sdl3_prefix}/include/SDL3") +set(_sdl3_include_dirs "${_sdl3_prefix}/include") unset(_sdl3_prefix) set(SDL3_LIBRARIES SDL3::SDL3) diff --git a/VisualC/tests/testautomation/testautomation.vcxproj b/VisualC/tests/testautomation/testautomation.vcxproj index 2cae2109..dbe1f3d8 100644 --- a/VisualC/tests/testautomation/testautomation.vcxproj +++ b/VisualC/tests/testautomation/testautomation.vcxproj @@ -209,17 +209,24 @@ + + $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) + $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) + $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) + $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) + + - + diff --git a/VisualC/tests/testpen/testpen.vcxproj b/VisualC/tests/testpen/testpen.vcxproj new file mode 100644 index 00000000..dc1a28e0 --- /dev/null +++ b/VisualC/tests/testpen/testpen.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {C4E04D18-EF76-4B42-B4C2-16A1BACDC1A3} + testpower + 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 + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/testpower.tlb + + + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Debug/testpower.tlb + + + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/testpower.tlb + + + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Release/testpower.tlb + + + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + {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/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 90e2ccb9..c6ea6f7a 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -33,6 +33,9 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */; }; + 000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = 000078E1881E857EBB6C0000 /* SDL_hashtable.c */; }; + 0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); }; 007317A40858DECD00B2BC32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179D0858DECD00B2BC32 /* Cocoa.framework */; platformFilters = (macos, ); }; 007317A60858DECD00B2BC32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179F0858DECD00B2BC32 /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, ); }; 00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); }; @@ -49,6 +52,9 @@ 566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; }; 566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; }; 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 63134A222A7902CF0021E9A6 /* SDL_pen.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A212A7902CF0021E9A6 /* SDL_pen.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */; }; + 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; }; 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; 75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; }; 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; }; @@ -63,6 +69,7 @@ A75FDBB823E4CBC700529352 /* ReadMe.txt in Resources */ = {isa = PBXBuildFile; fileRef = F59C710300D5CB5801000001 /* ReadMe.txt */; }; A75FDBC523EA380300529352 /* SDL_hidapi_rumble.h in Headers */ = {isa = PBXBuildFile; fileRef = A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */; }; A75FDBCE23EA380300529352 /* SDL_hidapi_rumble.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */; }; + A79745702B2E9D39009D224A /* SDL_hidapi_steamdeck.c in Sources */ = {isa = PBXBuildFile; fileRef = A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */; }; A7D8A94B23E2514000DCD162 /* SDL.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A57123E2513D00DCD162 /* SDL.c */; }; A7D8A95123E2514000DCD162 /* SDL_spinlock.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A57323E2513D00DCD162 /* SDL_spinlock.c */; }; A7D8A95723E2514000DCD162 /* SDL_atomic.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A57423E2513D00DCD162 /* SDL_atomic.c */; }; @@ -89,7 +96,6 @@ A7D8AB2523E2514100DCD162 /* SDL_log.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A5DD23E2513D00DCD162 /* SDL_log.c */; }; A7D8AB2B23E2514100DCD162 /* SDL_timer.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A5DF23E2513D00DCD162 /* SDL_timer.c */; }; A7D8AB3123E2514100DCD162 /* SDL_timer_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A5E023E2513D00DCD162 /* SDL_timer_c.h */; }; - A7D8AB3723E2514100DCD162 /* SDL_systimer.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A5E223E2513D00DCD162 /* SDL_systimer.c */; }; A7D8AB4923E2514100DCD162 /* SDL_systimer.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A5E823E2513D00DCD162 /* SDL_systimer.c */; }; A7D8AB5B23E2514100DCD162 /* SDL_offscreenevents_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A5EE23E2513D00DCD162 /* SDL_offscreenevents_c.h */; }; A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A5EF23E2513D00DCD162 /* SDL_offscreenwindow.c */; }; @@ -109,7 +115,6 @@ A7D8ABF723E2514100DCD162 /* SDL_nullvideo.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A60A23E2513D00DCD162 /* SDL_nullvideo.h */; }; A7D8ABFD23E2514100DCD162 /* SDL_nullevents_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A60B23E2513D00DCD162 /* SDL_nullevents_c.h */; }; A7D8AC0323E2514100DCD162 /* SDL_rect_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A60C23E2513D00DCD162 /* SDL_rect_c.h */; }; - A7D8AC0923E2514100DCD162 /* SDL_shape_internals.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A60D23E2513D00DCD162 /* SDL_shape_internals.h */; }; A7D8AC0F23E2514100DCD162 /* SDL_video.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A60E23E2513D00DCD162 /* SDL_video.c */; }; A7D8AC2D23E2514100DCD162 /* SDL_surface.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A61423E2513D00DCD162 /* SDL_surface.c */; }; A7D8AC3323E2514100DCD162 /* SDL_RLEaccel.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */; }; @@ -127,7 +132,6 @@ A7D8ADF223E2514100DCD162 /* SDL_blit_A.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A66423E2513E00DCD162 /* SDL_blit_A.c */; }; A7D8AE7623E2514100DCD162 /* SDL_clipboard.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A67B23E2513E00DCD162 /* SDL_clipboard.c */; }; A7D8AE7C23E2514100DCD162 /* SDL_yuv.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A67C23E2513E00DCD162 /* SDL_yuv.c */; }; - A7D8AE8223E2514100DCD162 /* SDL_cocoashape.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A67E23E2513E00DCD162 /* SDL_cocoashape.h */; }; A7D8AE8823E2514100DCD162 /* SDL_cocoaopengl.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A67F23E2513E00DCD162 /* SDL_cocoaopengl.m */; }; A7D8AE8E23E2514100DCD162 /* SDL_cocoakeyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A68023E2513E00DCD162 /* SDL_cocoakeyboard.h */; }; A7D8AE9423E2514100DCD162 /* SDL_cocoamodes.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A68123E2513E00DCD162 /* SDL_cocoamodes.m */; }; @@ -142,7 +146,6 @@ A7D8AED023E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A68B23E2513E00DCD162 /* SDL_cocoamessagebox.m */; }; A7D8AED623E2514100DCD162 /* SDL_cocoakeyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A68C23E2513E00DCD162 /* SDL_cocoakeyboard.m */; }; A7D8AEDC23E2514100DCD162 /* SDL_cocoaopengl.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A68D23E2513E00DCD162 /* SDL_cocoaopengl.h */; }; - A7D8AEE223E2514100DCD162 /* SDL_cocoashape.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A68E23E2513E00DCD162 /* SDL_cocoashape.m */; }; A7D8AEE823E2514100DCD162 /* SDL_cocoavulkan.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A68F23E2513E00DCD162 /* SDL_cocoavulkan.h */; }; A7D8AEEE23E2514100DCD162 /* SDL_cocoaopengles.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A69023E2513E00DCD162 /* SDL_cocoaopengles.h */; }; A7D8AEF423E2514100DCD162 /* SDL_cocoamodes.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A69123E2513E00DCD162 /* SDL_cocoamodes.h */; }; @@ -185,7 +188,6 @@ A7D8B39823E2514200DCD162 /* SDL_blit_copy.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A76623E2513E00DCD162 /* SDL_blit_copy.h */; }; A7D8B39E23E2514200DCD162 /* SDL_RLEaccel_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */; }; A7D8B3A423E2514200DCD162 /* SDL_fillrect.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A76823E2513E00DCD162 /* SDL_fillrect.c */; }; - A7D8B3AA23E2514200DCD162 /* SDL_shape.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A76923E2513E00DCD162 /* SDL_shape.c */; }; A7D8B3B023E2514200DCD162 /* SDL_yuv_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A76A23E2513E00DCD162 /* SDL_yuv_c.h */; }; A7D8B3B623E2514200DCD162 /* SDL_blit.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A76B23E2513E00DCD162 /* SDL_blit.h */; }; A7D8B3BF23E2514200DCD162 /* yuv_rgb.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A76E23E2513E00DCD162 /* yuv_rgb.c */; }; @@ -366,6 +368,11 @@ A7D8BBE923E2574800DCD162 /* SDL_uikitvulkan.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A62523E2513D00DCD162 /* SDL_uikitvulkan.m */; }; A7D8BBEA23E2574800DCD162 /* SDL_uikitwindow.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A62723E2513D00DCD162 /* SDL_uikitwindow.h */; }; A7D8BBEB23E2574800DCD162 /* SDL_uikitwindow.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A61A23E2513D00DCD162 /* SDL_uikitwindow.m */; }; + E4A568B62AF763940062EEC4 /* SDL_sysmain_callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = E4A568B52AF763940062EEC4 /* SDL_sysmain_callbacks.c */; }; + E4F7981A2AD8D84800669F54 /* SDL_core_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F798192AD8D84800669F54 /* SDL_core_unsupported.c */; }; + E4F7981C2AD8D85500669F54 /* SDL_dynapi_unsupported.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F7981B2AD8D85500669F54 /* SDL_dynapi_unsupported.h */; }; + E4F7981E2AD8D86A00669F54 /* SDL_render_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */; }; + E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F7981F2AD8D87F00669F54 /* SDL_video_unsupported.c */; }; F31A92C828D4CB39003BFD6A /* SDL_offscreenopengles.h in Headers */ = {isa = PBXBuildFile; fileRef = F31A92C628D4CB39003BFD6A /* SDL_offscreenopengles.h */; }; F31A92D228D4CB39003BFD6A /* SDL_offscreenopengles.c in Sources */ = {isa = PBXBuildFile; fileRef = F31A92C728D4CB39003BFD6A /* SDL_offscreenopengles.c */; }; F32305FF28939F6400E66D30 /* SDL_hidapi_combined.c in Sources */ = {isa = PBXBuildFile; fileRef = F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */; }; @@ -376,6 +383,10 @@ F32DDAD32AB795A30041EAA5 /* SDL_audioqueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F32DDACD2AB795A30041EAA5 /* SDL_audioqueue.h */; }; F32DDAD42AB795A30041EAA5 /* SDL_audioresample.c in Sources */ = {isa = PBXBuildFile; fileRef = F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */; }; F34B9895291DEFF500AAC96E /* SDL_hidapi_steam.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */; }; + F362B9192B3349E200D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9152B3349E200D30B94 /* controller_list.h */; }; + F362B91A2B3349E200D30B94 /* SDL_gamepad_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */; }; + F362B91B2B3349E200D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */; }; + F362B91C2B3349E200D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */; }; F36C7AD1294BA009004D61C3 /* SDL_runapp.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C7AD0294BA009004D61C3 /* SDL_runapp.c */; }; F376F6552559B4E300CFC0BC /* SDL_hidapi.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A81423E2513F00DCD162 /* SDL_hidapi.c */; }; F37A8E1A28405AA100C38E95 /* CMake in Resources */ = {isa = PBXBuildFile; fileRef = F37A8E1928405AA100C38E95 /* CMake */; }; @@ -405,6 +416,18 @@ F3B38CDB296E2E52005DA6D3 /* SDL_oldnames.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B38CCD296E2E52005DA6D3 /* SDL_oldnames.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3B38CDF296E2E52005DA6D3 /* SDL_intrin.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B38CCE296E2E52005DA6D3 /* SDL_intrin.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */; }; + F3DDCC562AFD42B600B0842B /* SDL_clipboard_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */; }; + F3DDCC572AFD42B600B0842B /* SDL_surface_pixel_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC4E2AFD42B500B0842B /* SDL_surface_pixel_impl.h */; }; + F3DDCC582AFD42B600B0842B /* SDL_video_capture.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DDCC4F2AFD42B500B0842B /* SDL_video_capture.c */; }; + F3DDCC592AFD42B600B0842B /* SDL_video_capture_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = F3DDCC502AFD42B500B0842B /* SDL_video_capture_apple.m */; }; + F3DDCC5A2AFD42B600B0842B /* SDL_video_capture_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC512AFD42B500B0842B /* SDL_video_capture_c.h */; }; + F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC522AFD42B600B0842B /* SDL_video_c.h */; }; + F3DDCC5C2AFD42B600B0842B /* SDL_sysvideocapture.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC532AFD42B600B0842B /* SDL_sysvideocapture.h */; }; + F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */; }; + F3DDCC5E2AFD42B600B0842B /* SDL_video_capture_v4l2.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DDCC552AFD42B600B0842B /* SDL_video_capture_v4l2.c */; }; + F3DDCC602AFD432500B0842B /* SDL_video_capture.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC5F2AFD432500B0842B /* SDL_video_capture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F3E5A6EB2AD5E0E600293D83 /* SDL_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */; }; + F3E5A6ED2AD5E10800293D83 /* SDL_properties.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E5A6EC2AD5E10800293D83 /* SDL_properties.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F07D5A269640160074468B /* SDL_hidapi_luna.c in Sources */ = {isa = PBXBuildFile; fileRef = F3F07D59269640160074468B /* SDL_hidapi_luna.c */; }; F3F7D8ED2933074E00816151 /* SDL_audio.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8AA2933074900816151 /* SDL_audio.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D8F12933074E00816151 /* SDL_platform.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8AB2933074900816151 /* SDL_platform.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -422,12 +445,10 @@ F3F7D9212933074E00816151 /* SDL_log.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8B72933074A00816151 /* SDL_log.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9252933074E00816151 /* SDL_egl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8B82933074A00816151 /* SDL_egl.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9292933074E00816151 /* SDL_atomic.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8B92933074A00816151 /* SDL_atomic.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F3F7D92D2933074E00816151 /* SDL_shape.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BA2933074A00816151 /* SDL_shape.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9312933074E00816151 /* SDL_surface.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BB2933074A00816151 /* SDL_surface.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9352933074E00816151 /* SDL_error.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BC2933074A00816151 /* SDL_error.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9392933074E00816151 /* SDL_opengles2_gl2ext.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BD2933074A00816151 /* SDL_opengles2_gl2ext.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D93D2933074E00816151 /* SDL_endian.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BE2933074A00816151 /* SDL_endian.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F3F7D9412933074E00816151 /* SDL_syswm.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8BF2933074A00816151 /* SDL_syswm.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9452933074E00816151 /* SDL_opengl_glext.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8C02933074A00816151 /* SDL_opengl_glext.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9492933074E00816151 /* SDL_scancode.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8C12933074B00816151 /* SDL_scancode.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D94D2933074E00816151 /* SDL_sensor.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8C22933074B00816151 /* SDL_sensor.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -495,6 +516,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 00003260407E1002EAC10000 /* SDL_main_callbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_main_callbacks.h; sourceTree = ""; }; + 000078E1881E857EBB6C0000 /* SDL_hashtable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hashtable.c; sourceTree = ""; }; + 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_main_callbacks.c; sourceTree = ""; }; + 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hashtable.h; sourceTree = ""; }; + 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysmain_callbacks.m; sourceTree = ""; }; 0073179D0858DECD00B2BC32 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 0073179F0858DECD00B2BC32 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 007317C10858E15000B2BC32 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; @@ -510,6 +536,9 @@ 566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = ""; }; 566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = ""; }; 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = ""; }; + 63134A212A7902CF0021E9A6 /* SDL_pen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_pen.h; path = SDL3/SDL_pen.h; sourceTree = ""; }; + 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_pen_c.h; sourceTree = ""; }; + 63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = ""; }; 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; @@ -535,6 +564,7 @@ A75FDBA723E4CB6F00529352 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_rumble.h; sourceTree = ""; }; A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_rumble.c; sourceTree = ""; }; + A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steamdeck.c; sourceTree = ""; }; A7D8A57123E2513D00DCD162 /* SDL.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL.c; sourceTree = ""; }; A7D8A57323E2513D00DCD162 /* SDL_spinlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_spinlock.c; sourceTree = ""; }; A7D8A57423E2513D00DCD162 /* SDL_atomic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_atomic.c; sourceTree = ""; }; @@ -561,7 +591,6 @@ A7D8A5DD23E2513D00DCD162 /* SDL_log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_log.c; sourceTree = ""; }; A7D8A5DF23E2513D00DCD162 /* SDL_timer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_timer.c; sourceTree = ""; }; A7D8A5E023E2513D00DCD162 /* SDL_timer_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_timer_c.h; sourceTree = ""; }; - A7D8A5E223E2513D00DCD162 /* SDL_systimer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_systimer.c; sourceTree = ""; }; A7D8A5E823E2513D00DCD162 /* SDL_systimer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_systimer.c; sourceTree = ""; }; A7D8A5EE23E2513D00DCD162 /* SDL_offscreenevents_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_offscreenevents_c.h; sourceTree = ""; }; A7D8A5EF23E2513D00DCD162 /* SDL_offscreenwindow.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_offscreenwindow.c; sourceTree = ""; }; @@ -581,7 +610,6 @@ A7D8A60A23E2513D00DCD162 /* SDL_nullvideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_nullvideo.h; sourceTree = ""; }; A7D8A60B23E2513D00DCD162 /* SDL_nullevents_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_nullevents_c.h; sourceTree = ""; }; A7D8A60C23E2513D00DCD162 /* SDL_rect_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_c.h; sourceTree = ""; }; - A7D8A60D23E2513D00DCD162 /* SDL_shape_internals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_shape_internals.h; sourceTree = ""; }; A7D8A60E23E2513D00DCD162 /* SDL_video.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_video.c; sourceTree = ""; }; A7D8A61423E2513D00DCD162 /* SDL_surface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_surface.c; sourceTree = ""; }; A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_RLEaccel.c; sourceTree = ""; }; @@ -625,7 +653,6 @@ A7D8A66423E2513E00DCD162 /* SDL_blit_A.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_blit_A.c; sourceTree = ""; }; A7D8A67B23E2513E00DCD162 /* SDL_clipboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_clipboard.c; sourceTree = ""; }; A7D8A67C23E2513E00DCD162 /* SDL_yuv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_yuv.c; sourceTree = ""; }; - A7D8A67E23E2513E00DCD162 /* SDL_cocoashape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoashape.h; sourceTree = ""; }; A7D8A67F23E2513E00DCD162 /* SDL_cocoaopengl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoaopengl.m; sourceTree = ""; }; A7D8A68023E2513E00DCD162 /* SDL_cocoakeyboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoakeyboard.h; sourceTree = ""; }; A7D8A68123E2513E00DCD162 /* SDL_cocoamodes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoamodes.m; sourceTree = ""; }; @@ -640,7 +667,6 @@ A7D8A68B23E2513E00DCD162 /* SDL_cocoamessagebox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoamessagebox.m; sourceTree = ""; }; A7D8A68C23E2513E00DCD162 /* SDL_cocoakeyboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoakeyboard.m; sourceTree = ""; }; A7D8A68D23E2513E00DCD162 /* SDL_cocoaopengl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoaopengl.h; sourceTree = ""; }; - A7D8A68E23E2513E00DCD162 /* SDL_cocoashape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoashape.m; sourceTree = ""; }; A7D8A68F23E2513E00DCD162 /* SDL_cocoavulkan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoavulkan.h; sourceTree = ""; }; A7D8A69023E2513E00DCD162 /* SDL_cocoaopengles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoaopengles.h; sourceTree = ""; }; A7D8A69123E2513E00DCD162 /* SDL_cocoamodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoamodes.h; sourceTree = ""; }; @@ -683,7 +709,6 @@ A7D8A76623E2513E00DCD162 /* SDL_blit_copy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blit_copy.h; sourceTree = ""; }; A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_RLEaccel_c.h; sourceTree = ""; }; A7D8A76823E2513E00DCD162 /* SDL_fillrect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_fillrect.c; sourceTree = ""; }; - A7D8A76923E2513E00DCD162 /* SDL_shape.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_shape.c; sourceTree = ""; }; A7D8A76A23E2513E00DCD162 /* SDL_yuv_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_yuv_c.h; sourceTree = ""; }; A7D8A76B23E2513E00DCD162 /* SDL_blit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_blit.h; sourceTree = ""; }; A7D8A76E23E2513E00DCD162 /* yuv_rgb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = yuv_rgb.c; sourceTree = ""; }; @@ -842,6 +867,11 @@ BECDF66B0761BA81005FE872 /* Info-Framework.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Framework.plist"; sourceTree = ""; }; BECDF66C0761BA81005FE872 /* SDL3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDL3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E2D187D228A5673500D2B4F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E4A568B52AF763940062EEC4 /* SDL_sysmain_callbacks.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysmain_callbacks.c; sourceTree = ""; }; + E4F798192AD8D84800669F54 /* SDL_core_unsupported.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_core_unsupported.c; sourceTree = ""; }; + E4F7981B2AD8D85500669F54 /* SDL_dynapi_unsupported.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_dynapi_unsupported.h; sourceTree = ""; }; + E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_render_unsupported.c; sourceTree = ""; }; + E4F7981F2AD8D87F00669F54 /* SDL_video_unsupported.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_video_unsupported.c; sourceTree = ""; }; F31A92C628D4CB39003BFD6A /* SDL_offscreenopengles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_offscreenopengles.h; sourceTree = ""; }; F31A92C728D4CB39003BFD6A /* SDL_offscreenopengles.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_offscreenopengles.c; sourceTree = ""; }; F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_combined.c; sourceTree = ""; }; @@ -851,6 +881,10 @@ F32DDACC2AB795A30041EAA5 /* SDL_audio_resampler_filter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_audio_resampler_filter.h; sourceTree = ""; }; F32DDACD2AB795A30041EAA5 /* SDL_audioqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_audioqueue.h; sourceTree = ""; }; F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_audioresample.c; sourceTree = ""; }; + F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = ""; }; + F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = ""; }; + F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_steam_virtual_gamepad.h; sourceTree = ""; }; + F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_steam_virtual_gamepad.c; sourceTree = ""; }; F36C7AD0294BA009004D61C3 /* SDL_runapp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_runapp.c; sourceTree = ""; }; F376F6182559B29300CFC0BC /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.1.sdk/System/Library/Frameworks/OpenGLES.framework; sourceTree = DEVELOPER_DIR; }; F376F61A2559B2AF00CFC0BC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; @@ -895,6 +929,18 @@ F3B38CCD296E2E52005DA6D3 /* SDL_oldnames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_oldnames.h; path = SDL3/SDL_oldnames.h; sourceTree = ""; }; F3B38CCE296E2E52005DA6D3 /* SDL_intrin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_intrin.h; path = SDL3/SDL_intrin.h; sourceTree = ""; }; F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_wii.c; sourceTree = ""; }; + F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_clipboard_c.h; sourceTree = ""; }; + F3DDCC4E2AFD42B500B0842B /* SDL_surface_pixel_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_surface_pixel_impl.h; sourceTree = ""; }; + F3DDCC4F2AFD42B500B0842B /* SDL_video_capture.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_video_capture.c; sourceTree = ""; }; + F3DDCC502AFD42B500B0842B /* SDL_video_capture_apple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_video_capture_apple.m; sourceTree = ""; }; + F3DDCC512AFD42B500B0842B /* SDL_video_capture_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_capture_c.h; sourceTree = ""; }; + F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; + F3DDCC532AFD42B600B0842B /* SDL_sysvideocapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysvideocapture.h; sourceTree = ""; }; + F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = ""; }; + F3DDCC552AFD42B600B0842B /* SDL_video_capture_v4l2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_video_capture_v4l2.c; sourceTree = ""; }; + F3DDCC5F2AFD432500B0842B /* SDL_video_capture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_video_capture.h; path = SDL3/SDL_video_capture.h; sourceTree = ""; }; + F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_properties.c; sourceTree = ""; }; + F3E5A6EC2AD5E10800293D83 /* SDL_properties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_properties.h; path = SDL3/SDL_properties.h; sourceTree = ""; }; F3F07D59269640160074468B /* SDL_hidapi_luna.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_luna.c; sourceTree = ""; }; F3F7D8AA2933074900816151 /* SDL_audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_audio.h; path = SDL3/SDL_audio.h; sourceTree = ""; }; F3F7D8AB2933074900816151 /* SDL_platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_platform.h; path = SDL3/SDL_platform.h; sourceTree = ""; }; @@ -912,12 +958,10 @@ F3F7D8B72933074A00816151 /* SDL_log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_log.h; path = SDL3/SDL_log.h; sourceTree = ""; }; F3F7D8B82933074A00816151 /* SDL_egl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_egl.h; path = SDL3/SDL_egl.h; sourceTree = ""; }; F3F7D8B92933074A00816151 /* SDL_atomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_atomic.h; path = SDL3/SDL_atomic.h; sourceTree = ""; }; - F3F7D8BA2933074A00816151 /* SDL_shape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_shape.h; path = SDL3/SDL_shape.h; sourceTree = ""; }; F3F7D8BB2933074A00816151 /* SDL_surface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_surface.h; path = SDL3/SDL_surface.h; sourceTree = ""; }; F3F7D8BC2933074A00816151 /* SDL_error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_error.h; path = SDL3/SDL_error.h; sourceTree = ""; }; F3F7D8BD2933074A00816151 /* SDL_opengles2_gl2ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_opengles2_gl2ext.h; path = SDL3/SDL_opengles2_gl2ext.h; sourceTree = ""; }; F3F7D8BE2933074A00816151 /* SDL_endian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_endian.h; path = SDL3/SDL_endian.h; sourceTree = ""; }; - F3F7D8BF2933074A00816151 /* SDL_syswm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syswm.h; path = SDL3/SDL_syswm.h; sourceTree = ""; }; F3F7D8C02933074A00816151 /* SDL_opengl_glext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_opengl_glext.h; path = SDL3/SDL_opengl_glext.h; sourceTree = ""; }; F3F7D8C12933074B00816151 /* SDL_scancode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_scancode.h; path = SDL3/SDL_scancode.h; sourceTree = ""; }; F3F7D8C22933074B00816151 /* SDL_sensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_sensor.h; path = SDL3/SDL_sensor.h; sourceTree = ""; }; @@ -987,10 +1031,28 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 000082EF09C89B62BD840000 /* main */ = { + isa = PBXGroup; + children = ( + E4A568B42AF763940062EEC4 /* generic */, + 00008B5A0CB83D2069E80000 /* ios */, + 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */, + 00003260407E1002EAC10000 /* SDL_main_callbacks.h */, + ); + path = main; + sourceTree = ""; + }; + 00008B5A0CB83D2069E80000 /* ios */ = { + isa = PBXGroup; + children = ( + 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */, + ); + path = ios; + sourceTree = ""; + }; 0153844A006D81B07F000001 /* Public Headers */ = { isa = PBXGroup; children = ( - F3F7D8CF2933074C00816151 /* SDL.h */, F3F7D8E02933074D00816151 /* SDL_assert.h */, F3F7D8B92933074A00816151 /* SDL_atomic.h */, F3F7D8AA2933074900816151 /* SDL_audio.h */, @@ -1019,26 +1081,28 @@ F3F7D8D92933074C00816151 /* SDL_loadso.h */, F3F7D8C42933074B00816151 /* SDL_locale.h */, F3F7D8B72933074A00816151 /* SDL_log.h */, - F3F7D8B02933074900816151 /* SDL_main.h */, F3B38CCA296E2E52005DA6D3 /* SDL_main_impl.h */, + F3F7D8B02933074900816151 /* SDL_main.h */, F3F7D8B62933074A00816151 /* SDL_messagebox.h */, F3F7D8D22933074C00816151 /* SDL_metal.h */, F3F7D8D52933074C00816151 /* SDL_misc.h */, F3F7D8DA2933074D00816151 /* SDL_mouse.h */, F3F7D8E62933074E00816151 /* SDL_mutex.h */, F3B38CCD296E2E52005DA6D3 /* SDL_oldnames.h */, - F3F7D8E12933074D00816151 /* SDL_opengl.h */, F3F7D8C02933074A00816151 /* SDL_opengl_glext.h */, + F3F7D8E12933074D00816151 /* SDL_opengl.h */, F3F7D8C62933074B00816151 /* SDL_opengles.h */, - F3F7D8C72933074B00816151 /* SDL_opengles2.h */, F3F7D8AE2933074900816151 /* SDL_opengles2_gl2.h */, F3F7D8BD2933074A00816151 /* SDL_opengles2_gl2ext.h */, F3F7D8C92933074B00816151 /* SDL_opengles2_gl2platform.h */, F3F7D8B12933074900816151 /* SDL_opengles2_khrplatform.h */, + F3F7D8C72933074B00816151 /* SDL_opengles2.h */, + 63134A212A7902CF0021E9A6 /* SDL_pen.h */, F3F7D8B52933074A00816151 /* SDL_pixels.h */, - F3F7D8AB2933074900816151 /* SDL_platform.h */, F3B38CCB296E2E52005DA6D3 /* SDL_platform_defines.h */, + F3F7D8AB2933074900816151 /* SDL_platform.h */, F3F7D8DB2933074D00816151 /* SDL_power.h */, + F3E5A6EC2AD5E10800293D83 /* SDL_properties.h */, F3F7D8DF2933074D00816151 /* SDL_quit.h */, F3F7D8E22933074D00816151 /* SDL_rect.h */, F3F7D8DE2933074D00816151 /* SDL_render.h */, @@ -1046,17 +1110,17 @@ F3F7D8C82933074B00816151 /* SDL_rwops.h */, F3F7D8C12933074B00816151 /* SDL_scancode.h */, F3F7D8C22933074B00816151 /* SDL_sensor.h */, - F3F7D8BA2933074A00816151 /* SDL_shape.h */, F3F7D8AC2933074900816151 /* SDL_stdinc.h */, F3F7D8BB2933074A00816151 /* SDL_surface.h */, F3F7D8E82933074E00816151 /* SDL_system.h */, - F3F7D8BF2933074A00816151 /* SDL_syswm.h */, F3F7D8CD2933074C00816151 /* SDL_thread.h */, F3F7D8B22933074900816151 /* SDL_timer.h */, F3F7D8AF2933074900816151 /* SDL_touch.h */, F3F7D8E42933074D00816151 /* SDL_version.h */, + F3DDCC5F2AFD432500B0842B /* SDL_video_capture.h */, F3F7D8C52933074B00816151 /* SDL_video.h */, F3F7D8D42933074C00816151 /* SDL_vulkan.h */, + F3F7D8CF2933074C00816151 /* SDL.h */, ); name = "Public Headers"; path = ../../include; @@ -1106,6 +1170,7 @@ A7D8A91123E2514000DCD162 /* libm */, A7D8A85D23E2513F00DCD162 /* loadso */, 566E26CB246274AE00718109 /* locale */, + 000082EF09C89B62BD840000 /* main */, 5616CA47252BB278005D5928 /* misc */, A7D8A7DF23E2513F00DCD162 /* power */, A7D8A8DA23E2514000DCD162 /* render */, @@ -1119,6 +1184,8 @@ A7D8A57523E2513D00DCD162 /* SDL_error_c.h */, A7D8A8BF23E2513F00DCD162 /* SDL_error.c */, F382071C284F362F004DD584 /* SDL_guid.c */, + 000078E1881E857EBB6C0000 /* SDL_hashtable.c */, + 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */, A7D8A8D123E2514000DCD162 /* SDL_hints_c.h */, A7D8A5AB23E2513D00DCD162 /* SDL_hints.c */, A7D8A58323E2513D00DCD162 /* SDL_internal.h */, @@ -1126,6 +1193,7 @@ A1BB8B6227F6CF330057CFA8 /* SDL_list.h */, F386F6E42884663E001840AA /* SDL_log_c.h */, A7D8A5DD23E2513D00DCD162 /* SDL_log.c */, + F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */, F386F6E52884663E001840AA /* SDL_utils_c.h */, F386F6E62884663E001840AA /* SDL_utils.c */, A7D8A57123E2513D00DCD162 /* SDL.c */, @@ -1310,6 +1378,7 @@ A7D8A5DB23E2513D00DCD162 /* SDL_dynapi_procs.h */, A7D8A5DA23E2513D00DCD162 /* SDL_dynapi.c */, A7D8A5D823E2513D00DCD162 /* SDL_dynapi.h */, + E4F7981B2AD8D85500669F54 /* SDL_dynapi_unsupported.h */, ); path = dynapi; sourceTree = ""; @@ -1317,7 +1386,6 @@ A7D8A5DE23E2513D00DCD162 /* timer */ = { isa = PBXGroup; children = ( - A7D8A5E123E2513D00DCD162 /* dummy */, A7D8A5E723E2513D00DCD162 /* unix */, A7D8A5E023E2513D00DCD162 /* SDL_timer_c.h */, A7D8A5DF23E2513D00DCD162 /* SDL_timer.c */, @@ -1325,14 +1393,6 @@ path = timer; sourceTree = ""; }; - A7D8A5E123E2513D00DCD162 /* dummy */ = { - isa = PBXGroup; - children = ( - A7D8A5E223E2513D00DCD162 /* SDL_systimer.c */, - ); - path = dummy; - sourceTree = ""; - }; A7D8A5E723E2513D00DCD162 /* unix */ = { isa = PBXGroup; children = ( @@ -1363,6 +1423,7 @@ A7D8A64C23E2513D00DCD162 /* SDL_blit.c */, A7D8A76B23E2513E00DCD162 /* SDL_blit.h */, A7D8A77323E2513E00DCD162 /* SDL_bmp.c */, + F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */, A7D8A67B23E2513E00DCD162 /* SDL_clipboard.c */, A7D8A60423E2513D00DCD162 /* SDL_egl_c.h */, A7D8A6B623E2513E00DCD162 /* SDL_egl.c */, @@ -1370,14 +1431,21 @@ A7D8A74023E2513E00DCD162 /* SDL_pixels_c.h */, A7D8A64D23E2513D00DCD162 /* SDL_pixels.c */, A7D8A60C23E2513D00DCD162 /* SDL_rect_c.h */, + F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */, A7D8A63423E2513D00DCD162 /* SDL_rect.c */, A7D8A76723E2513E00DCD162 /* SDL_RLEaccel_c.h */, A7D8A61523E2513D00DCD162 /* SDL_RLEaccel.c */, - A7D8A60D23E2513D00DCD162 /* SDL_shape_internals.h */, - A7D8A76923E2513E00DCD162 /* SDL_shape.c */, A7D8A60323E2513D00DCD162 /* SDL_stretch.c */, + F3DDCC4E2AFD42B500B0842B /* SDL_surface_pixel_impl.h */, A7D8A61423E2513D00DCD162 /* SDL_surface.c */, A7D8A61723E2513D00DCD162 /* SDL_sysvideo.h */, + F3DDCC532AFD42B600B0842B /* SDL_sysvideocapture.h */, + F3DDCC522AFD42B600B0842B /* SDL_video_c.h */, + F3DDCC502AFD42B500B0842B /* SDL_video_capture_apple.m */, + F3DDCC512AFD42B500B0842B /* SDL_video_capture_c.h */, + F3DDCC552AFD42B600B0842B /* SDL_video_capture_v4l2.c */, + F3DDCC4F2AFD42B500B0842B /* SDL_video_capture.c */, + E4F7981F2AD8D87F00669F54 /* SDL_video_unsupported.c */, A7D8A60E23E2513D00DCD162 /* SDL_video.c */, A7D8A63E23E2513D00DCD162 /* SDL_vulkan_internal.h */, A7D8A64023E2513D00DCD162 /* SDL_vulkan_utils.c */, @@ -1471,8 +1539,6 @@ A7D8A67F23E2513E00DCD162 /* SDL_cocoaopengl.m */, A7D8A69023E2513E00DCD162 /* SDL_cocoaopengles.h */, A7D8A68223E2513E00DCD162 /* SDL_cocoaopengles.m */, - A7D8A67E23E2513E00DCD162 /* SDL_cocoashape.h */, - A7D8A68E23E2513E00DCD162 /* SDL_cocoashape.m */, A7D8A69323E2513E00DCD162 /* SDL_cocoavideo.h */, A7D8A68523E2513E00DCD162 /* SDL_cocoavideo.m */, A7D8A68F23E2513E00DCD162 /* SDL_cocoavulkan.h */, @@ -1601,12 +1667,16 @@ A7D8A7BE23E2513E00DCD162 /* hidapi */, A7D8A7A123E2513E00DCD162 /* steam */, 75E09157241EA924004729E1 /* virtual */, - A7D8A7AD23E2513E00DCD162 /* SDL_gamepad.c */, - A7D8A7A923E2513E00DCD162 /* SDL_joystick.c */, + F362B9152B3349E200D30B94 /* controller_list.h */, F3820712284F3609004DD584 /* controller_type.c */, A7D8A7D923E2513E00DCD162 /* controller_type.h */, + F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */, A7D8A79E23E2513E00DCD162 /* SDL_gamepad_db.h */, + A7D8A7AD23E2513E00DCD162 /* SDL_gamepad.c */, A7D8A7D023E2513E00DCD162 /* SDL_joystick_c.h */, + A7D8A7A923E2513E00DCD162 /* SDL_joystick.c */, + F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */, + F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */, A7D8A7CF23E2513E00DCD162 /* SDL_sysjoystick.h */, A7D8A7CB23E2513E00DCD162 /* usb_ids.h */, ); @@ -1642,6 +1712,7 @@ A7D8A7BE23E2513E00DCD162 /* hidapi */ = { isa = PBXGroup; children = ( + A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */, F3F07D59269640160074468B /* SDL_hidapi_luna.c */, @@ -1868,6 +1939,7 @@ A7D8A8FF23E2514000DCD162 /* SDL_d3dmath.c */, A7D8A8DC23E2514000DCD162 /* SDL_d3dmath.h */, A7D8A8DB23E2514000DCD162 /* SDL_render.c */, + E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */, A7D8A8EE23E2514000DCD162 /* SDL_sysrender.h */, A7D8A8EC23E2514000DCD162 /* SDL_yuv_sw_c.h */, A7D8A8ED23E2514000DCD162 /* SDL_yuv_sw.c */, @@ -1983,6 +2055,8 @@ A7D8A93823E2514000DCD162 /* SDL_keyboard.c */, A7D8A92B23E2514000DCD162 /* SDL_mouse_c.h */, A7D8A92A23E2514000DCD162 /* SDL_mouse.c */, + 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */, + 63134A242A7902FD0021E9A6 /* SDL_pen.c */, A7D8A93C23E2514000DCD162 /* SDL_quit.c */, A7D8A93723E2514000DCD162 /* SDL_touch_c.h */, A7D8A93E23E2514000DCD162 /* SDL_touch.c */, @@ -2000,9 +2074,18 @@ path = SDL3; sourceTree = ""; }; + E4A568B42AF763940062EEC4 /* generic */ = { + isa = PBXGroup; + children = ( + E4A568B52AF763940062EEC4 /* SDL_sysmain_callbacks.c */, + ); + path = generic; + sourceTree = ""; + }; F36C7ACF294B9F5E004D61C3 /* core */ = { isa = PBXGroup; children = ( + E4F798192AD8D84800669F54 /* SDL_core_unsupported.c */, F36C7AD0294BA009004D61C3 /* SDL_runapp.c */, ); path = core; @@ -2048,7 +2131,9 @@ A7D8B61723E2514300DCD162 /* SDL_assert_c.h in Headers */, F3F7D9292933074E00816151 /* SDL_atomic.h in Headers */, F3F7D8ED2933074E00816151 /* SDL_audio.h in Headers */, + F3DDCC602AFD432500B0842B /* SDL_video_capture.h in Headers */, A7D8B7A023E2514400DCD162 /* SDL_audio_c.h in Headers */, + F3DDCC5A2AFD42B600B0842B /* SDL_video_capture_c.h in Headers */, A7D8B7B223E2514400DCD162 /* SDL_audiodev_c.h in Headers */, F3F7D9E12933074E00816151 /* SDL_begin_code.h in Headers */, F3F7D9A52933074E00816151 /* SDL_bits.h in Headers */, @@ -2064,6 +2149,7 @@ A7D8BB6F23E2514500DCD162 /* SDL_clipboardevents_c.h in Headers */, F3F7D9D92933074E00816151 /* SDL_close_code.h in Headers */, A7D8AECA23E2514100DCD162 /* SDL_cocoaclipboard.h in Headers */, + F3DDCC5C2AFD42B600B0842B /* SDL_sysvideocapture.h in Headers */, A7D8AF1223E2514100DCD162 /* SDL_cocoaevents.h in Headers */, A7D8AE8E23E2514100DCD162 /* SDL_cocoakeyboard.h in Headers */, A7D8AF0623E2514100DCD162 /* SDL_cocoamessagebox.h in Headers */, @@ -2072,7 +2158,6 @@ A7D8AF1E23E2514100DCD162 /* SDL_cocoamouse.h in Headers */, A7D8AEDC23E2514100DCD162 /* SDL_cocoaopengl.h in Headers */, A7D8AEEE23E2514100DCD162 /* SDL_cocoaopengles.h in Headers */, - A7D8AE8223E2514100DCD162 /* SDL_cocoashape.h in Headers */, A7D8AF0023E2514100DCD162 /* SDL_cocoavideo.h in Headers */, A7D8AEE823E2514100DCD162 /* SDL_cocoavulkan.h in Headers */, A7D8AEFA23E2514100DCD162 /* SDL_cocoawindow.h in Headers */, @@ -2083,6 +2168,7 @@ F3F7D9B92933074E00816151 /* SDL_cpuinfo.h in Headers */, F3990E062A788303000D8759 /* SDL_hidapi_ios.h in Headers */, A7D8B98023E2514400DCD162 /* SDL_d3dmath.h in Headers */, + F362B91A2B3349E200D30B94 /* SDL_gamepad_c.h in Headers */, A7D8B8A223E2514400DCD162 /* SDL_diskaudio.h in Headers */, A7D8BB3F23E2514500DCD162 /* SDL_displayevents_c.h in Headers */, A7D8BA1923E2514400DCD162 /* SDL_draw.h in Headers */, @@ -2096,6 +2182,7 @@ A7D8AB1023E2514100DCD162 /* SDL_dynapi_overrides.h in Headers */, A7D8AB1C23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */, F3F7D9252933074E00816151 /* SDL_egl.h in Headers */, + F362B9192B3349E200D30B94 /* controller_list.h in Headers */, A7D8ABD923E2514100DCD162 /* SDL_egl_c.h in Headers */, F3F7D93D2933074E00816151 /* SDL_endian.h in Headers */, F3F7D9352933074E00816151 /* SDL_error.h in Headers */, @@ -2135,6 +2222,7 @@ F3F7D91D2933074E00816151 /* SDL_messagebox.h in Headers */, F3F7D98D2933074E00816151 /* SDL_metal.h in Headers */, F395C1BA2569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */, + F362B91B2B3349E200D30B94 /* SDL_steam_virtual_gamepad.h in Headers */, F3F7D9992933074E00816151 /* SDL_misc.h in Headers */, F3F7D9AD2933074E00816151 /* SDL_mouse.h in Headers */, A7D8BB1B23E2514500DCD162 /* SDL_mouse_c.h in Headers */, @@ -2150,6 +2238,8 @@ F3B38CDB296E2E52005DA6D3 /* SDL_oldnames.h in Headers */, F3F7D9C92933074E00816151 /* SDL_opengl.h in Headers */, F3F7D9452933074E00816151 /* SDL_opengl_glext.h in Headers */, + F3E5A6ED2AD5E10800293D83 /* SDL_properties.h in Headers */, + E4F7981C2AD8D85500669F54 /* SDL_dynapi_unsupported.h in Headers */, F3F7D95D2933074E00816151 /* SDL_opengles.h in Headers */, F3F7D9612933074E00816151 /* SDL_opengles2.h in Headers */, F3F7D8FD2933074E00816151 /* SDL_opengles2_gl2.h in Headers */, @@ -2179,8 +2269,6 @@ A7D8B98C23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */, A7D8B99B23E2514400DCD162 /* SDL_shaders_metal_macos.h in Headers */, A7D8B9A123E2514400DCD162 /* SDL_shaders_metal_tvos.h in Headers */, - F3F7D92D2933074E00816151 /* SDL_shape.h in Headers */, - A7D8AC0923E2514100DCD162 /* SDL_shape_internals.h in Headers */, F3F7D8F52933074E00816151 /* SDL_stdinc.h in Headers */, A7D8BBC723E2561500DCD162 /* SDL_steamcontroller.h in Headers */, F3F7D9312933074E00816151 /* SDL_surface.h in Headers */, @@ -2199,7 +2287,6 @@ A7D8B42823E2514300DCD162 /* SDL_systhread_c.h in Headers */, 5616CA4D252BB2A6005D5928 /* SDL_sysurl.h in Headers */, A7D8AC3F23E2514100DCD162 /* SDL_sysvideo.h in Headers */, - F3F7D9412933074E00816151 /* SDL_syswm.h in Headers */, F3F7D9792933074E00816151 /* SDL_thread.h in Headers */, A7D8B3EC23E2514300DCD162 /* SDL_thread_c.h in Headers */, F3F7D90D2933074E00816151 /* SDL_timer.h in Headers */, @@ -2207,6 +2294,7 @@ F3F7D9012933074E00816151 /* SDL_touch.h in Headers */, A7D8BB6323E2514500DCD162 /* SDL_touch_c.h in Headers */, A1626A522617008D003F1973 /* SDL_triangle.h in Headers */, + F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */, A7D8BBD223E2574800DCD162 /* SDL_uikitappdelegate.h in Headers */, A7D8BBD423E2574800DCD162 /* SDL_uikitclipboard.h in Headers */, A7D8BBD623E2574800DCD162 /* SDL_uikitevents.h in Headers */, @@ -2221,6 +2309,7 @@ A7D8BBE623E2574800DCD162 /* SDL_uikitviewcontroller.h in Headers */, A7D8BBE823E2574800DCD162 /* SDL_uikitvulkan.h in Headers */, A7D8BBEA23E2574800DCD162 /* SDL_uikitwindow.h in Headers */, + F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */, F386F6F02884663E001840AA /* SDL_utils_c.h in Headers */, F3973FA228A59BDD00B84553 /* SDL_vacopy.h in Headers */, F3F7D9D52933074E00816151 /* SDL_version.h in Headers */, @@ -2252,6 +2341,7 @@ A7D8B56F23E2514300DCD162 /* usb_ids.h in Headers */, A7D8B25423E2514200DCD162 /* vk_icd.h in Headers */, A7D8B24E23E2514200DCD162 /* vk_layer.h in Headers */, + F3DDCC562AFD42B600B0842B /* SDL_clipboard_c.h in Headers */, A7D8B26623E2514200DCD162 /* vk_platform.h in Headers */, A7D8B2AE23E2514200DCD162 /* vk_sdk_platform.h in Headers */, A7D8B26023E2514200DCD162 /* vulkan.h in Headers */, @@ -2267,11 +2357,14 @@ A7D8B27823E2514200DCD162 /* vulkan_wayland.h in Headers */, A7D8B27E23E2514200DCD162 /* vulkan_win32.h in Headers */, A7D8B29023E2514200DCD162 /* vulkan_xcb.h in Headers */, + F3DDCC572AFD42B600B0842B /* SDL_surface_pixel_impl.h in Headers */, A7D8B29C23E2514200DCD162 /* vulkan_xlib.h in Headers */, A7D8B28A23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */, A7D8B3D423E2514300DCD162 /* yuv_rgb.h in Headers */, A7D8B3C823E2514200DCD162 /* yuv_rgb_sse_func.h in Headers */, A7D8B3CE23E2514300DCD162 /* yuv_rgb_std_func.h in Headers */, + 63134A222A7902CF0021E9A6 /* SDL_pen.h in Headers */, + 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2407,12 +2500,13 @@ A7D8BAAF23E2514400DCD162 /* s_atan.c in Sources */, A7D8B75223E2514300DCD162 /* SDL_sysloadso.c in Sources */, A7D8BBE123E2574800DCD162 /* SDL_uikitopenglview.m in Sources */, + A79745702B2E9D39009D224A /* SDL_hidapi_steamdeck.c in Sources */, A7D8B98623E2514400DCD162 /* SDL_render_metal.m in Sources */, A7D8AE7623E2514100DCD162 /* SDL_clipboard.c in Sources */, A7D8AEC423E2514100DCD162 /* SDL_cocoaevents.m in Sources */, A7D8B86623E2514400DCD162 /* SDL_audiocvt.c in Sources */, - A7D8B3AA23E2514200DCD162 /* SDL_shape.c in Sources */, A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */, + F3DDCC5E2AFD42B600B0842B /* SDL_video_capture_v4l2.c in Sources */, A7D8BBE323E2574800DCD162 /* SDL_uikitvideo.m in Sources */, 5616CA4E252BB2A6005D5928 /* SDL_sysurl.m in Sources */, A7D8A97523E2514000DCD162 /* SDL_coremotionsensor.m in Sources */, @@ -2430,6 +2524,7 @@ A7D8AB7323E2514100DCD162 /* SDL_offscreenframebuffer.c in Sources */, A7D8B3BF23E2514200DCD162 /* yuv_rgb.c in Sources */, A7D8B43423E2514300DCD162 /* SDL_systhread.c in Sources */, + F3DDCC592AFD42B600B0842B /* SDL_video_capture_apple.m in Sources */, A7D8BB3323E2514500DCD162 /* SDL_windowevents.c in Sources */, A7D8BABB23E2514400DCD162 /* s_scalbn.c in Sources */, F3973FAB28A59BDD00B84553 /* SDL_crc16.c in Sources */, @@ -2437,7 +2532,7 @@ F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */, A7D8B9DD23E2514400DCD162 /* SDL_blendpoint.c in Sources */, A7D8B4EE23E2514300DCD162 /* SDL_gamepad.c in Sources */, - A7D8AB3723E2514100DCD162 /* SDL_systimer.c in Sources */, + E4A568B62AF763940062EEC4 /* SDL_sysmain_callbacks.c in Sources */, A7D8BA1323E2514400DCD162 /* SDL_render_sw.c in Sources */, A7D8B42223E2514300DCD162 /* SDL_syssem.c in Sources */, A7D8B53923E2514300DCD162 /* SDL_hidapi_xbox360.c in Sources */, @@ -2464,6 +2559,7 @@ A7D8B86023E2514400DCD162 /* SDL_audiotypecvt.c in Sources */, A7D8BBC523E2561500DCD162 /* SDL_steamcontroller.c in Sources */, A7D8AD3223E2514100DCD162 /* SDL_blit_N.c in Sources */, + F3DDCC582AFD42B600B0842B /* SDL_video_capture.c in Sources */, A7D8BB7B23E2514500DCD162 /* SDL_dropevents.c in Sources */, A7D8BACD23E2514500DCD162 /* e_atan2.c in Sources */, A7D8BA8B23E2514400DCD162 /* s_sin.c in Sources */, @@ -2477,7 +2573,6 @@ A7D8AED023E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */, F376F6552559B4E300CFC0BC /* SDL_hidapi.c in Sources */, A7D8BA2B23E2514400DCD162 /* SDL_blendfillrect.c in Sources */, - A7D8AEE223E2514100DCD162 /* SDL_cocoashape.m in Sources */, A7D8BBD323E2574800DCD162 /* SDL_uikitappdelegate.m in Sources */, A7D8AEB823E2514100DCD162 /* SDL_cocoamouse.m in Sources */, F32DDAD12AB795A30041EAA5 /* SDL_audioqueue.c in Sources */, @@ -2491,11 +2586,13 @@ A7D8BAD323E2514500DCD162 /* s_tan.c in Sources */, A7D8AA6523E2514000DCD162 /* SDL_hints.c in Sources */, A7D8B53F23E2514300DCD162 /* SDL_hidapi_ps4.c in Sources */, + F362B91C2B3349E200D30B94 /* SDL_steam_virtual_gamepad.c in Sources */, A7D8AD6E23E2514100DCD162 /* SDL_pixels.c in Sources */, A7D8B75E23E2514300DCD162 /* SDL_sysloadso.c in Sources */, A7D8BBD723E2574800DCD162 /* SDL_uikitevents.m in Sources */, A7D8B5F323E2514300DCD162 /* SDL_syspower.c in Sources */, A7D8B95023E2514400DCD162 /* SDL_iconv.c in Sources */, + F3E5A6EB2AD5E0E600293D83 /* SDL_properties.c in Sources */, A7D8BA9D23E2514400DCD162 /* s_fabs.c in Sources */, F395C1B12569C6A000942BFF /* SDL_mfijoystick.m in Sources */, A7D8B99223E2514400DCD162 /* SDL_shaders_metal.metal in Sources */, @@ -2524,6 +2621,7 @@ A7D8B79A23E2514400DCD162 /* SDL_dummyaudio.c in Sources */, A7D8B3A423E2514200DCD162 /* SDL_fillrect.c in Sources */, A7D8ABDF23E2514100DCD162 /* SDL_nullframebuffer.c in Sources */, + E4F7981A2AD8D84800669F54 /* SDL_core_unsupported.c in Sources */, A7D8A96923E2514000DCD162 /* SDL_dummysensor.c in Sources */, A7D8B95C23E2514400DCD162 /* SDL_string.c in Sources */, A7D8BA7F23E2514400DCD162 /* SDL_render_gl.c in Sources */, @@ -2544,6 +2642,7 @@ A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, A7D8B96223E2514400DCD162 /* SDL_strtokr.c in Sources */, A7D8BB7523E2514500DCD162 /* SDL_clipboardevents.c in Sources */, + E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */, A1BB8B6327F6CF330057CFA8 /* SDL_list.c in Sources */, A7D8BAB523E2514400DCD162 /* k_cos.c in Sources */, A7D8B54523E2514300DCD162 /* SDL_hidapijoystick.c in Sources */, @@ -2563,6 +2662,7 @@ A7D8BBD523E2574800DCD162 /* SDL_uikitclipboard.m in Sources */, A7D8B5C923E2514300DCD162 /* SDL_rwopsbundlesupport.m in Sources */, F386F6F92884663E001840AA /* SDL_utils.c in Sources */, + E4F7981E2AD8D86A00669F54 /* SDL_render_unsupported.c in Sources */, A7D8AC0F23E2514100DCD162 /* SDL_video.c in Sources */, A7D8BA5B23E2514400DCD162 /* SDL_shaders_gles2.c in Sources */, A7D8B14023E2514200DCD162 /* SDL_blit_1.c in Sources */, @@ -2584,6 +2684,10 @@ A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m in Sources */, A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */, 566E26D8246274CC00718109 /* SDL_locale.c in Sources */, + 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */, + 000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */, + 0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */, + 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Xcode/SDL/pkg-support/resources/License.txt b/Xcode/SDL/pkg-support/resources/License.txt index 523c51e4..42f37361 100644 --- a/Xcode/SDL/pkg-support/resources/License.txt +++ b/Xcode/SDL/pkg-support/resources/License.txt @@ -1,6 +1,6 @@ Simple DirectMedia Layer -Copyright (C) 1997-2023 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 diff --git a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj index 2642bca1..f188f537 100644 --- a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj +++ b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 55; objects = { /* Begin PBXAggregateTarget section */ @@ -153,7 +153,6 @@ F35E56DF2983130F00A43A5F /* testautomation_keyboard.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56C72983130E00A43A5F /* testautomation_keyboard.c */; }; F35E56E02983130F00A43A5F /* testautomation_sdltest.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56C82983130E00A43A5F /* testautomation_sdltest.c */; }; F35E56E12983130F00A43A5F /* testautomation_guid.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56C92983130E00A43A5F /* testautomation_guid.c */; }; - F35E56E22983130F00A43A5F /* testautomation_syswm.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56CA2983130E00A43A5F /* testautomation_syswm.c */; }; F35E56E32983130F00A43A5F /* testautomation_surface.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56CB2983130F00A43A5F /* testautomation_surface.c */; }; F35E56E42983130F00A43A5F /* testautomation.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56CC2983130F00A43A5F /* testautomation.c */; }; F35E56E52983130F00A43A5F /* testautomation_mouse.c in Sources */ = {isa = PBXBuildFile; fileRef = F35E56CD2983130F00A43A5F /* testautomation_mouse.c */; }; @@ -1329,7 +1328,6 @@ F35E56C72983130E00A43A5F /* testautomation_keyboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_keyboard.c; sourceTree = ""; }; F35E56C82983130E00A43A5F /* testautomation_sdltest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_sdltest.c; sourceTree = ""; }; F35E56C92983130E00A43A5F /* testautomation_guid.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_guid.c; sourceTree = ""; }; - F35E56CA2983130E00A43A5F /* testautomation_syswm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_syswm.c; sourceTree = ""; }; F35E56CB2983130F00A43A5F /* testautomation_surface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_surface.c; sourceTree = ""; }; F35E56CC2983130F00A43A5F /* testautomation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation.c; sourceTree = ""; }; F35E56CD2983130F00A43A5F /* testautomation_mouse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_mouse.c; sourceTree = ""; }; @@ -1780,7 +1778,6 @@ F35E56C82983130E00A43A5F /* testautomation_sdltest.c */, F35E56BE2983130C00A43A5F /* testautomation_stdlib.c */, F35E56CB2983130F00A43A5F /* testautomation_surface.c */, - F35E56CA2983130E00A43A5F /* testautomation_syswm.c */, F35E56BD2983130B00A43A5F /* testautomation_timer.c */, F35E56C12983130C00A43A5F /* testautomation_video.c */, F35E56CC2983130F00A43A5F /* testautomation.c */, @@ -3357,7 +3354,6 @@ buildActionMask = 2147483647; files = ( F35E56D12983130F00A43A5F /* testautomation_render.c in Sources */, - F35E56E22983130F00A43A5F /* testautomation_syswm.c in Sources */, F399C6512A7892D800C86979 /* testautomation_intrinsics.c in Sources */, F35E56D22983130F00A43A5F /* testautomation_rwops.c in Sources */, F35E56E32983130F00A43A5F /* testautomation_surface.c in Sources */, diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index f79bf186..1a9d1c65 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -273,6 +273,7 @@ public class HIDDeviceManager { final int XB1_IFACE_SUBCLASS = 71; final int XB1_IFACE_PROTOCOL = 208; final int[] SUPPORTED_VENDORS = { + 0x03f0, // HP 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz @@ -358,6 +359,12 @@ public class HIDDeviceManager { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); + if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT"); + return; + } + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index a80b4941..0d2aeebf 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -23,9 +23,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.text.Editable; -import android.text.InputType; -import android.text.Selection; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; @@ -39,12 +36,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Button; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -186,6 +180,14 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; private static final int SDL_SYSTEM_CURSOR_NO = 10; private static final int SDL_SYSTEM_CURSOR_HAND = 11; + private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT = 12; + private static final int SDL_SYSTEM_CURSOR_WINDOW_TOP = 13; + private static final int SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT = 14; + private static final int SDL_SYSTEM_CURSOR_WINDOW_RIGHT = 15; + private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT = 16; + private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOM = 17; + private static final int SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT = 18; + private static final int SDL_SYSTEM_CURSOR_WINDOW_LEFT = 19; protected static final int SDL_ORIENTATION_UNKNOWN = 0; protected static final int SDL_ORIENTATION_LANDSCAPE = 1; @@ -210,7 +212,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; - protected static DummyEdit mTextEdit; + protected static SDLDummyEdit mTextEdit; protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; protected static SDLClipboardHandler mClipboardHandler; @@ -238,6 +240,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return mMotionListener; } + /** + * This method creates a Runnable which invokes SDL_main. The default implementation + * uses the getMainSharedObject() and getMainFunction() methods to invoke native + * code from the specified shared library. + */ + protected Runnable createSDLMainRunnable() { + return new SDLMain(); + } + /** * This method returns the name of the shared object with the application entry point * It can be overridden by derived classes. @@ -672,11 +683,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Wait for "SDLThread" thread to end try { - // 500ms timeout, because: + // Use a timeout because: // C SDLmain() thread might have started (mSDLThread.start() called) // while the SDL_Init() might not have been called yet, // and so the previous QUIT event will be discarded by SDL_Init() and app is running, not exiting. - SDLActivity.mSDLThread.join(500); + SDLActivity.mSDLThread.join(1000); } catch(Exception e) { Log.v(TAG, "Problem stopping SDLThread: " + e); } @@ -777,13 +788,13 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Try a transition to resumed state if (mNextNativeState == NativeState.RESUMED) { - if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { + if (mSurface.mIsSurfaceReady && (mHasFocus || mHasMultiWindow) && mIsResumedCalled) { if (mSDLThread == null) { // This is the entry point to the C app. // Start up the C app thread and enable sensor input for the first time // FIXME: Why aren't we enabling sensor input at start? - mSDLThread = new Thread(new SDLMain(), "SDLThread"); + mSDLThread = new Thread(SDLActivity.mSingleton.createSDLMainRunnable(), "SDLThread"); mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); mSDLThread.start(); @@ -799,11 +810,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } // Messages from the SDLMain thread - static final int COMMAND_CHANGE_TITLE = 1; - static final int COMMAND_CHANGE_WINDOW_STYLE = 2; - static final int COMMAND_TEXTEDIT_HIDE = 3; - static final int COMMAND_SET_KEEP_SCREEN_ON = 5; - + protected static final int COMMAND_CHANGE_TITLE = 1; + protected static final int COMMAND_CHANGE_WINDOW_STYLE = 2; + protected static final int COMMAND_TEXTEDIT_HIDE = 3; + protected static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; protected static boolean mFullscreenModeActive; @@ -911,7 +921,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh Handler commandHandler = new SDLCommandHandler(); // Send a message from the SDLMain thread - boolean sendCommand(int command, Object data) { + protected boolean sendCommand(int command, Object data) { Message msg = commandHandler.obtainMessage(); msg.arg1 = command; msg.obj = data; @@ -1123,23 +1133,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh * This method is called by SDL using JNI. */ public static boolean shouldMinimizeOnFocusLoss() { -/* - if (Build.VERSION.SDK_INT >= 24) { - if (mSingleton == null) { - return true; - } - - if (mSingleton.isInMultiWindowMode()) { - return false; - } - - if (mSingleton.isInPictureInPictureMode()) { - return false; - } - } - - return true; -*/ return false; } @@ -1344,7 +1337,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh params.topMargin = y; if (mTextEdit == null) { - mTextEdit = new DummyEdit(SDL.getContext()); + mTextEdit = new SDLDummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { @@ -1828,6 +1821,30 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh case SDL_SYSTEM_CURSOR_HAND: cursor_type = 1002; //PointerIcon.TYPE_HAND; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; + break; } if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { @@ -1898,11 +1915,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh try { class OneShotTask implements Runnable { - String mMessage; - int mDuration; - int mGravity; - int mXOffset; - int mYOffset; + private final String mMessage; + private final int mDuration; + private final int mGravity; + private final int mXOffset; + private final int mYOffset; OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) { mMessage = message; @@ -1966,186 +1983,6 @@ class SDLMain implements Runnable { } } -/* This is a fake invisible editor view that receives the input and defines the - * pan&scan region - */ -class DummyEdit extends View implements View.OnKeyListener { - InputConnection ic; - - public DummyEdit(Context context) { - super(context); - setFocusableInTouchMode(true); - setFocusable(true); - setOnKeyListener(this); - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - return SDLActivity.handleKeyEvent(v, keyCode, event, ic); - } - - // - @Override - public boolean onKeyPreIme (int keyCode, KeyEvent event) { - // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event - // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 - // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not - // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout - // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android - // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) - if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) { - SDLActivity.onNativeKeyboardFocusLost(); - } - } - return super.onKeyPreIme(keyCode, event); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - ic = new SDLInputConnection(this, true); - - outAttrs.inputType = InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_FLAG_MULTI_LINE; - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | - EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; - - return ic; - } -} - -class SDLInputConnection extends BaseInputConnection { - - protected EditText mEditText; - protected String mCommittedText = ""; - - public SDLInputConnection(View targetView, boolean fullEditor) { - super(targetView, fullEditor); - mEditText = new EditText(SDL.getContext()); - } - - @Override - public Editable getEditable() { - return mEditText.getEditableText(); - } - - @Override - public boolean sendKeyEvent(KeyEvent event) { - /* - * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard) - * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses - * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys - * that still do, we empty this out. - */ - - /* - * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key - * as we do with physical keyboards, let's just use it to hide the keyboard. - */ - - if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { - if (SDLActivity.onNativeSoftReturnKey()) { - return true; - } - } - - return super.sendKeyEvent(event); - } - - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - if (!super.commitText(text, newCursorPosition)) { - return false; - } - updateText(); - return true; - } - - @Override - public boolean setComposingText(CharSequence text, int newCursorPosition) { - if (!super.setComposingText(text, newCursorPosition)) { - return false; - } - updateText(); - return true; - } - - @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { - if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) { - // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection - // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 - if (beforeLength > 0 && afterLength == 0) { - // backspace(s) - while (beforeLength-- > 0) { - nativeGenerateScancodeForUnichar('\b'); - } - return true; - } - } - - if (!super.deleteSurroundingText(beforeLength, afterLength)) { - return false; - } - updateText(); - return true; - } - - protected void updateText() { - final Editable content = getEditable(); - if (content == null) { - return; - } - - String text = content.toString(); - int compareLength = Math.min(text.length(), mCommittedText.length()); - int matchLength, offset; - - /* Backspace over characters that are no longer in the string */ - for (matchLength = 0; matchLength < compareLength; ) { - int codePoint = mCommittedText.codePointAt(matchLength); - if (codePoint != text.codePointAt(matchLength)) { - break; - } - matchLength += Character.charCount(codePoint); - } - /* FIXME: This doesn't handle graphemes, like '🌬️' */ - for (offset = matchLength; offset < mCommittedText.length(); ) { - int codePoint = mCommittedText.codePointAt(offset); - nativeGenerateScancodeForUnichar('\b'); - offset += Character.charCount(codePoint); - } - - if (matchLength < text.length()) { - String pendingText = text.subSequence(matchLength, text.length()).toString(); - for (offset = 0; offset < pendingText.length(); ) { - int codePoint = pendingText.codePointAt(offset); - if (codePoint == '\n') { - if (SDLActivity.onNativeSoftReturnKey()) { - return; - } - } - /* Higher code points don't generate simulated scancodes */ - if (codePoint < 128) { - nativeGenerateScancodeForUnichar((char)codePoint); - } - offset += Character.charCount(codePoint); - } - SDLInputConnection.nativeCommitText(pendingText, 0); - } - mCommittedText = text; - } - - public static native void nativeCommitText(String text, int newCursorPosition); - - public static native void nativeGenerateScancodeForUnichar(char c); -} - class SDLClipboardHandler implements ClipboardManager.OnPrimaryClipChangedListener { diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java new file mode 100644 index 00000000..dca28145 --- /dev/null +++ b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java @@ -0,0 +1,62 @@ +package org.libsdl.app; + +import android.content.*; +import android.text.InputType; +import android.view.*; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +/* This is a fake invisible editor view that receives the input and defines the + * pan&scan region + */ +public class SDLDummyEdit extends View implements View.OnKeyListener +{ + InputConnection ic; + + public SDLDummyEdit(Context context) { + super(context); + setFocusableInTouchMode(true); + setFocusable(true); + setOnKeyListener(this); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return SDLActivity.handleKeyEvent(v, keyCode, event, ic); + } + + // + @Override + public boolean onKeyPreIme (int keyCode, KeyEvent event) { + // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event + // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 + // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not + // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout + // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android + // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) + if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { + if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) { + SDLActivity.onNativeKeyboardFocusLost(); + } + } + return super.onKeyPreIme(keyCode, event); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + ic = new SDLInputConnection(this, true); + + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE; + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | + EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; + + return ic; + } +} + diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java b/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java new file mode 100644 index 00000000..e1d29a88 --- /dev/null +++ b/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java @@ -0,0 +1,136 @@ +package org.libsdl.app; + +import android.content.*; +import android.os.Build; +import android.text.Editable; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.widget.EditText; + +public class SDLInputConnection extends BaseInputConnection +{ + protected EditText mEditText; + protected String mCommittedText = ""; + + public SDLInputConnection(View targetView, boolean fullEditor) { + super(targetView, fullEditor); + mEditText = new EditText(SDL.getContext()); + } + + @Override + public Editable getEditable() { + return mEditText.getEditableText(); + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + /* + * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard) + * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses + * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys + * that still do, we empty this out. + */ + + /* + * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key + * as we do with physical keyboards, let's just use it to hide the keyboard. + */ + + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { + if (SDLActivity.onNativeSoftReturnKey()) { + return true; + } + } + + return super.sendKeyEvent(event); + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + if (!super.commitText(text, newCursorPosition)) { + return false; + } + updateText(); + return true; + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (!super.setComposingText(text, newCursorPosition)) { + return false; + } + updateText(); + return true; + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) { + // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection + // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 + if (beforeLength > 0 && afterLength == 0) { + // backspace(s) + while (beforeLength-- > 0) { + nativeGenerateScancodeForUnichar('\b'); + } + return true; + } + } + + if (!super.deleteSurroundingText(beforeLength, afterLength)) { + return false; + } + updateText(); + return true; + } + + protected void updateText() { + final Editable content = getEditable(); + if (content == null) { + return; + } + + String text = content.toString(); + int compareLength = Math.min(text.length(), mCommittedText.length()); + int matchLength, offset; + + /* Backspace over characters that are no longer in the string */ + for (matchLength = 0; matchLength < compareLength; ) { + int codePoint = mCommittedText.codePointAt(matchLength); + if (codePoint != text.codePointAt(matchLength)) { + break; + } + matchLength += Character.charCount(codePoint); + } + /* FIXME: This doesn't handle graphemes, like '🌬️' */ + for (offset = matchLength; offset < mCommittedText.length(); ) { + int codePoint = mCommittedText.codePointAt(offset); + nativeGenerateScancodeForUnichar('\b'); + offset += Character.charCount(codePoint); + } + + if (matchLength < text.length()) { + String pendingText = text.subSequence(matchLength, text.length()).toString(); + for (offset = 0; offset < pendingText.length(); ) { + int codePoint = pendingText.codePointAt(offset); + if (codePoint == '\n') { + if (SDLActivity.onNativeSoftReturnKey()) { + return; + } + } + /* Higher code points don't generate simulated scancodes */ + if (codePoint < 128) { + nativeGenerateScancodeForUnichar((char)codePoint); + } + offset += Character.charCount(codePoint); + } + SDLInputConnection.nativeCommitText(pendingText, 0); + } + mCommittedText = text; + } + + public static native void nativeCommitText(String text, int newCursorPosition); + + public static native void nativeGenerateScancodeForUnichar(char c); +} + diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java index c4c14491..6cd26865 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -164,13 +164,10 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, } } - // Don't skip in MultiWindow. + // Don't skip if we might be multi-window or have popup dialogs if (skip) { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - if (SDLActivity.mSingleton.isInMultiWindowMode()) { - Log.v("SDL", "Don't skip in Multi-Window"); - skip = false; - } + skip = false; } } @@ -196,6 +193,24 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, return SDLActivity.handleKeyEvent(v, keyCode, event, null); } + private float getNormalizedX(float x) + { + if (mWidth <= 1) { + return 0.5f; + } else { + return (x / (mWidth - 1)); + } + } + + private float getNormalizedY(float y) + { + if (mHeight <= 1) { + return 0.5f; + } else { + return (y / (mHeight - 1)); + } + } + // Touch events @Override public boolean onTouch(View v, MotionEvent event) { @@ -242,8 +257,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, case MotionEvent.ACTION_MOVE: for (i = 0; i < pointerCount; i++) { pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); p = event.getPressure(i); if (p > 1.0f) { // may be larger than 1.0f on some devices @@ -267,8 +282,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, } pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); p = event.getPressure(i); if (p > 1.0f) { // may be larger than 1.0f on some devices @@ -281,8 +296,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, case MotionEvent.ACTION_CANCEL: for (i = 0; i < pointerCount; i++) { pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); p = event.getPressure(i); if (p > 1.0f) { // may be larger than 1.0f on some devices diff --git a/build-scripts/SDL_migration.cocci b/build-scripts/SDL_migration.cocci index df9acc00..6cb692f3 100644 --- a/build-scripts/SDL_migration.cocci +++ b/build-scripts/SDL_migration.cocci @@ -1240,21 +1240,11 @@ typedef SDL_GameControllerButton, SDL_GamepadButton; (...) @@ @@ -- SDL_GameControllerMappingForIndex -+ SDL_GetGamepadMappingForIndex - (...) -@@ -@@ - SDL_GameControllerName + SDL_GetGamepadName (...) @@ @@ -- SDL_GameControllerNumMappings -+ SDL_GetNumGamepadMappings - (...) -@@ -@@ - SDL_GameControllerOpen + SDL_OpenGamepad (...) @@ -1934,10 +1924,10 @@ expression e2; + SDL_BlitSurfaceUnchecked (...) @@ +expression e1, e2, e3, e4; @@ -- SDL_LowerBlitScaled -+ SDL_BlitSurfaceUncheckedScaled - (...) +- SDL_LowerBlitScaled(e1, e2, e3, e4) ++ SDL_BlitSurfaceUncheckedScaled(e1, e2, e3, e4, SDL_SCALEMODE_NEAREST) @@ @@ - SDL_SetClipRect @@ -1954,10 +1944,10 @@ expression e2; + SDL_BlitSurface (...) @@ +expression e1, e2, e3, e4; @@ -- SDL_UpperBlitScaled -+ SDL_BlitSurfaceScaled - (...) +- SDL_UpperBlitScaled(e1, e2, e3, e4) ++ SDL_BlitSurfaceScaled(e1, e2, e3, e4, SDL_SCALEMODE_NEAREST) @@ @@ - SDL_RenderGetD3D11Device @@ -2735,3 +2725,42 @@ typedef SDL_cond, SDL_Condition; - SDL_WriteBE64 + SDL_WriteU64BE (...) +@@ +expression e, n; +@@ +- SDL_GetWindowData(e, n) ++ SDL_GetProperty(SDL_GetWindowProperties(e), n) +@@ +expression e, n, v; +@@ +- SDL_SetWindowData(e, n, v) ++ SDL_SetProperty(SDL_GetWindowProperties(e), n, v, NULL, NULL) +@@ +expression w, i, s; +@@ +- SDL_Vulkan_CreateSurface(w, i, s) ++ SDL_Vulkan_CreateSurface(w, i, NULL, s) +@@ +@@ +- SDL_RenderFlush ++ SDL_FlushRenderer + (...) +@@ +@@ +- SDL_CONTROLLERSTEAMHANDLEUPDATED ++ SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED +@@ +@@ +- SDL_GameControllerGetSteamHandle ++ SDL_GetGamepadSteamHandle + (...) +@@ +expression e1, e2, e3, e4; +@@ +- SDL_SoftStretch(e1, e2, e3, e4) ++ SDL_SoftStretch(e1, e2, e3, e4, SDL_SCALEMODE_NEAREST) +@@ +expression e1, e2, e3, e4; +@@ +- SDL_SoftStretchLinear(e1, e2, e3, e4) ++ SDL_SoftStretch(e1, e2, e3, e4, SDL_SCALEMODE_LINEAR) diff --git a/build-scripts/add-source-to-projects.pl b/build-scripts/add-source-to-projects.pl index 4530e92d..6d703dc3 100755 --- a/build-scripts/add-source-to-projects.pl +++ b/build-scripts/add-source-to-projects.pl @@ -438,6 +438,7 @@ sub process_visualstudio { $filter = lc(dirname($addpath)); $filter =~ s/\Asrc\///; # there's no filter for the base "src/" dir, where SDL.c and friends live. $filter =~ s/\//\\/g; + $filter = '' if $filter eq 'src'; if ($filter ne '') { # see if the filter already exists, otherwise add it. diff --git a/build-scripts/check_stdlib_usage.py b/build-scripts/check_stdlib_usage.py index 830a04a3..5e62ff18 100755 --- a/build-scripts/check_stdlib_usage.py +++ b/build-scripts/check_stdlib_usage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # # Simple DirectMedia Layer -# Copyright (C) 1997-2023 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 diff --git a/build-scripts/gen_audio_channel_conversion.c b/build-scripts/gen_audio_channel_conversion.c index 6c98fa34..f774e7db 100644 --- a/build-scripts/gen_audio_channel_conversion.c +++ b/build-scripts/gen_audio_channel_conversion.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -269,7 +269,7 @@ static void write_converter(const int fromchans, const int tochans) "\n", lowercase(fromstr), lowercase(tostr)); if (convert_backwards) { /* must convert backwards when growing the output in-place. */ - printf(" /* convert backwards, since output is growing in-place. */\n"); + printf(" // convert backwards, since output is growing in-place.\n"); printf(" src += (num_frames-1)"); if (fromchans != 1) { printf(" * %d", fromchans); @@ -406,7 +406,7 @@ int main(void) printf( "/*\n" " Simple DirectMedia Layer\n" - " Copyright (C) 1997-2023 Sam Lantinga \n" + " Copyright (C) 1997-2024 Sam Lantinga \n" "\n" " This software is provided 'as-is', without any express or implied\n" " warranty. In no event will the authors be held liable for any damages\n" @@ -425,7 +425,7 @@ int main(void) " 3. This notice may not be removed or altered from any source distribution.\n" "*/\n" "\n" - "/* DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_channel_conversion.c */\n" + "// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_channel_conversion.c\n" "\n" "\n" "typedef void (*SDL_AudioChannelConverter)(float *dst, const float *src, int num_frames);\n" diff --git a/build-scripts/gen_audio_resampler_filter.c b/build-scripts/gen_audio_resampler_filter.c index 19991bd9..c115484a 100644 --- a/build-scripts/gen_audio_resampler_filter.c +++ b/build-scripts/gen_audio_resampler_filter.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ Built with: gcc -o genfilter build-scripts/gen_audio_resampler_filter.c -lm && ./genfilter > src/audio/SDL_audio_resampler_filter.h - */ +*/ /* SDL's resampler uses a "bandlimited interpolation" algorithm: @@ -109,7 +109,7 @@ int main(void) printf( "/*\n" " Simple DirectMedia Layer\n" - " Copyright (C) 1997-2023 Sam Lantinga \n" + " Copyright (C) 1997-2024 Sam Lantinga \n" "\n" " This software is provided 'as-is', without any express or implied\n" " warranty. In no event will the authors be held liable for any damages\n" @@ -128,7 +128,7 @@ int main(void) " 3. This notice may not be removed or altered from any source distribution.\n" "*/\n" "\n" - "/* DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c */\n" + "// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c\n" "\n" "#define RESAMPLER_ZERO_CROSSINGS %d\n" "#define RESAMPLER_BITS_PER_SAMPLE %d\n" diff --git a/build-scripts/rename_api.py b/build-scripts/rename_api.py index fe0ac678..7c02b9e9 100755 --- a/build-scripts/rename_api.py +++ b/build-scripts/rename_api.py @@ -36,6 +36,10 @@ def main(): if not header.exists(): raise Exception("Couldn't find header %s" % header) + header_name = header.name + if (header.name == "SDL_gamepad.h"): + header_name = "SDL_gamecontroller.h" + header_text = header.read_text() # Replace the symbols in source code @@ -62,8 +66,8 @@ def main(): oldname = args.args[i + 0] newname = args.args[i + 1] - add_symbol_to_oldnames(header.name, oldname, newname) - add_symbol_to_migration(header.name, args.type, oldname, newname) + add_symbol_to_oldnames(header_name, oldname, newname) + add_symbol_to_migration(header_name, args.type, oldname, newname) add_symbol_to_coccinelle(args.type, oldname, newname) i += 2 diff --git a/build-scripts/rename_symbols.py b/build-scripts/rename_symbols.py index d6cf879e..33a92dde 100755 --- a/build-scripts/rename_symbols.py +++ b/build-scripts/rename_symbols.py @@ -25,7 +25,7 @@ def main(): else: if len(args.args) < 3: - print("Usage: %s oldname newname files_or_directories ..." % sys.argv[0]) + print("Usage: %s [--substring] oldname newname files_or_directories ..." % sys.argv[0]) exit(1) replacements = { args.args[0]: args.args[1] } diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index c2bb8edb..58abdf4e 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -1090,7 +1090,7 @@ if ($copy_direction == 1) { # --copy-to-headers my $dent = $_; if ($dent =~ /\A(.*?)\.md\Z/) { # we only bridge Markdown files here. next if $1 eq 'FrontPage'; - filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\r\n"); + filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\n"); } } closedir(DH); diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 00000000..62950c01 --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1,138 @@ +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVFILTER +# - AVUTIL +# - POSTPROC +# - SWSCALE +# the following target will be defined +# FFmpeg::SDL:: - link to this target to +# the following variables will be defined +# FFmpeg__FOUND - System has +# FFmpeg__INCLUDE_DIRS - Include directory necessary for using the headers +# FFmpeg__LIBRARIES - Link these to use +# FFmpeg__DEFINITIONS - Compiler switches required for using +# FFmpeg__VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# Copyright (c) 2023, Sam lantinga, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) +include("${CMAKE_CURRENT_LIST_DIR}/PkgConfigHelper.cmake") + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if(NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) + foreach(_component IN LISTS FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_REQUIRED_${_component} TRUE) + endforeach() +endif() + +find_package(PkgConfig QUIET) + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} QUIET ${_pkgconfig}) + endif() + + find_path(FFmpeg_${_component}_INCLUDE_DIRS + NAMES ${_header} + HINTS + ${PC_${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(FFmpeg_${_component}_LIBRARY + NAMES ${_library} + HINTS + ${PC_${_component}_LIBRARY_DIRS} + ) + + if(FFmpeg_${_component}_INCLUDE_DIRS AND FFmpeg_${_component}_LIBRARY) + set(FFmpeg_${_component}_FOUND TRUE) + endif() + + if(PC_${_component}_FOUND) + get_flags_from_pkg_config("${FFmpeg_${_component}_LIBRARY}" "PC_${_component}" "${_component}") + endif() + + set(FFmpeg_${_component}_VERSION "${PC_${_component}_VERSION}") + + set(FFmpeg_${_component}_COMPILE_OPTIONS "${${_component}_options}" CACHE STRING "Extra compile options of FFmpeg ${_component}") + + set(FFmpeg_${_component}_LIBRARIES "${${_component}_link_libraries}" CACHE STRING "Extra link libraries of FFmpeg ${_component}") + + set(FFmpeg_${_component}_LINK_OPTIONS "${${_component}_link_options}" CACHE STRING "Extra link flags of FFmpeg ${_component}") + + set(FFmpeg_${_component}_LINK_DIRECTORIES "${${_component}_link_directories}" CACHE PATH "Extra link directories of FFmpeg ${_component}") + + mark_as_advanced( + FFmpeg_${_component}_INCLUDE_DIRS + FFmpeg_${_component}_LIBRARY + FFmpeg_${_component}_COMPILE_OPTIONS + FFmpeg_${_component}_LIBRARIES + FFmpeg_${_component}_LINK_OPTIONS + FFmpeg_${_component}_LINK_DIRECTORIES + ) +endmacro() + +# Check for all possible component. +find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) +find_component(AVFORMAT libavformat avformat libavformat/avformat.h) +find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) +find_component(AVUTIL libavutil avutil libavutil/avutil.h) +find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) +find_component(SWSCALE libswscale swscale libswscale/swscale.h) +find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) +find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS) +foreach(_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS FFmpeg_${_component}_INCLUDE_DIRS FFmpeg_${_component}_LIBRARY) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) + +set(FFMPEG_LIBRARIES) +if(FFmpeg_FOUND) + foreach(_component IN LISTS FFmpeg_FIND_COMPONENTS) + if(FFmpeg_${_component}_FOUND) + list(APPEND FFMPEG_LIBRARIES FFmpeg::SDL::${_component}) + if(NOT TARGET FFmpeg::SDL::${_component}) + add_library(FFmpeg::SDL::${_component} UNKNOWN IMPORTED) + set_target_properties(FFmpeg::SDL::${_component} PROPERTIES + IMPORTED_LOCATION "${FFmpeg_${_component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FFmpeg_${_component}_INCLUDE_DIRS}" + INTERFACE_COMPILE_OPTIONS "${FFmpeg_${_component}_COMPILE_OPTIONS}" + INTERFACE_LINK_LIBRARIES "${FFmpeg_${_component}_LIBRARIES}" + INTERFACE_LINK_OPTIONS "${FFmpeg_${_component}_LINK_OPTIONS}" + INTERFACE_LINK_DIRECTORIES "${FFmpeg_${_component}_LINK_DIRECTORIES}" + ) + endif() + endif() + endforeach() +endif() diff --git a/cmake/PkgConfigHelper.cmake b/cmake/PkgConfigHelper.cmake index c25fbd51..7070fac7 100644 --- a/cmake/PkgConfigHelper.cmake +++ b/cmake/PkgConfigHelper.cmake @@ -2,13 +2,11 @@ function(get_flags_from_pkg_config _library _pc_prefix _out_prefix) if("${_library}" MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$") - set(_include_dirs ${_pc_prefix}_STATIC_INCLUDE_DIRS) set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) else() - set(_include_dirs ${_pc_prefix}_INCLUDE_DIRS) set(_cflags ${_pc_prefix}_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_LIBRARIES) set(_link_options ${_pc_prefix}_LDFLAGS_OTHER) @@ -21,9 +19,6 @@ function(get_flags_from_pkg_config _library _pc_prefix _out_prefix) # Work around CMake's flag deduplication when pc files use `-framework A` instead of `-Wl,-framework,A` string(REPLACE "-framework;" "-Wl,-framework," "_filtered_link_options" "${${_link_options}}") - set(${_out_prefix}_include_dirs - "${${_include_dirs}}" - PARENT_SCOPE) set(${_out_prefix}_compile_options "${${_cflags}}" PARENT_SCOPE) diff --git a/cmake/SDL3Config.cmake.in b/cmake/SDL3Config.cmake.in index d8b029d4..10f87283 100644 --- a/cmake/SDL3Config.cmake.in +++ b/cmake/SDL3Config.cmake.in @@ -48,6 +48,11 @@ else() endif() endif() +if(ANDROID AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL3jarTargets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/SDL3jarTargets.cmake") + set(SDL3_Jar_FOUND TRUE) +endif() + if(SDL3_SDL3-shared_FOUND OR SDL3_SDL3-static_FOUND) set(SDL3_SDL3_FOUND TRUE) endif() diff --git a/cmake/SDL3jarTargets.cmake.in b/cmake/SDL3jarTargets.cmake.in new file mode 100644 index 00000000..732d293c --- /dev/null +++ b/cmake/SDL3jarTargets.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +set_and_check(SDL3_JAR "@PACKAGE_SDL_INSTALL_JAVADIR@/SDL3/SDL3-@SDL3_VERSION@.jar") + +if(NOT TARGET SDL3::Jar) + add_library(SDL3::Jar INTERFACE IMPORTED) + set_property(TARGET SDL3::Jar PROPERTY JAR_FILE "${SDL3_JAR}") +endif() + +unset(SDL3_JAR) diff --git a/cmake/macros.cmake b/cmake/macros.cmake index 32fc9f1a..c04c760a 100644 --- a/cmake/macros.cmake +++ b/cmake/macros.cmake @@ -82,16 +82,48 @@ if(APPLE) enable_language(OBJC) endif() +function(SDL_detect_linker) + if(CMAKE_VERSION VERSION_LESS 3.29) + if(NOT DEFINED SDL_CMAKE_C_COMPILER_LINKER_ID) + execute_process(COMMAND ${CMAKE_LINKER} -v OUTPUT_VARIABLE LINKER_OUTPUT ERROR_VARIABLE LINKER_OUTPUT) + string(REGEX REPLACE "[\r\n]" " " LINKER_OUTPUT "${LINKER_OUTPUT}") + if(LINKER_OUTPUT MATCHES ".*Microsoft.*") + set(linker MSVC) + else() + set(linker GNUlike) + endif() + message(STATUS "Linker identification: ${linker}") + set(SDL_CMAKE_C_COMPILER_LINKER_ID "${linker}" CACHE STRING "Linker identification") + mark_as_advanced(SDL_CMAKE_C_COMPILER_LINKER_ID) + endif() + set(CMAKE_C_COMPILER_LINKER_ID "${SDL_CMAKE_C_COMPILER_LINKER_ID}" PARENT_SCOPE) + endif() +endfunction() + +function(check_linker_supports_version_file VAR) + SDL_detect_linker() + if(CMAKE_C_COMPILER_LINKER_ID MATCHES "^(MSVC)$") + set(LINKER_SUPPORTS_VERSION_SCRIPT FALSE) + else() + cmake_push_check_state(RESET) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/dummy.sym" "n_0 {\n global:\n func;\n local: *;\n};\n") + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/dummy.sym") + check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" LINKER_SUPPORTS_VERSION_SCRIPT FAIL_REGEX "(unsupported|syntax error|unrecognized option)") + cmake_pop_check_state() + endif() + set(${VAR} "${LINKER_SUPPORTS_VERSION_SCRIPT}" PARENT_SCOPE) +endfunction() + if(CMAKE_VERSION VERSION_LESS 3.18) function(check_linker_flag LANG FLAG VAR) - cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${FLAG} ) + cmake_push_check_state(RESET) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${FLAG}) if(LANG STREQUAL "C") include(CheckCSourceCompiles) - check_c_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "warning") + check_c_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "(unsupported|syntax error)") elseif(LANG STREQUAL "CXX") include(CheckCXXSourceCompiles) - check_cxx_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "warning") + check_cxx_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "(unsupported|syntax error)") else() message(FATAL_ERROR "Unsupported language: ${LANG}") endif() @@ -169,3 +201,17 @@ function(SDL_PrintSummary) message(STATUS "") endif() endfunction() + +function(SDL_install_pdb TARGET DIRECTORY) + get_property(type TARGET ${TARGET} PROPERTY TYPE) + if(type MATCHES "^(SHARED_LIBRARY|EXECUTABLE)$") + install(FILES $ DESTINATION "${DIRECTORY}" OPTIONAL) + elseif(type STREQUAL "STATIC_LIBRARY") + # FIXME: Use $=5.0") + set(PulseAudio_PKG_CONFIG_SPEC "libpulse>=0.9.15") pkg_check_modules(PC_PULSEAUDIO IMPORTED_TARGET ${PulseAudio_PKG_CONFIG_SPEC}) if(PC_PULSEAUDIO_FOUND) set(HAVE_PULSEAUDIO TRUE) @@ -307,7 +307,9 @@ macro(CheckX11) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/x11/*.c") set(SDL_VIDEO_DRIVER_X11 1) - # !!! FIXME: why is this disabled for Apple? + # Note: Disabled on Apple because the dynamic mode backend for X11 doesn't + # work properly on Apple during several issues like inconsistent paths + # among platforms. See #6778 (https://github.com/libsdl-org/SDL/issues/6778) if(APPLE) set(SDL_X11_SHARED OFF) endif() @@ -533,11 +535,6 @@ macro(CheckWayland) WaylandProtocolGen("${WAYLAND_SCANNER}" "${WAYLAND_SCANNER_CODE_MODE}" "${SDL3_SOURCE_DIR}/wayland-protocols/${_XML}" "${_PROTL}") endforeach() - if(SDL_WAYLAND_QT_TOUCH) - set(HAVE_WAYLAND_QT_TOUCH TRUE) - set(SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH 1) - endif() - if(SDL_WAYLAND_SHARED AND NOT HAVE_SDL_LOADSO) message(WARNING "You must have SDL_LoadObject() support for dynamic Wayland loading") endif() @@ -560,10 +557,10 @@ macro(CheckWayland) set(LibDecor_PKG_CONFIG_SPEC libdecor-0) pkg_check_modules(PC_LIBDECOR IMPORTED_TARGET ${LibDecor_PKG_CONFIG_SPEC}) if(PC_LIBDECOR_FOUND) - # Version 0.1.2 or higher is needed for suspended window state and statically linked min/max getters. - if(PC_LIBDECOR_VERSION VERSION_GREATER_EQUAL "0.1.2") - set(SDL_HAVE_LIBDECOR_VER_0_1_2 1) - set(LibDecor_PKG_CONFIG_SPEC "libdecor-0>=0.1.2") + # Version 0.2.0 or higher is needed for suspended window state and statically linked min/max getters. + if(PC_LIBDECOR_VERSION VERSION_GREATER_EQUAL "0.2.0") + set(SDL_HAVE_LIBDECOR_VER_0_2_0 1) + set(LibDecor_PKG_CONFIG_SPEC "libdecor-0>=0.2.0") endif() set(HAVE_WAYLAND_LIBDECOR TRUE) set(HAVE_LIBDECOR_H 1) @@ -1024,7 +1021,7 @@ macro(CheckHIDAPI) if(SDL_HIDAPI_LIBUSB) set(HAVE_LIBUSB FALSE) - set(LibUSB_PKG_CONFIG_SPEC libusb-1.0) + set(LibUSB_PKG_CONFIG_SPEC libusb-1.0>=1.0.16) pkg_check_modules(PC_LIBUSB IMPORTED_TARGET ${LibUSB_PKG_CONFIG_SPEC}) if(PC_LIBUSB_FOUND) cmake_push_check_state() diff --git a/cmake/sdlcompilers.cmake b/cmake/sdlcompilers.cmake index 40bb3b6d..5fd3081b 100644 --- a/cmake/sdlcompilers.cmake +++ b/cmake/sdlcompilers.cmake @@ -49,12 +49,12 @@ function(SDL_AddCommonCompilerFlags TARGET) check_c_compiler_flag(-Wundef HAVE_GCC_WUNDEF) if(HAVE_GCC_WUNDEF) - target_compile_options(${TARGET} PRIVATE "-Wundef") + target_compile_options(${TARGET} PRIVATE "$<$:-Wundef>") endif() check_c_compiler_flag(-fno-strict-aliasing HAVE_GCC_NO_STRICT_ALIASING) if(HAVE_GCC_NO_STRICT_ALIASING) - target_compile_options(${TARGET} PRIVATE "-fno-strict-aliasing") + target_compile_options(${TARGET} PRIVATE "$<$:-fno-strict-aliasing>") endif() check_c_compiler_flag(-Wdocumentation HAVE_GCC_WDOCUMENTATION) @@ -62,10 +62,10 @@ function(SDL_AddCommonCompilerFlags TARGET) if(SDL_WERROR) check_c_compiler_flag(-Werror=documentation HAVE_GCC_WERROR_DOCUMENTATION) if(HAVE_GCC_WERROR_DOCUMENTATION) - target_compile_options(${TARGET} PRIVATE "-Werror=documentation") + target_compile_options(${TARGET} PRIVATE "$<$:-Werror=documentation>") endif() endif() - target_compile_options(${TARGET} PRIVATE "-Wdocumentation") + target_compile_options(${TARGET} PRIVATE "$<$:-Wdocumentation>") endif() check_c_compiler_flag(-Wdocumentation-unknown-command HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND) @@ -73,30 +73,30 @@ function(SDL_AddCommonCompilerFlags TARGET) if(SDL_WERROR) check_c_compiler_flag(-Werror=documentation-unknown-command HAVE_GCC_WERROR_DOCUMENTATION_UNKNOWN_COMMAND) if(HAVE_GCC_WERROR_DOCUMENTATION_UNKNOWN_COMMAND) - target_compile_options(${TARGET} PRIVATE "-Werror=documentation-unknown-command") + target_compile_options(${TARGET} PRIVATE "$<$:-Werror=documentation-unknown-command>") endif() endif() - target_compile_options(${TARGET} PRIVATE "-Wdocumentation-unknown-command") + target_compile_options(${TARGET} PRIVATE "$<$:-Wdocumentation-unknown-command>") endif() check_c_compiler_flag(-fcomment-block-commands=threadsafety HAVE_GCC_COMMENT_BLOCK_COMMANDS) if(HAVE_GCC_COMMENT_BLOCK_COMMANDS) - target_compile_options(${TARGET} PRIVATE "-fcomment-block-commands=threadsafety") + target_compile_options(${TARGET} PRIVATE "$<$:-fcomment-block-commands=threadsafety>") else() check_c_compiler_flag(/clang:-fcomment-block-commands=threadsafety HAVE_CLANG_COMMENT_BLOCK_COMMANDS) if(HAVE_CLANG_COMMENT_BLOCK_COMMANDS) - target_compile_options(${TARGET} PRIVATE "/clang:-fcomment-block-commands=threadsafety") + target_compile_options(${TARGET} PRIVATE "$<$:/clang:-fcomment-block-commands=threadsafety>") endif() endif() check_c_compiler_flag(-Wshadow HAVE_GCC_WSHADOW) if(HAVE_GCC_WSHADOW) - target_compile_options(${TARGET} PRIVATE "-Wshadow") + target_compile_options(${TARGET} PRIVATE "$<$:-Wshadow>") endif() check_c_compiler_flag(-Wunused-local-typedefs HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS) if(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS) - target_compile_options(${TARGET} PRIVATE "-Wno-unused-local-typedefs") + target_compile_options(${TARGET} PRIVATE "$<$:-Wno-unused-local-typedefs>") endif() endif() @@ -109,7 +109,7 @@ function(SDL_AddCommonCompilerFlags TARGET) elseif(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QNX) check_c_compiler_flag(-Werror HAVE_WERROR) if(HAVE_WERROR) - target_compile_options(${TARGET} PRIVATE "-Werror") + target_compile_options(${TARGET} PRIVATE "$<$:-Werror>") endif() endif() endif() @@ -117,12 +117,12 @@ function(SDL_AddCommonCompilerFlags TARGET) if(USE_CLANG) check_c_compiler_flag("-fcolor-diagnostics" COMPILER_SUPPORTS_FCOLOR_DIAGNOSTICS) if(COMPILER_SUPPORTS_FCOLOR_DIAGNOSTICS) - target_compile_options(${TARGET} PRIVATE "-fcolor-diagnostics") + target_compile_options(${TARGET} PRIVATE "$<$:-fcolor-diagnostics>") endif() else() check_c_compiler_flag("-fdiagnostics-color=always" COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS) if(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS) - target_compile_options(${TARGET} PRIVATE "-fdiagnostics-color=always") + target_compile_options(${TARGET} PRIVATE "$<$:-fdiagnostics-color=always>") endif() endif() endfunction() diff --git a/cmake/sdlmanpages.cmake b/cmake/sdlmanpages.cmake index cc706dd7..dc3ebb6b 100644 --- a/cmake/sdlmanpages.cmake +++ b/cmake/sdlmanpages.cmake @@ -19,7 +19,7 @@ function(SDL_generate_manpages) endif() if(NOT ARG_HEADERS_DIR) - set(ARG_HEADERS_DIR "${PROJECT_SOURCE_DIR}/include/SDL3") + message(FATAL_ERROR "Missing required HEADERS_DIR argument") endif() # FIXME: get rid of SYMBOL and let the perl script figure out the dependencies diff --git a/cmake/sdltargets.cmake b/cmake/sdltargets.cmake index 46496239..d4fe9137 100644 --- a/cmake/sdltargets.cmake +++ b/cmake/sdltargets.cmake @@ -332,6 +332,9 @@ function(configure_sdl3_pc) endif() # Calculate prefix relative to location of sdl3.pc + if(NOT IS_ABSOLUTE "${CMAKE_INSTALL_PREFIX}") + set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_PREFIX}") + endif() file(RELATIVE_PATH SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${CMAKE_INSTALL_PREFIX}/${SDL_PKGCONFIG_INSTALLDIR}" "${CMAKE_INSTALL_PREFIX}") string(REGEX REPLACE "[/]+$" "" SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}") set(SDL_PKGCONFIG_PREFIX "\${pcfiledir}/${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}") diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt index 83ee81dc..f03590f2 100644 --- a/cmake/test/CMakeLists.txt +++ b/cmake/test/CMakeLists.txt @@ -93,4 +93,8 @@ find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3) add_executable(gui-whatever WIN32 main_gui.c) target_link_libraries(gui-whatever PRIVATE SDL3::SDL3) +if(ANDROID) + find_package(SDL3 REQUIRED CONFIG COMPONENTS Jar) +endif() + feature_summary(WHAT ALL) diff --git a/cmake/test/main_gui.c b/cmake/test/main_gui.c index 715c4306..c8cc03c1 100644 --- a/cmake/test/main_gui.c +++ b/cmake/test/main_gui.c @@ -10,7 +10,7 @@ int main(int argc, char *argv[]) return 1; } window = SDL_CreateWindow("Hello SDL", 640, 480, 0); - if (window == NULL) { + if (!window) { SDL_Log("could not create window: %s\n", SDL_GetError()); return 1; } diff --git a/docs/README-android.md b/docs/README-android.md index 2eaee31b..588b43d0 100644 --- a/docs/README-android.md +++ b/docs/README-android.md @@ -1,563 +1,563 @@ -Android -================================================================================ - -Matt Styles wrote a tutorial on building SDL for Android with Visual Studio: -http://trederia.blogspot.de/2017/03/building-sdl2-for-android-with-visual.html - -The rest of this README covers the Android gradle style build process. - - -Requirements -================================================================================ - -Android SDK (version 34 or later) -https://developer.android.com/sdk/index.html - -Android NDK r15c or later -https://developer.android.com/tools/sdk/ndk/index.html - -Minimum API level supported by SDL: 19 (Android 4.4) - - -How the port works -================================================================================ - -- Android applications are Java-based, optionally with parts written in C -- As SDL apps are C-based, we use a small Java shim that uses JNI to talk to - the SDL library -- This means that your application C code must be placed inside an Android - Java project, along with some C support code that communicates with Java -- This eventually produces a standard Android .apk package - -The Android Java code implements an "Activity" and can be found in: -android-project/app/src/main/java/org/libsdl/app/SDLActivity.java - -The Java code loads your game code, the SDL shared library, and -dispatches to native functions implemented in the SDL library: -src/core/android/SDL_android.c - - -Building an app -================================================================================ - -For simple projects you can use the script located at build-scripts/androidbuild.sh - -There's two ways of using it: - - androidbuild.sh com.yourcompany.yourapp < sources.list - androidbuild.sh com.yourcompany.yourapp source1.c source2.c ...sourceN.c - -sources.list should be a text file with a source file name in each line -Filenames should be specified relative to the current directory, for example if -you are in the build-scripts directory and want to create the testgles.c test, you'll -run: - - ./androidbuild.sh org.libsdl.testgles ../test/testgles.c - -One limitation of this script is that all sources provided will be aggregated into -a single directory, thus all your source files should have a unique name. - -Once the project is complete the script will tell you where the debug APK is located. -If you want to create a signed release APK, you can use the project created by this -utility to generate it. - -Finally, a word of caution: re running androidbuild.sh wipes any changes you may have -done in the build directory for the app! - - - -For more complex projects, follow these instructions: - -1. Get the source code for SDL and copy the 'android-project' directory located at SDL/android-project to a suitable location. Also make sure to rename it to your project name (In these examples: YOURPROJECT). - - (The 'android-project' directory can basically be seen as a sort of starting point for the android-port of your project. It contains the glue code between the Android Java 'frontend' and the SDL code 'backend'. It also contains some standard behaviour, like how events should be handled, which you will be able to change.) - -2. Move or [symlink](https://en.wikipedia.org/wiki/Symbolic_link) the SDL directory into the "YOURPROJECT/app/jni" directory - -(This is needed as the source of SDL has to be compiled by the Android compiler) - -3. Edit "YOURPROJECT/app/jni/src/Android.mk" to include your source files. - -(They should be separated by spaces after the "LOCAL_SRC_FILES := " declaration) - -4a. If you want to use Android Studio, simply open your 'YOURPROJECT' directory and start building. - -4b. If you want to build manually, run './gradlew installDebug' in the project directory. This compiles the .java, creates an .apk with the native code embedded, and installs it on any connected Android device - - -If you already have a project that uses CMake, the instructions change somewhat: - -1. Do points 1 and 2 from the instruction above. -2. Edit "YOURPROJECT/app/build.gradle" to comment out or remove sections containing ndk-build - and uncomment the cmake sections. Add arguments to the CMake invocation as needed. -3. Edit "YOURPROJECT/app/jni/CMakeLists.txt" to include your project (it defaults to - adding the "src" subdirectory). Note that you'll have SDL3 and SDL3-static - as targets in your project, so you should have "target_link_libraries(yourgame SDL3)" - in your CMakeLists.txt file. Also be aware that you should use add_library() instead of - add_executable() for the target containing your "main" function. - -If you wish to use Android Studio, you can skip the last step. - -4. Run './gradlew installDebug' or './gradlew installRelease' in the project directory. It will build and install your .apk on any - connected Android device - -Here's an explanation of the files in the Android project, so you can customize them: - - android-project/app - build.gradle - build info including the application version and SDK - src/main/AndroidManifest.xml - package manifest. Among others, it contains the class name of the main Activity and the package name of the application. - jni/ - directory holding native code - jni/Application.mk - Application JNI settings, including target platform and STL library - jni/Android.mk - Android makefile that can call recursively the Android.mk files in all subdirectories - jni/CMakeLists.txt - Top-level CMake project that adds SDL as a subproject - jni/SDL/ - (symlink to) directory holding the SDL library files - jni/SDL/Android.mk - Android makefile for creating the SDL shared library - jni/src/ - directory holding your C/C++ source - jni/src/Android.mk - Android makefile that you should customize to include your source code and any library references - jni/src/CMakeLists.txt - CMake file that you may customize to include your source code and any library references - src/main/assets/ - directory holding asset files for your application - src/main/res/ - directory holding resources for your application - src/main/res/mipmap-* - directories holding icons for different phone hardware - src/main/res/values/strings.xml - strings used in your application, including the application name - src/main/java/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. You should instead subclass this for your application. - - -Customizing your application name -================================================================================ - -To customize your application name, edit AndroidManifest.xml and replace -"org.libsdl.app" with an identifier for your product package. - -Then create a Java class extending SDLActivity and place it in a directory -under src matching your package, e.g. - - src/com/gamemaker/game/MyGame.java - -Here's an example of a minimal class file: - - --- MyGame.java -------------------------- - package com.gamemaker.game; - - import org.libsdl.app.SDLActivity; - - /** - * A sample wrapper class that just calls SDLActivity - */ - - public class MyGame extends SDLActivity { } - - ------------------------------------------ - -Then replace "SDLActivity" in AndroidManifest.xml with the name of your -class, .e.g. "MyGame" - - -Customizing your application icon -================================================================================ - -Conceptually changing your icon is just replacing the "ic_launcher.png" files in -the drawable directories under the res directory. There are several directories -for different screen sizes. - - -Loading assets -================================================================================ - -Any files you put in the "app/src/main/assets" directory of your project -directory will get bundled into the application package and you can load -them using the standard functions in SDL_rwops.h. - -There are also a few Android specific functions that allow you to get other -useful paths for saving and loading data: -* SDL_AndroidGetInternalStoragePath() -* SDL_AndroidGetExternalStorageState() -* SDL_AndroidGetExternalStoragePath() - -See SDL_system.h for more details on these functions. - -The asset packaging system will, by default, compress certain file extensions. -SDL includes two asset file access mechanisms, the preferred one is the so -called "File Descriptor" method, which is faster and doesn't involve the Dalvik -GC, but given this method does not work on compressed assets, there is also the -"Input Stream" method, which is automatically used as a fall back by SDL. You -may want to keep this fact in mind when building your APK, specially when large -files are involved. -For more information on which extensions get compressed by default and how to -disable this behaviour, see for example: - -http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/ - - -Pause / Resume behaviour -================================================================================ - -If SDL_HINT_ANDROID_BLOCK_ON_PAUSE hint is set (the default), -the event loop will block itself when the app is paused (ie, when the user -returns to the main Android dashboard). Blocking is better in terms of battery -use, and it allows your app to spring back to life instantaneously after resume -(versus polling for a resume message). - -Upon resume, SDL will attempt to restore the GL context automatically. -In modern devices (Android 3.0 and up) this will most likely succeed and your -app can continue to operate as it was. - -However, there's a chance (on older hardware, or on systems under heavy load), -where the GL context can not be restored. In that case you have to listen for -a specific message (SDL_EVENT_RENDER_DEVICE_RESET) and restore your textures -manually or quit the app. - -You should not use the SDL renderer API while the app going in background: -- SDL_EVENT_WILL_ENTER_BACKGROUND: - after you read this message, GL context gets backed-up and you should not - use the SDL renderer API. - - When this event is received, you have to set the render target to NULL, if you're using it. - (eg call SDL_SetRenderTarget(renderer, NULL)) - -- SDL_EVENT_DID_ENTER_FOREGROUND: - GL context is restored, and the SDL renderer API is available (unless you - receive SDL_EVENT_RENDER_DEVICE_RESET). - -Activity lifecycle -================================================================================ - -You can control activity re-creation (eg. onCreate()) behaviour. This allows to keep -or re-initialize java and native static datas, see SDL_hints.h: -- SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY - -Mouse / Touch events -================================================================================ - -In some case, SDL generates synthetic mouse (resp. touch) events for touch -(resp. mouse) devices. -To enable/disable this behavior, see SDL_hints.h: -- SDL_HINT_TOUCH_MOUSE_EVENTS -- SDL_HINT_MOUSE_TOUCH_EVENTS - -Misc -================================================================================ - -For some device, it appears to works better setting explicitly GL attributes -before creating a window: - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); - -Threads and the Java VM -================================================================================ - -For a quick tour on how Linux native threads interoperate with the Java VM, take -a look here: https://developer.android.com/guide/practices/jni.html - -If you want to use threads in your SDL app, it's strongly recommended that you -do so by creating them using SDL functions. This way, the required attach/detach -handling is managed by SDL automagically. If you have threads created by other -means and they make calls to SDL functions, make sure that you call -Android_JNI_SetupThread() before doing anything else otherwise SDL will attach -your thread automatically anyway (when you make an SDL call), but it'll never -detach it. - - -If you ever want to use JNI in a native thread (created by "SDL_CreateThread()"), -it won't be able to find your java class and method because of the java class loader -which is different for native threads, than for java threads (eg your "main()"). - -the work-around is to find class/method, in you "main()" thread, and to use them -in your native thread. - -see: -https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class - -Using STL -================================================================================ - -You can use STL in your project by creating an Application.mk file in the jni -folder and adding the following line: - - APP_STL := c++_shared - -For more information go here: - https://developer.android.com/ndk/guides/cpp-support - - -Using the emulator -================================================================================ - -There are some good tips and tricks for getting the most out of the -emulator here: https://developer.android.com/tools/devices/emulator.html - -Especially useful is the info on setting up OpenGL ES 2.0 emulation. - -Notice that this software emulator is incredibly slow and needs a lot of disk space. -Using a real device works better. - - -Troubleshooting -================================================================================ - -You can see if adb can see any devices with the following command: - - adb devices - -You can see the output of log messages on the default device with: - - adb logcat - -You can push files to the device with: - - adb push local_file remote_path_and_file - -You can push files to the SD Card at /sdcard, for example: - - adb push moose.dat /sdcard/moose.dat - -You can see the files on the SD card with a shell command: - - adb shell ls /sdcard/ - -You can start a command shell on the default device with: - - adb shell - -You can remove the library files of your project (and not the SDL lib files) with: - - ndk-build clean - -You can do a build with the following command: - - ndk-build - -You can see the complete command line that ndk-build is using by passing V=1 on the command line: - - ndk-build V=1 - -If your application crashes in native code, you can use ndk-stack to get a symbolic stack trace: - https://developer.android.com/ndk/guides/ndk-stack - -If you want to go through the process manually, you can use addr2line to convert the -addresses in the stack trace to lines in your code. - -For example, if your crash looks like this: - - I/DEBUG ( 31): signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 400085d0 - I/DEBUG ( 31): r0 00000000 r1 00001000 r2 00000003 r3 400085d4 - I/DEBUG ( 31): r4 400085d0 r5 40008000 r6 afd41504 r7 436c6a7c - I/DEBUG ( 31): r8 436c6b30 r9 435c6fb0 10 435c6f9c fp 4168d82c - I/DEBUG ( 31): ip 8346aff0 sp 436c6a60 lr afd1c8ff pc afd1c902 cpsr 60000030 - I/DEBUG ( 31): #00 pc 0001c902 /system/lib/libc.so - I/DEBUG ( 31): #01 pc 0001ccf6 /system/lib/libc.so - I/DEBUG ( 31): #02 pc 000014bc /data/data/org.libsdl.app/lib/libmain.so - I/DEBUG ( 31): #03 pc 00001506 /data/data/org.libsdl.app/lib/libmain.so - -You can see that there's a crash in the C library being called from the main code. -I run addr2line with the debug version of my code: - - arm-eabi-addr2line -C -f -e obj/local/armeabi/libmain.so - -and then paste in the number after "pc" in the call stack, from the line that I care about: -000014bc - -I get output from addr2line showing that it's in the quit function, in testspriteminimal.c, on line 23. - -You can add logging to your code to help show what's happening: - - #include - - __android_log_print(ANDROID_LOG_INFO, "foo", "Something happened! x = %d", x); - -If you need to build without optimization turned on, you can create a file called -"Application.mk" in the jni directory, with the following line in it: - - APP_OPTIM := debug - - -Memory debugging -================================================================================ - -The best (and slowest) way to debug memory issues on Android is valgrind. -Valgrind has support for Android out of the box, just grab code using: - - svn co svn://svn.valgrind.org/valgrind/trunk valgrind - -... and follow the instructions in the file README.android to build it. - -One thing I needed to do on macOS was change the path to the toolchain, -and add ranlib to the environment variables: -export RANLIB=$NDKROOT/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-ranlib - -Once valgrind is built, you can create a wrapper script to launch your -application with it, changing org.libsdl.app to your package identifier: - - --- start_valgrind_app ------------------- - #!/system/bin/sh - export TMPDIR=/data/data/org.libsdl.app - exec /data/local/Inst/bin/valgrind --log-file=/sdcard/valgrind.log --error-limit=no $* - ------------------------------------------ - -Then push it to the device: - - adb push start_valgrind_app /data/local - -and make it executable: - - adb shell chmod 755 /data/local/start_valgrind_app - -and tell Android to use the script to launch your application: - - adb shell setprop wrap.org.libsdl.app "logwrapper /data/local/start_valgrind_app" - -If the setprop command says "could not set property", it's likely that -your package name is too long and you should make it shorter by changing -AndroidManifest.xml and the path to your class file in android-project/src - -You can then launch your application normally and waaaaaaaiiittt for it. -You can monitor the startup process with the logcat command above, and -when it's done (or even while it's running) you can grab the valgrind -output file: - - adb pull /sdcard/valgrind.log - -When you're done instrumenting with valgrind, you can disable the wrapper: - - adb shell setprop wrap.org.libsdl.app "" - - -Graphics debugging -================================================================================ - -If you are developing on a compatible Tegra-based tablet, NVidia provides -Tegra Graphics Debugger at their website. Because SDL3 dynamically loads EGL -and GLES libraries, you must follow their instructions for installing the -interposer library on a rooted device. The non-rooted instructions are not -compatible with applications that use SDL3 for video. - -The Tegra Graphics Debugger is available from NVidia here: -https://developer.nvidia.com/tegra-graphics-debugger - - -Why is API level 19 the minimum required? -================================================================================ - -The latest NDK toolchain doesn't support targeting earlier than API level 19. -As of this writing, according to https://www.composables.com/tools/distribution-chart -about 99.7% of the Android devices accessing Google Play support API level 19 or -higher (August 2023). - - -A note regarding the use of the "dirty rectangles" rendering technique -================================================================================ - -If your app uses a variation of the "dirty rectangles" rendering technique, -where you only update a portion of the screen on each frame, you may notice a -variety of visual glitches on Android, that are not present on other platforms. -This is caused by SDL's use of EGL as the support system to handle OpenGL ES/ES2 -contexts, in particular the use of the eglSwapBuffers function. As stated in the -documentation for the function "The contents of ancillary buffers are always -undefined after calling eglSwapBuffers". -Setting the EGL_SWAP_BEHAVIOR attribute of the surface to EGL_BUFFER_PRESERVED -is not possible for SDL as it requires EGL 1.4, available only on the API level -17+, so the only workaround available on this platform is to redraw the entire -screen each frame. - -Reference: http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html - - -Ending your application -================================================================================ - -Two legitimate ways: - -- return from your main() function. Java side will automatically terminate the -Activity by calling Activity.finish(). - -- Android OS can decide to terminate your application by calling onDestroy() -(see Activity life cycle). Your application will receive an SDL_EVENT_QUIT you -can handle to save things and quit. - -Don't call exit() as it stops the activity badly. - -NB: "Back button" can be handled as a SDL_EVENT_KEY_DOWN/UP events, with Keycode -SDLK_AC_BACK, for any purpose. - -Known issues -================================================================================ - -- The number of buttons reported for each joystick is hardcoded to be 36, which -is the current maximum number of buttons Android can report. - -Building the SDL tests -================================================================================ - -SDL's CMake build system can create APK's for the tests. -It can build all tests with a single command without a dependency on gradle or Android Studio. -The APK's are signed with a debug certificate. -The only caveat is that the APK's support a single architecture. - -### Requirements -- SDL source tree -- CMake -- ninja or make -- Android Platform SDK -- Android NDK -- Android Build tools -- Java JDK (version should be compatible with Android) -- keytool (usually provided with the Java JDK), used for generating a debug certificate -- zip - -### CMake configuration - -When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through `SDL_ANDROID_HOME`. -``` -cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=23 -DSDL_TESTS=ON -``` - -Remarks: -- `android.toolchain.cmake` can usually be found at `$ANDROID_HOME/ndk/x.y.z/build/cmake/android.toolchain.cmake` -- `ANDROID_ABI` should be one of `arm64-v8a`, `armeabi-v7a`, `x86` or `x86_64`. -- When CMake is unable to find required paths, use `cmake-gui` to override required `SDL_ANDROID_` CMake cache variables. - -### Building the APK's - -For the `testsprite` executable, the `testsprite-apk` target will build the associated APK: -``` -cmake --build . --target testsprite-apk -``` - -APK's of all tests can be built with the `sdl-test-apks` target: -``` -cmake --build . --target sdl-test-apks -``` - -### Installation/removal of the tests - -`testsprite.apk` APK can be installed on your Android machine using the `install-testsprite` target: -``` -cmake --build . --target install-testsprite -``` - -APK's of all tests can be installed with the `install-sdl-test-apks` target: -``` -cmake --build . --target install-sdl-test-apks -``` - -All SDL tests can be uninstalled with the `uninstall-sdl-test-apks` target: -``` -cmake --build . --target uninstall-sdl-test-apks -``` - -### Starting the tests - -After installation, the tests can be started using the Android Launcher GUI. -Alternatively, they can also be started using CMake targets. - -This command will start the testsprite executable: -``` -cmake --build . --target start-testsprite -``` - -There is also a convenience target which will build, install and start a test: -``` -cmake --build . --target build-install-start-testsprite -``` - -Not all tests provide a GUI. For those, you can use `adb logcat` to read the output of stdout. +Android +================================================================================ + +Matt Styles wrote a tutorial on building SDL for Android with Visual Studio: +http://trederia.blogspot.de/2017/03/building-sdl2-for-android-with-visual.html + +The rest of this README covers the Android gradle style build process. + + +Requirements +================================================================================ + +Android SDK (version 34 or later) +https://developer.android.com/sdk/index.html + +Android NDK r15c or later +https://developer.android.com/tools/sdk/ndk/index.html + +Minimum API level supported by SDL: 19 (Android 4.4) + + +How the port works +================================================================================ + +- Android applications are Java-based, optionally with parts written in C +- As SDL apps are C-based, we use a small Java shim that uses JNI to talk to + the SDL library +- This means that your application C code must be placed inside an Android + Java project, along with some C support code that communicates with Java +- This eventually produces a standard Android .apk package + +The Android Java code implements an "Activity" and can be found in: +android-project/app/src/main/java/org/libsdl/app/SDLActivity.java + +The Java code loads your game code, the SDL shared library, and +dispatches to native functions implemented in the SDL library: +src/core/android/SDL_android.c + + +Building an app +================================================================================ + +For simple projects you can use the script located at build-scripts/androidbuild.sh + +There's two ways of using it: + + androidbuild.sh com.yourcompany.yourapp < sources.list + androidbuild.sh com.yourcompany.yourapp source1.c source2.c ...sourceN.c + +sources.list should be a text file with a source file name in each line +Filenames should be specified relative to the current directory, for example if +you are in the build-scripts directory and want to create the testgles.c test, you'll +run: + + ./androidbuild.sh org.libsdl.testgles ../test/testgles.c + +One limitation of this script is that all sources provided will be aggregated into +a single directory, thus all your source files should have a unique name. + +Once the project is complete the script will tell you where the debug APK is located. +If you want to create a signed release APK, you can use the project created by this +utility to generate it. + +Finally, a word of caution: re running androidbuild.sh wipes any changes you may have +done in the build directory for the app! + + + +For more complex projects, follow these instructions: + +1. Get the source code for SDL and copy the 'android-project' directory located at SDL/android-project to a suitable location. Also make sure to rename it to your project name (In these examples: YOURPROJECT). + + (The 'android-project' directory can basically be seen as a sort of starting point for the android-port of your project. It contains the glue code between the Android Java 'frontend' and the SDL code 'backend'. It also contains some standard behaviour, like how events should be handled, which you will be able to change.) + +2. Move or [symlink](https://en.wikipedia.org/wiki/Symbolic_link) the SDL directory into the "YOURPROJECT/app/jni" directory + +(This is needed as the source of SDL has to be compiled by the Android compiler) + +3. Edit "YOURPROJECT/app/jni/src/Android.mk" to include your source files. + +(They should be separated by spaces after the "LOCAL_SRC_FILES := " declaration) + +4a. If you want to use Android Studio, simply open your 'YOURPROJECT' directory and start building. + +4b. If you want to build manually, run './gradlew installDebug' in the project directory. This compiles the .java, creates an .apk with the native code embedded, and installs it on any connected Android device + + +If you already have a project that uses CMake, the instructions change somewhat: + +1. Do points 1 and 2 from the instruction above. +2. Edit "YOURPROJECT/app/build.gradle" to comment out or remove sections containing ndk-build + and uncomment the cmake sections. Add arguments to the CMake invocation as needed. +3. Edit "YOURPROJECT/app/jni/CMakeLists.txt" to include your project (it defaults to + adding the "src" subdirectory). Note that you'll have SDL3 and SDL3-static + as targets in your project, so you should have "target_link_libraries(yourgame SDL3)" + in your CMakeLists.txt file. Also be aware that you should use add_library() instead of + add_executable() for the target containing your "main" function. + +If you wish to use Android Studio, you can skip the last step. + +4. Run './gradlew installDebug' or './gradlew installRelease' in the project directory. It will build and install your .apk on any + connected Android device + +Here's an explanation of the files in the Android project, so you can customize them: + + android-project/app + build.gradle - build info including the application version and SDK + src/main/AndroidManifest.xml - package manifest. Among others, it contains the class name of the main Activity and the package name of the application. + jni/ - directory holding native code + jni/Application.mk - Application JNI settings, including target platform and STL library + jni/Android.mk - Android makefile that can call recursively the Android.mk files in all subdirectories + jni/CMakeLists.txt - Top-level CMake project that adds SDL as a subproject + jni/SDL/ - (symlink to) directory holding the SDL library files + jni/SDL/Android.mk - Android makefile for creating the SDL shared library + jni/src/ - directory holding your C/C++ source + jni/src/Android.mk - Android makefile that you should customize to include your source code and any library references + jni/src/CMakeLists.txt - CMake file that you may customize to include your source code and any library references + src/main/assets/ - directory holding asset files for your application + src/main/res/ - directory holding resources for your application + src/main/res/mipmap-* - directories holding icons for different phone hardware + src/main/res/values/strings.xml - strings used in your application, including the application name + src/main/java/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. You should instead subclass this for your application. + + +Customizing your application name +================================================================================ + +To customize your application name, edit AndroidManifest.xml and replace +"org.libsdl.app" with an identifier for your product package. + +Then create a Java class extending SDLActivity and place it in a directory +under src matching your package, e.g. + + src/com/gamemaker/game/MyGame.java + +Here's an example of a minimal class file: + + --- MyGame.java -------------------------- + package com.gamemaker.game; + + import org.libsdl.app.SDLActivity; + + /** + * A sample wrapper class that just calls SDLActivity + */ + + public class MyGame extends SDLActivity { } + + ------------------------------------------ + +Then replace "SDLActivity" in AndroidManifest.xml with the name of your +class, .e.g. "MyGame" + + +Customizing your application icon +================================================================================ + +Conceptually changing your icon is just replacing the "ic_launcher.png" files in +the drawable directories under the res directory. There are several directories +for different screen sizes. + + +Loading assets +================================================================================ + +Any files you put in the "app/src/main/assets" directory of your project +directory will get bundled into the application package and you can load +them using the standard functions in SDL_rwops.h. + +There are also a few Android specific functions that allow you to get other +useful paths for saving and loading data: +* SDL_AndroidGetInternalStoragePath() +* SDL_AndroidGetExternalStorageState() +* SDL_AndroidGetExternalStoragePath() + +See SDL_system.h for more details on these functions. + +The asset packaging system will, by default, compress certain file extensions. +SDL includes two asset file access mechanisms, the preferred one is the so +called "File Descriptor" method, which is faster and doesn't involve the Dalvik +GC, but given this method does not work on compressed assets, there is also the +"Input Stream" method, which is automatically used as a fall back by SDL. You +may want to keep this fact in mind when building your APK, specially when large +files are involved. +For more information on which extensions get compressed by default and how to +disable this behaviour, see for example: + +http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/ + + +Pause / Resume behaviour +================================================================================ + +If SDL_HINT_ANDROID_BLOCK_ON_PAUSE hint is set (the default), +the event loop will block itself when the app is paused (ie, when the user +returns to the main Android dashboard). Blocking is better in terms of battery +use, and it allows your app to spring back to life instantaneously after resume +(versus polling for a resume message). + +Upon resume, SDL will attempt to restore the GL context automatically. +In modern devices (Android 3.0 and up) this will most likely succeed and your +app can continue to operate as it was. + +However, there's a chance (on older hardware, or on systems under heavy load), +where the GL context can not be restored. In that case you have to listen for +a specific message (SDL_EVENT_RENDER_DEVICE_RESET) and restore your textures +manually or quit the app. + +You should not use the SDL renderer API while the app going in background: +- SDL_EVENT_WILL_ENTER_BACKGROUND: + after you read this message, GL context gets backed-up and you should not + use the SDL renderer API. + + When this event is received, you have to set the render target to NULL, if you're using it. + (eg call SDL_SetRenderTarget(renderer, NULL)) + +- SDL_EVENT_DID_ENTER_FOREGROUND: + GL context is restored, and the SDL renderer API is available (unless you + receive SDL_EVENT_RENDER_DEVICE_RESET). + +Activity lifecycle +================================================================================ + +You can control activity re-creation (eg. onCreate()) behaviour. This allows to keep +or re-initialize java and native static datas, see SDL_hints.h: +- SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY + +Mouse / Touch events +================================================================================ + +In some case, SDL generates synthetic mouse (resp. touch) events for touch +(resp. mouse) devices. +To enable/disable this behavior, see SDL_hints.h: +- SDL_HINT_TOUCH_MOUSE_EVENTS +- SDL_HINT_MOUSE_TOUCH_EVENTS + +Misc +================================================================================ + +For some device, it appears to works better setting explicitly GL attributes +before creating a window: + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + +Threads and the Java VM +================================================================================ + +For a quick tour on how Linux native threads interoperate with the Java VM, take +a look here: https://developer.android.com/guide/practices/jni.html + +If you want to use threads in your SDL app, it's strongly recommended that you +do so by creating them using SDL functions. This way, the required attach/detach +handling is managed by SDL automagically. If you have threads created by other +means and they make calls to SDL functions, make sure that you call +Android_JNI_SetupThread() before doing anything else otherwise SDL will attach +your thread automatically anyway (when you make an SDL call), but it'll never +detach it. + + +If you ever want to use JNI in a native thread (created by "SDL_CreateThread()"), +it won't be able to find your java class and method because of the java class loader +which is different for native threads, than for java threads (eg your "main()"). + +the work-around is to find class/method, in you "main()" thread, and to use them +in your native thread. + +see: +https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class + +Using STL +================================================================================ + +You can use STL in your project by creating an Application.mk file in the jni +folder and adding the following line: + + APP_STL := c++_shared + +For more information go here: + https://developer.android.com/ndk/guides/cpp-support + + +Using the emulator +================================================================================ + +There are some good tips and tricks for getting the most out of the +emulator here: https://developer.android.com/tools/devices/emulator.html + +Especially useful is the info on setting up OpenGL ES 2.0 emulation. + +Notice that this software emulator is incredibly slow and needs a lot of disk space. +Using a real device works better. + + +Troubleshooting +================================================================================ + +You can see if adb can see any devices with the following command: + + adb devices + +You can see the output of log messages on the default device with: + + adb logcat + +You can push files to the device with: + + adb push local_file remote_path_and_file + +You can push files to the SD Card at /sdcard, for example: + + adb push moose.dat /sdcard/moose.dat + +You can see the files on the SD card with a shell command: + + adb shell ls /sdcard/ + +You can start a command shell on the default device with: + + adb shell + +You can remove the library files of your project (and not the SDL lib files) with: + + ndk-build clean + +You can do a build with the following command: + + ndk-build + +You can see the complete command line that ndk-build is using by passing V=1 on the command line: + + ndk-build V=1 + +If your application crashes in native code, you can use ndk-stack to get a symbolic stack trace: + https://developer.android.com/ndk/guides/ndk-stack + +If you want to go through the process manually, you can use addr2line to convert the +addresses in the stack trace to lines in your code. + +For example, if your crash looks like this: + + I/DEBUG ( 31): signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 400085d0 + I/DEBUG ( 31): r0 00000000 r1 00001000 r2 00000003 r3 400085d4 + I/DEBUG ( 31): r4 400085d0 r5 40008000 r6 afd41504 r7 436c6a7c + I/DEBUG ( 31): r8 436c6b30 r9 435c6fb0 10 435c6f9c fp 4168d82c + I/DEBUG ( 31): ip 8346aff0 sp 436c6a60 lr afd1c8ff pc afd1c902 cpsr 60000030 + I/DEBUG ( 31): #00 pc 0001c902 /system/lib/libc.so + I/DEBUG ( 31): #01 pc 0001ccf6 /system/lib/libc.so + I/DEBUG ( 31): #02 pc 000014bc /data/data/org.libsdl.app/lib/libmain.so + I/DEBUG ( 31): #03 pc 00001506 /data/data/org.libsdl.app/lib/libmain.so + +You can see that there's a crash in the C library being called from the main code. +I run addr2line with the debug version of my code: + + arm-eabi-addr2line -C -f -e obj/local/armeabi/libmain.so + +and then paste in the number after "pc" in the call stack, from the line that I care about: +000014bc + +I get output from addr2line showing that it's in the quit function, in testspriteminimal.c, on line 23. + +You can add logging to your code to help show what's happening: + + #include + + __android_log_print(ANDROID_LOG_INFO, "foo", "Something happened! x = %d", x); + +If you need to build without optimization turned on, you can create a file called +"Application.mk" in the jni directory, with the following line in it: + + APP_OPTIM := debug + + +Memory debugging +================================================================================ + +The best (and slowest) way to debug memory issues on Android is valgrind. +Valgrind has support for Android out of the box, just grab code using: + + svn co svn://svn.valgrind.org/valgrind/trunk valgrind + +... and follow the instructions in the file README.android to build it. + +One thing I needed to do on macOS was change the path to the toolchain, +and add ranlib to the environment variables: +export RANLIB=$NDKROOT/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-ranlib + +Once valgrind is built, you can create a wrapper script to launch your +application with it, changing org.libsdl.app to your package identifier: + + --- start_valgrind_app ------------------- + #!/system/bin/sh + export TMPDIR=/data/data/org.libsdl.app + exec /data/local/Inst/bin/valgrind --log-file=/sdcard/valgrind.log --error-limit=no $* + ------------------------------------------ + +Then push it to the device: + + adb push start_valgrind_app /data/local + +and make it executable: + + adb shell chmod 755 /data/local/start_valgrind_app + +and tell Android to use the script to launch your application: + + adb shell setprop wrap.org.libsdl.app "logwrapper /data/local/start_valgrind_app" + +If the setprop command says "could not set property", it's likely that +your package name is too long and you should make it shorter by changing +AndroidManifest.xml and the path to your class file in android-project/src + +You can then launch your application normally and waaaaaaaiiittt for it. +You can monitor the startup process with the logcat command above, and +when it's done (or even while it's running) you can grab the valgrind +output file: + + adb pull /sdcard/valgrind.log + +When you're done instrumenting with valgrind, you can disable the wrapper: + + adb shell setprop wrap.org.libsdl.app "" + + +Graphics debugging +================================================================================ + +If you are developing on a compatible Tegra-based tablet, NVidia provides +Tegra Graphics Debugger at their website. Because SDL3 dynamically loads EGL +and GLES libraries, you must follow their instructions for installing the +interposer library on a rooted device. The non-rooted instructions are not +compatible with applications that use SDL3 for video. + +The Tegra Graphics Debugger is available from NVidia here: +https://developer.nvidia.com/tegra-graphics-debugger + + +Why is API level 19 the minimum required? +================================================================================ + +The latest NDK toolchain doesn't support targeting earlier than API level 19. +As of this writing, according to https://www.composables.com/tools/distribution-chart +about 99.7% of the Android devices accessing Google Play support API level 19 or +higher (August 2023). + + +A note regarding the use of the "dirty rectangles" rendering technique +================================================================================ + +If your app uses a variation of the "dirty rectangles" rendering technique, +where you only update a portion of the screen on each frame, you may notice a +variety of visual glitches on Android, that are not present on other platforms. +This is caused by SDL's use of EGL as the support system to handle OpenGL ES/ES2 +contexts, in particular the use of the eglSwapBuffers function. As stated in the +documentation for the function "The contents of ancillary buffers are always +undefined after calling eglSwapBuffers". +Setting the EGL_SWAP_BEHAVIOR attribute of the surface to EGL_BUFFER_PRESERVED +is not possible for SDL as it requires EGL 1.4, available only on the API level +17+, so the only workaround available on this platform is to redraw the entire +screen each frame. + +Reference: http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html + + +Ending your application +================================================================================ + +Two legitimate ways: + +- return from your main() function. Java side will automatically terminate the +Activity by calling Activity.finish(). + +- Android OS can decide to terminate your application by calling onDestroy() +(see Activity life cycle). Your application will receive an SDL_EVENT_QUIT you +can handle to save things and quit. + +Don't call exit() as it stops the activity badly. + +NB: "Back button" can be handled as a SDL_EVENT_KEY_DOWN/UP events, with Keycode +SDLK_AC_BACK, for any purpose. + +Known issues +================================================================================ + +- The number of buttons reported for each joystick is hardcoded to be 36, which +is the current maximum number of buttons Android can report. + +Building the SDL tests +================================================================================ + +SDL's CMake build system can create APK's for the tests. +It can build all tests with a single command without a dependency on gradle or Android Studio. +The APK's are signed with a debug certificate. +The only caveat is that the APK's support a single architecture. + +### Requirements +- SDL source tree +- CMake +- ninja or make +- Android Platform SDK +- Android NDK +- Android Build tools +- Java JDK (version should be compatible with Android) +- keytool (usually provided with the Java JDK), used for generating a debug certificate +- zip + +### CMake configuration + +When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through `SDL_ANDROID_HOME`. +``` +cmake .. -DCMAKE_TOOLCHAIN_FILE= -DANDROID_ABI= -DSDL_ANDROID_HOME= -DANDROID_PLATFORM=23 -DSDL_TESTS=ON +``` + +Remarks: +- `android.toolchain.cmake` can usually be found at `$ANDROID_HOME/ndk/x.y.z/build/cmake/android.toolchain.cmake` +- `ANDROID_ABI` should be one of `arm64-v8a`, `armeabi-v7a`, `x86` or `x86_64`. +- When CMake is unable to find required paths, use `cmake-gui` to override required `SDL_ANDROID_` CMake cache variables. + +### Building the APK's + +For the `testsprite` executable, the `testsprite-apk` target will build the associated APK: +``` +cmake --build . --target testsprite-apk +``` + +APK's of all tests can be built with the `sdl-test-apks` target: +``` +cmake --build . --target sdl-test-apks +``` + +### Installation/removal of the tests + +`testsprite.apk` APK can be installed on your Android machine using the `install-testsprite` target: +``` +cmake --build . --target install-testsprite +``` + +APK's of all tests can be installed with the `install-sdl-test-apks` target: +``` +cmake --build . --target install-sdl-test-apks +``` + +All SDL tests can be uninstalled with the `uninstall-sdl-test-apks` target: +``` +cmake --build . --target uninstall-sdl-test-apks +``` + +### Starting the tests + +After installation, the tests can be started using the Android Launcher GUI. +Alternatively, they can also be started using CMake targets. + +This command will start the testsprite executable: +``` +cmake --build . --target start-testsprite +``` + +There is also a convenience target which will build, install and start a test: +``` +cmake --build . --target build-install-start-testsprite +``` + +Not all tests provide a GUI. For those, you can use `adb logcat` to read the output of stdout. diff --git a/docs/README-cmake.md b/docs/README-cmake.md index 4accbcf5..337a41ab 100644 --- a/docs/README-cmake.md +++ b/docs/README-cmake.md @@ -1,328 +1,328 @@ -# CMake - -[www.cmake.org](https://www.cmake.org/) - -The CMake build system is supported on the following platforms: - -* FreeBSD -* Linux -* Microsoft Visual C -* MinGW and Msys -* macOS, iOS, and tvOS, with support for XCode -* Android -* Emscripten -* FreeBSD -* Haiku -* Nintendo 3DS -* Playstation 2 -* Playstation Vita -* QNX 7.x/8.x -* RiscOS - -## Building SDL - -Assuming the source tree of SDL is located at `~/sdl`, -this will configure and build SDL in the `~/build` directory: -```sh -cmake -S ~/sdl -B ~/build -cmake --build ~/build -``` - -Installation can be done using: -```sh -cmake --install ~/build --prefix /usr/local # '--install' requires CMake 3.15, or newer -``` - -This will install SDL to /usr/local. - -### Building SDL tests - -You can build the SDL test programs by adding `-DSDL_TESTS=ON` to the first cmake command above: -```sh -cmake -S ~/sdl -B ~/build -DSDL_TEST_LIBRARY=ON -DSDL_TESTS=ON -``` -and then building normally. In this example, the test programs will be built and can be run from `~/build/tests/`. - -## Including SDL in your project - -SDL can be included in your project in 2 major ways: -- using a system SDL library, provided by your (*nix) distribution or a package manager -- using a vendored SDL library: this is SDL copied or symlinked in a subfolder. - -The following CMake script supports both, depending on the value of `MYGAME_VENDORED`. - -```cmake -cmake_minimum_required(VERSION 3.5) -project(mygame) - -# Create an option to switch between a system sdl library and a vendored SDL library -option(MYGAME_VENDORED "Use vendored libraries" OFF) - -if(MYGAME_VENDORED) - # This assumes you have added SDL as a submodule in vendored/SDL - add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL) -else() - # 1. Look for a SDL3 package, - # 2. look for the SDL3-shared component, and - # 3. fail if the shared component cannot be found. - find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) -endif() - -# Create your game executable target as usual -add_executable(mygame WIN32 mygame.c) - -# Link to the actual SDL3 library. -target_link_libraries(mygame PRIVATE SDL3::SDL3) -``` - -### A system SDL library - -For CMake to find SDL, it must be installed in [a default location CMake is looking for](https://cmake.org/cmake/help/latest/command/find_package.html#config-mode-search-procedure). - -The following components are available, to be used as an argument of `find_package`. - -| Component name | Description | -|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| SDL3-shared | The SDL3 shared library, available through the `SDL3::SDL3-shared` target | -| SDL3-static | The SDL3 static library, available through the `SDL3::SDL3-static` target | -| SDL3_test | The SDL3_test static library, available through the `SDL3::SDL3_test` target | -| SDL3 | The SDL3 library, available through the `SDL3::SDL3` target. This is an alias of `SDL3::SDL3-shared` or `SDL3::SDL3-static`. This component is always available. | -| Headers | The SDL3 headers, available through the `SDL3::Headers` target. This component is always available. | - - -### Using a vendored SDL - -This only requires a copy of SDL in a subdirectory + `add_subdirectory`. -Alternatively, use [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). -Depending on the configuration, the same targets as a system SDL package are available. - -## CMake configuration options - -### Build optimized library - -By default, CMake provides 4 build types: `Debug`, `Release`, `RelWithDebInfo` and `MinSizeRel`. -The main difference(s) between these are the optimization options and the generation of debug info. -To configure SDL as an optimized `Release` library, configure SDL with: -```sh -cmake ~/SDL -DCMAKE_BUILD_TYPE=Release -``` -To build it, run: -```sh -cmake --build . --config Release -``` - -### Shared or static - -By default, only a shared SDL library is built and installed. -The options `-DSDL_SHARED=` and `-DSDL_STATIC=` accept boolean values to change this. - -### Pass custom compile options to the compiler - -- Use [`CMAKE__FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS.html) to pass extra -flags to the compiler. -- Use [`CMAKE_EXE_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXE_LINKER_FLAGS.html) to pass extra option to the linker for executables. -- Use [`CMAKE_SHARED_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LINKER_FLAGS.html) to pass extra options to the linker for shared libraries. - -#### Examples - -- build a SDL library optimized for (more) modern x64 microprocessor architectures. - - With gcc or clang: - ```sh - cmake ~/sdl -DCMAKE_C_FLAGS="-march=x86-64-v3" -DCMAKE_CXX_FLAGS="-march=x86-64-v3" - ``` - With Visual C: - ```sh - cmake .. -DCMAKE_C_FLAGS="/ARCH:AVX2" -DCMAKE_CXX_FLAGS="/ARCH:AVX2" - ``` - -### iOS/tvOS - -CMake 3.14+ natively includes support for iOS and tvOS. SDL binaries may be built -using Xcode or Make, possibly among other build-systems. - -When using a recent version of CMake (3.14+), it should be possible to: - -- build SDL for iOS, both static and dynamic -- build SDL test apps (as iOS/tvOS .app bundles) -- generate a working SDL_build_config.h for iOS (using SDL_build_config.h.cmake as a basis) - -To use, set the following CMake variables when running CMake's configuration stage: - -- `CMAKE_SYSTEM_NAME=` (either `iOS` or `tvOS`) -- `CMAKE_OSX_SYSROOT=` (examples: `iphoneos`, `iphonesimulator`, `iphoneos12.4`, `/full/path/to/iPhoneOS.sdk`, - `appletvos`, `appletvsimulator`, `appletvos12.4`, `/full/path/to/AppleTVOS.sdk`, etc.) -- `CMAKE_OSX_ARCHITECTURES=` (example: "arm64;armv7s;x86_64") - - -#### Examples - -- for iOS-Simulator, using the latest, installed SDK: - - ```bash - cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 - ``` - -- for iOS-Device, using the latest, installed SDK, 64-bit only - - ```bash - cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES=arm64 - ``` - -- for iOS-Device, using the latest, installed SDK, mixed 32/64 bit - - ```cmake - cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES="arm64;armv7s" - ``` - -- for iOS-Device, using a specific SDK revision (iOS 12.4, in this example): - - ```cmake - cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos12.4 -DCMAKE_OSX_ARCHITECTURES=arm64 - ``` - -- for iOS-Simulator, using the latest, installed SDK, and building SDL test apps (as .app bundles): - - ```cmake - cmake ~/sdl -DSDL_TESTS=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 - ``` - -- for tvOS-Simulator, using the latest, installed SDK: - - ```cmake - cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvsimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 - ``` - -- for tvOS-Device, using the latest, installed SDK: - - ```cmake - cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64` - ``` - -- for QNX/aarch64, using the latest, installed SDK: - - ```cmake - cmake ~/sdl -DCMAKE_TOOLCHAIN_FILE=~/sdl/build-scripts/cmake-toolchain-qnx-aarch64le.cmake -DSDL_X11=0 - ``` - -## SDL-specific CMake options - -SDL can be customized through (platform-specific) CMake options. -The following table shows generic options that are available for most platforms. -At the end of SDL CMake configuration, a table shows all CMake options along with its detected value. - -| CMake option | Valid values | Description | -|-------------------------------|--------------|-----------------------------------------------------------------------------------------------------| -| `-DSDL_SHARED=` | `ON`/`OFF` | Build SDL shared library (not all platforms support this) (`libSDL3.so`/`libSDL3.dylib`/`SDL3.dll`) | -| `-DSDL_STATIC=` | `ON`/`OFF` | Build SDL static library (`libSDL3.a`/`SDL3-static.lib`) | -| `-DSDL_TEST_LIBRARY=` | `ON`/`OFF` | Build SDL test library (`libSDL3_test.a`/`SDL3_test.lib`) | -| `-DSDL_TESTS=` | `ON`/`OFF` | Build SDL test programs (**requires `-DSDL_TEST_LIBRARY=ON`**) | -| `-DSDL_DISABLE_INSTALL=` | `ON`/`OFF` | Don't create a SDL install target | -| `-DSDL_DISABLE_INSTALL_DOCS=` | `ON`/`OFF` | Don't install the SDL documentation | -| `-DSDL_INSTALL_TESTS=` | `ON`/`OFF` | Install the SDL test programs | - -## Help, it doesn't work! - -Below, a SDL3 CMake project can be found that builds 99.9% of time (assuming you have internet connectivity). -When you have a problem with building or using SDL, please modify it until it reproduces your issue. - -```cmake -cmake_minimum_required(VERSION 3.16) -project(sdl_issue) - -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# !!!!!! !!!!!! -# !!!!!! This CMake script is not using "CMake best practices". !!!!!! -# !!!!!! Don't use it in your project. !!!!!! -# !!!!!! !!!!!! -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -# 1. Try system SDL3 package first -find_package(SDL3 QUIET) -if(SDL3_FOUND) - message(STATUS "Using SDL3 via find_package") -endif() - -# 2. Try using a vendored SDL library -if(NOT SDL3_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL/CMakeLists.txt") - add_subdirectory(SDL) - message(STATUS "Using SDL3 via add_subdirectory") - set(SDL3_FOUND TRUE) -endif() - -# 3. Download SDL, and use that. -if(NOT SDL3_FOUND) - include(FetchContent) - set(SDL_SHARED TRUE CACHE BOOL "Build a SDL shared library (if available)") - set(SDL_STATIC TRUE CACHE BOOL "Build a SDL static library (if available)") - FetchContent_Declare( - SDL - GIT_REPOSITORY https://github.com/libsdl-org/SDL.git - GIT_TAG main # Replace this with a particular git tag or git hash - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE - ) - message(STATUS "Using SDL3 via FetchContent") - FetchContent_MakeAvailable(SDL) - set_property(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_deps/sdl-src" PROPERTY EXCLUDE_FROM_ALL TRUE) -endif() - -file(WRITE main.c [===========================================[ -/** - * Modify this source such that it reproduces your problem. - */ - -/* START of source modifications */ - -#include - -int main(int argc, char *argv[]) { - if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { - SDL_Log("SDL_Init failed (%s)", SDL_GetError()); - return 1; - } - - SDL_Window *window = NULL; - SDL_Renderer *renderer = NULL; - - if (SDL_CreateWindowAndRenderer(640, 480, 0, &window, &renderer) < 0) { - SDL_Log("SDL_CreateWindowAndRenderer failed (%s)", SDL_GetError()); - SDL_Quit(); - return 1; - } - SDL_SetWindowTitle(window, "SDL issue"); - - while (1) { - int finished = 0; - SDL_Event event; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_QUIT) { - finished = 1; - break; - } - } - if (finished) { - break; - } - - SDL_SetRenderDrawColor(renderer, 80, 80, 80, SDL_ALPHA_OPAQUE); - SDL_RenderClear(renderer); - SDL_RenderPresent(renderer); - } - - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - - SDL_Quit(); -} - -/* END of source modifications */ - -]===========================================]) - -add_executable(sdl_issue main.c) - -target_link_libraries(sdl_issue PRIVATE SDL3::SDL3) -# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-shared) -# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-static) -``` +# CMake + +[www.cmake.org](https://www.cmake.org/) + +The CMake build system is supported on the following platforms: + +* FreeBSD +* Linux +* Microsoft Visual C +* MinGW and Msys +* macOS, iOS, and tvOS, with support for XCode +* Android +* Emscripten +* FreeBSD +* Haiku +* Nintendo 3DS +* Playstation 2 +* Playstation Vita +* QNX 7.x/8.x +* RiscOS + +## Building SDL + +Assuming the source tree of SDL is located at `~/sdl`, +this will configure and build SDL in the `~/build` directory: +```sh +cmake -S ~/sdl -B ~/build +cmake --build ~/build +``` + +Installation can be done using: +```sh +cmake --install ~/build --prefix /usr/local # '--install' requires CMake 3.15, or newer +``` + +This will install SDL to /usr/local. + +### Building SDL tests + +You can build the SDL test programs by adding `-DSDL_TESTS=ON` to the first cmake command above: +```sh +cmake -S ~/sdl -B ~/build -DSDL_TEST_LIBRARY=ON -DSDL_TESTS=ON +``` +and then building normally. In this example, the test programs will be built and can be run from `~/build/tests/`. + +## Including SDL in your project + +SDL can be included in your project in 2 major ways: +- using a system SDL library, provided by your (*nix) distribution or a package manager +- using a vendored SDL library: this is SDL copied or symlinked in a subfolder. + +The following CMake script supports both, depending on the value of `MYGAME_VENDORED`. + +```cmake +cmake_minimum_required(VERSION 3.5) +project(mygame) + +# Create an option to switch between a system sdl library and a vendored SDL library +option(MYGAME_VENDORED "Use vendored libraries" OFF) + +if(MYGAME_VENDORED) + # This assumes you have added SDL as a submodule in vendored/SDL + add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL) +else() + # 1. Look for a SDL3 package, + # 2. look for the SDL3-shared component, and + # 3. fail if the shared component cannot be found. + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) +endif() + +# Create your game executable target as usual +add_executable(mygame WIN32 mygame.c) + +# Link to the actual SDL3 library. +target_link_libraries(mygame PRIVATE SDL3::SDL3) +``` + +### A system SDL library + +For CMake to find SDL, it must be installed in [a default location CMake is looking for](https://cmake.org/cmake/help/latest/command/find_package.html#config-mode-search-procedure). + +The following components are available, to be used as an argument of `find_package`. + +| Component name | Description | +|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SDL3-shared | The SDL3 shared library, available through the `SDL3::SDL3-shared` target | +| SDL3-static | The SDL3 static library, available through the `SDL3::SDL3-static` target | +| SDL3_test | The SDL3_test static library, available through the `SDL3::SDL3_test` target | +| SDL3 | The SDL3 library, available through the `SDL3::SDL3` target. This is an alias of `SDL3::SDL3-shared` or `SDL3::SDL3-static`. This component is always available. | +| Headers | The SDL3 headers, available through the `SDL3::Headers` target. This component is always available. | + + +### Using a vendored SDL + +This only requires a copy of SDL in a subdirectory + `add_subdirectory`. +Alternatively, use [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). +Depending on the configuration, the same targets as a system SDL package are available. + +## CMake configuration options + +### Build optimized library + +By default, CMake provides 4 build types: `Debug`, `Release`, `RelWithDebInfo` and `MinSizeRel`. +The main difference(s) between these are the optimization options and the generation of debug info. +To configure SDL as an optimized `Release` library, configure SDL with: +```sh +cmake ~/SDL -DCMAKE_BUILD_TYPE=Release +``` +To build it, run: +```sh +cmake --build . --config Release +``` + +### Shared or static + +By default, only a shared SDL library is built and installed. +The options `-DSDL_SHARED=` and `-DSDL_STATIC=` accept boolean values to change this. + +### Pass custom compile options to the compiler + +- Use [`CMAKE__FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS.html) to pass extra +flags to the compiler. +- Use [`CMAKE_EXE_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXE_LINKER_FLAGS.html) to pass extra option to the linker for executables. +- Use [`CMAKE_SHARED_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LINKER_FLAGS.html) to pass extra options to the linker for shared libraries. + +#### Examples + +- build a SDL library optimized for (more) modern x64 microprocessor architectures. + + With gcc or clang: + ```sh + cmake ~/sdl -DCMAKE_C_FLAGS="-march=x86-64-v3" -DCMAKE_CXX_FLAGS="-march=x86-64-v3" + ``` + With Visual C: + ```sh + cmake .. -DCMAKE_C_FLAGS="/ARCH:AVX2" -DCMAKE_CXX_FLAGS="/ARCH:AVX2" + ``` + +### iOS/tvOS + +CMake 3.14+ natively includes support for iOS and tvOS. SDL binaries may be built +using Xcode or Make, possibly among other build-systems. + +When using a recent version of CMake (3.14+), it should be possible to: + +- build SDL for iOS, both static and dynamic +- build SDL test apps (as iOS/tvOS .app bundles) +- generate a working SDL_build_config.h for iOS (using SDL_build_config.h.cmake as a basis) + +To use, set the following CMake variables when running CMake's configuration stage: + +- `CMAKE_SYSTEM_NAME=` (either `iOS` or `tvOS`) +- `CMAKE_OSX_SYSROOT=` (examples: `iphoneos`, `iphonesimulator`, `iphoneos12.4`, `/full/path/to/iPhoneOS.sdk`, + `appletvos`, `appletvsimulator`, `appletvos12.4`, `/full/path/to/AppleTVOS.sdk`, etc.) +- `CMAKE_OSX_ARCHITECTURES=` (example: "arm64;armv7s;x86_64") + + +#### Examples + +- for iOS-Simulator, using the latest, installed SDK: + + ```bash + cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 + ``` + +- for iOS-Device, using the latest, installed SDK, 64-bit only + + ```bash + cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES=arm64 + ``` + +- for iOS-Device, using the latest, installed SDK, mixed 32/64 bit + + ```cmake + cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES="arm64;armv7s" + ``` + +- for iOS-Device, using a specific SDK revision (iOS 12.4, in this example): + + ```cmake + cmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos12.4 -DCMAKE_OSX_ARCHITECTURES=arm64 + ``` + +- for iOS-Simulator, using the latest, installed SDK, and building SDL test apps (as .app bundles): + + ```cmake + cmake ~/sdl -DSDL_TESTS=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 + ``` + +- for tvOS-Simulator, using the latest, installed SDK: + + ```cmake + cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvsimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 + ``` + +- for tvOS-Device, using the latest, installed SDK: + + ```cmake + cmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64` + ``` + +- for QNX/aarch64, using the latest, installed SDK: + + ```cmake + cmake ~/sdl -DCMAKE_TOOLCHAIN_FILE=~/sdl/build-scripts/cmake-toolchain-qnx-aarch64le.cmake -DSDL_X11=0 + ``` + +## SDL-specific CMake options + +SDL can be customized through (platform-specific) CMake options. +The following table shows generic options that are available for most platforms. +At the end of SDL CMake configuration, a table shows all CMake options along with its detected value. + +| CMake option | Valid values | Description | +|-------------------------------|--------------|-----------------------------------------------------------------------------------------------------| +| `-DSDL_SHARED=` | `ON`/`OFF` | Build SDL shared library (not all platforms support this) (`libSDL3.so`/`libSDL3.dylib`/`SDL3.dll`) | +| `-DSDL_STATIC=` | `ON`/`OFF` | Build SDL static library (`libSDL3.a`/`SDL3-static.lib`) | +| `-DSDL_TEST_LIBRARY=` | `ON`/`OFF` | Build SDL test library (`libSDL3_test.a`/`SDL3_test.lib`) | +| `-DSDL_TESTS=` | `ON`/`OFF` | Build SDL test programs (**requires `-DSDL_TEST_LIBRARY=ON`**) | +| `-DSDL_DISABLE_INSTALL=` | `ON`/`OFF` | Don't create a SDL install target | +| `-DSDL_DISABLE_INSTALL_DOCS=` | `ON`/`OFF` | Don't install the SDL documentation | +| `-DSDL_INSTALL_TESTS=` | `ON`/`OFF` | Install the SDL test programs | + +## Help, it doesn't work! + +Below, a SDL3 CMake project can be found that builds 99.9% of time (assuming you have internet connectivity). +When you have a problem with building or using SDL, please modify it until it reproduces your issue. + +```cmake +cmake_minimum_required(VERSION 3.16) +project(sdl_issue) + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!!!!! !!!!!! +# !!!!!! This CMake script is not using "CMake best practices". !!!!!! +# !!!!!! Don't use it in your project. !!!!!! +# !!!!!! !!!!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +# 1. Try system SDL3 package first +find_package(SDL3 QUIET) +if(SDL3_FOUND) + message(STATUS "Using SDL3 via find_package") +endif() + +# 2. Try using a vendored SDL library +if(NOT SDL3_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL/CMakeLists.txt") + add_subdirectory(SDL) + message(STATUS "Using SDL3 via add_subdirectory") + set(SDL3_FOUND TRUE) +endif() + +# 3. Download SDL, and use that. +if(NOT SDL3_FOUND) + include(FetchContent) + set(SDL_SHARED TRUE CACHE BOOL "Build a SDL shared library (if available)") + set(SDL_STATIC TRUE CACHE BOOL "Build a SDL static library (if available)") + FetchContent_Declare( + SDL + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG main # Replace this with a particular git tag or git hash + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + message(STATUS "Using SDL3 via FetchContent") + FetchContent_MakeAvailable(SDL) + set_property(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_deps/sdl-src" PROPERTY EXCLUDE_FROM_ALL TRUE) +endif() + +file(WRITE main.c [===========================================[ +/** + * Modify this source such that it reproduces your problem. + */ + +/* START of source modifications */ + +#include + +int main(int argc, char *argv[]) { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + SDL_Log("SDL_Init failed (%s)", SDL_GetError()); + return 1; + } + + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + + if (SDL_CreateWindowAndRenderer(640, 480, 0, &window, &renderer) < 0) { + SDL_Log("SDL_CreateWindowAndRenderer failed (%s)", SDL_GetError()); + SDL_Quit(); + return 1; + } + SDL_SetWindowTitle(window, "SDL issue"); + + while (1) { + int finished = 0; + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) { + finished = 1; + break; + } + } + if (finished) { + break; + } + + SDL_SetRenderDrawColor(renderer, 80, 80, 80, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + + SDL_Quit(); +} + +/* END of source modifications */ + +]===========================================]) + +add_executable(sdl_issue main.c) + +target_link_libraries(sdl_issue PRIVATE SDL3::SDL3) +# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-shared) +# target_link_libraries(sdl_issue PRIVATE SDL3::SDL3-static) +``` diff --git a/docs/README-contributing.md b/docs/README-contributing.md index 63b60cdd..347f0f82 100644 --- a/docs/README-contributing.md +++ b/docs/README-contributing.md @@ -1,97 +1,97 @@ -# Contributing to SDL - -We appreciate your interest in contributing to SDL, this document will describe how to report bugs, contribute code or ideas or edit documentation. - -**Table Of Contents** - -- [Filing a GitHub issue](#filing-a-github-issue) - - [Reporting a bug](#reporting-a-bug) - - [Suggesting enhancements](#suggesting-enhancements) -- [Contributing code](#contributing-code) - - [Forking the project](#forking-the-project) - - [Following the style guide](#following-the-style-guide) - - [Running the tests](#running-the-tests) - - [Opening a pull request](#opening-a-pull-request) -- [Contributing to the documentation](#contributing-to-the-documentation) - - [Editing a function documentation](#editing-a-function-documentation) - - [Editing the wiki](#editing-the-wiki) - -## Filing a GitHub issue - -### Reporting a bug - -If you think you have found a bug and would like to report it, here are the steps you should take: - -- Before opening a new issue, ensure your bug has not already been reported on the [GitHub Issues page](https://github.com/libsdl-org/SDL/issues). -- On the issue tracker, click on [New Issue](https://github.com/libsdl-org/SDL/issues/new). -- Include details about your environment, such as your Operating System and SDL version. -- If possible, provide a small example that reproduces your bug. - -### Suggesting enhancements - -If you want to suggest changes for the project, here are the steps you should take: - -- Check if the suggestion has already been made on: - - the [issue tracker](https://github.com/libsdl-org/SDL/issues); - - the [discourse forum](https://discourse.libsdl.org/); - - or if a [pull request](https://github.com/libsdl-org/SDL/pulls) already exists. -- On the issue tracker, click on [New Issue](https://github.com/libsdl-org/SDL/issues/new). -- Describe what change you would like to happen. - -## Contributing code - -This section will cover how the process of forking the project, making a change and opening a pull request. - -### Forking the project - -The first step consists in making a fork of the project, this is only necessary for the first contribution. - -Head over to https://github.com/libsdl-org/SDL and click on the `Fork` button in the top right corner of your screen, you may leave the fields unchanged and click `Create Fork`. - -You will be redirected to your fork of the repository, click the green `Code` button and copy the git clone link. - -If you had already forked the repository, you may update it from the web page using the `Fetch upstream` button. - -### Following the style guide - -Code formatting is done using a custom `.clang-format` file, you can learn more about how to run it [here](https://clang.llvm.org/docs/ClangFormat.html). - -Some legacy code may not be formatted, as such avoid formatting the whole file at once and only format around your changes. - -For your commit message to be properly displayed on GitHub, it should contain: - -- A short description of the commit of 50 characters or less on the first line. -- If necessary, add a blank line followed by a long description, each line should be 72 characters or less. - -For example: - -``` -Fix crash in SDL_FooBar. - -This addresses the issue #123456 by making sure Foo was successful -before calling Bar. -``` - -### Running the tests - -Tests allow you to verify if your changes did not break any behaviour, here are the steps to follow: - -- Before pushing, run the `testautomation` suite on your machine, there should be no more failing tests after your change than before. -- After pushing to your fork, Continuous Integration (GitHub Actions) will ensure compilation and tests still pass on other systems. - -### Opening a pull request - -- Head over to your fork's GitHub page. -- Click on the `Contribute` button and `Open Pull Request`. -- Fill out the pull request template. -- If any changes are requested, you can add new commits to your fork and they will be automatically added to the pull request. - -## Contributing to the documentation - -### Editing a function documentation - -The wiki documentation for API functions is synchronised from the headers' doxygen comments. As such, all modifications to syntax; function parameters; return value; version; related functions should be done in the header directly. - -### Editing the wiki - -Other changes to the wiki should done directly from https://wiki.libsdl.org/ ... Just click the "edit" link at the bottom of any page! +# Contributing to SDL + +We appreciate your interest in contributing to SDL, this document will describe how to report bugs, contribute code or ideas or edit documentation. + +**Table Of Contents** + +- [Filing a GitHub issue](#filing-a-github-issue) + - [Reporting a bug](#reporting-a-bug) + - [Suggesting enhancements](#suggesting-enhancements) +- [Contributing code](#contributing-code) + - [Forking the project](#forking-the-project) + - [Following the style guide](#following-the-style-guide) + - [Running the tests](#running-the-tests) + - [Opening a pull request](#opening-a-pull-request) +- [Contributing to the documentation](#contributing-to-the-documentation) + - [Editing a function documentation](#editing-a-function-documentation) + - [Editing the wiki](#editing-the-wiki) + +## Filing a GitHub issue + +### Reporting a bug + +If you think you have found a bug and would like to report it, here are the steps you should take: + +- Before opening a new issue, ensure your bug has not already been reported on the [GitHub Issues page](https://github.com/libsdl-org/SDL/issues). +- On the issue tracker, click on [New Issue](https://github.com/libsdl-org/SDL/issues/new). +- Include details about your environment, such as your Operating System and SDL version. +- If possible, provide a small example that reproduces your bug. + +### Suggesting enhancements + +If you want to suggest changes for the project, here are the steps you should take: + +- Check if the suggestion has already been made on: + - the [issue tracker](https://github.com/libsdl-org/SDL/issues); + - the [discourse forum](https://discourse.libsdl.org/); + - or if a [pull request](https://github.com/libsdl-org/SDL/pulls) already exists. +- On the issue tracker, click on [New Issue](https://github.com/libsdl-org/SDL/issues/new). +- Describe what change you would like to happen. + +## Contributing code + +This section will cover how the process of forking the project, making a change and opening a pull request. + +### Forking the project + +The first step consists in making a fork of the project, this is only necessary for the first contribution. + +Head over to https://github.com/libsdl-org/SDL and click on the `Fork` button in the top right corner of your screen, you may leave the fields unchanged and click `Create Fork`. + +You will be redirected to your fork of the repository, click the green `Code` button and copy the git clone link. + +If you had already forked the repository, you may update it from the web page using the `Fetch upstream` button. + +### Following the style guide + +Code formatting is done using a custom `.clang-format` file, you can learn more about how to run it [here](https://clang.llvm.org/docs/ClangFormat.html). + +Some legacy code may not be formatted, as such avoid formatting the whole file at once and only format around your changes. + +For your commit message to be properly displayed on GitHub, it should contain: + +- A short description of the commit of 50 characters or less on the first line. +- If necessary, add a blank line followed by a long description, each line should be 72 characters or less. + +For example: + +``` +Fix crash in SDL_FooBar. + +This addresses the issue #123456 by making sure Foo was successful +before calling Bar. +``` + +### Running the tests + +Tests allow you to verify if your changes did not break any behaviour, here are the steps to follow: + +- Before pushing, run the `testautomation` suite on your machine, there should be no more failing tests after your change than before. +- After pushing to your fork, Continuous Integration (GitHub Actions) will ensure compilation and tests still pass on other systems. + +### Opening a pull request + +- Head over to your fork's GitHub page. +- Click on the `Contribute` button and `Open Pull Request`. +- Fill out the pull request template. +- If any changes are requested, you can add new commits to your fork and they will be automatically added to the pull request. + +## Contributing to the documentation + +### Editing a function documentation + +The wiki documentation for API functions is synchronised from the headers' doxygen comments. As such, all modifications to syntax; function parameters; return value; version; related functions should be done in the header directly. + +### Editing the wiki + +Other changes to the wiki should done directly from https://wiki.libsdl.org/ ... Just click the "edit" link at the bottom of any page! diff --git a/docs/README-dynapi.md b/docs/README-dynapi.md index a0f34124..665b7c65 100644 --- a/docs/README-dynapi.md +++ b/docs/README-dynapi.md @@ -1,138 +1,138 @@ -# Dynamic API - -Originally posted on Ryan's Google+ account. - -Background: - -- The Steam Runtime has (at least in theory) a really kick-ass build of SDL, - but developers are shipping their own SDL with individual Steam games. - These games might stop getting updates, but a newer SDL might be needed later. - Certainly we'll always be fixing bugs in SDL, even if a new video target isn't - ever needed, and these fixes won't make it to a game shipping its own SDL. -- Even if we replace the SDL in those games with a compatible one, that is to - say, edit a developer's Steam depot (yuck!), there are developers that are - statically linking SDL that we can't do this for. We can't even force the - dynamic loader to ignore their SDL in this case, of course. -- If you don't ship an SDL with the game in some form, people that disabled the - Steam Runtime, or just tried to run the game from the command line instead of - Steam might find themselves unable to run the game, due to a missing dependency. -- If you want to ship on non-Steam platforms like GOG or Humble Bundle, or target - generic Linux boxes that may or may not have SDL installed, you have to ship - the library or risk a total failure to launch. So now, you might have to have - a non-Steam build plus a Steam build (that is, one with and one without SDL - included), which is inconvenient if you could have had one universal build - that works everywhere. -- We like the zlib license, but the biggest complaint from the open source - community about the license change is the static linking. The LGPL forced this - as a legal, not technical issue, but zlib doesn't care. Even those that aren't - concerned about the GNU freedoms found themselves solving the same problems: - swapping in a newer SDL to an older game often times can save the day. - Static linking stops this dead. - -So here's what we did: - -SDL now has, internally, a table of function pointers. So, this is what SDL_Init -now looks like: - -```c -UInt32 SDL_Init(Uint32 flags) -{ - return jump_table.SDL_Init(flags); -} -``` - -Except that is all done with a bunch of macro magic so we don't have to maintain -every one of these. - -What is jump_table.SDL_init()? Eventually, that's a function pointer of the real -SDL_Init() that you've been calling all this time. But at startup, it looks more -like this: - -```c -Uint32 SDL_Init_DEFAULT(Uint32 flags) -{ - SDL_InitDynamicAPI(); - return jump_table.SDL_Init(flags); -} -``` - -SDL_InitDynamicAPI() fills in jump_table with all the actual SDL function -pointers, which means that this `_DEFAULT` function never gets called again. -First call to any SDL function sets the whole thing up. - -So you might be asking, what was the value in that? Isn't this what the operating -system's dynamic loader was supposed to do for us? Yes, but now we've got this -level of indirection, we can do things like this: - -```bash -export SDL3_DYNAMIC_API=/my/actual/libSDL3.so.0 -./MyGameThatIsStaticallyLinkedToSDL -``` - -And now, this game that is statically linked to SDL, can still be overridden -with a newer, or better, SDL. The statically linked one will only be used as -far as calling into the jump table in this case. But in cases where no override -is desired, the statically linked version will provide its own jump table, -and everyone is happy. - -So now: -- Developers can statically link SDL, and users can still replace it. - (We'd still rather you ship a shared library, though!) -- Developers can ship an SDL with their game, Valve can override it for, say, - new features on SteamOS, or distros can override it for their own needs, - but it'll also just work in the default case. -- Developers can ship the same package to everyone (Humble Bundle, GOG, etc), - and it'll do the right thing. -- End users (and Valve) can update a game's SDL in almost any case, - to keep abandoned games running on newer platforms. -- Everyone develops with SDL exactly as they have been doing all along. - Same headers, same ABI. Just get the latest version to enable this magic. - - -A little more about SDL_InitDynamicAPI(): - -Internally, InitAPI does some locking to make sure everything waits until a -single thread initializes everything (although even SDL_CreateThread() goes -through here before spinning a thread, too), and then decides if it should use -an external SDL library. If not, it sets up the jump table using the current -SDL's function pointers (which might be statically linked into a program, or in -a shared library of its own). If so, it loads that library and looks for and -calls a single function: - -```c -SInt32 SDL_DYNAPI_entry(Uint32 version, void *table, Uint32 tablesize); -``` - -That function takes a version number (more on that in a moment), the address of -the jump table, and the size, in bytes, of the table. -Now, we've got policy here: this table's layout never changes; new stuff gets -added to the end. Therefore SDL_DYNAPI_entry() knows that it can provide all -the needed functions if tablesize <= sizeof its own jump table. If tablesize is -bigger (say, SDL 3.0.4 is trying to load SDL 3.0.3), then we know to abort, but -if it's smaller, we know we can provide the entire API that the caller needs. - -The version variable is a failsafe switch. -Right now it's always 1. This number changes when there are major API changes -(so we know if the tablesize might be smaller, or entries in it have changed). -Right now SDL_DYNAPI_entry gives up if the version doesn't match, but it's not -inconceivable to have a small dispatch library that only supplies this one -function and loads different, otherwise-incompatible SDL libraries and has the -right one initialize the jump table based on the version. For something that -must generically catch lots of different versions of SDL over time, like the -Steam Client, this isn't a bad option. - -Finally, I'm sure some people are reading this and thinking, -"I don't want that overhead in my project!" - -To which I would point out that the extra function call through the jump table -probably wouldn't even show up in a profile, but lucky you: this can all be -disabled. You can build SDL without this if you absolutely must, but we would -encourage you not to do that. However, on heavily locked down platforms like -iOS, or maybe when debugging, it makes sense to disable it. The way this is -designed in SDL, you just have to change one #define, and the entire system -vaporizes out, and SDL functions exactly like it always did. Most of it is -macro magic, so the system is contained to one C file and a few headers. -However, this is on by default and you have to edit a header file to turn it -off. Our hopes is that if we make it easy to disable, but not too easy, -everyone will ultimately be able to get what they want, but we've gently -nudged everyone towards what we think is the best solution. +# Dynamic API + +Originally posted on Ryan's Google+ account. + +Background: + +- The Steam Runtime has (at least in theory) a really kick-ass build of SDL, + but developers are shipping their own SDL with individual Steam games. + These games might stop getting updates, but a newer SDL might be needed later. + Certainly we'll always be fixing bugs in SDL, even if a new video target isn't + ever needed, and these fixes won't make it to a game shipping its own SDL. +- Even if we replace the SDL in those games with a compatible one, that is to + say, edit a developer's Steam depot (yuck!), there are developers that are + statically linking SDL that we can't do this for. We can't even force the + dynamic loader to ignore their SDL in this case, of course. +- If you don't ship an SDL with the game in some form, people that disabled the + Steam Runtime, or just tried to run the game from the command line instead of + Steam might find themselves unable to run the game, due to a missing dependency. +- If you want to ship on non-Steam platforms like GOG or Humble Bundle, or target + generic Linux boxes that may or may not have SDL installed, you have to ship + the library or risk a total failure to launch. So now, you might have to have + a non-Steam build plus a Steam build (that is, one with and one without SDL + included), which is inconvenient if you could have had one universal build + that works everywhere. +- We like the zlib license, but the biggest complaint from the open source + community about the license change is the static linking. The LGPL forced this + as a legal, not technical issue, but zlib doesn't care. Even those that aren't + concerned about the GNU freedoms found themselves solving the same problems: + swapping in a newer SDL to an older game often times can save the day. + Static linking stops this dead. + +So here's what we did: + +SDL now has, internally, a table of function pointers. So, this is what SDL_Init +now looks like: + +```c +UInt32 SDL_Init(Uint32 flags) +{ + return jump_table.SDL_Init(flags); +} +``` + +Except that is all done with a bunch of macro magic so we don't have to maintain +every one of these. + +What is jump_table.SDL_init()? Eventually, that's a function pointer of the real +SDL_Init() that you've been calling all this time. But at startup, it looks more +like this: + +```c +Uint32 SDL_Init_DEFAULT(Uint32 flags) +{ + SDL_InitDynamicAPI(); + return jump_table.SDL_Init(flags); +} +``` + +SDL_InitDynamicAPI() fills in jump_table with all the actual SDL function +pointers, which means that this `_DEFAULT` function never gets called again. +First call to any SDL function sets the whole thing up. + +So you might be asking, what was the value in that? Isn't this what the operating +system's dynamic loader was supposed to do for us? Yes, but now we've got this +level of indirection, we can do things like this: + +```bash +export SDL3_DYNAMIC_API=/my/actual/libSDL3.so.0 +./MyGameThatIsStaticallyLinkedToSDL +``` + +And now, this game that is statically linked to SDL, can still be overridden +with a newer, or better, SDL. The statically linked one will only be used as +far as calling into the jump table in this case. But in cases where no override +is desired, the statically linked version will provide its own jump table, +and everyone is happy. + +So now: +- Developers can statically link SDL, and users can still replace it. + (We'd still rather you ship a shared library, though!) +- Developers can ship an SDL with their game, Valve can override it for, say, + new features on SteamOS, or distros can override it for their own needs, + but it'll also just work in the default case. +- Developers can ship the same package to everyone (Humble Bundle, GOG, etc), + and it'll do the right thing. +- End users (and Valve) can update a game's SDL in almost any case, + to keep abandoned games running on newer platforms. +- Everyone develops with SDL exactly as they have been doing all along. + Same headers, same ABI. Just get the latest version to enable this magic. + + +A little more about SDL_InitDynamicAPI(): + +Internally, InitAPI does some locking to make sure everything waits until a +single thread initializes everything (although even SDL_CreateThread() goes +through here before spinning a thread, too), and then decides if it should use +an external SDL library. If not, it sets up the jump table using the current +SDL's function pointers (which might be statically linked into a program, or in +a shared library of its own). If so, it loads that library and looks for and +calls a single function: + +```c +SInt32 SDL_DYNAPI_entry(Uint32 version, void *table, Uint32 tablesize); +``` + +That function takes a version number (more on that in a moment), the address of +the jump table, and the size, in bytes, of the table. +Now, we've got policy here: this table's layout never changes; new stuff gets +added to the end. Therefore SDL_DYNAPI_entry() knows that it can provide all +the needed functions if tablesize <= sizeof its own jump table. If tablesize is +bigger (say, SDL 3.0.4 is trying to load SDL 3.0.3), then we know to abort, but +if it's smaller, we know we can provide the entire API that the caller needs. + +The version variable is a failsafe switch. +Right now it's always 1. This number changes when there are major API changes +(so we know if the tablesize might be smaller, or entries in it have changed). +Right now SDL_DYNAPI_entry gives up if the version doesn't match, but it's not +inconceivable to have a small dispatch library that only supplies this one +function and loads different, otherwise-incompatible SDL libraries and has the +right one initialize the jump table based on the version. For something that +must generically catch lots of different versions of SDL over time, like the +Steam Client, this isn't a bad option. + +Finally, I'm sure some people are reading this and thinking, +"I don't want that overhead in my project!" + +To which I would point out that the extra function call through the jump table +probably wouldn't even show up in a profile, but lucky you: this can all be +disabled. You can build SDL without this if you absolutely must, but we would +encourage you not to do that. However, on heavily locked down platforms like +iOS, or maybe when debugging, it makes sense to disable it. The way this is +designed in SDL, you just have to change one #define, and the entire system +vaporizes out, and SDL functions exactly like it always did. Most of it is +macro magic, so the system is contained to one C file and a few headers. +However, this is on by default and you have to edit a header file to turn it +off. Our hopes is that if we make it easy to disable, but not too easy, +everyone will ultimately be able to get what they want, but we've gently +nudged everyone towards what we think is the best solution. diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index 5826d847..31553e06 100644 --- a/docs/README-emscripten.md +++ b/docs/README-emscripten.md @@ -1,365 +1,365 @@ -# Emscripten - -## The state of things - -(As of September 2023, but things move quickly and we don't update this -document often.) - -In modern times, all the browsers you probably care about (Chrome, Firefox, -Edge, and Safari, on Windows, macOS, Linux, iOS and Android), support some -reasonable base configurations: - -- WebAssembly (don't bother with asm.js any more) -- WebGL (which will look like OpenGL ES 2 or 3 to your app). -- Threads (see caveats, though!) -- Game controllers -- Autoupdating (so you can assume they have a recent version of the browser) - -All this to say we're at the point where you don't have to make a lot of -concessions to get even a fairly complex SDL-based game up and running. - - -## RTFM - -This document is a quick rundown of some high-level details. The -documentation at [emscripten.org](https://emscripten.org/) is vast -and extremely detailed for a wide variety of topics, and you should at -least skim through it at some point. - - -## Porting your app to Emscripten - -Many many things just need some simple adjustments and they'll compile -like any other C/C++ code, as long as SDL was handling the platform-specific -work for your program. - -First, you probably need this in at least one of your source files: - -```c -#ifdef __EMSCRIPTEN__ -#include -#endif -``` - -Second: assembly language code has to go. Replace it with C. You can even use -[x86 SIMD intrinsic functions in Emscripten](https://emscripten.org/docs/porting/simd.html)! - -Third: Middleware has to go. If you have a third-party library you link -against, you either need an Emscripten port of it, or the source code to it -to compile yourself, or you need to remove it. - -Fourth: You still start in a function called main(), but you need to get out of -it and into a function that gets called repeatedly, and returns quickly, -called a mainloop. - -Somewhere in your program, you probably have something that looks like a more -complicated version of this: - -```c -void main(void) -{ - initialize_the_game(); - while (game_is_still_running) { - check_for_new_input(); - think_about_stuff(); - draw_the_next_frame(); - } - deinitialize_the_game(); -} -``` - -This will not work on Emscripten, because the main thread needs to be free -to do stuff and can't sit in this loop forever. So Emscripten lets you set up -a [mainloop](https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop). - -```c -static void mainloop(void) /* this will run often, possibly at the monitor's refresh rate */ -{ - if (!game_is_still_running) { - deinitialize_the_game(); - #ifdef __EMSCRIPTEN__ - emscripten_cancel_main_loop(); /* this should "kill" the app. */ - #else - exit(0); - #endif - } - - check_for_new_input(); - think_about_stuff(); - draw_the_next_frame(); -} - -void main(void) -{ - initialize_the_game(); - #ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(mainloop, 0, 1); - #else - while (1) { mainloop(); } - #endif -} -``` - -Basically, `emscripten_set_main_loop(mainloop, 0, 1);` says "run -`mainloop` over and over until I end the program." The function will -run, and return, freeing the main thread for other tasks, and then -run again when it's time. The `1` parameter does some magic to make -your main() function end immediately; this is useful because you -don't want any shutdown code that might be sitting below this code -to actually run if main() were to continue on, since we're just -getting started. - -There's a lot of little details that are beyond the scope of this -document, but that's the biggest intial set of hurdles to porting -your app to the web. - - -## Do you need threads? - -If you plan to use threads, they work on all major browsers now. HOWEVER, -they bring with them a lot of careful considerations. Rendering _must_ -be done on the main thread. This is a general guideline for many -platforms, but a hard requirement on the web. - -Many other things also must happen on the main thread; often times SDL -and Emscripten make efforts to "proxy" work to the main thread that -must be there, but you have to be careful (and read more detailed -documentation than this for the finer points). - -Even when using threads, your main thread needs to set an Emscripten -mainloop that runs quickly and returns, or things will fail to work -correctly. - -You should definitely read [Emscripten's pthreads docs](https://emscripten.org/docs/porting/pthreads.html) -for all the finer points. Mostly SDL's thread API will work as expected, -but is built on pthreads, so it shares the same little incompatibilities -that are documented there, such as where you can use a mutex, and when -a thread will start running, etc. - - -IMPORTANT: You have to decide to either build something that uses -threads or something that doesn't; you can't have one build -that works everywhere. This is an Emscripten (or maybe WebAssembly? -Or just web browsers in general?) limitation. If you aren't using -threads, it's easier to not enable them at all, at build time. - -If you use threads, you _have to_ run from a web server that has -[COOP/COEP headers set correctly](https://web.dev/why-coop-coep/) -or your program will fail to start at all. - -If building with threads, `__EMSCRIPTEN_PTHREADS__` will be defined -for checking with the C preprocessor, so you can build something -different depending on what sort of build you're compiling. - - -## Audio - -Audio works as expected at the API level, but not exactly like other -platforms. - -You'll only see a single default audio device. Audio capture also works; -if the browser pops up a prompt to ask for permission to access the -microphone, the SDL_OpenAudioDevice call will succeed and start producing -silence at a regular interval. Once the user approves the request, real -audio data will flow. If the user denies it, the app is not informed and -will just continue to receive silence. - -Modern web browsers will not permit web pages to produce sound before the -user has interacted with them (clicked or tapped on them, usually); this is -for several reasons, not the least of which being that no one likes when a -random browser tab suddenly starts making noise and the user has to scramble -to figure out which and silence it. - -SDL will allow you to open the audio device for playback in this -circumstance, and your audio callback will fire, but SDL will throw the audio -data away until the user interacts with the page. This helps apps that depend -on the audio callback to make progress, and also keeps audio playback in sync -once the app is finally allowed to make noise. - -There are two reasonable ways to deal with the silence at the app level: -if you are writing some sort of media player thing, where the user expects -there to be a volume control when you mouseover the canvas, just default -that control to a muted state; if the user clicks on the control to unmute -it, on this first click, open the audio device. This allows the media to -play at start, and the user can reasonably opt-in to listening. - -Many games do not have this sort of UI, and are more rigid about starting -audio along with everything else at the start of the process. For these, your -best bet is to write a little Javascript that puts up a "Click here to play!" -UI, and upon the user clicking, remove that UI and then call the Emscripten -app's main() function. As far as the application knows, the audio device was -available to be opened as soon as the program started, and since this magic -happens in a little Javascript, you don't have to change your C/C++ code at -all to make it happen. - -Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385 -for some Javascript code to steal for this approach. - - -## Rendering - -If you use SDL's 2D render API, it will use GLES2 internally, which -Emscripten will turn into WebGL calls. You can also use OpenGL ES 2 -directly by creating a GL context and drawing into it. - -Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually -present anything on the screen until your return from your mainloop -function. - - -## Building SDL/emscripten - -First: do you _really_ need to build SDL from source? - -If you aren't developing SDL itself, have a desire to mess with its source -code, or need something on the bleeding edge, don't build SDL. Just use -Emscripten's packaged version! - -Compile and link your app with `-sUSE_SDL=2` and it'll use a build of -SDL packaged with Emscripten. This comes from the same source code and -fixes the Emscripten project makes to SDL are generally merged into SDL's -revision control, so often this is much easier for app developers. - -`-sUSE_SDL=1` will select Emscripten's JavaScript reimplementation of SDL -1.2 instead; if you need SDL 1.2, this might be fine, but we generally -recommend you don't use SDL 1.2 in modern times. - - -If you want to build SDL, though... - -SDL currently requires at least Emscripten 3.1.35 to build. Newer versions -are likely to work, as well. - - -Build: - -This works on Linux/Unix and macOS. Please send comments about Windows. - -Make sure you've [installed emsdk](https://emscripten.org/docs/getting_started/downloads.html) -first, and run `source emsdk_env.sh` at the command line so it finds the -tools. - -(These cmake options might be overkill, but this has worked for me.) - -```bash -mkdir build -cd build -emcmake cmake .. -# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. -emmake make -j4 -``` - -If you want to build with thread support, something like this works: - -```bash -mkdir build -cd build -emcmake cmake -DSDL_THREADS=On .. -# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. -emmake make -j4 -``` - -To build the tests, add `-DSDL_TESTS=On` to the `emcmake cmake` command line. - - -## Building your app - -You need to compile with `emcc` instead of `gcc` or `clang` or whatever, but -mostly it uses the same command line arguments as Clang. - -Link against the SDL/build/libSDL3.a file you generated by building SDL, -link with `-sUSE_SDL=2` to use Emscripten's prepackaged SDL2 build. - -Usually you would produce a binary like this: - -```bash -gcc -o mygame mygame.c # or whatever -``` - -But for Emscripten, you want to output something else: - -```bash -emcc -o index.html mygame.c -``` - -This will produce several files...support Javascript and WebAssembly (.wasm) -files. The `-o index.html` will produce a simple HTML page that loads and -runs your app. You will (probably) eventually want to replace or customize -that file and do `-o index.js` instead to just build the code pieces. - -If you're working on a program of any serious size, you'll likely need to -link with `-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1gb` to get access -to more memory. If using pthreads, you'll need the `-sMAXIMUM_MEMORY=1gb` -or the app will fail to start on iOS browsers, but this might be a bug that -goes away in the future. - - -## Data files - -Your game probably has data files. Here's how to access them. - -Filesystem access works like a Unix filesystem; you have a single directory -tree, possibly interpolated from several mounted locations, no drive letters, -'/' for a path separator. You can access them with standard file APIs like -open() or fopen() or SDL_RWops. You can read or write from the filesystem. - -By default, you probably have a "MEMFS" filesystem (all files are stored in -memory, but access to them is immediate and doesn't need to block). There are -other options, like "IDBFS" (files are stored in a local database, so they -don't need to be in RAM all the time and they can persist between runs of the -program, but access is not synchronous). You can mix and match these file -systems, mounting a MEMFS filesystem at one place and idbfs elsewhere, etc, -but that's beyond the scope of this document. Please refer to Emscripten's -[page on the topic](https://emscripten.org/docs/porting/files/file_systems_overview.html) -for more info. - -The _easiest_ (but not the best) way to get at your data files is to embed -them in the app itself. Emscripten's linker has support for automating this. - -```bash -emcc -o index.html loopwave.c --embed-file=../test/sample.wav@/sounds/sample.wav -``` - -This will pack ../test/sample.wav in your app, and make it available at -"/sounds/sample.wav" at runtime. Emscripten makes sure this data is available -before your main() function runs, and since it's in MEMFS, you can just -read it like you do on other platforms. `--embed-file` can also accept a -directory to pack an entire tree, and you can specify the argument multiple -times to pack unrelated things into the final installation. - -Note that this is absolutely the best approach if you have a few small -files to include and shouldn't worry about the issue further. However, if you -have hundreds of megabytes and/or thousands of files, this is not so great, -since the user will download it all every time they load your page, and it -all has to live in memory at runtime. - -[Emscripten's documentation on the matter](https://emscripten.org/docs/porting/files/packaging_files.html) -gives other options and details, and is worth a read. - - -## Debugging - -Debugging web apps is a mixed bag. You should compile and link with -`-gsource-map`, which embeds a ton of source-level debugging information into -the build, and make sure _the app source code is available on the web server_, -which is often a scary proposition for various reasons. - -When you debug from the browser's tools and hit a breakpoint, you can step -through the actual C/C++ source code, though, which can be nice. - -If you try debugging in Firefox and it doesn't work well for no apparent -reason, try Chrome, and vice-versa. These tools are still relatively new, -and improving all the time. - -SDL_Log() (or even plain old printf) will write to the Javascript console, -and honestly I find printf-style debugging to be easier than setting up a build -for proper debugging, so use whatever tools work best for you. - - -## Questions? - -Please give us feedback on this document at [the SDL bug tracker](https://github.com/libsdl-org/SDL/issues). -If something is wrong or unclear, we want to know! - - - +# Emscripten + +## The state of things + +(As of September 2023, but things move quickly and we don't update this +document often.) + +In modern times, all the browsers you probably care about (Chrome, Firefox, +Edge, and Safari, on Windows, macOS, Linux, iOS and Android), support some +reasonable base configurations: + +- WebAssembly (don't bother with asm.js any more) +- WebGL (which will look like OpenGL ES 2 or 3 to your app). +- Threads (see caveats, though!) +- Game controllers +- Autoupdating (so you can assume they have a recent version of the browser) + +All this to say we're at the point where you don't have to make a lot of +concessions to get even a fairly complex SDL-based game up and running. + + +## RTFM + +This document is a quick rundown of some high-level details. The +documentation at [emscripten.org](https://emscripten.org/) is vast +and extremely detailed for a wide variety of topics, and you should at +least skim through it at some point. + + +## Porting your app to Emscripten + +Many many things just need some simple adjustments and they'll compile +like any other C/C++ code, as long as SDL was handling the platform-specific +work for your program. + +First, you probably need this in at least one of your source files: + +```c +#ifdef __EMSCRIPTEN__ +#include +#endif +``` + +Second: assembly language code has to go. Replace it with C. You can even use +[x86 SIMD intrinsic functions in Emscripten](https://emscripten.org/docs/porting/simd.html)! + +Third: Middleware has to go. If you have a third-party library you link +against, you either need an Emscripten port of it, or the source code to it +to compile yourself, or you need to remove it. + +Fourth: You still start in a function called main(), but you need to get out of +it and into a function that gets called repeatedly, and returns quickly, +called a mainloop. + +Somewhere in your program, you probably have something that looks like a more +complicated version of this: + +```c +void main(void) +{ + initialize_the_game(); + while (game_is_still_running) { + check_for_new_input(); + think_about_stuff(); + draw_the_next_frame(); + } + deinitialize_the_game(); +} +``` + +This will not work on Emscripten, because the main thread needs to be free +to do stuff and can't sit in this loop forever. So Emscripten lets you set up +a [mainloop](https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop). + +```c +static void mainloop(void) /* this will run often, possibly at the monitor's refresh rate */ +{ + if (!game_is_still_running) { + deinitialize_the_game(); + #ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); /* this should "kill" the app. */ + #else + exit(0); + #endif + } + + check_for_new_input(); + think_about_stuff(); + draw_the_next_frame(); +} + +void main(void) +{ + initialize_the_game(); + #ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(mainloop, 0, 1); + #else + while (1) { mainloop(); } + #endif +} +``` + +Basically, `emscripten_set_main_loop(mainloop, 0, 1);` says "run +`mainloop` over and over until I end the program." The function will +run, and return, freeing the main thread for other tasks, and then +run again when it's time. The `1` parameter does some magic to make +your main() function end immediately; this is useful because you +don't want any shutdown code that might be sitting below this code +to actually run if main() were to continue on, since we're just +getting started. + +There's a lot of little details that are beyond the scope of this +document, but that's the biggest intial set of hurdles to porting +your app to the web. + + +## Do you need threads? + +If you plan to use threads, they work on all major browsers now. HOWEVER, +they bring with them a lot of careful considerations. Rendering _must_ +be done on the main thread. This is a general guideline for many +platforms, but a hard requirement on the web. + +Many other things also must happen on the main thread; often times SDL +and Emscripten make efforts to "proxy" work to the main thread that +must be there, but you have to be careful (and read more detailed +documentation than this for the finer points). + +Even when using threads, your main thread needs to set an Emscripten +mainloop that runs quickly and returns, or things will fail to work +correctly. + +You should definitely read [Emscripten's pthreads docs](https://emscripten.org/docs/porting/pthreads.html) +for all the finer points. Mostly SDL's thread API will work as expected, +but is built on pthreads, so it shares the same little incompatibilities +that are documented there, such as where you can use a mutex, and when +a thread will start running, etc. + + +IMPORTANT: You have to decide to either build something that uses +threads or something that doesn't; you can't have one build +that works everywhere. This is an Emscripten (or maybe WebAssembly? +Or just web browsers in general?) limitation. If you aren't using +threads, it's easier to not enable them at all, at build time. + +If you use threads, you _have to_ run from a web server that has +[COOP/COEP headers set correctly](https://web.dev/why-coop-coep/) +or your program will fail to start at all. + +If building with threads, `__EMSCRIPTEN_PTHREADS__` will be defined +for checking with the C preprocessor, so you can build something +different depending on what sort of build you're compiling. + + +## Audio + +Audio works as expected at the API level, but not exactly like other +platforms. + +You'll only see a single default audio device. Audio capture also works; +if the browser pops up a prompt to ask for permission to access the +microphone, the SDL_OpenAudioDevice call will succeed and start producing +silence at a regular interval. Once the user approves the request, real +audio data will flow. If the user denies it, the app is not informed and +will just continue to receive silence. + +Modern web browsers will not permit web pages to produce sound before the +user has interacted with them (clicked or tapped on them, usually); this is +for several reasons, not the least of which being that no one likes when a +random browser tab suddenly starts making noise and the user has to scramble +to figure out which and silence it. + +SDL will allow you to open the audio device for playback in this +circumstance, and your audio callback will fire, but SDL will throw the audio +data away until the user interacts with the page. This helps apps that depend +on the audio callback to make progress, and also keeps audio playback in sync +once the app is finally allowed to make noise. + +There are two reasonable ways to deal with the silence at the app level: +if you are writing some sort of media player thing, where the user expects +there to be a volume control when you mouseover the canvas, just default +that control to a muted state; if the user clicks on the control to unmute +it, on this first click, open the audio device. This allows the media to +play at start, and the user can reasonably opt-in to listening. + +Many games do not have this sort of UI, and are more rigid about starting +audio along with everything else at the start of the process. For these, your +best bet is to write a little Javascript that puts up a "Click here to play!" +UI, and upon the user clicking, remove that UI and then call the Emscripten +app's main() function. As far as the application knows, the audio device was +available to be opened as soon as the program started, and since this magic +happens in a little Javascript, you don't have to change your C/C++ code at +all to make it happen. + +Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385 +for some Javascript code to steal for this approach. + + +## Rendering + +If you use SDL's 2D render API, it will use GLES2 internally, which +Emscripten will turn into WebGL calls. You can also use OpenGL ES 2 +directly by creating a GL context and drawing into it. + +Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually +present anything on the screen until your return from your mainloop +function. + + +## Building SDL/emscripten + +First: do you _really_ need to build SDL from source? + +If you aren't developing SDL itself, have a desire to mess with its source +code, or need something on the bleeding edge, don't build SDL. Just use +Emscripten's packaged version! + +Compile and link your app with `-sUSE_SDL=2` and it'll use a build of +SDL packaged with Emscripten. This comes from the same source code and +fixes the Emscripten project makes to SDL are generally merged into SDL's +revision control, so often this is much easier for app developers. + +`-sUSE_SDL=1` will select Emscripten's JavaScript reimplementation of SDL +1.2 instead; if you need SDL 1.2, this might be fine, but we generally +recommend you don't use SDL 1.2 in modern times. + + +If you want to build SDL, though... + +SDL currently requires at least Emscripten 3.1.35 to build. Newer versions +are likely to work, as well. + + +Build: + +This works on Linux/Unix and macOS. Please send comments about Windows. + +Make sure you've [installed emsdk](https://emscripten.org/docs/getting_started/downloads.html) +first, and run `source emsdk_env.sh` at the command line so it finds the +tools. + +(These cmake options might be overkill, but this has worked for me.) + +```bash +mkdir build +cd build +emcmake cmake .. +# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. +emmake make -j4 +``` + +If you want to build with thread support, something like this works: + +```bash +mkdir build +cd build +emcmake cmake -DSDL_THREADS=On .. +# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. +emmake make -j4 +``` + +To build the tests, add `-DSDL_TESTS=On` to the `emcmake cmake` command line. + + +## Building your app + +You need to compile with `emcc` instead of `gcc` or `clang` or whatever, but +mostly it uses the same command line arguments as Clang. + +Link against the SDL/build/libSDL3.a file you generated by building SDL, +link with `-sUSE_SDL=2` to use Emscripten's prepackaged SDL2 build. + +Usually you would produce a binary like this: + +```bash +gcc -o mygame mygame.c # or whatever +``` + +But for Emscripten, you want to output something else: + +```bash +emcc -o index.html mygame.c +``` + +This will produce several files...support Javascript and WebAssembly (.wasm) +files. The `-o index.html` will produce a simple HTML page that loads and +runs your app. You will (probably) eventually want to replace or customize +that file and do `-o index.js` instead to just build the code pieces. + +If you're working on a program of any serious size, you'll likely need to +link with `-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1gb` to get access +to more memory. If using pthreads, you'll need the `-sMAXIMUM_MEMORY=1gb` +or the app will fail to start on iOS browsers, but this might be a bug that +goes away in the future. + + +## Data files + +Your game probably has data files. Here's how to access them. + +Filesystem access works like a Unix filesystem; you have a single directory +tree, possibly interpolated from several mounted locations, no drive letters, +'/' for a path separator. You can access them with standard file APIs like +open() or fopen() or SDL_RWops. You can read or write from the filesystem. + +By default, you probably have a "MEMFS" filesystem (all files are stored in +memory, but access to them is immediate and doesn't need to block). There are +other options, like "IDBFS" (files are stored in a local database, so they +don't need to be in RAM all the time and they can persist between runs of the +program, but access is not synchronous). You can mix and match these file +systems, mounting a MEMFS filesystem at one place and idbfs elsewhere, etc, +but that's beyond the scope of this document. Please refer to Emscripten's +[page on the topic](https://emscripten.org/docs/porting/files/file_systems_overview.html) +for more info. + +The _easiest_ (but not the best) way to get at your data files is to embed +them in the app itself. Emscripten's linker has support for automating this. + +```bash +emcc -o index.html loopwave.c --embed-file=../test/sample.wav@/sounds/sample.wav +``` + +This will pack ../test/sample.wav in your app, and make it available at +"/sounds/sample.wav" at runtime. Emscripten makes sure this data is available +before your main() function runs, and since it's in MEMFS, you can just +read it like you do on other platforms. `--embed-file` can also accept a +directory to pack an entire tree, and you can specify the argument multiple +times to pack unrelated things into the final installation. + +Note that this is absolutely the best approach if you have a few small +files to include and shouldn't worry about the issue further. However, if you +have hundreds of megabytes and/or thousands of files, this is not so great, +since the user will download it all every time they load your page, and it +all has to live in memory at runtime. + +[Emscripten's documentation on the matter](https://emscripten.org/docs/porting/files/packaging_files.html) +gives other options and details, and is worth a read. + + +## Debugging + +Debugging web apps is a mixed bag. You should compile and link with +`-gsource-map`, which embeds a ton of source-level debugging information into +the build, and make sure _the app source code is available on the web server_, +which is often a scary proposition for various reasons. + +When you debug from the browser's tools and hit a breakpoint, you can step +through the actual C/C++ source code, though, which can be nice. + +If you try debugging in Firefox and it doesn't work well for no apparent +reason, try Chrome, and vice-versa. These tools are still relatively new, +and improving all the time. + +SDL_Log() (or even plain old printf) will write to the Javascript console, +and honestly I find printf-style debugging to be easier than setting up a build +for proper debugging, so use whatever tools work best for you. + + +## Questions? + +Please give us feedback on this document at [the SDL bug tracker](https://github.com/libsdl-org/SDL/issues). +If something is wrong or unclear, we want to know! + + + diff --git a/docs/README-gdk.md b/docs/README-gdk.md index fefe1635..9a948583 100644 --- a/docs/README-gdk.md +++ b/docs/README-gdk.md @@ -1,159 +1,174 @@ -GDK -===== - -This port allows SDL applications to run via Microsoft's Game Development Kit (GDK). - -Windows (GDK) and Xbox One/Xbox Series (GDKX) are supported. Although most of the Xbox code is included in the public SDL source code, NDA access is required for a small number of source files. If you have access to GDKX, these required Xbox files are posted on the GDK forums [here](https://forums.xboxlive.com/questions/130003/). - - -Requirements ------------- - -* Microsoft Visual Studio 2022 (in theory, it should also work in 2017 or 2019, but this has not been tested) -* Microsoft GDK June 2022 or newer (public release [here](https://github.com/microsoft/GDK/releases/tag/June_2022)) -* To publish a package or successfully authenticate a user, you will need to create an app id/configure services in Partner Center. However, for local testing purposes (without authenticating on Xbox Live), the identifiers used by the GDK test programs in the included solution will work. - - -Windows GDK Status ------- - -The Windows GDK port supports the full set of Win32 APIs, renderers, controllers, input devices, etc., as the normal Windows x64 build of SDL. - -* Additionally, the GDK port adds the following: - * Compile-time platform detection for SDL programs. The `__GDK__` is `#define`d on every GDK platform, and the `__WINGDK__` is `#define`d on Windows GDK, specifically. (This distinction exists because other GDK platforms support a smaller subset of functionality. This allows you to mark code for "any" GDK separate from Windows GDK.) - * GDK-specific setup: - * Initializing/uninitializing the game runtime, and initializing Xbox Live services - * Creating a global task queue and setting it as the default for the process. When running any async operations, passing in `NULL` as the task queue will make the task get added to the global task queue. - - * An implementation on `WinMain` that performs the above GDK setup that you can use by #include'ing SDL_main.h in the source file that includes your standard main() function. If you are unable to do this, you can instead manually call `SDL_RunApp` from your entry point, passing in your `SDL_main` function and `NULL` as the parameters. To use `SDL_RunApp`, `#define SDL_MAIN_HANDLED` before `#include `. - * Global task queue callbacks are dispatched during `SDL_PumpEvents` (which is also called internally if using `SDL_PollEvent`). - * You can get the handle of the global task queue through `SDL_GDKGetTaskQueue`, if needed. When done with the queue, be sure to use `XTaskQueueCloseHandle` to decrement the reference count (otherwise it will cause a resource leak). - -* Single-player games have some additional features available: - * Call `SDL_GDKGetDefaultUser` to get the default XUserHandle pointer. - * `SDL_GetPrefPath` still works, but only for single-player titles. - -These functions mostly wrap around async APIs, and thus should be treated as synchronous alternatives. Also note that the single-player functions return on any OS errors, so be sure to validate the return values! - -* What doesn't work: - * Compilation with anything other than through the included Visual C++ solution file - -## VisualC-GDK Solution - -The included `VisualC-GDK/SDL.sln` solution includes the following targets for the Gaming.Desktop.x64 configuration: - -* SDL3 (DLL) - This is the typical SDL3.dll, but for Gaming.Desktop.x64. -* tests/testgamecontroller - Standard SDL test program demonstrating controller functionality. -* tests/testgdk - GDK-specific test program that demonstrates using the global task queue to login a user into Xbox Live. - *NOTE*: As of the June 2022 GDK, you cannot test user logins without a valid Title ID and MSAAppId. You will need to manually change the identifiers in the `MicrosoftGame.config` to your valid IDs from Partner Center if you wish to test this. -* tests/testsprite - Standard SDL test program demonstrating sprite drawing functionality. - -If you set one of the test programs as a startup project, you can run it directly from Visual Studio. - -Windows GDK Setup, Detailed Steps ---------------------- - -These steps assume you already have a game using SDL that runs on Windows x64 along with a corresponding Visual Studio solution file for the x64 version. If you don't have this, it's easiest to use one of the test program vcxproj files in the `VisualC-GDK` directory as a starting point, though you will still need to do most of the steps below. - -### 1. Add a Gaming.Desktop.x64 Configuration ### - -In your game's existing Visual Studio Solution, go to Build > Configuration Manager. From the "Active solution platform" drop-down select "New...". From the drop-down list, select Gaming.Desktop.x64 and copy the settings from the x64 configuration. - -### 2. Build SDL3 for GDK ### - -Open `VisualC-GDK/SDL.sln` in Visual Studio, you need to build the SDL3 target for the Gaming.Desktop.x64 platform (Release is recommended). You will need to copy/keep track of the `SDL3.dll`, `XCurl.dll` (which is output by Gaming.Desktop.x64), and `SDL3.lib` output files for your game project. - -*Alternatively*, you could setup your solution file to instead reference the SDL3 project file targets from the SDL source, and add those projects as a dependency. This would mean that SDL3 would be built when your game is built. - -### 3. Configuring Project Settings ### - -While the Gaming.Desktop.x64 configuration sets most of the required settings, there are some additional items to configure for your game project under the Gaming.Desktop.x64 Configuration: - -* Under C/C++ > General > Additional Include Directories, make sure the `SDL/include` path is referenced -* Under Linker > General > Additional Library Directories, make sure to reference the path where the newly-built SDL3.lib are -* Under Linker > Input > Additional Dependencies, you need the following: - * `SDL3.lib` - * `xgameruntime.lib` - * `../Microsoft.Xbox.Services.141.GDK.C.Thunks.lib` -* Note that in general, the GDK libraries depend on the MSVC C/C++ runtime, so there is no way to remove this dependency from a GDK program that links against GDK. - -### 4. Setting up SDL_main ### - -Rather than using your own implementation of `WinMain`, it's recommended that you instead `#include ` and declare a standard main function. If you are unable to do this, you can instead manually call `SDL_RunApp` from your entry point, passing in your `SDL_main` function and `NULL` as the parameters; in that case `#define SDL_MAIN_HANDLED` before including SDL_main.h - -### 5. Required DLLs ### - -The game will not launch in the debugger unless required DLLs are included in the directory that contains the game's .exe file. You need to make sure that the following files are copied into the directory: - -* Your SDL3.dll -* "$(Console_GRDKExtLibRoot)Xbox.Services.API.C\DesignTime\CommonConfiguration\Neutral\Lib\Release\Microsoft.Xbox.Services.141.GDK.C.Thunks.dll" -* XCurl.dll - -You can either copy these in a post-build step, or you can add the dlls into the project and set its Configuration Properties > General > Item type to "Copy file," which will also copy them into the output directory. - -### 6. Setting up MicrosoftGame.config ### - -You can copy `VisualC-GDK/tests/testgdk/MicrosoftGame.config` and use that as a starting point in your project. Minimally, you will want to change the Executable Name attribute, the DefaultDisplayName, and the Description. - -This file must be copied into the same directory as the game's .exe file. As with the DLLs, you can either use a post-build step or the "Copy file" item type. - -For basic testing, you do not need to change anything else in `MicrosoftGame.config`. However, if you want to test any Xbox Live services (such as logging in users) _or_ publish a package, you will need to setup a Game app on Partner Center. - -Then, you need to set the following values to the values from Partner Center: - -* Identity tag - Name and Publisher attributes -* TitleId -* MSAAppId - -### 7. Adding Required Logos - -Several logo PNG files are required to be able to launch the game, even from the debugger. You can use the sample logos provided in `VisualC-GDK/logos`. As with the other files, they must be copied into the same directory as the game's .exe file. - - -### 8. Copying any Data Files ### - -When debugging GDK games, there is no way to specify a working directory. Therefore, any required game data must also be copied into the output directory, likely in a post-build step. - - -### 9. Build and Run from Visual Studio ### - -At this point, you should be able to build and run your game from the Visual Studio Debugger. If you get any linker errors, make sure you double-check that you referenced all the required libs. - -If you are testing Xbox Live functionality, it's likely you will need to change to the Sandbox for your title. To do this: - -1. Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu -2. Switch the sandbox name with: - `XblPCSandbox SANDBOX.#` -3. (To switch back to the retail sandbox): - `XblPCSandbox RETAIL` - -### 10. Packaging and Installing Locally - -You can use one of the test program's `PackageLayout.xml` as a starting point. Minimally, you will need to change the exe to the correct name and also reference any required game data. As with the other data files, it's easiest if you have this copy to the output directory, although it's not a requirement as you can specify relative paths to files. - -To create the package: - -1. Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu -2. `cd` to the directory containing the `PackageLayout.xml` with the correct paths (if you use the local path as in the sample package layout, this would be from your .exe output directory) -3. `mkdir Package` to create an output directory -4. To package the file into the `Package` directory, use: - `makepkg pack /f PackageLayout.xml /lt /d . /nogameos /pc /pd Package` -5. To install the package, use: - `wdapp install PACKAGENAME.msixvc` -6. Once the package is installed, you can run it from the start menu. -7. As with when running from Visual Studio, if you need to test any Xbox Live functionality you must switch to the correct sandbox. - - -Troubleshooting ---------------- - -#### Xbox Live Login does not work - -As of June 2022 GDK, you must have a valid Title Id and MSAAppId in order to test Xbox Live functionality such as user login. Make sure these are set correctly in the `MicrosoftGame.config`. This means that even testgdk will not let you login without setting these properties to valid values. - -Furthermore, confirm that your PC is set to the correct sandbox. - - -#### "The current user has already installed an unpackaged version of this app. A packaged version cannot replace this." error when installing - -Prior to June 2022 GDK, running from the Visual Studio debugger would still locally register the app (and it would appear on the start menu). To fix this, you have to uninstall it (it's simplest to right click on it from the start menu to uninstall it). +GDK +===== + +This port allows SDL applications to run via Microsoft's Game Development Kit (GDK). + +Windows (GDK) and Xbox One/Xbox Series (GDKX) are both supported and all the required code is included in this public SDL release. However, only licensed Xbox developers have access to the GDKX libraries which will allow you to build the Xbox targets. + + +Requirements +------------ + +* Microsoft Visual Studio 2022 (in theory, it should also work in 2017 or 2019, but this has not been tested) +* Microsoft GDK October 2023 Update 1 or newer (public release [here](https://github.com/microsoft/GDK/releases/tag/October_2023_Update_1)) +* For Xbox, you will need the corresponding GDKX version (licensed developers only) +* To publish a package or successfully authenticate a user, you will need to create an app id/configure services in Partner Center. However, for local testing purposes (without authenticating on Xbox Live), the test identifiers used by the GDK test programs in the included solution work. + + +Windows GDK Status +------ + +The Windows GDK port supports the full set of Win32 APIs, renderers, controllers, input devices, etc., as the normal Windows x64 build of SDL. + +* Additionally, the GDK port adds the following: + * Compile-time platform detection for SDL programs. The `__GDK__` is `#define`d on every GDK platform, and the `__WINGDK__` is `#define`d on Windows GDK, specifically. (This distinction exists because other GDK platforms support a smaller subset of functionality. This allows you to mark code for "any" GDK separate from Windows GDK.) + * GDK-specific setup: + * Initializing/uninitializing the game runtime, and initializing Xbox Live services + * Creating a global task queue and setting it as the default for the process. When running any async operations, passing in `NULL` as the task queue will make the task get added to the global task queue. + + * An implementation on `WinMain` that performs the above GDK setup that you can use by #include'ing SDL_main.h in the source file that includes your standard main() function. If you are unable to do this, you can instead manually call `SDL_RunApp` from your entry point, passing in your `SDL_main` function and `NULL` as the parameters. To use `SDL_RunApp`, `#define SDL_MAIN_HANDLED` before `#include `. + * Global task queue callbacks are dispatched during `SDL_PumpEvents` (which is also called internally if using `SDL_PollEvent`). + * You can get the handle of the global task queue through `SDL_GDKGetTaskQueue`, if needed. When done with the queue, be sure to use `XTaskQueueCloseHandle` to decrement the reference count (otherwise it will cause a resource leak). + +* Single-player games have some additional features available: + * Call `SDL_GDKGetDefaultUser` to get the default XUserHandle pointer. + * `SDL_GetPrefPath` still works, but only for single-player titles. + + These functions mostly wrap around async APIs, and thus should be treated as synchronous alternatives. Also note that the single-player functions return on any OS errors, so be sure to validate the return values! + +* What doesn't work: + * Compilation with anything other than through the included Visual C++ solution file + +## VisualC-GDK Solution + +The included `VisualC-GDK/SDL.sln` solution includes the following targets for the Gaming.Desktop.x64 configuration: + +* SDL3 (DLL) - This is the typical SDL3.dll, but for Gaming.Desktop.x64. +* tests/testgamecontroller - Standard SDL test program demonstrating controller functionality. +* tests/testgdk - GDK-specific test program that demonstrates using the global task queue to login a user into Xbox Live. + *NOTE*: As of the June 2022 GDK, you cannot test user logins without a valid Title ID and MSAAppId. You will need to manually change the identifiers in the `MicrosoftGame.config` to your valid IDs from Partner Center if you wish to test this. +* tests/testsprite - Standard SDL test program demonstrating sprite drawing functionality. + +If you set one of the test programs as a startup project, you can run it directly from Visual Studio. + +Windows GDK Setup, Detailed Steps +--------------------- + +These steps assume you already have a game using SDL that runs on Windows x64 along with a corresponding Visual Studio solution file for the x64 version. If you don't have this, it's easiest to use one of the test program vcxproj files in the `VisualC-GDK` directory as a starting point, though you will still need to do most of the steps below. + +### 1. Add a Gaming.Desktop.x64 Configuration ### + +In your game's existing Visual Studio Solution, go to Build > Configuration Manager. From the "Active solution platform" drop-down select "New...". From the drop-down list, select Gaming.Desktop.x64 and copy the settings from the x64 configuration. + +### 2. Build SDL3 for GDK ### + +Open `VisualC-GDK/SDL.sln` in Visual Studio, you need to build the SDL3 target for the Gaming.Desktop.x64 platform (Release is recommended). You will need to copy/keep track of the `SDL3.dll`, `XCurl.dll` (which is output by Gaming.Desktop.x64), and `SDL3.lib` output files for your game project. + +*Alternatively*, you could setup your solution file to instead reference the SDL3 project file targets from the SDL source, and add those projects as a dependency. This would mean that SDL3 would be built when your game is built. + +### 3. Configuring Project Settings ### + +While the Gaming.Desktop.x64 configuration sets most of the required settings, there are some additional items to configure for your game project under the Gaming.Desktop.x64 Configuration: + +* Under C/C++ > General > Additional Include Directories, make sure the `SDL/include` path is referenced +* Under Linker > General > Additional Library Directories, make sure to reference the path where the newly-built SDL3.lib are +* Under Linker > Input > Additional Dependencies, you need the following: + * `SDL3.lib` + * `xgameruntime.lib` + * `../Microsoft.Xbox.Services.GDK.C.Thunks.lib` +* Note that in general, the GDK libraries depend on the MSVC C/C++ runtime, so there is no way to remove this dependency from a GDK program that links against GDK. + +### 4. Setting up SDL_main ### + +Rather than using your own implementation of `WinMain`, it's recommended that you instead `#include ` and declare a standard main function. If you are unable to do this, you can instead manually call `SDL_RunApp` from your entry point, passing in your `SDL_main` function and `NULL` as the parameters; in that case `#define SDL_MAIN_HANDLED` before including SDL_main.h + +### 5. Required DLLs ### + +The game will not launch in the debugger unless required DLLs are included in the directory that contains the game's .exe file. You need to make sure that the following files are copied into the directory: + +* Your SDL3.dll +* "$(Console_GRDKExtLibRoot)Xbox.Services.API.C\DesignTime\CommonConfiguration\Neutral\Lib\Release\Microsoft.Xbox.Services.GDK.C.Thunks.dll" +* XCurl.dll + +You can either copy these in a post-build step, or you can add the dlls into the project and set its Configuration Properties > General > Item type to "Copy file," which will also copy them into the output directory. + +### 6. Setting up MicrosoftGame.config ### + +You can copy `VisualC-GDK/tests/testgdk/MicrosoftGame.config` and use that as a starting point in your project. Minimally, you will want to change the Executable Name attribute, the DefaultDisplayName, and the Description. + +This file must be copied into the same directory as the game's .exe file. As with the DLLs, you can either use a post-build step or the "Copy file" item type. + +For basic testing, you do not need to change anything else in `MicrosoftGame.config`. However, if you want to test any Xbox Live services (such as logging in users) _or_ publish a package, you will need to setup a Game app on Partner Center. + +Then, you need to set the following values to the values from Partner Center: + +* Identity tag - Name and Publisher attributes +* TitleId +* MSAAppId + +### 7. Adding Required Logos + +Several logo PNG files are required to be able to launch the game, even from the debugger. You can use the sample logos provided in `VisualC-GDK/logos`. As with the other files, they must be copied into the same directory as the game's .exe file. + + +### 8. Copying any Data Files ### + +When debugging GDK games, there is no way to specify a working directory. Therefore, any required game data must also be copied into the output directory, likely in a post-build step. + + +### 9. Build and Run from Visual Studio ### + +At this point, you should be able to build and run your game from the Visual Studio Debugger. If you get any linker errors, make sure you double-check that you referenced all the required libs. + +If you are testing Xbox Live functionality, it's likely you will need to change to the Sandbox for your title. To do this: + +1. Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu +2. Switch the sandbox name with: + `XblPCSandbox SANDBOX.#` +3. (To switch back to the retail sandbox): + `XblPCSandbox RETAIL` + +### 10. Packaging and Installing Locally + +You can use one of the test program's `PackageLayout.xml` as a starting point. Minimally, you will need to change the exe to the correct name and also reference any required game data. As with the other data files, it's easiest if you have this copy to the output directory, although it's not a requirement as you can specify relative paths to files. + +To create the package: + +1. Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu +2. `cd` to the directory containing the `PackageLayout.xml` with the correct paths (if you use the local path as in the sample package layout, this would be from your .exe output directory) +3. `mkdir Package` to create an output directory +4. To package the file into the `Package` directory, use: + `makepkg pack /f PackageLayout.xml /lt /d . /nogameos /pc /pd Package` +5. To install the package, use: + `wdapp install PACKAGENAME.msixvc` +6. Once the package is installed, you can run it from the start menu. +7. As with when running from Visual Studio, if you need to test any Xbox Live functionality you must switch to the correct sandbox. + +Xbox GDKX Setup +--------------------- +In general, the same process in the Windows GDK instructions work. There are just a few additional notes: +* For Xbox One consoles, use the Gaming.Xbox.XboxOne.x64 target +* For Xbox Series consoles, use the Gaming.Xbox.Scarlett.x64 target +* The Xbox One target sets the `__XBOXONE__` define and the Xbox Series target sets the `__XBOXSERIES__` define +* You don't need to link against the Xbox.Services Thunks lib nor include that dll in your package (it doesn't exist for Xbox) +* The shader blobs for Xbox are created in a pre-build step for the Xbox targets, rather than included in the source (due to NDA and version compatability reasons) +* To create a package, use: + `makepkg pack /f PackageLayout.xml /lt /d . /pd Package` +* To install the package, use: + `xbapp install [PACKAGE].xvc` +* For some reason, if you make changes that require SDL3.dll to build, and you are running through the debugger (instead of a package), you have to rebuild your .exe target for the debugger to recognize the dll has changed and needs to be transferred to the console again +* While there are successful releases of Xbox titles using this port, it is not as extensively tested as other targets + +Troubleshooting +--------------- + +#### Xbox Live Login does not work + +As of June 2022 GDK, you must have a valid Title Id and MSAAppId in order to test Xbox Live functionality such as user login. Make sure these are set correctly in the `MicrosoftGame.config`. This means that even testgdk will not let you login without setting these properties to valid values. + +Furthermore, confirm that your PC is set to the correct sandbox. + + +#### "The current user has already installed an unpackaged version of this app. A packaged version cannot replace this." error when installing + +Prior to June 2022 GDK, running from the Visual Studio debugger would still locally register the app (and it would appear on the start menu). To fix this, you have to uninstall it (it's simplest to right click on it from the start menu to uninstall it). diff --git a/docs/README-git.md b/docs/README-git.md index fd12fd9f..3f03488a 100644 --- a/docs/README-git.md +++ b/docs/README-git.md @@ -1,19 +1,19 @@ -git -========= - -The latest development version of SDL is available via git. -Git allows you to get up-to-the-minute fixes and enhancements; -as a developer works on a source tree, you can use "git" to mirror that -source tree instead of waiting for an official release. Please look -at the Git website ( https://git-scm.com/ ) for more -information on using git, where you can also download software for -macOS, Windows, and Unix systems. - - git clone https://github.com/libsdl-org/SDL - -If you are building SDL via configure, you will need to run autogen.sh -before running configure. - -There is a web interface to the Git repository at: - http://github.com/libsdl-org/SDL/ - +git +========= + +The latest development version of SDL is available via git. +Git allows you to get up-to-the-minute fixes and enhancements; +as a developer works on a source tree, you can use "git" to mirror that +source tree instead of waiting for an official release. Please look +at the Git website ( https://git-scm.com/ ) for more +information on using git, where you can also download software for +macOS, Windows, and Unix systems. + + git clone https://github.com/libsdl-org/SDL + +If you are building SDL via configure, you will need to run autogen.sh +before running configure. + +There is a web interface to the Git repository at: + http://github.com/libsdl-org/SDL/ + diff --git a/docs/README-hg.md b/docs/README-hg.md index bd4e6726..b42aaa5b 100644 --- a/docs/README-hg.md +++ b/docs/README-hg.md @@ -1,7 +1,7 @@ -Mercurial -====== - -We are no longer hosted in Mercurial. Please see README-git.md for details. - -Thanks! - +Mercurial +====== + +We are no longer hosted in Mercurial. Please see README-git.md for details. + +Thanks! + diff --git a/docs/README-highdpi.md b/docs/README-highdpi.md index 6c3d5148..fd28575d 100644 --- a/docs/README-highdpi.md +++ b/docs/README-highdpi.md @@ -1,8 +1,8 @@ - -SDL 3.0 has new support for high DPI displays - -Displays now have a content display scale, which is the expected scale for content based on the DPI settings of the display. For example, a 4K display might have a 2.0 (200%) display scale, which means that the user expects UI elements to be twice as big on this display, to aid in readability. You can query the display content scale using `SDL_GetDisplayContentScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. - -The window size is now distinct from the window pixel size, and the ratio between the two is the window pixel density. If the window is created with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, SDL will try to match the native pixel density for the display, otherwise it will try to have the pixel size match the window size. You can query the window pixel density using `SDL_GetWindowPixelDensity()`. You can query the window pixel size using `SDL_GetWindowSizeInPixels()`, and when this changes you get an `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event. You are guaranteed to get a `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event when a window is created and resized, and you can use this event to create and resize your graphics context for the window. - -The window has a display scale, which is the scale from the pixel resolution to the desired content size, e.g. the combination of the pixel density and the content scale. For example, a 3840x2160 window displayed at 200% on Windows, and a 1920x1080 window with the high density flag on a 2x display on macOS will both have a pixel size of 3840x2160 and a display scale of 2.0. You can query the window display scale using `SDL_GetWindowDisplayScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. + +SDL 3.0 has new support for high DPI displays + +Displays now have a content display scale, which is the expected scale for content based on the DPI settings of the display. For example, a 4K display might have a 2.0 (200%) display scale, which means that the user expects UI elements to be twice as big on this display, to aid in readability. You can query the display content scale using `SDL_GetDisplayContentScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. + +The window size is now distinct from the window pixel size, and the ratio between the two is the window pixel density. If the window is created with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, SDL will try to match the native pixel density for the display, otherwise it will try to have the pixel size match the window size. You can query the window pixel density using `SDL_GetWindowPixelDensity()`. You can query the window pixel size using `SDL_GetWindowSizeInPixels()`, and when this changes you get an `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event. You are guaranteed to get a `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event when a window is created and resized, and you can use this event to create and resize your graphics context for the window. + +The window has a display scale, which is the scale from the pixel resolution to the desired content size, e.g. the combination of the pixel density and the content scale. For example, a 3840x2160 window displayed at 200% on Windows, and a 1920x1080 window with the high density flag on a 2x display on macOS will both have a pixel size of 3840x2160 and a display scale of 2.0. You can query the window display scale using `SDL_GetWindowDisplayScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. diff --git a/docs/README-ios.md b/docs/README-ios.md index 643b342e..221cb27d 100644 --- a/docs/README-ios.md +++ b/docs/README-ios.md @@ -1,292 +1,272 @@ -iOS -====== - -Building the Simple DirectMedia Layer for iOS 9.0+ -============================================================================== - -Requirements: macOS 10.9 or later and the iOS 9.0 or newer SDK. - -Instructions: - -1. Open SDL.xcodeproj (located in Xcode/SDL) in Xcode. -2. Select your desired target, and hit build. - - -Using the Simple DirectMedia Layer for iOS -============================================================================== - -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 -4. Select the project in the main view, go to the "Info" tab and under "Custom iOS Target Properties" remove the line "Main storyboard file base name" -5. Select the project in the main view, go to the "Build Settings" tab, select "All", and edit "Header Search Path" and drag over the SDL "Public Headers" folder from the left -6. Select the project in the main view, go to the "Build Phases" tab, select "Link Binary With Libraries", and add SDL3.framework from "Framework-iOS" -7. Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and select "Embed & Sign" for the SDL library. -8. Add the source files that you would normally have for an SDL program, making sure to have #include "SDL.h" at the top of the file containing your main() function. -9. Add any assets that your application needs. -10. Enjoy! - - -TODO: Add information regarding App Store requirements such as icons, etc. - - -Notes -- Retina / High-DPI and window sizes -============================================================================== - -Window and display mode sizes in SDL are in points rather than in pixels. -On iOS this means that a window created on an iPhone 6 will have a size in -points of 375 x 667, rather than a size in pixels of 750 x 1334. All iOS apps -are expected to size their content based on points rather than pixels, -as this allows different iOS devices to have different pixel densities -(Retina versus non-Retina screens, etc.) without apps caring too much. - -SDL_GetWindowSize() and mouse coordinates are in points rather than pixels, -but the window will have a much greater pixel density when the device supports -it, and the SDL_GetWindowSizeInPixels() can be called to determine the size -in pixels of the drawable screen framebuffer. - -The SDL 2D rendering API will automatically handle this for you, by default -providing a rendering area in points, and you can call SDL_SetRenderLogicalPresentation() -to gain access to the higher density resolution. - -Some OpenGL ES functions such as glViewport expect sizes in pixels rather than -sizes in points. When doing 2D rendering with OpenGL ES, an orthographic projection -matrix using the size in points (SDL_GetWindowSize()) can be used in order to -display content at the same scale no matter whether a Retina device is used or not. - - -Notes -- Application events -============================================================================== - -On iOS the application goes through a fixed life cycle and you will get -notifications of state changes via application events. When these events -are delivered you must handle them in an event callback because the OS may -not give you any processing time after the events are delivered. - -e.g. - - int HandleAppEvents(void *userdata, SDL_Event *event) - { - switch (event->type) - { - case SDL_EVENT_TERMINATING: - /* Terminate the app. - Shut everything down before returning from this function. - */ - return 0; - case SDL_EVENT_LOW_MEMORY: - /* You will get this when your app is paused and iOS wants more memory. - Release as much memory as possible. - */ - return 0; - case SDL_EVENT_WILL_ENTER_BACKGROUND: - /* Prepare your app to go into the background. Stop loops, etc. - This gets called when the user hits the home button, or gets a call. - */ - return 0; - case SDL_EVENT_DID_ENTER_BACKGROUND: - /* This will get called if the user accepted whatever sent your app to the background. - If the user got a phone call and canceled it, you'll instead get an SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops. - When you get this, you have 5 seconds to save all your state or the app will be terminated. - Your app is NOT active at this point. - */ - return 0; - case SDL_EVENT_WILL_ENTER_FOREGROUND: - /* This call happens when your app is coming back to the foreground. - Restore all your state here. - */ - return 0; - case SDL_EVENT_DID_ENTER_FOREGROUND: - /* Restart your loops here. - Your app is interactive and getting CPU again. - */ - return 0; - default: - /* No special processing, add it to the event queue */ - return 1; - } - } - - int main(int argc, char *argv[]) - { - SDL_SetEventFilter(HandleAppEvents, NULL); - - ... run your main loop - - return 0; - } - - -Notes -- Accelerometer as Joystick -============================================================================== - -SDL for iPhone supports polling the built in accelerometer as a joystick device. For an example on how to do this, see the accelerometer.c in the demos directory. - -The main thing to note when using the accelerometer with SDL is that while the iPhone natively reports accelerometer as floating point values in units of g-force, SDL_GetJoystickAxis() reports joystick values as signed integers. Hence, in order to convert between the two, some clamping and scaling is necessary on the part of the iPhone SDL joystick driver. To convert SDL_GetJoystickAxis() reported values BACK to units of g-force, simply multiply the values by SDL_IPHONE_MAX_GFORCE / 0x7FFF. - - -Notes -- OpenGL ES -============================================================================== - -Your SDL application for iOS uses OpenGL ES for video by default. - -OpenGL ES for iOS supports several display pixel formats, such as RGBA8 and RGB565, which provide a 32 bit and 16 bit color buffer respectively. By default, the implementation uses RGB565, but you may use RGBA8 by setting each color component to 8 bits in SDL_GL_SetAttribute(). - -If your application doesn't use OpenGL's depth buffer, you may find significant performance improvement by setting SDL_GL_DEPTH_SIZE to 0. - -Finally, if your application completely redraws the screen each frame, you may find significant performance improvement by setting the attribute SDL_GL_RETAINED_BACKING to 0. - -OpenGL ES on iOS doesn't use the traditional system-framebuffer setup provided in other operating systems. Special care must be taken because of this: - -- The drawable Renderbuffer must be bound to the GL_RENDERBUFFER binding point when SDL_GL_SwapWindow() is called. -- The drawable Framebuffer Object must be bound while rendering to the screen and when SDL_GL_SwapWindow() is called. -- If multisample antialiasing (MSAA) is used and glReadPixels is used on the screen, the drawable framebuffer must be resolved to the MSAA resolve framebuffer (via glBlitFramebuffer or glResolveMultisampleFramebufferAPPLE), and the MSAA resolve framebuffer must be bound to the GL_READ_FRAMEBUFFER binding point, before glReadPixels is called. - -The above objects can be obtained via SDL_GetWindowWMInfo() (in SDL_syswm.h). - - -Notes -- Keyboard -============================================================================== - -The SDL keyboard API has been extended to support on-screen keyboards: - -void SDL_StartTextInput() - -- enables text events and reveals the onscreen keyboard. - -void SDL_StopTextInput() - -- disables text events and hides the onscreen keyboard. - -SDL_bool SDL_TextInputActive() - -- returns whether or not text events are enabled (and the onscreen keyboard is visible) - - -Notes -- Mouse -============================================================================== - -iOS now supports Bluetooth mice on iPad, but by default will provide the mouse input as touch. In order for SDL to see the real mouse events, you should set the key UIApplicationSupportsIndirectInputEvents to true in your Info.plist - - -Notes -- Reading and Writing files -============================================================================== - -Each application installed on iPhone resides in a sandbox which includes its own Application Home directory. Your application may not access files outside this directory. - -Once your application is installed its directory tree looks like: - - MySDLApp Home/ - MySDLApp.app - Documents/ - Library/ - Preferences/ - tmp/ - -When your SDL based iPhone application starts up, it sets the working directory to the main bundle (MySDLApp Home/MySDLApp.app), where your application resources are stored. You cannot write to this directory. Instead, I advise you to write document files to "../Documents/" and preferences to "../Library/Preferences". - -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 -============================================================================== - -Windows: - Full-size, single window applications only. You cannot create multi-window SDL applications for iPhone OS. The application window will fill the display, though you have the option of turning on or off the menu-bar (pass SDL_CreateWindow() the flag SDL_WINDOW_BORDERLESS). - -Textures: - The optimal texture formats on iOS are SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_XBGR8888, and SDL_PIXELFORMAT_RGB24 pixel formats. - -Loading Shared Objects: - This is disabled by default since it seems to break the terms of the iOS SDK agreement for iOS versions prior to iOS 8. It can be re-enabled in SDL_config_ios.h. - - -Notes -- CoreBluetooth.framework -============================================================================== - -SDL_JOYSTICK_HIDAPI is disabled by default. It can give you access to a lot -more game controller devices, but it requires permission from the user before -your app will be able to talk to the Bluetooth hardware. "Made For iOS" -branded controllers do not need this as we don't have to speak to them -directly with raw bluetooth, so many apps can live without this. - -You'll need to link with CoreBluetooth.framework and add something like this -to your Info.plist: - -NSBluetoothPeripheralUsageDescription -MyApp would like to remain connected to nearby bluetooth Game Controllers and Game Pads even when you're not using the app. - - -Game Center -============================================================================== - -Game Center integration might require that you break up your main loop in order to yield control back to the system. In other words, instead of running an endless main loop, you run each frame in a callback function, using: - - int SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam); - -This will set up the given function to be called back on the animation callback, and then you have to return from main() to let the Cocoa event loop run. - -e.g. - - extern "C" - void ShowFrame(void*) - { - ... do event handling, frame logic and rendering ... - } - - int main(int argc, char *argv[]) - { - ... initialize game ... - - #ifdef __IOS__ - // Initialize the Game Center for scoring and matchmaking - InitGameCenter(); - - // Set up the game to run in the window animation callback on iOS - // so that Game Center and so forth works correctly. - SDL_iPhoneSetAnimationCallback(window, 1, ShowFrame, NULL); - #else - while ( running ) { - ShowFrame(0); - DelayFrame(); - } - #endif - return 0; - } - - -Deploying to older versions of iOS -============================================================================== - -SDL supports deploying to older versions of iOS than are supported by the latest version of Xcode, all the way back to iOS 8.0 - -In order to do that you need to download an older version of Xcode: -https://developer.apple.com/download/more/?name=Xcode - -Open the package contents of the older Xcode and your newer version of Xcode and copy over the folders in Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport - -Then open the file Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist and add the versions of iOS you want to deploy to the key Root/DefaultProperties/DEPLOYMENT_TARGET_SUGGESTED_VALUES - -Open your project and set your deployment target to the desired version of iOS - -Finally, remove GameController from the list of frameworks linked by your application and edit the build settings for "Other Linker Flags" and add -weak_framework GameController +iOS +====== + +Building the Simple DirectMedia Layer for iOS 9.0+ +============================================================================== + +Requirements: macOS 10.9 or later and the iOS 9.0 or newer SDK. + +Instructions: + +1. Open SDL.xcodeproj (located in Xcode/SDL) in Xcode. +2. Select your desired target, and hit build. + + +Using the Simple DirectMedia Layer for iOS +============================================================================== + +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 +4. Select the project in the main view, go to the "Info" tab and under "Custom iOS Target Properties" remove the line "Main storyboard file base name" +5. Select the project in the main view, go to the "Build Settings" tab, select "All", and edit "Header Search Path" and drag over the SDL "Public Headers" folder from the left +6. Select the project in the main view, go to the "Build Phases" tab, select "Link Binary With Libraries", and add SDL3.framework from "Framework-iOS" +7. Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and select "Embed & Sign" for the SDL library. +8. Add the source files that you would normally have for an SDL program, making sure to have #include "SDL.h" at the top of the file containing your main() function. +9. Add any assets that your application needs. +10. Enjoy! + + +TODO: Add information regarding App Store requirements such as icons, etc. + + +Notes -- Retina / High-DPI and window sizes +============================================================================== + +Window and display mode sizes in SDL are in points rather than in pixels. +On iOS this means that a window created on an iPhone 6 will have a size in +points of 375 x 667, rather than a size in pixels of 750 x 1334. All iOS apps +are expected to size their content based on points rather than pixels, +as this allows different iOS devices to have different pixel densities +(Retina versus non-Retina screens, etc.) without apps caring too much. + +SDL_GetWindowSize() and mouse coordinates are in points rather than pixels, +but the window will have a much greater pixel density when the device supports +it, and the SDL_GetWindowSizeInPixels() can be called to determine the size +in pixels of the drawable screen framebuffer. + +The SDL 2D rendering API will automatically handle this for you, by default +providing a rendering area in points, and you can call SDL_SetRenderLogicalPresentation() +to gain access to the higher density resolution. + +Some OpenGL ES functions such as glViewport expect sizes in pixels rather than +sizes in points. When doing 2D rendering with OpenGL ES, an orthographic projection +matrix using the size in points (SDL_GetWindowSize()) can be used in order to +display content at the same scale no matter whether a Retina device is used or not. + + +Notes -- Application events +============================================================================== + +On iOS the application goes through a fixed life cycle and you will get +notifications of state changes via application events. When these events +are delivered you must handle them in an event callback because the OS may +not give you any processing time after the events are delivered. + +e.g. + + int HandleAppEvents(void *userdata, SDL_Event *event) + { + switch (event->type) + { + case SDL_EVENT_TERMINATING: + /* Terminate the app. + Shut everything down before returning from this function. + */ + return 0; + case SDL_EVENT_LOW_MEMORY: + /* You will get this when your app is paused and iOS wants more memory. + Release as much memory as possible. + */ + return 0; + case SDL_EVENT_WILL_ENTER_BACKGROUND: + /* Prepare your app to go into the background. Stop loops, etc. + This gets called when the user hits the home button, or gets a call. + */ + return 0; + case SDL_EVENT_DID_ENTER_BACKGROUND: + /* This will get called if the user accepted whatever sent your app to the background. + If the user got a phone call and canceled it, you'll instead get an SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops. + When you get this, you have 5 seconds to save all your state or the app will be terminated. + Your app is NOT active at this point. + */ + return 0; + case SDL_EVENT_WILL_ENTER_FOREGROUND: + /* This call happens when your app is coming back to the foreground. + Restore all your state here. + */ + return 0; + case SDL_EVENT_DID_ENTER_FOREGROUND: + /* Restart your loops here. + Your app is interactive and getting CPU again. + */ + return 0; + default: + /* No special processing, add it to the event queue */ + return 1; + } + } + + int main(int argc, char *argv[]) + { + SDL_SetEventFilter(HandleAppEvents, NULL); + + ... run your main loop + + return 0; + } + + +Notes -- Accelerometer as Joystick +============================================================================== + +SDL for iPhone supports polling the built in accelerometer as a joystick device. For an example on how to do this, see the accelerometer.c in the demos directory. + +The main thing to note when using the accelerometer with SDL is that while the iPhone natively reports accelerometer as floating point values in units of g-force, SDL_GetJoystickAxis() reports joystick values as signed integers. Hence, in order to convert between the two, some clamping and scaling is necessary on the part of the iPhone SDL joystick driver. To convert SDL_GetJoystickAxis() reported values BACK to units of g-force, simply multiply the values by SDL_IPHONE_MAX_GFORCE / 0x7FFF. + + +Notes -- Keyboard +============================================================================== + +The SDL keyboard API has been extended to support on-screen keyboards: + +void SDL_StartTextInput() + -- enables text events and reveals the onscreen keyboard. + +void SDL_StopTextInput() + -- disables text events and hides the onscreen keyboard. + +SDL_bool SDL_TextInputActive() + -- returns whether or not text events are enabled (and the onscreen keyboard is visible) + + +Notes -- Mouse +============================================================================== + +iOS now supports Bluetooth mice on iPad, but by default will provide the mouse input as touch. In order for SDL to see the real mouse events, you should set the key UIApplicationSupportsIndirectInputEvents to true in your Info.plist + + +Notes -- Reading and Writing files +============================================================================== + +Each application installed on iPhone resides in a sandbox which includes its own Application Home directory. Your application may not access files outside this directory. + +Once your application is installed its directory tree looks like: + + MySDLApp Home/ + MySDLApp.app + Documents/ + Library/ + Preferences/ + tmp/ + +When your SDL based iPhone application starts up, it sets the working directory to the main bundle (MySDLApp Home/MySDLApp.app), where your application resources are stored. You cannot write to this directory. Instead, I advise you to write document files to "../Documents/" and preferences to "../Library/Preferences". + +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 +============================================================================== + +Windows: + Full-size, single window applications only. You cannot create multi-window SDL applications for iPhone OS. The application window will fill the display, though you have the option of turning on or off the menu-bar (pass SDL_CreateWindow() the flag SDL_WINDOW_BORDERLESS). + +Textures: + The optimal texture formats on iOS are SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_XBGR8888, and SDL_PIXELFORMAT_RGB24 pixel formats. + +Loading Shared Objects: + This is disabled by default since it seems to break the terms of the iOS SDK agreement for iOS versions prior to iOS 8. It can be re-enabled in SDL_config_ios.h. + + +Notes -- CoreBluetooth.framework +============================================================================== + +SDL_JOYSTICK_HIDAPI is disabled by default. It can give you access to a lot +more game controller devices, but it requires permission from the user before +your app will be able to talk to the Bluetooth hardware. "Made For iOS" +branded controllers do not need this as we don't have to speak to them +directly with raw bluetooth, so many apps can live without this. + +You'll need to link with CoreBluetooth.framework and add something like this +to your Info.plist: + +NSBluetoothPeripheralUsageDescription +MyApp would like to remain connected to nearby bluetooth Game Controllers and Game Pads even when you're not using the app. + + +Game Center +============================================================================== + +Game Center integration might require that you break up your main loop in order to yield control back to the system. In other words, instead of running an endless main loop, you run each frame in a callback function, using: + + int SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam); + +This will set up the given function to be called back on the animation callback, and then you have to return from main() to let the Cocoa event loop run. + +e.g. + + extern "C" + void ShowFrame(void*) + { + ... do event handling, frame logic and rendering ... + } + + int main(int argc, char *argv[]) + { + ... initialize game ... + + #ifdef __IOS__ + // Initialize the Game Center for scoring and matchmaking + InitGameCenter(); + + // Set up the game to run in the window animation callback on iOS + // so that Game Center and so forth works correctly. + SDL_iPhoneSetAnimationCallback(window, 1, ShowFrame, NULL); + #else + while ( running ) { + ShowFrame(0); + DelayFrame(); + } + #endif + return 0; + } + + +Deploying to older versions of iOS +============================================================================== + +SDL supports deploying to older versions of iOS than are supported by the latest version of Xcode, all the way back to iOS 8.0 + +In order to do that you need to download an older version of Xcode: +https://developer.apple.com/download/more/?name=Xcode + +Open the package contents of the older Xcode and your newer version of Xcode and copy over the folders in Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport + +Then open the file Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist and add the versions of iOS you want to deploy to the key Root/DefaultProperties/DEPLOYMENT_TARGET_SUGGESTED_VALUES + +Open your project and set your deployment target to the desired version of iOS + +Finally, remove GameController from the list of frameworks linked by your application and edit the build settings for "Other Linker Flags" and add -weak_framework GameController diff --git a/docs/README-kmsbsd.md b/docs/README-kmsbsd.md index 1bd6d0fd..76048170 100644 --- a/docs/README-kmsbsd.md +++ b/docs/README-kmsbsd.md @@ -1,27 +1,27 @@ -KMSDRM on *BSD -================================================== - -KMSDRM is supported on FreeBSD and OpenBSD. DragonFlyBSD works but requires being a root user. NetBSD isn't supported yet because the application will crash when creating the KMSDRM screen. - -WSCONS support has been brought back, but only as an input backend. It will not be brought back as a video backend to ease maintenance. - -OpenBSD note: Note that the video backend assumes that the user has read/write permissions to the /dev/drm* devices. - - -SDL WSCONS input backend features -=================================================== -1. It is keymap-aware; it will work properly with different keymaps. -2. It has mouse support. -3. Accent input is supported. -4. Compose keys are supported. -5. AltGr and Meta Shift keys work as intended. - -Partially working or no input on OpenBSD/NetBSD. -================================================== - -The WSCONS input backend needs read/write access to the /dev/wskbd* devices, without which it will not work properly. /dev/wsmouse must also be read/write accessible, otherwise mouse input will not work. - -Partially working or no input on FreeBSD. -================================================== - -The evdev devices are only accessible to the root user by default. Edit devfs rules to allow access to such devices. The /dev/kbd* devices are also only accessible to the root user by default. Edit devfs rules to allow access to such devices. +KMSDRM on *BSD +================================================== + +KMSDRM is supported on FreeBSD and OpenBSD. DragonFlyBSD works but requires being a root user. NetBSD isn't supported yet because the application will crash when creating the KMSDRM screen. + +WSCONS support has been brought back, but only as an input backend. It will not be brought back as a video backend to ease maintenance. + +OpenBSD note: Note that the video backend assumes that the user has read/write permissions to the /dev/drm* devices. + + +SDL WSCONS input backend features +=================================================== +1. It is keymap-aware; it will work properly with different keymaps. +2. It has mouse support. +3. Accent input is supported. +4. Compose keys are supported. +5. AltGr and Meta Shift keys work as intended. + +Partially working or no input on OpenBSD/NetBSD. +================================================== + +The WSCONS input backend needs read/write access to the /dev/wskbd* devices, without which it will not work properly. /dev/wsmouse must also be read/write accessible, otherwise mouse input will not work. + +Partially working or no input on FreeBSD. +================================================== + +The evdev devices are only accessible to the root user by default. Edit devfs rules to allow access to such devices. The /dev/kbd* devices are also only accessible to the root user by default. Edit devfs rules to allow access to such devices. diff --git a/docs/README-linux.md b/docs/README-linux.md index 768a7125..4ef7b97d 100644 --- a/docs/README-linux.md +++ b/docs/README-linux.md @@ -1,88 +1,88 @@ -Linux -================================================================================ - -By default SDL will only link against glibc, the rest of the features will be -enabled dynamically at runtime depending on the available features on the target -system. So, for example if you built SDL with XRandR support and the target -system does not have the XRandR libraries installed, it will be disabled -at runtime, and you won't get a missing library error, at least with the -default configuration parameters. - - -Build Dependencies --------------------------------------------------------------------------------- - -Ubuntu 18.04, all available features enabled: - - sudo apt-get install build-essential git make \ - pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ - libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ - libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ - libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ - libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev - -Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev` to that command line. - -Fedora 35, all available features enabled: - - sudo yum install gcc git-core make cmake \ - alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ - libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ - libXi-devel libXScrnSaver-devel dbus-devel ibus-devel fcitx-devel \ - systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ - mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ - libdrm-devel mesa-libgbm-devel libusb-devel libdecor-devel \ - pipewire-jack-audio-connection-kit-devel \ - -NOTES: -- The sndio audio target is unavailable on Fedora (but probably not what you - should want to use anyhow). - - -Joystick does not work --------------------------------------------------------------------------------- - -If you compiled or are using a version of SDL with udev support (and you should!) -there's a few issues that may cause SDL to fail to detect your joystick. To -debug this, start by installing the evtest utility. On Ubuntu/Debian: - - sudo apt-get install evtest - -Then run: - - sudo evtest - -You'll hopefully see your joystick listed along with a name like "/dev/input/eventXX" -Now run: - - cat /dev/input/event/XX - -If you get a permission error, you need to set a udev rule to change the mode of -your device (see below) - -Also, try: - - sudo udevadm info --query=all --name=input/eventXX - -If you see a line stating ID_INPUT_JOYSTICK=1, great, if you don't see it, -you need to set up an udev rule to force this variable. - -A combined rule for the Saitek Pro Flight Rudder Pedals to fix both issues looks -like: - - SUBSYSTEM=="input", ATTRS{idProduct}=="0763", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1" - SUBSYSTEM=="input", ATTRS{idProduct}=="0764", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1" - -You can set up similar rules for your device by changing the values listed in -idProduct and idVendor. To obtain these values, try: - - sudo udevadm info -a --name=input/eventXX | grep idVendor - sudo udevadm info -a --name=input/eventXX | grep idProduct - -If multiple values come up for each of these, the one you want is the first one of each. - -On other systems which ship with an older udev (such as CentOS), you may need -to set up a rule such as: - - SUBSYSTEM=="input", ENV{ID_CLASS}=="joystick", ENV{ID_INPUT_JOYSTICK}="1" - +Linux +================================================================================ + +By default SDL will only link against glibc, the rest of the features will be +enabled dynamically at runtime depending on the available features on the target +system. So, for example if you built SDL with XRandR support and the target +system does not have the XRandR libraries installed, it will be disabled +at runtime, and you won't get a missing library error, at least with the +default configuration parameters. + + +Build Dependencies +-------------------------------------------------------------------------------- + +Ubuntu 18.04, all available features enabled: + + sudo apt-get install build-essential git make \ + pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ + libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ + libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev + +Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev` to that command line. + +Fedora 35, all available features enabled: + + sudo yum install gcc git-core make cmake \ + alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ + libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ + libXi-devel libXScrnSaver-devel dbus-devel ibus-devel fcitx-devel \ + systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ + mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ + libdrm-devel mesa-libgbm-devel libusb-devel libdecor-devel \ + pipewire-jack-audio-connection-kit-devel \ + +NOTES: +- The sndio audio target is unavailable on Fedora (but probably not what you + should want to use anyhow). + + +Joystick does not work +-------------------------------------------------------------------------------- + +If you compiled or are using a version of SDL with udev support (and you should!) +there's a few issues that may cause SDL to fail to detect your joystick. To +debug this, start by installing the evtest utility. On Ubuntu/Debian: + + sudo apt-get install evtest + +Then run: + + sudo evtest + +You'll hopefully see your joystick listed along with a name like "/dev/input/eventXX" +Now run: + + cat /dev/input/event/XX + +If you get a permission error, you need to set a udev rule to change the mode of +your device (see below) + +Also, try: + + sudo udevadm info --query=all --name=input/eventXX + +If you see a line stating ID_INPUT_JOYSTICK=1, great, if you don't see it, +you need to set up an udev rule to force this variable. + +A combined rule for the Saitek Pro Flight Rudder Pedals to fix both issues looks +like: + + SUBSYSTEM=="input", ATTRS{idProduct}=="0763", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1" + SUBSYSTEM=="input", ATTRS{idProduct}=="0764", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1" + +You can set up similar rules for your device by changing the values listed in +idProduct and idVendor. To obtain these values, try: + + sudo udevadm info -a --name=input/eventXX | grep idVendor + sudo udevadm info -a --name=input/eventXX | grep idProduct + +If multiple values come up for each of these, the one you want is the first one of each. + +On other systems which ship with an older udev (such as CentOS), you may need +to set up a rule such as: + + SUBSYSTEM=="input", ENV{ID_CLASS}=="joystick", ENV{ID_INPUT_JOYSTICK}="1" + diff --git a/docs/README-macos.md b/docs/README-macos.md index d66aa428..832278fa 100644 --- a/docs/README-macos.md +++ b/docs/README-macos.md @@ -1,252 +1,252 @@ -# macOS - -These instructions are for people using Apple's macOS. - -From the developer's point of view, macOS is a sort of hybrid Mac and -Unix system, and you have the option of using either traditional -command line tools or Apple's IDE Xcode. - -# Command Line Build - -To build SDL using the command line, use the CMake build script: - -```bash -mkdir build -cd build -cmake .. -cmake --build . -sudo cmake --install . -``` - - -You can also build SDL as a Universal library (a single binary for both -64-bit Intel and ARM architectures): - -```bash -mkdir build -cd build -cmake .. "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" -cmake --build . -sudo cmake --install . -``` - -Please note that building SDL requires at least Xcode 6 and the 10.9 SDK. -PowerPC support for macOS has been officially dropped as of SDL 2.0.2. -32-bit Intel and macOS 10.8 runtime support has been officially dropped as -of SDL 2.24.0. - -To use the library once it's built, you essential have two possibilities: -use the traditional autoconf/automake/make method, or use Xcode. - - -# Caveats for using SDL with macOS - -If you register your own NSApplicationDelegate (using [NSApp setDelegate:]), -SDL will not register its own. This means that SDL will not terminate using -SDL_Quit if it receives a termination request, it will terminate like a -normal app, and it will not send a SDL_EVENT_DROP_FILE when you request to open a -file with the app. To solve these issues, put the following code in your -NSApplicationDelegate implementation: - - -```objc -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender -{ - if (SDL_GetEventState(SDL_EVENT_QUIT) == SDL_ENABLE) { - SDL_Event event; - event.type = SDL_EVENT_QUIT; - SDL_PushEvent(&event); - } - - return NSTerminateCancel; -} - -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename -{ - if (SDL_GetEventState(SDL_EVENT_DROP_FILE) == SDL_ENABLE) { - SDL_Event event; - event.type = SDL_EVENT_DROP_FILE; - event.drop.file = SDL_strdup([filename UTF8String]); - return (SDL_PushEvent(&event) > 0); - } - - return NO; -} -``` - -# Using the Simple DirectMedia Layer with a traditional Makefile - -An existing build system for your SDL app has good chances to work almost -unchanged on macOS, as long as you link with the SDL framework. However, -to produce a "real" Mac binary that you can distribute to users, you need -to put the generated binary into a so called "bundle", which is basically -a fancy folder with a name like "MyCoolGame.app". - -To get this build automatically, add something like the following rule to -your Makefile.am: - -```make -bundle_contents = APP_NAME.app/Contents -APP_NAME_bundle: EXE_NAME - mkdir -p $(bundle_contents)/MacOS - mkdir -p $(bundle_contents)/Resources - echo "APPL????" > $(bundle_contents)/PkgInfo - $(INSTALL_PROGRAM) $< $(bundle_contents)/MacOS/ -``` - -You should replace `EXE_NAME` with the name of the executable. `APP_NAME` is -what will be visible to the user in the Finder. Usually it will be the same -as `EXE_NAME` but capitalized. E.g. if `EXE_NAME` is "testgame" then `APP_NAME` -usually is "TestGame". You might also want to use `@PACKAGE@` to use the -package name as specified in your configure.ac file. - -If your project builds more than one application, you will have to do a bit -more. For each of your target applications, you need a separate rule. - -If you want the created bundles to be installed, you may want to add this -rule to your Makefile.am: - -```make -install-exec-hook: APP_NAME_bundle - rm -rf $(DESTDIR)$(prefix)/Applications/APP_NAME.app - mkdir -p $(DESTDIR)$(prefix)/Applications/ - cp -r $< /$(DESTDIR)$(prefix)Applications/ -``` - -This rule takes the Bundle created by the rule from step 3 and installs them -into "$(DESTDIR)$(prefix)/Applications/". - -Again, if you want to install multiple applications, you will have to augment -the make rule accordingly. - -But beware! That is only part of the story! With the above, you end up with -a barebones .app bundle, which is double-clickable from the Finder. But -there are some more things you should do before shipping your product... - -1. You'll need to copy the SDL framework into the Contents/Frameworks - folder in your bundle, so it is included along with your application. - -2. Add an 'Info.plist' to your application. That is a special XML file which - contains some meta-information about your application (like some copyright - information, the version of your app, the name of an optional icon file, - and other things). Part of that information is displayed by the Finder - when you click on the .app, or if you look at the "Get Info" window. - More information about Info.plist files can be found on Apple's homepage. - - -As a final remark, let me add that I use some of the techniques (and some -variations of them) in [Exult](https://github.com/exult/exult) and -[ScummVM](https://github.com/scummvm/scummvm); both are available in source on -the net, so feel free to take a peek at them for inspiration! - - -# Using the Simple DirectMedia Layer with Xcode - -These instructions are for using Apple's Xcode IDE to build SDL applications. - -## First steps - -The first thing to do is to unpack the Xcode.tar.gz archive in the -top level SDL directory (where the Xcode.tar.gz archive resides). -Because Stuffit Expander will unpack the archive into a subdirectory, -you should unpack the archive manually from the command line: - -```bash -cd [path_to_SDL_source] -tar zxf Xcode.tar.gz -``` - -This will create a new folder called Xcode, which you can browse -normally from the Finder. - -## Building the Framework - -The SDL Library is packaged as a framework bundle, an organized -relocatable folder hierarchy of executable code, interface headers, -and additional resources. For practical purposes, you can think of a -framework as a more user and system-friendly shared library, whose library -file behaves more or less like a standard UNIX shared library. - -To build the framework, simply open the framework project and build it. -By default, the framework bundle "SDL.framework" is installed in -/Library/Frameworks. Therefore, the testers and project stationary expect -it to be located there. However, it will function the same in any of the -following locations: - -* ~/Library/Frameworks -* /Local/Library/Frameworks -* /System/Library/Frameworks - -## Build Options - -There are two "Build Styles" (See the "Targets" tab) for SDL. -"Deployment" should be used if you aren't tweaking the SDL library. -"Development" should be used to debug SDL apps or the library itself. - -## Building the Testers - -Open the SDLTest project and build away! - -## Using the Project Stationary - -Copy the stationary to the indicated folders to access it from -the "New Project" and "Add target" menus. What could be easier? - -## Setting up a new project by hand - -Some of you won't want to use the Stationary so I'll give some tips: - -(this is accurate as of Xcode 12.5.) - -* Click "File" -> "New" -> "Project... -* Choose "macOS" and then "App" from the "Application" section. -* Fill out the options in the next window. User interface is "XIB" and - Language is "Objective-C". -* Remove "main.m" from your project -* Remove "MainMenu.xib" from your project -* Remove "AppDelegates.*" from your project -* Add "\$(HOME)/Library/Frameworks/SDL.framework/Headers" to include path -* Add "\$(HOME)/Library/Frameworks" to the frameworks search path -* Add "-framework SDL -framework Foundation -framework AppKit" to "OTHER_LDFLAGS" -* Add your files -* Clean and build - -## Building from command line - -Use `xcode-build` in the same directory as your .pbxproj file - -## Running your app - -You can send command line args to your app by either invoking it from -the command line (in *.app/Contents/MacOS) or by entering them in the -Executables" panel of the target settings. - -# Implementation Notes - -Some things that may be of interest about how it all works... - -## Working directory - -In SDL 1.2, the working directory of your SDL app is by default set to its -parent, but this is no longer the case in SDL 2.0. SDL2 does change the -working directory, which means it'll be whatever the command line prompt -that launched the program was using, or if launched by double-clicking in -the finger, it will be "/", the _root of the filesystem_. Plan accordingly! -You can use SDL_GetBasePath() to find where the program is running from and -chdir() there directly. - - -## You have a Cocoa App! - -Your SDL app is essentially a Cocoa application. When your app -starts up and the libraries finish loading, a Cocoa procedure is called, -which sets up the working directory and calls your main() method. -You are free to modify your Cocoa app with generally no consequence -to SDL. You cannot, however, easily change the SDL window itself. -Functionality may be added in the future to help this. - -# Bug reports - -Bugs are tracked at [the GitHub issue tracker](https://github.com/libsdl-org/SDL/issues/). -Please feel free to report bugs there! - +# macOS + +These instructions are for people using Apple's macOS. + +From the developer's point of view, macOS is a sort of hybrid Mac and +Unix system, and you have the option of using either traditional +command line tools or Apple's IDE Xcode. + +# Command Line Build + +To build SDL using the command line, use the CMake build script: + +```bash +mkdir build +cd build +cmake .. +cmake --build . +sudo cmake --install . +``` + + +You can also build SDL as a Universal library (a single binary for both +64-bit Intel and ARM architectures): + +```bash +mkdir build +cd build +cmake .. "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" +cmake --build . +sudo cmake --install . +``` + +Please note that building SDL requires at least Xcode 6 and the 10.9 SDK. +PowerPC support for macOS has been officially dropped as of SDL 2.0.2. +32-bit Intel and macOS 10.8 runtime support has been officially dropped as +of SDL 2.24.0. + +To use the library once it's built, you essential have two possibilities: +use the traditional autoconf/automake/make method, or use Xcode. + + +# Caveats for using SDL with macOS + +If you register your own NSApplicationDelegate (using [NSApp setDelegate:]), +SDL will not register its own. This means that SDL will not terminate using +SDL_Quit if it receives a termination request, it will terminate like a +normal app, and it will not send a SDL_EVENT_DROP_FILE when you request to open a +file with the app. To solve these issues, put the following code in your +NSApplicationDelegate implementation: + + +```objc +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + if (SDL_GetEventState(SDL_EVENT_QUIT) == SDL_ENABLE) { + SDL_Event event; + event.type = SDL_EVENT_QUIT; + SDL_PushEvent(&event); + } + + return NSTerminateCancel; +} + +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + if (SDL_GetEventState(SDL_EVENT_DROP_FILE) == SDL_ENABLE) { + SDL_Event event; + event.type = SDL_EVENT_DROP_FILE; + event.drop.file = SDL_strdup([filename UTF8String]); + return (SDL_PushEvent(&event) > 0); + } + + return NO; +} +``` + +# Using the Simple DirectMedia Layer with a traditional Makefile + +An existing build system for your SDL app has good chances to work almost +unchanged on macOS, as long as you link with the SDL framework. However, +to produce a "real" Mac binary that you can distribute to users, you need +to put the generated binary into a so called "bundle", which is basically +a fancy folder with a name like "MyCoolGame.app". + +To get this build automatically, add something like the following rule to +your Makefile.am: + +```make +bundle_contents = APP_NAME.app/Contents +APP_NAME_bundle: EXE_NAME + mkdir -p $(bundle_contents)/MacOS + mkdir -p $(bundle_contents)/Resources + echo "APPL????" > $(bundle_contents)/PkgInfo + $(INSTALL_PROGRAM) $< $(bundle_contents)/MacOS/ +``` + +You should replace `EXE_NAME` with the name of the executable. `APP_NAME` is +what will be visible to the user in the Finder. Usually it will be the same +as `EXE_NAME` but capitalized. E.g. if `EXE_NAME` is "testgame" then `APP_NAME` +usually is "TestGame". You might also want to use `@PACKAGE@` to use the +package name as specified in your configure.ac file. + +If your project builds more than one application, you will have to do a bit +more. For each of your target applications, you need a separate rule. + +If you want the created bundles to be installed, you may want to add this +rule to your Makefile.am: + +```make +install-exec-hook: APP_NAME_bundle + rm -rf $(DESTDIR)$(prefix)/Applications/APP_NAME.app + mkdir -p $(DESTDIR)$(prefix)/Applications/ + cp -r $< /$(DESTDIR)$(prefix)Applications/ +``` + +This rule takes the Bundle created by the rule from step 3 and installs them +into "$(DESTDIR)$(prefix)/Applications/". + +Again, if you want to install multiple applications, you will have to augment +the make rule accordingly. + +But beware! That is only part of the story! With the above, you end up with +a barebones .app bundle, which is double-clickable from the Finder. But +there are some more things you should do before shipping your product... + +1. You'll need to copy the SDL framework into the Contents/Frameworks + folder in your bundle, so it is included along with your application. + +2. Add an 'Info.plist' to your application. That is a special XML file which + contains some meta-information about your application (like some copyright + information, the version of your app, the name of an optional icon file, + and other things). Part of that information is displayed by the Finder + when you click on the .app, or if you look at the "Get Info" window. + More information about Info.plist files can be found on Apple's homepage. + + +As a final remark, let me add that I use some of the techniques (and some +variations of them) in [Exult](https://github.com/exult/exult) and +[ScummVM](https://github.com/scummvm/scummvm); both are available in source on +the net, so feel free to take a peek at them for inspiration! + + +# Using the Simple DirectMedia Layer with Xcode + +These instructions are for using Apple's Xcode IDE to build SDL applications. + +## First steps + +The first thing to do is to unpack the Xcode.tar.gz archive in the +top level SDL directory (where the Xcode.tar.gz archive resides). +Because Stuffit Expander will unpack the archive into a subdirectory, +you should unpack the archive manually from the command line: + +```bash +cd [path_to_SDL_source] +tar zxf Xcode.tar.gz +``` + +This will create a new folder called Xcode, which you can browse +normally from the Finder. + +## Building the Framework + +The SDL Library is packaged as a framework bundle, an organized +relocatable folder hierarchy of executable code, interface headers, +and additional resources. For practical purposes, you can think of a +framework as a more user and system-friendly shared library, whose library +file behaves more or less like a standard UNIX shared library. + +To build the framework, simply open the framework project and build it. +By default, the framework bundle "SDL.framework" is installed in +/Library/Frameworks. Therefore, the testers and project stationary expect +it to be located there. However, it will function the same in any of the +following locations: + +* ~/Library/Frameworks +* /Local/Library/Frameworks +* /System/Library/Frameworks + +## Build Options + +There are two "Build Styles" (See the "Targets" tab) for SDL. +"Deployment" should be used if you aren't tweaking the SDL library. +"Development" should be used to debug SDL apps or the library itself. + +## Building the Testers + +Open the SDLTest project and build away! + +## Using the Project Stationary + +Copy the stationary to the indicated folders to access it from +the "New Project" and "Add target" menus. What could be easier? + +## Setting up a new project by hand + +Some of you won't want to use the Stationary so I'll give some tips: + +(this is accurate as of Xcode 12.5.) + +* Click "File" -> "New" -> "Project... +* Choose "macOS" and then "App" from the "Application" section. +* Fill out the options in the next window. User interface is "XIB" and + Language is "Objective-C". +* Remove "main.m" from your project +* Remove "MainMenu.xib" from your project +* Remove "AppDelegates.*" from your project +* Add "\$(HOME)/Library/Frameworks/SDL.framework/Headers" to include path +* Add "\$(HOME)/Library/Frameworks" to the frameworks search path +* Add "-framework SDL -framework Foundation -framework AppKit" to "OTHER_LDFLAGS" +* Add your files +* Clean and build + +## Building from command line + +Use `xcode-build` in the same directory as your .pbxproj file + +## Running your app + +You can send command line args to your app by either invoking it from +the command line (in *.app/Contents/MacOS) or by entering them in the +Executables" panel of the target settings. + +# Implementation Notes + +Some things that may be of interest about how it all works... + +## Working directory + +In SDL 1.2, the working directory of your SDL app is by default set to its +parent, but this is no longer the case in SDL 2.0. SDL2 does change the +working directory, which means it'll be whatever the command line prompt +that launched the program was using, or if launched by double-clicking in +the finger, it will be "/", the _root of the filesystem_. Plan accordingly! +You can use SDL_GetBasePath() to find where the program is running from and +chdir() there directly. + + +## You have a Cocoa App! + +Your SDL app is essentially a Cocoa application. When your app +starts up and the libraries finish loading, a Cocoa procedure is called, +which sets up the working directory and calls your main() method. +You are free to modify your Cocoa app with generally no consequence +to SDL. You cannot, however, easily change the SDL window itself. +Functionality may be added in the future to help this. + +# Bug reports + +Bugs are tracked at [the GitHub issue tracker](https://github.com/libsdl-org/SDL/issues/). +Please feel free to report bugs there! + diff --git a/docs/README-main-functions.md b/docs/README-main-functions.md new file mode 100644 index 00000000..a9d8fca5 --- /dev/null +++ b/docs/README-main-functions.md @@ -0,0 +1,194 @@ +# Where an SDL program starts running. + +## History + +SDL has a long, complicated history with starting a program. + +In most of the civilized world, an application starts in a C-callable +function named "main". You probably learned it a long time ago: + +```c +int main(int argc, char **argv) +{ + printf("Hello world!\n"); + return 0; +} +``` + +But not all platforms work like this. Windows apps might want a different +function named "WinMain", for example, so SDL set out to paper over this +difference. + +Generally how this would work is: your app would always use the "standard" +`main(argc, argv)` function as its entry point, and `#include` the proper +SDL header before that, which did some macro magic. On platforms that used +a standard `main`, it would do nothing and what you saw was what you got. + +But those other platforms! If they needed something that _wasn't_ `main`, +SDL's macro magic would quietly rename your function to `SDL_main`, and +provide its own entry point that called it. Your app was none the wiser and +your code worked everywhere without changes. + + +## The main entry point in SDL3 + +Previous versions of SDL had a static library, SDLmain, that you would link +your app against. SDL3 still has the same macro tricks, but the static library +is gone. Now it's supplied by a "single-header library," which means you +`#include ` and that header will insert a small amount of +code into the source file that included it, so you no longer have to worry +about linking against an extra library that you might need on some platforms. +You just build your app and it works. + +You should _only_ include SDL_main.h from one file (the umbrella header, +SDL.h, does _not_ include it), and know that it will `#define main` to +something else, so if you use this symbol elsewhere as a variable name, etc, +it can cause you unexpected problems. + +SDL_main.h will also include platform-specific code (WinMain or whatnot) that +calls your _actual_ main function. This is compiled directly into your +program. + +If for some reason you need to include SDL_main.h in a file but also _don't_ +want it to generate this platform-specific code, you should define a special +macro before including the header: + + +```c +#define SDL_MAIN_NOIMPL +``` + +If you are moving from SDL2, remove any references to the SDLmain static +library from your build system, and you should be done. Things should work as +they always have. + +If you have never controlled your process's entry point (you are using SDL +as a module from a general-purpose scripting language interpreter, or you're +using SDL in a plugin for some otherwise-unrelated app), then there is nothing +required of you here; there is no startup code in SDL's entry point code that +is required, so using SDL_main.h is completely optional. Just start using +the SDL API when you are ready. + + +## Main callbacks in SDL3 + +There is a second option in SDL3 for how to structure your program. This is +completely optional and you can ignore it if you're happy using a standard +"main" function. + +Some platforms would rather your program operate in chunks. Most of the time, +games tend to look like this at the highest level: + +```c +int main(int argc, char **argv) +{ + initialize(); + while (keep_running()) { + handle_new_events(); + do_one_frame_of_stuff(); + } + deinitialize(); +} +``` + +There are platforms that would rather be in charge of that `while` loop: +iOS would rather you return from main() immediately and then it will let you +know that it's time to update and draw the next frame of video. Emscripten +(programs that run on a web page) absolutely requires this to function at all. +Video targets like Wayland can notify the app when to draw a new frame, to +save battery life and cooperate with the compositor more closely. + +In most cases, you can add special-case code to your program to deal with this +on different platforms, but SDL3 offers a system to handle this transparently on +the app's behalf. + +To use this, you have to redesign the highest level of your app a little. Once +you do, it'll work on all supported SDL platforms without problems and +`#ifdef`s in your code. + +Instead of providing a "main" function, under this system, you would provide +several functions that SDL will call as appropriate. + +Using the callback entry points works on every platform, because on platforms +that don't require them, we can fake them with a simple loop in an internal +implementation of the usual SDL_main. + +The primary way we expect people to write SDL apps is still with SDL_main, and +this is not intended to replace it. If the app chooses to use this, it just +removes some platform-specific details they might have to otherwise manage, +and maybe removes a barrier to entry on some future platform. And you might +find you enjoy structuring your program like this more! + + +## How to use main callbacks in SDL3 + +To enable the callback entry points, you include SDL_main.h with an extra define, +from a single source file in your project: + +```c +#define SDL_MAIN_USE_CALLBACKS +#include +``` + +Once you do this, you do not write a "main" function at all (and if you do, +the app will likely fail to link). Instead, you provide the following +functions: + +First: + +```c +int SDL_AppInit(int argc, char **argv); +``` + +This will be called _once_ before anything else. argc/argv work like they +always do. If this returns 0, the app runs. If it returns < 0, the app calls +SDL_AppQuit and terminates with an exit code that reports an error to the +platform. If it returns > 0, the app calls SDL_AppQuit and terminates with +an exit code that reports success to the platform. This function should not +go into an infinite mainloop; it should do any one-time startup it requires +and then return. + +Then: + +```c +int SDL_AppIterate(void); +``` + +This is called over and over, possibly at the refresh rate of the display or +some other metric that the platform dictates. This is where the heart of your +app runs. It should return as quickly as reasonably possible, but it's not a +"run one memcpy and that's all the time you have" sort of thing. The app +should do any game updates, and render a frame of video. If it returns < 0, +SDL will call SDL_AppQuit and terminate the process with an exit code that +reports an error to the platform. If it returns > 0, the app calls +SDL_AppQuit and terminates with an exit code that reports success to the +platform. If it returns 0, then SDL_AppIterate will be called again at some +regular frequency. The platform may choose to run this more or less (perhaps +less in the background, etc), or it might just call this function in a loop +as fast as possible. You do not check the event queue in this function +(SDL_AppEvent exists for that). + +Next: + +```c +int SDL_AppEvent(const SDL_Event *event); +``` + +This will be called whenever an SDL event arrives, on the thread that runs +SDL_AppIterate. Your app should also not call SDL_PollEvent, SDL_PumpEvent, +etc, as SDL will manage all this for you. Return values are the same as from +SDL_AppIterate(), so you can terminate in response to SDL_EVENT_QUIT, etc. + + +Finally: + +```c +void SDL_AppQuit(void); +``` + +This is called once before terminating the app--assuming the app isn't being +forcibly killed or crashed--as a last chance to clean up. After this returns, +SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app +to call it, too). Process termination proceeds as if the app returned normally +from main(), so atexit handles will run, if your platform supports that. + diff --git a/docs/README-migration.md b/docs/README-migration.md index c4dd52b5..959fd0de 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -1,1288 +1,1521 @@ -# Migrating to SDL 3.0 - -This guide provides useful information for migrating applications from SDL 2.0 to SDL 3.0. - -Details on API changes are organized by SDL 2.0 header below. - -The file with your main() function should include , as that is no longer included in SDL.h. - -Many functions and symbols have been renamed. We have provided a handy Python script [rename_symbols.py](https://github.com/libsdl-org/SDL/blob/main/build-scripts/rename_symbols.py) to rename SDL2 functions to their SDL3 counterparts: -```sh -rename_symbols.py --all-symbols source_code_path -``` - -It's also possible to apply a semantic patch to migrate more easily to SDL3: [SDL_migration.cocci](https://github.com/libsdl-org/SDL/blob/main/build-scripts/SDL_migration.cocci) - -SDL headers should now be included as `#include `. Typically that's the only header you'll need in your application unless you are using OpenGL or Vulkan functionality. We have provided a handy Python script [rename_headers.py](https://github.com/libsdl-org/SDL/blob/main/build-scripts/rename_headers.py) to rename SDL2 headers to their SDL3 counterparts: -```sh -rename_headers.py source_code_path -``` - -CMake users should use this snippet to include SDL support in their project: -``` -find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) -target_link_libraries(mygame PRIVATE SDL3::SDL3) -``` - -Autotools users should use this snippet to include SDL support in their project: -``` -PKG_CHECK_MODULES([SDL3], [sdl3]) -``` -and then add $SDL3_CFLAGS to their project CFLAGS and $SDL3_LIBS to their project LDFLAGS - -Makefile users can use this snippet to include SDL support in their project: -``` -CFLAGS += $(shell pkg-config sdl3 --cflags) -LDFLAGS += $(shell pkg-config sdl3 --libs) -``` - -The SDL3test library has been renamed SDL3_test. - -The SDLmain library has been removed, it's been entirely replaced by SDL_main.h. - -The vi format comments have been removed from source code. Vim users can use the [editorconfig plugin](https://github.com/editorconfig/editorconfig-vim) to automatically set tab spacing for the SDL coding style. - -## SDL_atomic.h - -The following structures have been renamed: -- SDL_atomic_t => SDL_AtomicInt - -## SDL_audio.h - -The audio subsystem in SDL3 is dramatically different than SDL2. The primary way to play audio is no longer an audio callback; instead you bind SDL_AudioStreams to devices; however, there is still a callback method available if needed. - -The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface. - -SDL3 will not implicitly initialize the audio subsystem on your behalf if you open a device without doing so. Please explicitly call SDL_Init(SDL_INIT_AUDIO) at some point. - -SDL3's audio subsystem offers an enormous amount of power over SDL2, but if you just want a simple migration of your existing code, you can ignore most of it. The simplest migration path from SDL2 looks something like this: - -In SDL2, you might have done something like this to play audio... - -```c - void SDLCALL MyAudioCallback(void *userdata, Uint8 * stream, int len) - { - /* calculate a little more audio here, maybe using `userdata`, write it to `stream` */ - } - - /* ...somewhere near startup... */ - SDL_AudioSpec my_desired_audio_format; - SDL_zero(my_desired_audio_format); - my_desired_audio_format.format = AUDIO_S16; - my_desired_audio_format.channels = 2; - my_desired_audio_format.freq = 44100; - my_desired_audio_format.samples = 1024; - my_desired_audio_format.callback = MyAudioCallback; - my_desired_audio_format.userdata = &my_audio_callback_user_data; - SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(NULL, 0, &my_desired_audio_format, NULL, 0); - SDL_PauseAudioDevice(my_audio_device, 0); -``` - -...in SDL3, you can do this... - -```c - void SDLCALL MyAudioCallback(SDL_AudioStream *stream, int len, void *userdata) - { - /* calculate a little more audio here, maybe using `userdata`, write it to `stream` */ - SDL_PutAudioStreamData(stream, newdata, len); - } - - /* ...somewhere near startup... */ - const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; - SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, MyAudioCallback, &my_audio_callback_user_data); - SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); -``` - -If you used SDL_QueueAudio instead of a callback in SDL2, this is also straightforward. - -```c - /* ...somewhere near startup... */ - const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; - SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL, NULL); - SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); - - /* ...in your main loop... */ - /* calculate a little more audio into `buf`, add it to `stream` */ - SDL_PutAudioStreamData(stream, buf, buflen); - -``` - -...these same migration examples apply to audio capture, just using SDL_GetAudioStreamData instead of SDL_PutAudioStreamData. - -SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. - -The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app. - -SDL_AudioDeviceID now represents both an open audio device's handle (a "logical" device) and the instance ID that the hardware owns as long as it exists on the system (a "physical" device). The separation between device instances and device indexes is gone, and logical and physical devices are almost entirely interchangeable at the API level. - -Devices are opened by physical device instance ID, and a new logical instance ID is generated by the open operation; This allows any device to be opened multiple times, possibly by unrelated pieces of code. SDL will manage the logical devices to provide a single stream of audio to the physical device behind the scenes. - -Devices are not opened by an arbitrary string name anymore, but by device instance ID (or magic numbers to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a default device ID and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it. - -Many functions that would accept a device index and an `iscapture` parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into output and capture device lists. - -Rather than iterating over audio devices using a device index, there are new functions, SDL_GetAudioOutputDevices() and SDL_GetAudioCaptureDevices(), to get the current list of devices, and new functions to get information about devices from their instance ID: - -```c -{ - if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { - int i, num_devices; - SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(&num_devices); - if (devices) { - for (i = 0; i < num_devices; ++i) { - SDL_AudioDeviceID instance_id = devices[i]; - char *name = SDL_GetAudioDeviceName(instance_id); - SDL_Log("AudioDevice %" SDL_PRIu32 ": %s\n", instance_id, name); - SDL_free(name); - } - SDL_free(devices); - } - SDL_QuitSubSystem(SDL_INIT_AUDIO); - } -} -``` - -SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. If the app assigns a callback to a specific stream, it can use the stream's lock through SDL_LockAudioStream() if necessary. - -SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_ResumeAudioDevice(). - -Audio devices, opened by SDL_OpenAudioDevice(), no longer start in a paused state, as they don't begin processing audio until a stream is bound. - -SDL_GetAudioDeviceStatus() has been removed; there is now SDL_AudioDevicePaused(). - -SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality. - -APIs that use channel counts used to use a Uint8 for the channel; now they use int. - -SDL_AudioSpec has been reduced; now it only holds format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL2 SDL_AudioSpec fields aren't relevant anymore. - -SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead. - -SDL_GetDefaultAudioInfo() is removed; SDL_GetAudioDeviceFormat() with SDL_AUDIO_DEVICE_DEFAULT_OUTPUT or SDL_AUDIO_DEVICE_DEFAULT_CAPTURE. There is no replacement for querying the default device name; the string is no longer used to open devices, and SDL3 will migrate between physical devices on the fly if the system default changes, so if you must show this to the user, a generic name like "System default" is recommended. - -SDL_MixAudio() has been removed, as it relied on legacy SDL 1.2 quirks; SDL_MixAudioFormat() remains and offers the same functionality. - -SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. - -SDL_FreeWAV has been removed and calls can be replaced with SDL_free. - -SDL_LoadWAV() is a proper function now and no longer a macro (but offers the same functionality otherwise). - -SDL_LoadWAV_RW() and SDL_LoadWAV() return an int now: zero on success, -1 on error, like most of SDL. They no longer return a pointer to an SDL_AudioSpec. - -SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead. - -Code that used to look like this: -```c - SDL_AudioCVT cvt; - SDL_BuildAudioCVT(&cvt, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); - cvt.len = src_len; - cvt.buf = (Uint8 *) SDL_malloc(src_len * cvt.len_mult); - SDL_memcpy(cvt.buf, src_data, src_len); - SDL_ConvertAudio(&cvt); - do_something(cvt.buf, cvt.len_cvt); -``` -should be changed to: -```c - Uint8 *dst_data = NULL; - int dst_len = 0; - const SDL_AudioSpec src_spec = { src_format, src_channels, src_rate }; - const SDL_AudioSpec dst_spec = { dst_format, dst_channels, dst_rate }; - if (SDL_ConvertAudioSamples(&src_spec, src_data, src_len, &dst_spec, &dst_data, &dst_len) < 0) { - /* error */ - } - do_something(dst_data, dst_len); - SDL_free(dst_data); -``` - -AUDIO_U16, AUDIO_U16LSB, AUDIO_U16MSB, and AUDIO_U16SYS have been removed. They were not heavily used, and one could not memset a buffer in this format to silence with a single byte value. Use a different audio format. - -If you need to convert U16 audio data to a still-supported format at runtime, the fastest, lossless conversion is to SDL_AUDIO_S16: - -```c - /* this converts the buffer in-place. The buffer size does not change. */ - Sint16 *audio_ui16_to_si16(Uint16 *buffer, const size_t num_samples) - { - size_t i; - const Uint16 *src = buffer; - Sint16 *dst = (Sint16 *) buffer; - - for (i = 0; i < num_samples; i++) { - dst[i] = (Sint16) (src[i] ^ 0x8000); - } - - return dst; - } -``` - -All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage. - -In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamGet in SDL2). The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. - -In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior. - -In SDL2, SDL_AUDIODEVICEREMOVED events would fire for open devices with the `which` field set to the SDL_AudioDeviceID of the lost device, and in later SDL2 releases, would also fire this event with a `which` field of zero for unopened devices, to signify that the app might want to refresh the available device list. In SDL3, this event works the same, except it won't ever fire with a zero; in this case it'll return the physical device's SDL_AudioDeviceID. Any still-open SDL_AudioDeviceIDs generated from this device with SDL_OpenAudioDevice() will also fire a separate event. - -The following functions have been renamed: -* SDL_AudioStreamAvailable() => SDL_GetAudioStreamAvailable() -* SDL_AudioStreamClear() => SDL_ClearAudioStream() -* SDL_AudioStreamFlush() => SDL_FlushAudioStream() -* SDL_AudioStreamGet() => SDL_GetAudioStreamData() -* SDL_AudioStreamPut() => SDL_PutAudioStreamData() -* SDL_FreeAudioStream() => SDL_DestroyAudioStream() -* SDL_NewAudioStream() => SDL_CreateAudioStream() - - -The following functions have been removed: -* SDL_GetNumAudioDevices() -* SDL_GetAudioDeviceSpec() -* SDL_ConvertAudio() -* SDL_BuildAudioCVT() -* SDL_OpenAudio() -* SDL_CloseAudio() -* SDL_PauseAudio() -* SDL_GetAudioStatus() -* SDL_GetAudioDeviceStatus() -* SDL_GetDefaultAudioInfo() -* SDL_LockAudio() -* SDL_LockAudioDevice() -* SDL_UnlockAudio() -* SDL_UnlockAudioDevice() -* SDL_MixAudio() -* SDL_QueueAudio() -* SDL_DequeueAudio() -* SDL_ClearAudioQueue() -* SDL_GetQueuedAudioSize() - -The following symbols have been renamed: -* AUDIO_F32 => SDL_AUDIO_F32LE -* AUDIO_F32LSB => SDL_AUDIO_F32LE -* AUDIO_F32MSB => SDL_AUDIO_F32BE -* AUDIO_F32SYS => SDL_AUDIO_F32 -* AUDIO_S16 => SDL_AUDIO_S16LE -* AUDIO_S16LSB => SDL_AUDIO_S16LE -* AUDIO_S16MSB => SDL_AUDIO_S16BE -* AUDIO_S16SYS => SDL_AUDIO_S16 -* AUDIO_S32 => SDL_AUDIO_S32LE -* AUDIO_S32LSB => SDL_AUDIO_S32LE -* AUDIO_S32MSB => SDL_AUDIO_S32BE -* AUDIO_S32SYS => SDL_AUDIO_S32 -* AUDIO_S8 => SDL_AUDIO_S8 -* AUDIO_U8 => SDL_AUDIO_U8 - -## SDL_cpuinfo.h - -The intrinsics headers (mmintrin.h, etc.) have been moved to `` and are no longer automatically included in SDL.h. - -SDL_Has3DNow() has been removed; there is no replacement. - -SDL_SIMDAlloc(), SDL_SIMDRealloc(), and SDL_SIMDFree() have been removed. You can use SDL_aligned_alloc() and SDL_aligned_free() with SDL_SIMDGetAlignment() to get the same functionality. - -## SDL_events.h - -The timestamp member of the SDL_Event structure now represents nanoseconds, and is populated with SDL_GetTicksNS() - -The timestamp_us member of the sensor events has been renamed sensor_timestamp and now represents nanoseconds. This value is filled in from the hardware, if available, and may not be synchronized with values returned from SDL_GetTicksNS(). - -You should set the event.common.timestamp field before passing an event to SDL_PushEvent(). If the timestamp is 0 it will be filled in with SDL_GetTicksNS(). - -Mouse events use floating point values for mouse coordinates and relative motion values. You can get sub-pixel motion depending on the platform and display scaling. - -The SDL_DISPLAYEVENT_* events have been moved to top level events, and SDL_DISPLAYEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_DISPLAYEVENT and then checking for display events. You can compare the event >= SDL_EVENT_DISPLAY_FIRST and <= SDL_EVENT_DISPLAY_LAST if you need to see whether it's a display event. - -The SDL_WINDOWEVENT_* events have been moved to top level events, and SDL_WINDOWEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_WINDOWEVENT and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event. - -The SDL_EVENT_WINDOW_RESIZED event is always sent, even in response to SDL_SetWindowSize(). - -The SDL_EVENT_WINDOW_SIZE_CHANGED event has been removed, and you can use SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED to detect window backbuffer size changes. - -The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor. - -SDL_QUERY, SDL_IGNORE, SDL_ENABLE, and SDL_DISABLE have been removed. You can use the functions SDL_SetEventEnabled() and SDL_EventEnabled() to set and query event processing state. - -The following symbols have been renamed: -* SDL_APP_DIDENTERBACKGROUND => SDL_EVENT_DID_ENTER_BACKGROUND -* SDL_APP_DIDENTERFOREGROUND => SDL_EVENT_DID_ENTER_FOREGROUND -* SDL_APP_LOWMEMORY => SDL_EVENT_LOW_MEMORY -* SDL_APP_TERMINATING => SDL_EVENT_TERMINATING -* SDL_APP_WILLENTERBACKGROUND => SDL_EVENT_WILL_ENTER_BACKGROUND -* SDL_APP_WILLENTERFOREGROUND => SDL_EVENT_WILL_ENTER_FOREGROUND -* SDL_AUDIODEVICEADDED => SDL_EVENT_AUDIO_DEVICE_ADDED -* SDL_AUDIODEVICEREMOVED => SDL_EVENT_AUDIO_DEVICE_REMOVED -* SDL_CLIPBOARDUPDATE => SDL_EVENT_CLIPBOARD_UPDATE -* SDL_CONTROLLERAXISMOTION => SDL_EVENT_GAMEPAD_AXIS_MOTION -* SDL_CONTROLLERBUTTONDOWN => SDL_EVENT_GAMEPAD_BUTTON_DOWN -* SDL_CONTROLLERBUTTONUP => SDL_EVENT_GAMEPAD_BUTTON_UP -* SDL_CONTROLLERDEVICEADDED => SDL_EVENT_GAMEPAD_ADDED -* SDL_CONTROLLERDEVICEREMAPPED => SDL_EVENT_GAMEPAD_REMAPPED -* SDL_CONTROLLERDEVICEREMOVED => SDL_EVENT_GAMEPAD_REMOVED -* SDL_CONTROLLERSENSORUPDATE => SDL_EVENT_GAMEPAD_SENSOR_UPDATE -* SDL_CONTROLLERTOUCHPADDOWN => SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN -* SDL_CONTROLLERTOUCHPADMOTION => SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION -* SDL_CONTROLLERTOUCHPADUP => SDL_EVENT_GAMEPAD_TOUCHPAD_UP -* SDL_DROPBEGIN => SDL_EVENT_DROP_BEGIN -* SDL_DROPCOMPLETE => SDL_EVENT_DROP_COMPLETE -* SDL_DROPFILE => SDL_EVENT_DROP_FILE -* SDL_DROPTEXT => SDL_EVENT_DROP_TEXT -* SDL_FINGERDOWN => SDL_EVENT_FINGER_DOWN -* SDL_FINGERMOTION => SDL_EVENT_FINGER_MOTION -* SDL_FINGERUP => SDL_EVENT_FINGER_UP -* SDL_FIRSTEVENT => SDL_EVENT_FIRST -* SDL_JOYAXISMOTION => SDL_EVENT_JOYSTICK_AXIS_MOTION -* SDL_JOYBATTERYUPDATED => SDL_EVENT_JOYSTICK_BATTERY_UPDATED -* SDL_JOYBUTTONDOWN => SDL_EVENT_JOYSTICK_BUTTON_DOWN -* SDL_JOYBUTTONUP => SDL_EVENT_JOYSTICK_BUTTON_UP -* SDL_JOYDEVICEADDED => SDL_EVENT_JOYSTICK_ADDED -* SDL_JOYDEVICEREMOVED => SDL_EVENT_JOYSTICK_REMOVED -* SDL_JOYHATMOTION => SDL_EVENT_JOYSTICK_HAT_MOTION -* SDL_KEYDOWN => SDL_EVENT_KEY_DOWN -* SDL_KEYMAPCHANGED => SDL_EVENT_KEYMAP_CHANGED -* SDL_KEYUP => SDL_EVENT_KEY_UP -* SDL_LASTEVENT => SDL_EVENT_LAST -* SDL_LOCALECHANGED => SDL_EVENT_LOCALE_CHANGED -* SDL_MOUSEBUTTONDOWN => SDL_EVENT_MOUSE_BUTTON_DOWN -* SDL_MOUSEBUTTONUP => SDL_EVENT_MOUSE_BUTTON_UP -* SDL_MOUSEMOTION => SDL_EVENT_MOUSE_MOTION -* SDL_MOUSEWHEEL => SDL_EVENT_MOUSE_WHEEL -* SDL_POLLSENTINEL => SDL_EVENT_POLL_SENTINEL -* SDL_QUIT => SDL_EVENT_QUIT -* SDL_RENDER_DEVICE_RESET => SDL_EVENT_RENDER_DEVICE_RESET -* SDL_RENDER_TARGETS_RESET => SDL_EVENT_RENDER_TARGETS_RESET -* SDL_SENSORUPDATE => SDL_EVENT_SENSOR_UPDATE -* SDL_SYSWMEVENT => SDL_EVENT_SYSWM -* SDL_TEXTEDITING => SDL_EVENT_TEXT_EDITING -* SDL_TEXTEDITING_EXT => SDL_EVENT_TEXT_EDITING_EXT -* SDL_TEXTINPUT => SDL_EVENT_TEXT_INPUT -* SDL_USEREVENT => SDL_EVENT_USER - -The following structures have been renamed: -* SDL_ControllerAxisEvent => SDL_GamepadAxisEvent -* SDL_ControllerButtonEvent => SDL_GamepadButtonEvent -* SDL_ControllerDeviceEvent => SDL_GamepadDeviceEvent -* SDL_ControllerSensorEvent => SDL_GamepadSensorEvent -* SDL_ControllerTouchpadEvent => SDL_GamepadTouchpadEvent - -The following functions have been removed: -* SDL_EventState() - replaced with SDL_SetEventEnabled() -* SDL_GetEventState() - replaced with SDL_EventEnabled() - -## SDL_gamecontroller.h - -SDL_gamecontroller.h has been renamed SDL_gamepad.h, and all APIs have been renamed to match. - -The SDL_EVENT_GAMEPAD_ADDED event now provides the joystick instance ID in the which member of the cdevice event structure. - -The functions SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() have been added to directly query the list of available gamepads. - -SDL_GameControllerGetSensorDataWithTimestamp() has been removed. If you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_GAMEPAD_SENSOR_UPDATE events. - -SDL_CONTROLLER_TYPE_VIRTUAL has been removed, so virtual controllers can emulate other gamepad types. If you need to know whether a controller is virtual, you can use SDL_IsJoystickVirtual(). - -SDL_CONTROLLER_TYPE_AMAZON_LUNA has been removed, and can be replaced with this code: -```c -SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id) -{ - return ((vendor_id == 0x1949 && product_id == 0x0419) || - (vendor_id == 0x0171 && product_id == 0x0419)); -} -``` - -SDL_CONTROLLER_TYPE_GOOGLE_STADIA has been removed, and can be replaced with this code: -```c -SDL_bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id) -{ - return (vendor_id == 0x18d1 && product_id == 0x9400); -} -``` - -SDL_CONTROLLER_TYPE_NVIDIA_SHIELD has been removed, and can be replaced with this code: -```c -SDL_bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id) -{ - return (vendor_id == 0x0955 && (product_id == 0x7210 || product_id == 0x7214)); -} -``` - -The following enums have been renamed: -* SDL_GameControllerAxis => SDL_GamepadAxis -* SDL_GameControllerBindType => SDL_GamepadBindingType -* SDL_GameControllerButton => SDL_GamepadButton -* SDL_GameControllerType => SDL_GamepadType - -The following structures have been renamed: -* SDL_GameController => SDL_Gamepad - -The following functions have been renamed: -* SDL_GameControllerAddMapping() => SDL_AddGamepadMapping() -* SDL_GameControllerAddMappingsFromFile() => SDL_AddGamepadMappingsFromFile() -* SDL_GameControllerAddMappingsFromRW() => SDL_AddGamepadMappingsFromRW() -* SDL_GameControllerClose() => SDL_CloseGamepad() -* SDL_GameControllerFromInstanceID() => SDL_GetGamepadFromInstanceID() -* SDL_GameControllerFromPlayerIndex() => SDL_GetGamepadFromPlayerIndex() -* SDL_GameControllerGetAppleSFSymbolsNameForAxis() => SDL_GetGamepadAppleSFSymbolsNameForAxis() -* SDL_GameControllerGetAppleSFSymbolsNameForButton() => SDL_GetGamepadAppleSFSymbolsNameForButton() -* SDL_GameControllerGetAttached() => SDL_GamepadConnected() -* SDL_GameControllerGetAxis() => SDL_GetGamepadAxis() -* SDL_GameControllerGetAxisFromString() => SDL_GetGamepadAxisFromString() -* SDL_GameControllerGetButton() => SDL_GetGamepadButton() -* SDL_GameControllerGetButtonFromString() => SDL_GetGamepadButtonFromString() -* SDL_GameControllerGetFirmwareVersion() => SDL_GetGamepadFirmwareVersion() -* SDL_GameControllerGetJoystick() => SDL_GetGamepadJoystick() -* SDL_GameControllerGetNumTouchpadFingers() => SDL_GetNumGamepadTouchpadFingers() -* SDL_GameControllerGetNumTouchpads() => SDL_GetNumGamepadTouchpads() -* SDL_GameControllerGetPlayerIndex() => SDL_GetGamepadPlayerIndex() -* SDL_GameControllerGetProduct() => SDL_GetGamepadProduct() -* SDL_GameControllerGetProductVersion() => SDL_GetGamepadProductVersion() -* SDL_GameControllerGetSensorData() => SDL_GetGamepadSensorData() -* SDL_GameControllerGetSensorDataRate() => SDL_GetGamepadSensorDataRate() -* SDL_GameControllerGetSerial() => SDL_GetGamepadSerial() -* SDL_GameControllerGetStringForAxis() => SDL_GetGamepadStringForAxis() -* SDL_GameControllerGetStringForButton() => SDL_GetGamepadStringForButton() -* SDL_GameControllerGetTouchpadFinger() => SDL_GetGamepadTouchpadFinger() -* SDL_GameControllerGetType() => SDL_GetGamepadType() -* SDL_GameControllerGetVendor() => SDL_GetGamepadVendor() -* SDL_GameControllerHasAxis() => SDL_GamepadHasAxis() -* SDL_GameControllerHasButton() => SDL_GamepadHasButton() -* SDL_GameControllerHasLED() => SDL_GamepadHasLED() -* SDL_GameControllerHasRumble() => SDL_GamepadHasRumble() -* SDL_GameControllerHasRumbleTriggers() => SDL_GamepadHasRumbleTriggers() -* SDL_GameControllerHasSensor() => SDL_GamepadHasSensor() -* SDL_GameControllerIsSensorEnabled() => SDL_GamepadSensorEnabled() -* SDL_GameControllerMapping() => SDL_GetGamepadMapping() -* SDL_GameControllerMappingForGUID() => SDL_GetGamepadMappingForGUID() -* SDL_GameControllerMappingForIndex() => SDL_GetGamepadMappingForIndex() -* SDL_GameControllerName() => SDL_GetGamepadName() -* SDL_GameControllerNumMappings() => SDL_GetNumGamepadMappings() -* SDL_GameControllerOpen() => SDL_OpenGamepad() -* SDL_GameControllerPath() => SDL_GetGamepadPath() -* SDL_GameControllerRumble() => SDL_RumbleGamepad() -* SDL_GameControllerRumbleTriggers() => SDL_RumbleGamepadTriggers() -* SDL_GameControllerSendEffect() => SDL_SendGamepadEffect() -* SDL_GameControllerSetLED() => SDL_SetGamepadLED() -* SDL_GameControllerSetPlayerIndex() => SDL_SetGamepadPlayerIndex() -* SDL_GameControllerSetSensorEnabled() => SDL_SetGamepadSensorEnabled() -* SDL_GameControllerUpdate() => SDL_UpdateGamepads() -* SDL_IsGameController() => SDL_IsGamepad() - -The following functions have been removed: -* SDL_GameControllerEventState() - replaced with SDL_SetGamepadEventsEnabled() and SDL_GamepadEventsEnabled() -* SDL_GameControllerGetBindForAxis() - replaced with SDL_GetGamepadBindings() -* SDL_GameControllerGetBindForButton() - replaced with SDL_GetGamepadBindings() -* SDL_GameControllerMappingForDeviceIndex() - replaced with SDL_GetGamepadInstanceMapping() -* SDL_GameControllerNameForIndex() - replaced with SDL_GetGamepadInstanceName() -* SDL_GameControllerPathForIndex() - replaced with SDL_GetGamepadInstancePath() -* SDL_GameControllerTypeForIndex() - replaced with SDL_GetGamepadInstanceType() - -The following symbols have been renamed: -* SDL_CONTROLLER_AXIS_INVALID => SDL_GAMEPAD_AXIS_INVALID -* SDL_CONTROLLER_AXIS_LEFTX => SDL_GAMEPAD_AXIS_LEFTX -* SDL_CONTROLLER_AXIS_LEFTY => SDL_GAMEPAD_AXIS_LEFTY -* SDL_CONTROLLER_AXIS_MAX => SDL_GAMEPAD_AXIS_MAX -* SDL_CONTROLLER_AXIS_RIGHTX => SDL_GAMEPAD_AXIS_RIGHTX -* SDL_CONTROLLER_AXIS_RIGHTY => SDL_GAMEPAD_AXIS_RIGHTY -* SDL_CONTROLLER_AXIS_TRIGGERLEFT => SDL_GAMEPAD_AXIS_LEFT_TRIGGER -* SDL_CONTROLLER_AXIS_TRIGGERRIGHT => SDL_GAMEPAD_AXIS_RIGHT_TRIGGER -* SDL_CONTROLLER_BINDTYPE_AXIS => SDL_GAMEPAD_BINDTYPE_AXIS -* SDL_CONTROLLER_BINDTYPE_BUTTON => SDL_GAMEPAD_BINDTYPE_BUTTON -* SDL_CONTROLLER_BINDTYPE_HAT => SDL_GAMEPAD_BINDTYPE_HAT -* SDL_CONTROLLER_BINDTYPE_NONE => SDL_GAMEPAD_BINDTYPE_NONE -* SDL_CONTROLLER_BUTTON_A => SDL_GAMEPAD_BUTTON_A -* SDL_CONTROLLER_BUTTON_B => SDL_GAMEPAD_BUTTON_B -* SDL_CONTROLLER_BUTTON_BACK => SDL_GAMEPAD_BUTTON_BACK -* SDL_CONTROLLER_BUTTON_DPAD_DOWN => SDL_GAMEPAD_BUTTON_DPAD_DOWN -* SDL_CONTROLLER_BUTTON_DPAD_LEFT => SDL_GAMEPAD_BUTTON_DPAD_LEFT -* SDL_CONTROLLER_BUTTON_DPAD_RIGHT => SDL_GAMEPAD_BUTTON_DPAD_RIGHT -* SDL_CONTROLLER_BUTTON_DPAD_UP => SDL_GAMEPAD_BUTTON_DPAD_UP -* SDL_CONTROLLER_BUTTON_GUIDE => SDL_GAMEPAD_BUTTON_GUIDE -* SDL_CONTROLLER_BUTTON_INVALID => SDL_GAMEPAD_BUTTON_INVALID -* SDL_CONTROLLER_BUTTON_LEFTSHOULDER => SDL_GAMEPAD_BUTTON_LEFT_SHOULDER -* SDL_CONTROLLER_BUTTON_LEFTSTICK => SDL_GAMEPAD_BUTTON_LEFT_STICK -* SDL_CONTROLLER_BUTTON_MAX => SDL_GAMEPAD_BUTTON_MAX -* SDL_CONTROLLER_BUTTON_MISC1 => SDL_GAMEPAD_BUTTON_MISC1 -* SDL_CONTROLLER_BUTTON_PADDLE1 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 -* SDL_CONTROLLER_BUTTON_PADDLE2 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 -* SDL_CONTROLLER_BUTTON_PADDLE3 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 -* SDL_CONTROLLER_BUTTON_PADDLE4 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 -* SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER -* SDL_CONTROLLER_BUTTON_RIGHTSTICK => SDL_GAMEPAD_BUTTON_RIGHT_STICK -* SDL_CONTROLLER_BUTTON_START => SDL_GAMEPAD_BUTTON_START -* SDL_CONTROLLER_BUTTON_TOUCHPAD => SDL_GAMEPAD_BUTTON_TOUCHPAD -* SDL_CONTROLLER_BUTTON_X => SDL_GAMEPAD_BUTTON_X -* SDL_CONTROLLER_BUTTON_Y => SDL_GAMEPAD_BUTTON_Y -* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT -* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR -* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT -* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO -* SDL_CONTROLLER_TYPE_PS3 => SDL_GAMEPAD_TYPE_PS3 -* SDL_CONTROLLER_TYPE_PS4 => SDL_GAMEPAD_TYPE_PS4 -* SDL_CONTROLLER_TYPE_PS5 => SDL_GAMEPAD_TYPE_PS5 -* SDL_CONTROLLER_TYPE_UNKNOWN => SDL_GAMEPAD_TYPE_STANDARD -* SDL_CONTROLLER_TYPE_VIRTUAL => SDL_GAMEPAD_TYPE_VIRTUAL -* SDL_CONTROLLER_TYPE_XBOX360 => SDL_GAMEPAD_TYPE_XBOX360 -* SDL_CONTROLLER_TYPE_XBOXONE => SDL_GAMEPAD_TYPE_XBOXONE - -## SDL_gesture.h - -The gesture API has been removed. There is no replacement planned in SDL3. -However, the SDL2 code has been moved to a single-header library that can -be dropped into an SDL3 or SDL2 program, to continue to provide this -functionality to your app and aid migration. That is located in the -[SDL_gesture GitHub repository](https://github.com/libsdl-org/SDL_gesture). - -## SDL_hints.h - -SDL_AddHintCallback() now returns a standard int result instead of void, returning 0 if the function succeeds or a negative error code if there was an error. - -Calling SDL_GetHint() with the name of the hint being changed from within a hint callback will now return the new value rather than the old value. The old value is still passed as a parameter to the hint callback. - -The following hints have been removed: -* SDL_HINT_VIDEO_HIGHDPI_DISABLED - high DPI support is always enabled -* SDL_HINT_IDLE_TIMER_DISABLED - use SDL_DisableScreenSaver instead -* SDL_HINT_MOUSE_RELATIVE_SCALING - mouse coordinates are no longer automatically scaled by the SDL renderer -* SDL_HINT_RENDER_LOGICAL_SIZE_MODE - the logical size mode is explicitly set with SDL_SetRenderLogicalPresentation() -* SDL_HINT_VIDEO_X11_FORCE_EGL - use SDL_HINT_VIDEO_FORCE_EGL instead -* SDL_HINT_VIDEO_X11_XINERAMA - Xinerama no longer supported by the X11 backend -* SDL_HINT_VIDEO_X11_XVIDMODE - Xvidmode no longer supported by the X11 backend - -* Renamed hints SDL_HINT_VIDEODRIVER and SDL_HINT_AUDIODRIVER to SDL_HINT_VIDEO_DRIVER and SDL_HINT_AUDIO_DRIVER -* Renamed environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER -* The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced with the unified hint SDL_HINT_APP_ID - -## SDL_init.h - -The following symbols have been renamed: -* SDL_INIT_GAMECONTROLLER => SDL_INIT_GAMEPAD - -The following symbols have been removed: -* SDL_INIT_NOPARACHUTE - -## SDL_joystick.h - -SDL_JoystickID has changed from Sint32 to Uint32, with an invalid ID being 0. - -Rather than iterating over joysticks using device index, there is a new function SDL_GetJoysticks() to get the current list of joysticks, and new functions to get information about joysticks from their instance ID: -```c -{ - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == 0) { - int i, num_joysticks; - SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks); - if (joysticks) { - for (i = 0; i < num_joysticks; ++i) { - SDL_JoystickID instance_id = joysticks[i]; - const char *name = SDL_GetJoystickInstanceName(instance_id); - const char *path = SDL_GetJoystickInstancePath(instance_id); - - SDL_Log("Joystick %" SDL_PRIu32 ": %s%s%s VID 0x%.4x, PID 0x%.4x\n", - instance_id, name ? name : "Unknown", path ? ", " : "", path ? path : "", SDL_GetJoystickInstanceVendor(instance_id), SDL_GetJoystickInstanceProduct(instance_id)); - } - SDL_free(joysticks); - } - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - } -} -``` - -The SDL_EVENT_JOYSTICK_ADDED event now provides the joystick instance ID in the `which` member of the jdevice event structure. - -The functions SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() have been added to directly query the list of available joysticks. - -SDL_AttachVirtualJoystick() and SDL_AttachVirtualJoystickEx() now return the joystick instance ID instead of a device index, and return 0 if there was an error. - -The following functions have been renamed: -* SDL_JoystickAttachVirtual() => SDL_AttachVirtualJoystick() -* SDL_JoystickAttachVirtualEx() => SDL_AttachVirtualJoystickEx() -* SDL_JoystickClose() => SDL_CloseJoystick() -* SDL_JoystickCurrentPowerLevel() => SDL_GetJoystickPowerLevel() -* SDL_JoystickDetachVirtual() => SDL_DetachVirtualJoystick() -* SDL_JoystickFromInstanceID() => SDL_GetJoystickFromInstanceID() -* SDL_JoystickFromPlayerIndex() => SDL_GetJoystickFromPlayerIndex() -* SDL_JoystickGetAttached() => SDL_JoystickConnected() -* SDL_JoystickGetAxis() => SDL_GetJoystickAxis() -* SDL_JoystickGetAxisInitialState() => SDL_GetJoystickAxisInitialState() -* SDL_JoystickGetButton() => SDL_GetJoystickButton() -* SDL_JoystickGetFirmwareVersion() => SDL_GetJoystickFirmwareVersion() -* SDL_JoystickGetGUID() => SDL_GetJoystickGUID() -* SDL_JoystickGetGUIDFromString() => SDL_GetJoystickGUIDFromString() -* SDL_JoystickGetGUIDString() => SDL_GetJoystickGUIDString() -* SDL_JoystickGetHat() => SDL_GetJoystickHat() -* SDL_JoystickGetPlayerIndex() => SDL_GetJoystickPlayerIndex() -* SDL_JoystickGetProduct() => SDL_GetJoystickProduct() -* SDL_JoystickGetProductVersion() => SDL_GetJoystickProductVersion() -* SDL_JoystickGetSerial() => SDL_GetJoystickSerial() -* SDL_JoystickGetType() => SDL_GetJoystickType() -* SDL_JoystickGetVendor() => SDL_GetJoystickVendor() -* SDL_JoystickInstanceID() => SDL_GetJoystickInstanceID() -* SDL_JoystickIsVirtual() => SDL_IsJoystickVirtual() -* SDL_JoystickName() => SDL_GetJoystickName() -* SDL_JoystickNumAxes() => SDL_GetNumJoystickAxes() -* SDL_JoystickNumButtons() => SDL_GetNumJoystickButtons() -* SDL_JoystickNumHats() => SDL_GetNumJoystickHats() -* SDL_JoystickOpen() => SDL_OpenJoystick() -* SDL_JoystickPath() => SDL_GetJoystickPath() -* SDL_JoystickRumble() => SDL_RumbleJoystick() -* SDL_JoystickRumbleTriggers() => SDL_RumbleJoystickTriggers() -* SDL_JoystickSendEffect() => SDL_SendJoystickEffect() -* SDL_JoystickSetLED() => SDL_SetJoystickLED() -* SDL_JoystickSetPlayerIndex() => SDL_SetJoystickPlayerIndex() -* SDL_JoystickSetVirtualAxis() => SDL_SetJoystickVirtualAxis() -* SDL_JoystickSetVirtualButton() => SDL_SetJoystickVirtualButton() -* SDL_JoystickSetVirtualHat() => SDL_SetJoystickVirtualHat() -* SDL_JoystickUpdate() => SDL_UpdateJoysticks() - -The following symbols have been renamed: -* SDL_JOYSTICK_TYPE_GAMECONTROLLER => SDL_JOYSTICK_TYPE_GAMEPAD - -The following functions have been removed: -* SDL_JoystickEventState() - replaced with SDL_SetJoystickEventsEnabled() and SDL_JoystickEventsEnabled() -* SDL_JoystickGetDeviceGUID() - replaced with SDL_GetJoystickInstanceGUID() -* SDL_JoystickGetDeviceInstanceID() -* SDL_JoystickGetDevicePlayerIndex() - replaced with SDL_GetJoystickInstancePlayerIndex() -* SDL_JoystickGetDeviceProduct() - replaced with SDL_GetJoystickInstanceProduct() -* SDL_JoystickGetDeviceProductVersion() - replaced with SDL_GetJoystickInstanceProductVersion() -* SDL_JoystickGetDeviceType() - replaced with SDL_GetJoystickInstanceType() -* SDL_JoystickGetDeviceVendor() - replaced with SDL_GetJoystickInstanceVendor() -* SDL_JoystickNameForIndex() - replaced with SDL_GetJoystickInstanceName() -* SDL_JoystickNumBalls() - API has been removed, see https://github.com/libsdl-org/SDL/issues/6766 -* SDL_JoystickPathForIndex() - replaced with SDL_GetJoystickInstancePath() -* SDL_NumJoysticks() - replaced with SDL_GetJoysticks() - -The following symbols have been removed: -* SDL_JOYBALLMOTION - -## SDL_keyboard.h - -The following functions have been renamed: -* SDL_IsScreenKeyboardShown() => SDL_ScreenKeyboardShown() -* SDL_IsTextInputActive() => SDL_TextInputActive() -* SDL_IsTextInputShown() => SDL_TextInputShown() - -## SDL_keycode.h - -The following symbols have been renamed: -* KMOD_ALT => SDL_KMOD_ALT -* KMOD_CAPS => SDL_KMOD_CAPS -* KMOD_CTRL => SDL_KMOD_CTRL -* KMOD_GUI => SDL_KMOD_GUI -* KMOD_LALT => SDL_KMOD_LALT -* KMOD_LCTRL => SDL_KMOD_LCTRL -* KMOD_LGUI => SDL_KMOD_LGUI -* KMOD_LSHIFT => SDL_KMOD_LSHIFT -* KMOD_MODE => SDL_KMOD_MODE -* KMOD_NONE => SDL_KMOD_NONE -* KMOD_NUM => SDL_KMOD_NUM -* KMOD_RALT => SDL_KMOD_RALT -* KMOD_RCTRL => SDL_KMOD_RCTRL -* KMOD_RESERVED => SDL_KMOD_RESERVED -* KMOD_RGUI => SDL_KMOD_RGUI -* KMOD_RSHIFT => SDL_KMOD_RSHIFT -* KMOD_SCROLL => SDL_KMOD_SCROLL -* KMOD_SHIFT => SDL_KMOD_SHIFT - -## SDL_loadso.h - -SDL_LoadFunction() now returns `SDL_FunctionPointer` instead of `void *`, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior. - -## SDL_main.h - -SDL3 doesn't have a static libSDLmain to link against anymore. -Instead SDL_main.h is now a header-only library **and not included by SDL.h anymore**. - -Using it is really simple: Just `#include ` in the source file with your standard -`int main(int argc, char* argv[])` function. - -## SDL_metal.h - -SDL_Metal_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. - -## SDL_mouse.h - -SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() - -SDL_GetMouseState(), SDL_GetGlobalMouseState(), SDL_GetRelativeMouseState(), SDL_WarpMouseInWindow(), and SDL_WarpMouseGlobal() all use floating point mouse positions, to provide sub-pixel precision on platforms that support it. - -The following functions have been renamed: -* SDL_FreeCursor() => SDL_DestroyCursor() - -## SDL_mutex.h - -The following functions have been renamed: -* SDL_CondBroadcast() => SDL_BroadcastCondition() -* SDL_CondSignal() => SDL_SignalCondition() -* SDL_CondWait() => SDL_WaitCondition() -* SDL_CondWaitTimeout() => SDL_WaitConditionTimeout() -* SDL_CreateCond() => SDL_CreateCondition() -* SDL_DestroyCond() => SDL_DestroyCondition() -* SDL_SemPost() => SDL_PostSemaphore() -* SDL_SemTryWait() => SDL_TryWaitSemaphore() -* SDL_SemValue() => SDL_GetSemaphoreValue() -* SDL_SemWait() => SDL_WaitSemaphore() -* SDL_SemWaitTimeout() => SDL_WaitSemaphoreTimeout() - -The following symbols have been renamed: -* SDL_cond => SDL_Condition -* SDL_mutex => SDL_Mutex -* SDL_sem => SDL_Semaphore - -## SDL_pixels.h - -SDL_CalculateGammaRamp has been removed, because SDL_SetWindowGammaRamp has been removed as well due to poor support in modern operating systems (see [SDL_video.h](#sdl_videoh)). - -The following functions have been renamed: -* SDL_AllocFormat() => SDL_CreatePixelFormat() -* SDL_AllocPalette() => SDL_CreatePalette() -* SDL_FreeFormat() => SDL_DestroyPixelFormat() -* SDL_FreePalette() => SDL_DestroyPalette() -* SDL_MasksToPixelFormatEnum() => SDL_GetPixelFormatEnumForMasks() -* SDL_PixelFormatEnumToMasks() => SDL_GetMasksForPixelFormatEnum() - -The following symbols have been renamed: -* SDL_DISPLAYEVENT_DISCONNECTED => SDL_EVENT_DISPLAY_DISCONNECTED -* SDL_DISPLAYEVENT_MOVED => SDL_EVENT_DISPLAY_MOVED -* SDL_DISPLAYEVENT_ORIENTATION => SDL_EVENT_DISPLAY_ORIENTATION -* SDL_WINDOWEVENT_CLOSE => SDL_EVENT_WINDOW_CLOSE_REQUESTED -* SDL_WINDOWEVENT_DISPLAY_CHANGED => SDL_EVENT_WINDOW_DISPLAY_CHANGED -* SDL_WINDOWEVENT_ENTER => SDL_EVENT_WINDOW_ENTER -* SDL_WINDOWEVENT_EXPOSED => SDL_EVENT_WINDOW_EXPOSED -* SDL_WINDOWEVENT_FOCUS_GAINED => SDL_EVENT_WINDOW_FOCUS_GAINED -* SDL_WINDOWEVENT_FOCUS_LOST => SDL_EVENT_WINDOW_FOCUS_LOST -* SDL_WINDOWEVENT_HIDDEN => SDL_EVENT_WINDOW_HIDDEN -* SDL_WINDOWEVENT_HIT_TEST => SDL_EVENT_WINDOW_HIT_TEST -* SDL_WINDOWEVENT_ICCPROF_CHANGED => SDL_EVENT_WINDOW_ICCPROF_CHANGED -* SDL_WINDOWEVENT_LEAVE => SDL_EVENT_WINDOW_LEAVE -* SDL_WINDOWEVENT_MAXIMIZED => SDL_EVENT_WINDOW_MAXIMIZED -* SDL_WINDOWEVENT_MINIMIZED => SDL_EVENT_WINDOW_MINIMIZED -* SDL_WINDOWEVENT_MOVED => SDL_EVENT_WINDOW_MOVED -* SDL_WINDOWEVENT_RESIZED => SDL_EVENT_WINDOW_RESIZED -* SDL_WINDOWEVENT_RESTORED => SDL_EVENT_WINDOW_RESTORED -* SDL_WINDOWEVENT_SHOWN => SDL_EVENT_WINDOW_SHOWN -* SDL_WINDOWEVENT_SIZE_CHANGED => SDL_EVENT_WINDOW_SIZE_CHANGED -* SDL_WINDOWEVENT_TAKE_FOCUS => SDL_EVENT_WINDOW_TAKE_FOCUS - - -## SDL_platform.h - -The preprocessor symbol `__MACOSX__` has been renamed `__MACOS__`, and `__IPHONEOS__` has been renamed `__IOS__` - -## SDL_rect.h - -The following functions have been renamed: -* SDL_EncloseFPoints() => SDL_GetRectEnclosingPointsFloat() -* SDL_EnclosePoints() => SDL_GetRectEnclosingPoints() -* SDL_FRectEmpty() => SDL_RectEmptyFloat() -* SDL_FRectEquals() => SDL_RectsEqualFloat() -* SDL_FRectEqualsEpsilon() => SDL_RectsEqualEpsilon() -* SDL_HasIntersection() => SDL_HasRectIntersection() -* SDL_HasIntersectionF() => SDL_HasRectIntersectionFloat() -* SDL_IntersectFRect() => SDL_GetRectIntersectionFloat() -* SDL_IntersectFRectAndLine() => SDL_GetRectAndLineIntersectionFloat() -* SDL_IntersectRect() => SDL_GetRectIntersection() -* SDL_IntersectRectAndLine() => SDL_GetRectAndLineIntersection() -* SDL_PointInFRect() => SDL_PointInRectFloat() -* SDL_RectEquals() => SDL_RectsEqual() -* SDL_UnionFRect() => SDL_GetRectUnionFloat() -* SDL_UnionRect() => SDL_GetRectUnion() - -## SDL_render.h - -SDL_GetRenderDriverInfo() has been removed, since most of the information it reported were -estimates and could not be accurate before creating a renderer. Often times this function -was used to figure out the index of a driver, so one would call it in a for-loop, looking -for the driver named "opengl" or whatnot. SDL_GetRenderDriver() has been added for this -functionality, which returns only the name of the driver. - -Additionally, SDL_CreateRenderer()'s second argument is no longer an integer index, but a -`const char *` representing a renderer's name; if you were just using a for-loop to find -which index is the "opengl" or whatnot driver, you can just pass that string directly -here, now. Passing NULL is the same as passing -1 here in SDL2, to signify you want SDL -to decide for you. - -The SDL_RENDERER_TARGETTEXTURE flag has been removed, all current renderers support target texture functionality. - -When a renderer is created, it will automatically set the logical size to the size of -the window in points. For high DPI displays, this will set up scaling from points to -pixels. You can disable this scaling with: -```c - SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED, SDL_SCALEMODE_NEAREST); -``` - -Mouse and touch events are no longer filtered to change their coordinates, instead you -can call SDL_ConvertEventToRenderCoordinates() to explicitly map event coordinates into -the rendering viewport. - -SDL_RenderWindowToLogical() and SDL_RenderLogicalToWindow() have been renamed SDL_RenderCoordinatesFromWindow() and SDL_RenderCoordinatesToWindow() and take floating point coordinates in both directions. - -The viewport, clipping state, and scale for render targets are now persistent and will remain set whenever they are active. - -The following functions have been renamed: -* SDL_GetRendererOutputSize() => SDL_GetCurrentRenderOutputSize() -* SDL_RenderCopy() => SDL_RenderTexture() -* SDL_RenderCopyEx() => SDL_RenderTextureRotated() -* SDL_RenderCopyExF() => SDL_RenderTextureRotated() -* SDL_RenderCopyF() => SDL_RenderTexture() -* SDL_RenderDrawLine() => SDL_RenderLine() -* SDL_RenderDrawLineF() => SDL_RenderLine() -* SDL_RenderDrawLines() => SDL_RenderLines() -* SDL_RenderDrawLinesF() => SDL_RenderLines() -* SDL_RenderDrawPoint() => SDL_RenderPoint() -* SDL_RenderDrawPointF() => SDL_RenderPoint() -* SDL_RenderDrawPoints() => SDL_RenderPoints() -* SDL_RenderDrawPointsF() => SDL_RenderPoints() -* SDL_RenderDrawRect() => SDL_RenderRect() -* SDL_RenderDrawRectF() => SDL_RenderRect() -* SDL_RenderDrawRects() => SDL_RenderRects() -* SDL_RenderDrawRectsF() => SDL_RenderRects() -* SDL_RenderFillRectF() => SDL_RenderFillRect() -* SDL_RenderFillRectsF() => SDL_RenderFillRects() -* SDL_RenderGetClipRect() => SDL_GetRenderClipRect() -* SDL_RenderGetIntegerScale() => SDL_GetRenderIntegerScale() -* SDL_RenderGetLogicalSize() => SDL_GetRenderLogicalPresentation() -* SDL_RenderGetMetalCommandEncoder() => SDL_GetRenderMetalCommandEncoder() -* SDL_RenderGetMetalLayer() => SDL_GetRenderMetalLayer() -* SDL_RenderGetScale() => SDL_GetRenderScale() -* SDL_RenderGetViewport() => SDL_GetRenderViewport() -* SDL_RenderGetWindow() => SDL_GetRenderWindow() -* SDL_RenderIsClipEnabled() => SDL_RenderClipEnabled() -* SDL_RenderLogicalToWindow() => SDL_RenderCoordinatesToWindow() -* SDL_RenderSetClipRect() => SDL_SetRenderClipRect() -* SDL_RenderSetIntegerScale() => SDL_SetRenderIntegerScale() -* SDL_RenderSetLogicalSize() => SDL_SetRenderLogicalPresentation() -* SDL_RenderSetScale() => SDL_SetRenderScale() -* SDL_RenderSetVSync() => SDL_SetRenderVSync() -* SDL_RenderSetViewport() => SDL_SetRenderViewport() -* SDL_RenderWindowToLogical() => SDL_RenderCoordinatesFromWindow() - -The following functions have been removed: -* SDL_RenderGetIntegerScale() -* SDL_RenderSetIntegerScale() - this is now explicit with SDL_LOGICAL_PRESENTATION_INTEGER_SCALE -* SDL_RenderTargetSupported() - render targets are always supported - -The following symbols have been renamed: -* SDL_ScaleModeBest => SDL_SCALEMODE_BEST -* SDL_ScaleModeLinear => SDL_SCALEMODE_LINEAR -* SDL_ScaleModeNearest => SDL_SCALEMODE_NEAREST - -## SDL_rwops.h - -The following symbols have been renamed: -* RW_SEEK_CUR => SDL_RW_SEEK_CUR -* RW_SEEK_END => SDL_RW_SEEK_END -* RW_SEEK_SET => SDL_RW_SEEK_SET - -SDL_RWread and SDL_RWwrite (and SDL_RWops::read, SDL_RWops::write) have a different function signature in SDL3. - -Previously they looked more like stdio: - -```c -size_t SDL_RWread(SDL_RWops *context, void *ptr, size_t size, size_t maxnum); -size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size, size_t maxnum); -``` - -But now they look more like POSIX: - -```c -size_t SDL_RWread(SDL_RWops *context, void *ptr, size_t size); -size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size); -``` - -Code that used to look like this: -``` -size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_RWops *stream) -{ - return SDL_RWread(stream, ptr, size, nitems); -} -``` -should be changed to: -``` -size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_RWops *stream) -{ - if (size > 0 && nitems > 0) { - return SDL_RWread(stream, ptr, size * nitems) / size; - } - return 0; -} -``` - -SDL_RWFromFP has been removed from the API, due to issues when the SDL library uses a different C runtime from the application. - -You can implement this in your own code easily: -```c -#include - - -static Sint64 SDLCALL stdio_seek(SDL_RWops *context, Sint64 offset, int whence) -{ - int stdiowhence; - - switch (whence) { - case SDL_RW_SEEK_SET: - stdiowhence = SEEK_SET; - break; - case SDL_RW_SEEK_CUR: - stdiowhence = SEEK_CUR; - break; - case SDL_RW_SEEK_END: - stdiowhence = SEEK_END; - break; - default: - return SDL_SetError("Unknown value for 'whence'"); - } - - if (fseek((FILE *)context->hidden.stdio.fp, (fseek_off_t)offset, stdiowhence) == 0) { - Sint64 pos = ftell((FILE *)context->hidden.stdio.fp); - if (pos < 0) { - return SDL_SetError("Couldn't get stream offset"); - } - return pos; - } - return SDL_Error(SDL_EFSEEK); -} - -static size_t SDLCALL stdio_read(SDL_RWops *context, void *ptr, size_t size) -{ - size_t bytes; - - bytes = fread(ptr, 1, size, (FILE *)context->hidden.stdio.fp); - if (bytes == 0 && ferror((FILE *)context->hidden.stdio.fp)) { - SDL_Error(SDL_EFREAD); - } - return bytes; -} - -static size_t SDLCALL stdio_write(SDL_RWops *context, const void *ptr, size_t size) -{ - size_t bytes; - - bytes = fwrite(ptr, 1, size, (FILE *)context->hidden.stdio.fp); - if (bytes == 0 && ferror((FILE *)context->hidden.stdio.fp)) { - SDL_Error(SDL_EFWRITE); - } - return bytes; -} - -static int SDLCALL stdio_close(SDL_RWops *context) -{ - int status = 0; - if (context->hidden.stdio.autoclose) { - if (fclose((FILE *)context->hidden.stdio.fp) != 0) { - status = SDL_Error(SDL_EFWRITE); - } - } - SDL_DestroyRW(context); - return status; -} - -SDL_RWops *SDL_RWFromFP(void *fp, SDL_bool autoclose) -{ - SDL_RWops *rwops = NULL; - - rwops = SDL_CreateRW(); - if (rwops != NULL) { - rwops->seek = stdio_seek; - rwops->read = stdio_read; - rwops->write = stdio_write; - rwops->close = stdio_close; - rwops->hidden.stdio.fp = fp; - rwops->hidden.stdio.autoclose = autoclose; - rwops->type = SDL_RWOPS_STDFILE; - } - return rwops; -} -``` - -The functions SDL_ReadU8(), SDL_ReadU16LE(), SDL_ReadU16BE(), SDL_ReadU32LE(), SDL_ReadU32BE(), SDL_ReadU64LE(), and SDL_ReadU64BE() now return SDL_TRUE if the read succeeded and SDL_FALSE if it didn't, and store the data in a pointer passed in as a parameter. - -The following functions have been renamed: -* SDL_AllocRW() => SDL_CreateRW() -* SDL_FreeRW() => SDL_DestroyRW() -* SDL_ReadBE16() => SDL_ReadU16BE() -* SDL_ReadBE32() => SDL_ReadU32BE() -* SDL_ReadBE64() => SDL_ReadU64BE() -* SDL_ReadLE16() => SDL_ReadU16LE() -* SDL_ReadLE32() => SDL_ReadU32LE() -* SDL_ReadLE64() => SDL_ReadU64LE() -* SDL_WriteBE16() => SDL_WriteU16BE() -* SDL_WriteBE32() => SDL_WriteU32BE() -* SDL_WriteBE64() => SDL_WriteU64BE() -* SDL_WriteLE16() => SDL_WriteU16LE() -* SDL_WriteLE32() => SDL_WriteU32LE() -* SDL_WriteLE64() => SDL_WriteU64LE() - -## SDL_sensor.h - -SDL_SensorID has changed from Sint32 to Uint32, with an invalid ID being 0. - -Rather than iterating over sensors using device index, there is a new function SDL_GetSensors() to get the current list of sensors, and new functions to get information about sensors from their instance ID: -```c -{ - if (SDL_InitSubSystem(SDL_INIT_SENSOR) == 0) { - int i, num_sensors; - SDL_SensorID *sensors = SDL_GetSensors(&num_sensors); - if (sensors) { - for (i = 0; i < num_sensors; ++i) { - SDL_Log("Sensor %" SDL_PRIu32 ": %s, type %d, platform type %d\n", - sensors[i], - SDL_GetSensorInstanceName(sensors[i]), - SDL_GetSensorInstanceType(sensors[i]), - SDL_GetSensorInstanceNonPortableType(sensors[i])); - } - SDL_free(sensors); - } - SDL_QuitSubSystem(SDL_INIT_SENSOR); - } -} -``` - -Removed SDL_SensorGetDataWithTimestamp(), if you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_SENSOR_UPDATE events. - - -The following functions have been renamed: -* SDL_SensorClose() => SDL_CloseSensor() -* SDL_SensorFromInstanceID() => SDL_GetSensorFromInstanceID() -* SDL_SensorGetData() => SDL_GetSensorData() -* SDL_SensorGetInstanceID() => SDL_GetSensorInstanceID() -* SDL_SensorGetName() => SDL_GetSensorName() -* SDL_SensorGetNonPortableType() => SDL_GetSensorNonPortableType() -* SDL_SensorGetType() => SDL_GetSensorType() -* SDL_SensorOpen() => SDL_OpenSensor() -* SDL_SensorUpdate() => SDL_UpdateSensors() - -The following functions have been removed: -* SDL_LockSensors() -* SDL_NumSensors() - replaced with SDL_GetSensors() -* SDL_SensorGetDeviceInstanceID() -* SDL_SensorGetDeviceName() - replaced with SDL_GetSensorInstanceName() -* SDL_SensorGetDeviceNonPortableType() - replaced with SDL_GetSensorInstanceNonPortableType() -* SDL_SensorGetDeviceType() - replaced with SDL_GetSensorInstanceType() -* SDL_UnlockSensors() - -## SDL_stdinc.h - -The standard C headers like stdio.h and stdlib.h are no longer included, you should include them directly in your project if you use non-SDL C runtime functions. -M_PI is no longer defined in SDL_stdinc.h, you can use the new symbols SDL_PI_D (double) and SDL_PI_F (float) instead. - - -The following functions have been renamed: -* SDL_strtokr() => SDL_strtok_r() - -## SDL_surface.h - -Removed unused 'flags' parameter from SDL_ConvertSurface and SDL_ConvertSurfaceFormat. - -SDL_CreateRGBSurface() and SDL_CreateRGBSurfaceWithFormat() have been combined into a new function SDL_CreateSurface(). -SDL_CreateRGBSurfaceFrom() and SDL_CreateRGBSurfaceWithFormatFrom() have been combined into a new function SDL_CreateSurfaceFrom(). - -You can implement the old functions in your own code easily: -```c -SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) -{ - return SDL_CreateSurface(width, height, - SDL_GetPixelFormatEnumForMasks(depth, Rmask, Gmask, Bmask, Amask)); -} - -SDL_Surface *SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format) -{ - return SDL_CreateSurface(width, height, format); -} - -SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) -{ - return SDL_CreateSurfaceFrom(pixels, width, height, pitch, - SDL_GetPixelFormatEnumForMasks(depth, Rmask, Gmask, Bmask, Amask)); -} - -SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 format) -{ - return SDL_CreateSurfaceFrom(pixels, width, height, pitch, format); -} - -``` - -But if you're migrating your code which uses masks, you probably have a format in mind, possibly one of these: -```c -// Various mask (R, G, B, A) and their corresponding format: -0xFF000000 0x00FF0000 0x0000FF00 0x000000FF => SDL_PIXELFORMAT_RGBA8888 -0x00FF0000 0x0000FF00 0x000000FF 0xFF000000 => SDL_PIXELFORMAT_ARGB8888 -0x0000FF00 0x00FF0000 0xFF000000 0x000000FF => SDL_PIXELFORMAT_BGRA8888 -0x000000FF 0x0000FF00 0x00FF0000 0xFF000000 => SDL_PIXELFORMAT_ABGR8888 -0x0000F800 0x000007E0 0x0000001F 0x00000000 => SDL_PIXELFORMAT_RGB565 -``` - - -The following functions have been renamed: -* SDL_FillRect() => SDL_FillSurfaceRect() -* SDL_FillRects() => SDL_FillSurfaceRects() -* SDL_FreeSurface() => SDL_DestroySurface() -* SDL_GetClipRect() => SDL_GetSurfaceClipRect() -* SDL_GetColorKey() => SDL_GetSurfaceColorKey() -* SDL_HasColorKey() => SDL_SurfaceHasColorKey() -* SDL_HasSurfaceRLE() => SDL_SurfaceHasRLE() -* SDL_LowerBlit() => SDL_BlitSurfaceUnchecked() -* SDL_LowerBlitScaled() => SDL_BlitSurfaceUncheckedScaled() -* SDL_SetClipRect() => SDL_SetSurfaceClipRect() -* SDL_SetColorKey() => SDL_SetSurfaceColorKey() -* SDL_UpperBlit() => SDL_BlitSurface() -* SDL_UpperBlitScaled() => SDL_BlitSurfaceScaled() - -## SDL_system.h - -SDL_AndroidGetExternalStorageState() takes the state as an output parameter and returns 0 if the function succeeds or a negative error code if there was an error. - -The following functions have been renamed: -* SDL_RenderGetD3D11Device() => SDL_GetRenderD3D11Device() -* SDL_RenderGetD3D9Device() => SDL_GetRenderD3D9Device() - -## SDL_syswm.h - -The structures in this file are versioned separately from the rest of SDL, allowing better backwards compatibility and limited forwards compatibility with your application. Instead of calling `SDL_VERSION(&info.version)` before calling SDL_GetWindowWMInfo(), you pass the version in explicitly as SDL_SYSWM_CURRENT_VERSION so SDL knows what fields you expect to be filled out. - -### SDL_GetWindowWMInfo - -This function now returns a standard int result instead of SDL_bool, returning 0 if the function succeeds or a negative error code if there was an error. You should also pass SDL_SYSWM_CURRENT_VERSION as the new third version parameter. The version member of the info structure will be filled in with the version of data that is returned, the minimum of the version you requested and the version supported by the runtime SDL library. - - -## SDL_thread.h - -The following functions have been renamed: -* SDL_TLSCleanup() => SDL_CleanupTLS() -* SDL_TLSCreate() => SDL_CreateTLS() -* SDL_TLSGet() => SDL_GetTLS() -* SDL_TLSSet() => SDL_SetTLS() - -## SDL_timer.h - -SDL_GetTicks() now returns a 64-bit value. Instead of using the SDL_TICKS_PASSED macro, you can directly compare tick values, e.g. -```c -Uint32 deadline = SDL_GetTicks() + 1000; -... -if (SDL_TICKS_PASSED(SDL_GetTicks(), deadline)) { - ... -} -``` -becomes: -```c -Uint64 deadline = SDL_GetTicks() + 1000 -... -if (SDL_GetTicks() >= deadline) { - ... -} -``` - -If you were using this macro for other things besides SDL ticks values, you can define it in your own code as: -```c -#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) -``` - -## SDL_touch.h - -SDL_GetNumTouchFingers() returns a negative error code if there was an error. - -## SDL_version.h - -SDL_GetRevisionNumber() has been removed from the API, it always returned 0 in SDL 2.0. - - -## SDL_video.h - -SDL_VideoInit() and SDL_VideoQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_VIDEO, which will properly refcount the subsystems. You can choose a specific video driver using SDL_VIDEO_DRIVER hint. - -Rather than iterating over displays using display index, there is a new function SDL_GetDisplays() to get the current list of displays, and functions which used to take a display index now take SDL_DisplayID, with an invalid ID being 0. -```c -{ - if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0) { - int i, num_displays = 0; - SDL_DisplayID *displays = SDL_GetDisplays(&num_displays); - if (displays) { - for (i = 0; i < num_displays; ++i) { - SDL_DisplayID instance_id = displays[i]; - const char *name = SDL_GetDisplayName(instance_id); - - SDL_Log("Display %" SDL_PRIu32 ": %s\n", instance_id, name ? name : "Unknown"); - } - SDL_free(displays); - } - SDL_QuitSubSystem(SDL_INIT_VIDEO); - } -} -``` - -SDL_CreateWindow() has been simplified and no longer takes a window position. You can use SDL_CreateWindowWithPosition() if you need to set the window position when creating it. - -The SDL_WINDOWPOS_UNDEFINED_DISPLAY() and SDL_WINDOWPOS_CENTERED_DISPLAY() macros take a display ID instead of display index. The display ID 0 has a special meaning in this case, and is used to indicate the primary display. - -The SDL_WINDOW_SHOWN flag has been removed. Windows are shown by default and can be created hidden by using the SDL_WINDOW_HIDDEN flag. - -The SDL_WINDOW_SKIP_TASKBAR flag has been replaced by the SDL_WINDOW_UTILITY flag, which has the same functionality. - -SDL_DisplayMode now includes the pixel density which can be greater than 1.0 for display modes that have a higher pixel size than the mode size. You should use SDL_GetWindowSizeInPixels() to get the actual pixel size of the window back buffer. - -The refresh rate in SDL_DisplayMode is now a float. - -Rather than iterating over display modes using an index, there is a new function SDL_GetFullscreenDisplayModes() to get the list of available fullscreen modes on a display. -```c -{ - SDL_DisplayID display = SDL_GetPrimaryDisplay(); - int num_modes = 0; - SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &num_modes); - if (modes) { - for (i = 0; i < num_modes; ++i) { - SDL_DisplayMode *mode = modes[i]; - SDL_Log("Display %" SDL_PRIu32 " mode %d: %dx%d@%gx %gHz\n", - display, i, mode->w, mode->h, mode->pixel_density, mode->refresh_rate); - } - SDL_free(modes); - } -} -``` - -SDL_GetDesktopDisplayMode() and SDL_GetCurrentDisplayMode() return pointers to display modes rather than filling in application memory. - -Windows now have an explicit fullscreen mode that is set, using SDL_SetWindowFullscreenMode(). The fullscreen mode for a window can be queried with SDL_GetWindowFullscreenMode(), which returns a pointer to the mode, or NULL if the window will be fullscreen desktop. SDL_SetWindowFullscreen() just takes a boolean value, setting the correct fullscreen state based on the selected mode. - -SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, and you can call SDL_GetWindowFullscreenMode() to see whether an exclusive fullscreen mode will be used or the fullscreen desktop mode will be used when the window is fullscreen. - -SDL_SetWindowBrightness and SDL_SetWindowGammaRamp have been removed from the API, because they interact poorly with modern operating systems and aren't able to limit their effects to the SDL window. - -Programs which have access to shaders can implement more robust versions of those functions using custom shader code rendered as a post-process effect. - -Removed SDL_GL_CONTEXT_EGL from OpenGL configuration attributes. You can instead use `SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);` - -SDL_GL_GetProcAddress() and SDL_EGL_GetProcAddress() now return `SDL_FunctionPointer` instead of `void *`, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior. - -SDL_GL_SwapWindow() returns 0 if the function succeeds or a negative error code if there was an error. - -SDL_GL_GetSwapInterval() takes the interval as an output parameter and returns 0 if the function succeeds or a negative error code if there was an error. - -SDL_GL_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. - -The SDL_WINDOW_TOOLTIP and SDL_WINDOW_POPUP_MENU window flags are now supported on Windows, Mac (Cocoa), X11, and Wayland. Creating windows with these flags must happen via the `SDL_CreatePopupWindow()` function. This function requires passing in the handle to a valid parent window for the popup, and the popup window is positioned relative to the parent. - -The following functions have been renamed: -* SDL_GetClosestDisplayMode() => SDL_GetClosestFullscreenDisplayMode() -* SDL_GetDisplayOrientation() => SDL_GetCurrentDisplayOrientation() -* SDL_GetPointDisplayIndex() => SDL_GetDisplayForPoint() -* SDL_GetRectDisplayIndex() => SDL_GetDisplayForRect() -* SDL_GetWindowDisplayIndex() => SDL_GetDisplayForWindow() -* SDL_GetWindowDisplayMode() => SDL_GetWindowFullscreenMode() -* SDL_IsScreenSaverEnabled() => SDL_ScreenSaverEnabled() -* SDL_SetWindowDisplayMode() => SDL_SetWindowFullscreenMode() - -The following functions have been removed: -* SDL_GetClosestFullscreenDisplayMode() -* SDL_GetDisplayDPI() - not reliable across platforms, approximately replaced by multiplying `display_scale` in the structure returned by SDL_GetDesktopDisplayMode() times 160 on iPhone and Android, and 96 on other platforms. -* SDL_GetDisplayMode() -* SDL_GetNumDisplayModes() - replaced with SDL_GetFullscreenDisplayModes() -* SDL_GetNumVideoDisplays() - replaced with SDL_GetDisplays() - -SDL_Window id type is named SDL_WindowID - -The following symbols have been renamed: -* SDL_WINDOW_ALLOW_HIGHDPI => SDL_WINDOW_HIGH_PIXEL_DENSITY -* SDL_WINDOW_INPUT_GRABBED => SDL_WINDOW_MOUSE_GRABBED - -## SDL_vulkan.h - -SDL_Vulkan_GetInstanceExtensions() no longer takes a window parameter. - -SDL_Vulkan_GetVkGetInstanceProcAddr() now returns `SDL_FunctionPointer` instead of `void *`, and should be cast to PFN_vkGetInstanceProcAddr. - -SDL_Vulkan_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. +# Migrating to SDL 3.0 + +This guide provides useful information for migrating applications from SDL 2.0 to SDL 3.0. + +Details on API changes are organized by SDL 2.0 header below. + +The file with your main() function should include , as that is no longer included in SDL.h. + +Many functions and symbols have been renamed. We have provided a handy Python script [rename_symbols.py](https://github.com/libsdl-org/SDL/blob/main/build-scripts/rename_symbols.py) to rename SDL2 functions to their SDL3 counterparts: +```sh +rename_symbols.py --all-symbols source_code_path +``` + +It's also possible to apply a semantic patch to migrate more easily to SDL3: [SDL_migration.cocci](https://github.com/libsdl-org/SDL/blob/main/build-scripts/SDL_migration.cocci) + +SDL headers should now be included as `#include `. Typically that's the only header you'll need in your application unless you are using OpenGL or Vulkan functionality. We have provided a handy Python script [rename_headers.py](https://github.com/libsdl-org/SDL/blob/main/build-scripts/rename_headers.py) to rename SDL2 headers to their SDL3 counterparts: +```sh +rename_headers.py source_code_path +``` + +CMake users should use this snippet to include SDL support in their project: +``` +find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) +target_link_libraries(mygame PRIVATE SDL3::SDL3) +``` + +Autotools users should use this snippet to include SDL support in their project: +``` +PKG_CHECK_MODULES([SDL3], [sdl3]) +``` +and then add $SDL3_CFLAGS to their project CFLAGS and $SDL3_LIBS to their project LDFLAGS + +Makefile users can use this snippet to include SDL support in their project: +``` +CFLAGS += $(shell pkg-config sdl3 --cflags) +LDFLAGS += $(shell pkg-config sdl3 --libs) +``` + +The SDL3test library has been renamed SDL3_test. + +The SDLmain library has been removed, it's been entirely replaced by SDL_main.h. + +The vi format comments have been removed from source code. Vim users can use the [editorconfig plugin](https://github.com/editorconfig/editorconfig-vim) to automatically set tab spacing for the SDL coding style. + +## SDL_atomic.h + +The following structures have been renamed: +- SDL_atomic_t => SDL_AtomicInt + +## SDL_audio.h + +The audio subsystem in SDL3 is dramatically different than SDL2. The primary way to play audio is no longer an audio callback; instead you bind SDL_AudioStreams to devices; however, there is still a callback method available if needed. + +The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface. + +SDL3 will not implicitly initialize the audio subsystem on your behalf if you open a device without doing so. Please explicitly call SDL_Init(SDL_INIT_AUDIO) at some point. + +SDL3's audio subsystem offers an enormous amount of power over SDL2, but if you just want a simple migration of your existing code, you can ignore most of it. The simplest migration path from SDL2 looks something like this: + +In SDL2, you might have done something like this to play audio... + +```c + void SDLCALL MyAudioCallback(void *userdata, Uint8 * stream, int len) + { + /* Calculate a little more audio here, maybe using `userdata`, write it to `stream` */ + } + + /* ...somewhere near startup... */ + SDL_AudioSpec my_desired_audio_format; + SDL_zero(my_desired_audio_format); + my_desired_audio_format.format = AUDIO_S16; + my_desired_audio_format.channels = 2; + my_desired_audio_format.freq = 44100; + my_desired_audio_format.samples = 1024; + my_desired_audio_format.callback = MyAudioCallback; + my_desired_audio_format.userdata = &my_audio_callback_user_data; + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(NULL, 0, &my_desired_audio_format, NULL, 0); + SDL_PauseAudioDevice(my_audio_device, 0); +``` + +...in SDL3, you can do this... + +```c + void SDLCALL MyNewAudioCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) + { + /* Calculate a little more audio here, maybe using `userdata`, write it to `stream` + * + * If you want to use the original callback, you could do something like this: + */ + if (additional_amount > 0) { + Uint8 *data = SDL_stack_alloc(Uint8, additional_amount); + if (data) { + MyAudioCallback(userdata, data, additional_amount); + SDL_PutAudioStreamData(stream, data, additional_amount); + SDL_stack_free(data); + } + } + } + + /* ...somewhere near startup... */ + const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; + SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, MyNewAudioCallback, &my_audio_callback_user_data); + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); +``` + +If you used SDL_QueueAudio instead of a callback in SDL2, this is also straightforward. + +```c + /* ...somewhere near startup... */ + const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; + SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL, NULL); + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); + + /* ...in your main loop... */ + /* calculate a little more audio into `buf`, add it to `stream` */ + SDL_PutAudioStreamData(stream, buf, buflen); + +``` + +...these same migration examples apply to audio capture, just using SDL_GetAudioStreamData instead of SDL_PutAudioStreamData. + +SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. + +The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app. + +SDL_AudioDeviceID now represents both an open audio device's handle (a "logical" device) and the instance ID that the hardware owns as long as it exists on the system (a "physical" device). The separation between device instances and device indexes is gone, and logical and physical devices are almost entirely interchangeable at the API level. + +Devices are opened by physical device instance ID, and a new logical instance ID is generated by the open operation; This allows any device to be opened multiple times, possibly by unrelated pieces of code. SDL will manage the logical devices to provide a single stream of audio to the physical device behind the scenes. + +Devices are not opened by an arbitrary string name anymore, but by device instance ID (or magic numbers to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a default device ID and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it. + +Many functions that would accept a device index and an `iscapture` parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into output and capture device lists. + +Rather than iterating over audio devices using a device index, there are new functions, SDL_GetAudioOutputDevices() and SDL_GetAudioCaptureDevices(), to get the current list of devices, and new functions to get information about devices from their instance ID: + +```c +{ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { + int i, num_devices; + SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(&num_devices); + if (devices) { + for (i = 0; i < num_devices; ++i) { + SDL_AudioDeviceID instance_id = devices[i]; + char *name = SDL_GetAudioDeviceName(instance_id); + SDL_Log("AudioDevice %" SDL_PRIu32 ": %s\n", instance_id, name); + SDL_free(name); + } + SDL_free(devices); + } + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } +} +``` + +SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. If the app assigns a callback to a specific stream, it can use the stream's lock through SDL_LockAudioStream() if necessary. + +SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_ResumeAudioDevice(). + +Audio devices, opened by SDL_OpenAudioDevice(), no longer start in a paused state, as they don't begin processing audio until a stream is bound. + +SDL_GetAudioDeviceStatus() has been removed; there is now SDL_AudioDevicePaused(). + +SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality. + +APIs that use channel counts used to use a Uint8 for the channel; now they use int. + +SDL_AudioSpec has been reduced; now it only holds format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL2 SDL_AudioSpec fields aren't relevant anymore. + +SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead. + +SDL_GetDefaultAudioInfo() is removed; SDL_GetAudioDeviceFormat() with SDL_AUDIO_DEVICE_DEFAULT_OUTPUT or SDL_AUDIO_DEVICE_DEFAULT_CAPTURE. There is no replacement for querying the default device name; the string is no longer used to open devices, and SDL3 will migrate between physical devices on the fly if the system default changes, so if you must show this to the user, a generic name like "System default" is recommended. + +SDL_MixAudio() has been removed, as it relied on legacy SDL 1.2 quirks; SDL_MixAudioFormat() remains and offers the same functionality. + +SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. + +SDL_FreeWAV has been removed and calls can be replaced with SDL_free. + +SDL_LoadWAV() is a proper function now and no longer a macro (but offers the same functionality otherwise). + +SDL_LoadWAV_RW() and SDL_LoadWAV() return an int now: zero on success, -1 on error, like most of SDL. They no longer return a pointer to an SDL_AudioSpec. + +SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead. + +Code that used to look like this: +```c + SDL_AudioCVT cvt; + SDL_BuildAudioCVT(&cvt, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); + cvt.len = src_len; + cvt.buf = (Uint8 *) SDL_malloc(src_len * cvt.len_mult); + SDL_memcpy(cvt.buf, src_data, src_len); + SDL_ConvertAudio(&cvt); + do_something(cvt.buf, cvt.len_cvt); +``` +should be changed to: +```c + Uint8 *dst_data = NULL; + int dst_len = 0; + const SDL_AudioSpec src_spec = { src_format, src_channels, src_rate }; + const SDL_AudioSpec dst_spec = { dst_format, dst_channels, dst_rate }; + if (SDL_ConvertAudioSamples(&src_spec, src_data, src_len, &dst_spec, &dst_data, &dst_len) < 0) { + /* error */ + } + do_something(dst_data, dst_len); + SDL_free(dst_data); +``` + +AUDIO_U16, AUDIO_U16LSB, AUDIO_U16MSB, and AUDIO_U16SYS have been removed. They were not heavily used, and one could not memset a buffer in this format to silence with a single byte value. Use a different audio format. + +If you need to convert U16 audio data to a still-supported format at runtime, the fastest, lossless conversion is to SDL_AUDIO_S16: + +```c + /* this converts the buffer in-place. The buffer size does not change. */ + Sint16 *audio_ui16_to_si16(Uint16 *buffer, const size_t num_samples) + { + size_t i; + const Uint16 *src = buffer; + Sint16 *dst = (Sint16 *) buffer; + + for (i = 0; i < num_samples; i++) { + dst[i] = (Sint16) (src[i] ^ 0x8000); + } + + return dst; + } +``` + +All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage. + +In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamGet in SDL2). The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. + +In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior. + +In SDL2, SDL_AUDIODEVICEREMOVED events would fire for open devices with the `which` field set to the SDL_AudioDeviceID of the lost device, and in later SDL2 releases, would also fire this event with a `which` field of zero for unopened devices, to signify that the app might want to refresh the available device list. In SDL3, this event works the same, except it won't ever fire with a zero; in this case it'll return the physical device's SDL_AudioDeviceID. Any still-open SDL_AudioDeviceIDs generated from this device with SDL_OpenAudioDevice() will also fire a separate event. + +The following functions have been renamed: +* SDL_AudioStreamAvailable() => SDL_GetAudioStreamAvailable() +* SDL_AudioStreamClear() => SDL_ClearAudioStream() +* SDL_AudioStreamFlush() => SDL_FlushAudioStream() +* SDL_AudioStreamGet() => SDL_GetAudioStreamData() +* SDL_AudioStreamPut() => SDL_PutAudioStreamData() +* SDL_FreeAudioStream() => SDL_DestroyAudioStream() +* SDL_NewAudioStream() => SDL_CreateAudioStream() + + +The following functions have been removed: +* SDL_GetNumAudioDevices() +* SDL_GetAudioDeviceSpec() +* SDL_ConvertAudio() +* SDL_BuildAudioCVT() +* SDL_OpenAudio() +* SDL_CloseAudio() +* SDL_PauseAudio() +* SDL_GetAudioStatus() +* SDL_GetAudioDeviceStatus() +* SDL_GetDefaultAudioInfo() +* SDL_LockAudio() +* SDL_LockAudioDevice() +* SDL_UnlockAudio() +* SDL_UnlockAudioDevice() +* SDL_MixAudio() +* SDL_QueueAudio() +* SDL_DequeueAudio() +* SDL_ClearAudioQueue() +* SDL_GetQueuedAudioSize() + +The following symbols have been renamed: +* AUDIO_F32 => SDL_AUDIO_F32LE +* AUDIO_F32LSB => SDL_AUDIO_F32LE +* AUDIO_F32MSB => SDL_AUDIO_F32BE +* AUDIO_F32SYS => SDL_AUDIO_F32 +* AUDIO_S16 => SDL_AUDIO_S16LE +* AUDIO_S16LSB => SDL_AUDIO_S16LE +* AUDIO_S16MSB => SDL_AUDIO_S16BE +* AUDIO_S16SYS => SDL_AUDIO_S16 +* AUDIO_S32 => SDL_AUDIO_S32LE +* AUDIO_S32LSB => SDL_AUDIO_S32LE +* AUDIO_S32MSB => SDL_AUDIO_S32BE +* AUDIO_S32SYS => SDL_AUDIO_S32 +* AUDIO_S8 => SDL_AUDIO_S8 +* AUDIO_U8 => SDL_AUDIO_U8 + +## SDL_cpuinfo.h + +The intrinsics headers (mmintrin.h, etc.) have been moved to `` and are no longer automatically included in SDL.h. + +SDL_Has3DNow() has been removed; there is no replacement. + +SDL_HasRDTSC() has been removed; there is no replacement. Don't use the RDTSC opcode in modern times, use SDL_GetPerformanceCounter and SDL_GetPerformanceFrequency instead. + +SDL_SIMDAlloc(), SDL_SIMDRealloc(), and SDL_SIMDFree() have been removed. You can use SDL_aligned_alloc() and SDL_aligned_free() with SDL_SIMDGetAlignment() to get the same functionality. + +## SDL_error.h + +The following functions have been removed: +* SDL_GetErrorMsg() - Can be implemented as `SDL_strlcpy(errstr, SDL_GetError(), maxlen);` + +## SDL_events.h + +The timestamp member of the SDL_Event structure now represents nanoseconds, and is populated with SDL_GetTicksNS() + +The timestamp_us member of the sensor events has been renamed sensor_timestamp and now represents nanoseconds. This value is filled in from the hardware, if available, and may not be synchronized with values returned from SDL_GetTicksNS(). + +You should set the event.common.timestamp field before passing an event to SDL_PushEvent(). If the timestamp is 0 it will be filled in with SDL_GetTicksNS(). + +Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, you should make a copy of it. + +Mouse events use floating point values for mouse coordinates and relative motion values. You can get sub-pixel motion depending on the platform and display scaling. + +The SDL_DISPLAYEVENT_* events have been moved to top level events, and SDL_DISPLAYEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_DISPLAYEVENT and then checking for display events. You can compare the event >= SDL_EVENT_DISPLAY_FIRST and <= SDL_EVENT_DISPLAY_LAST if you need to see whether it's a display event. + +The SDL_WINDOWEVENT_* events have been moved to top level events, and SDL_WINDOWEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_WINDOWEVENT and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event. + +The SDL_EVENT_WINDOW_RESIZED event is always sent, even in response to SDL_SetWindowSize(). + +The SDL_EVENT_WINDOW_SIZE_CHANGED event has been removed, and you can use SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED to detect window backbuffer size changes. + +The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor. + +SDL_QUERY, SDL_IGNORE, SDL_ENABLE, and SDL_DISABLE have been removed. You can use the functions SDL_SetEventEnabled() and SDL_EventEnabled() to set and query event processing state. + +SDL_AddEventWatch() now returns -1 if it fails because it ran out of memory and couldn't add the event watch callback. + +The following symbols have been renamed: +* SDL_APP_DIDENTERBACKGROUND => SDL_EVENT_DID_ENTER_BACKGROUND +* SDL_APP_DIDENTERFOREGROUND => SDL_EVENT_DID_ENTER_FOREGROUND +* SDL_APP_LOWMEMORY => SDL_EVENT_LOW_MEMORY +* SDL_APP_TERMINATING => SDL_EVENT_TERMINATING +* SDL_APP_WILLENTERBACKGROUND => SDL_EVENT_WILL_ENTER_BACKGROUND +* SDL_APP_WILLENTERFOREGROUND => SDL_EVENT_WILL_ENTER_FOREGROUND +* SDL_AUDIODEVICEADDED => SDL_EVENT_AUDIO_DEVICE_ADDED +* SDL_AUDIODEVICEREMOVED => SDL_EVENT_AUDIO_DEVICE_REMOVED +* SDL_CLIPBOARDUPDATE => SDL_EVENT_CLIPBOARD_UPDATE +* SDL_CONTROLLERAXISMOTION => SDL_EVENT_GAMEPAD_AXIS_MOTION +* SDL_CONTROLLERBUTTONDOWN => SDL_EVENT_GAMEPAD_BUTTON_DOWN +* SDL_CONTROLLERBUTTONUP => SDL_EVENT_GAMEPAD_BUTTON_UP +* SDL_CONTROLLERDEVICEADDED => SDL_EVENT_GAMEPAD_ADDED +* SDL_CONTROLLERDEVICEREMAPPED => SDL_EVENT_GAMEPAD_REMAPPED +* SDL_CONTROLLERDEVICEREMOVED => SDL_EVENT_GAMEPAD_REMOVED +* SDL_CONTROLLERSENSORUPDATE => SDL_EVENT_GAMEPAD_SENSOR_UPDATE +* SDL_CONTROLLERSTEAMHANDLEUPDATED => SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED +* SDL_CONTROLLERTOUCHPADDOWN => SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN +* SDL_CONTROLLERTOUCHPADMOTION => SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION +* SDL_CONTROLLERTOUCHPADUP => SDL_EVENT_GAMEPAD_TOUCHPAD_UP +* SDL_DROPBEGIN => SDL_EVENT_DROP_BEGIN +* SDL_DROPCOMPLETE => SDL_EVENT_DROP_COMPLETE +* SDL_DROPFILE => SDL_EVENT_DROP_FILE +* SDL_DROPTEXT => SDL_EVENT_DROP_TEXT +* SDL_FINGERDOWN => SDL_EVENT_FINGER_DOWN +* SDL_FINGERMOTION => SDL_EVENT_FINGER_MOTION +* SDL_FINGERUP => SDL_EVENT_FINGER_UP +* SDL_FIRSTEVENT => SDL_EVENT_FIRST +* SDL_JOYAXISMOTION => SDL_EVENT_JOYSTICK_AXIS_MOTION +* SDL_JOYBATTERYUPDATED => SDL_EVENT_JOYSTICK_BATTERY_UPDATED +* SDL_JOYBUTTONDOWN => SDL_EVENT_JOYSTICK_BUTTON_DOWN +* SDL_JOYBUTTONUP => SDL_EVENT_JOYSTICK_BUTTON_UP +* SDL_JOYDEVICEADDED => SDL_EVENT_JOYSTICK_ADDED +* SDL_JOYDEVICEREMOVED => SDL_EVENT_JOYSTICK_REMOVED +* SDL_JOYHATMOTION => SDL_EVENT_JOYSTICK_HAT_MOTION +* SDL_KEYDOWN => SDL_EVENT_KEY_DOWN +* SDL_KEYMAPCHANGED => SDL_EVENT_KEYMAP_CHANGED +* SDL_KEYUP => SDL_EVENT_KEY_UP +* SDL_LASTEVENT => SDL_EVENT_LAST +* SDL_LOCALECHANGED => SDL_EVENT_LOCALE_CHANGED +* SDL_MOUSEBUTTONDOWN => SDL_EVENT_MOUSE_BUTTON_DOWN +* SDL_MOUSEBUTTONUP => SDL_EVENT_MOUSE_BUTTON_UP +* SDL_MOUSEMOTION => SDL_EVENT_MOUSE_MOTION +* SDL_MOUSEWHEEL => SDL_EVENT_MOUSE_WHEEL +* SDL_POLLSENTINEL => SDL_EVENT_POLL_SENTINEL +* SDL_QUIT => SDL_EVENT_QUIT +* SDL_RENDER_DEVICE_RESET => SDL_EVENT_RENDER_DEVICE_RESET +* SDL_RENDER_TARGETS_RESET => SDL_EVENT_RENDER_TARGETS_RESET +* SDL_SENSORUPDATE => SDL_EVENT_SENSOR_UPDATE +* SDL_SYSWMEVENT => SDL_EVENT_SYSWM +* SDL_TEXTEDITING => SDL_EVENT_TEXT_EDITING +* SDL_TEXTEDITING_EXT => SDL_EVENT_TEXT_EDITING_EXT +* SDL_TEXTINPUT => SDL_EVENT_TEXT_INPUT +* SDL_USEREVENT => SDL_EVENT_USER + +The following structures have been renamed: +* SDL_ControllerAxisEvent => SDL_GamepadAxisEvent +* SDL_ControllerButtonEvent => SDL_GamepadButtonEvent +* SDL_ControllerDeviceEvent => SDL_GamepadDeviceEvent +* SDL_ControllerSensorEvent => SDL_GamepadSensorEvent +* SDL_ControllerTouchpadEvent => SDL_GamepadTouchpadEvent + +The following functions have been removed: +* SDL_EventState() - replaced with SDL_SetEventEnabled() +* SDL_GetEventState() - replaced with SDL_EventEnabled() + +## SDL_gamecontroller.h + +SDL_gamecontroller.h has been renamed SDL_gamepad.h, and all APIs have been renamed to match. + +The SDL_EVENT_GAMEPAD_ADDED event now provides the joystick instance ID in the which member of the cdevice event structure. + +The functions SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() have been added to directly query the list of available gamepads. + +The gamepad face buttons have been renamed from A/B/X/Y to North/South/East/West to indicate that they are positional rather than hardware-specific. You can use SDL_GetGamepadButtonLabel() to get the labels for the face buttons, e.g. A/B/X/Y or Cross/Circle/Square/Triangle. The hint SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS is ignored, and mappings that use this hint are translated correctly into positional buttons. Applications should provide a way for users to swap between South/East as their accept/cancel buttons, as this varies based on region and muscle memory. You can use an approach similar to the following to handle this: + +```c +#define CONFIRM_BUTTON SDL_GAMEPAD_BUTTON_SOUTH +#define CANCEL_BUTTON SDL_GAMEPAD_BUTTON_EAST + +SDL_bool flipped_buttons; + +void InitMappedButtons(SDL_Gamepad *gamepad) +{ + if (!GetFlippedButtonSetting(&flipped_buttons)) { + if (SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_B) { + flipped_buttons = SDL_TRUE; + } else { + flipped_buttons = SDL_FALSE; + } + } +} + +SDL_GamepadButton GetMappedButton(SDL_GamepadButton button) +{ + if (flipped_buttons) { + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_GAMEPAD_BUTTON_EAST; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_SOUTH; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_NORTH; + case SDL_GAMEPAD_BUTTON_NORTH: + return SDL_GAMEPAD_BUTTON_WEST; + default: + break; + } + } + return button; +} + +SDL_GamepadButtonLabel GetConfirmActionLabel(SDL_Gamepad *gamepad) +{ + return SDL_GetGamepadButtonLabel(gamepad, GetMappedButton(CONFIRM_BUTTON)); +} + +SDL_GamepadButtonLabel GetCancelActionLabel(SDL_Gamepad *gamepad) +{ + return SDL_GetGamepadButtonLabel(gamepad, GetMappedButton(CANCEL_BUTTON)); +} + +void HandleGamepadEvent(SDL_Event *event) +{ + if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + switch (GetMappedButton(event->gbutton.button)) { + case CONFIRM_BUTTON: + /* Handle confirm action */ + break; + case CANCEL_BUTTON: + /* Handle cancel action */ + break; + default: + /* ... */ + break; + } + } +} +``` + +SDL_GameControllerGetSensorDataWithTimestamp() has been removed. If you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_GAMEPAD_SENSOR_UPDATE events. + +SDL_CONTROLLER_TYPE_VIRTUAL has been removed, so virtual controllers can emulate other gamepad types. If you need to know whether a controller is virtual, you can use SDL_IsJoystickVirtual(). + +SDL_CONTROLLER_TYPE_AMAZON_LUNA has been removed, and can be replaced with this code: +```c +SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id) +{ + return ((vendor_id == 0x1949 && product_id == 0x0419) || + (vendor_id == 0x0171 && product_id == 0x0419)); +} +``` + +SDL_CONTROLLER_TYPE_GOOGLE_STADIA has been removed, and can be replaced with this code: +```c +SDL_bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id) +{ + return (vendor_id == 0x18d1 && product_id == 0x9400); +} +``` + +SDL_CONTROLLER_TYPE_NVIDIA_SHIELD has been removed, and can be replaced with this code: +```c +SDL_bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id) +{ + return (vendor_id == 0x0955 && (product_id == 0x7210 || product_id == 0x7214)); +} +``` + +The following enums have been renamed: +* SDL_GameControllerAxis => SDL_GamepadAxis +* SDL_GameControllerBindType => SDL_GamepadBindingType +* SDL_GameControllerButton => SDL_GamepadButton +* SDL_GameControllerType => SDL_GamepadType + +The following structures have been renamed: +* SDL_GameController => SDL_Gamepad + +The following functions have been renamed: +* SDL_GameControllerAddMapping() => SDL_AddGamepadMapping() +* SDL_GameControllerAddMappingsFromFile() => SDL_AddGamepadMappingsFromFile() +* SDL_GameControllerAddMappingsFromRW() => SDL_AddGamepadMappingsFromRW() +* SDL_GameControllerClose() => SDL_CloseGamepad() +* SDL_GameControllerFromInstanceID() => SDL_GetGamepadFromInstanceID() +* SDL_GameControllerFromPlayerIndex() => SDL_GetGamepadFromPlayerIndex() +* SDL_GameControllerGetAppleSFSymbolsNameForAxis() => SDL_GetGamepadAppleSFSymbolsNameForAxis() +* SDL_GameControllerGetAppleSFSymbolsNameForButton() => SDL_GetGamepadAppleSFSymbolsNameForButton() +* SDL_GameControllerGetAttached() => SDL_GamepadConnected() +* SDL_GameControllerGetAxis() => SDL_GetGamepadAxis() +* SDL_GameControllerGetAxisFromString() => SDL_GetGamepadAxisFromString() +* SDL_GameControllerGetButton() => SDL_GetGamepadButton() +* SDL_GameControllerGetButtonFromString() => SDL_GetGamepadButtonFromString() +* SDL_GameControllerGetFirmwareVersion() => SDL_GetGamepadFirmwareVersion() +* SDL_GameControllerGetJoystick() => SDL_GetGamepadJoystick() +* SDL_GameControllerGetNumTouchpadFingers() => SDL_GetNumGamepadTouchpadFingers() +* SDL_GameControllerGetNumTouchpads() => SDL_GetNumGamepadTouchpads() +* SDL_GameControllerGetPlayerIndex() => SDL_GetGamepadPlayerIndex() +* SDL_GameControllerGetProduct() => SDL_GetGamepadProduct() +* SDL_GameControllerGetProductVersion() => SDL_GetGamepadProductVersion() +* SDL_GameControllerGetSensorData() => SDL_GetGamepadSensorData() +* SDL_GameControllerGetSensorDataRate() => SDL_GetGamepadSensorDataRate() +* SDL_GameControllerGetSerial() => SDL_GetGamepadSerial() +* SDL_GameControllerGetSteamHandle() => SDL_GetGamepadSteamHandle() +* SDL_GameControllerGetStringForAxis() => SDL_GetGamepadStringForAxis() +* SDL_GameControllerGetStringForButton() => SDL_GetGamepadStringForButton() +* SDL_GameControllerGetTouchpadFinger() => SDL_GetGamepadTouchpadFinger() +* SDL_GameControllerGetType() => SDL_GetGamepadType() +* SDL_GameControllerGetVendor() => SDL_GetGamepadVendor() +* SDL_GameControllerHasAxis() => SDL_GamepadHasAxis() +* SDL_GameControllerHasButton() => SDL_GamepadHasButton() +* SDL_GameControllerHasLED() => SDL_GamepadHasLED() +* SDL_GameControllerHasRumble() => SDL_GamepadHasRumble() +* SDL_GameControllerHasRumbleTriggers() => SDL_GamepadHasRumbleTriggers() +* SDL_GameControllerHasSensor() => SDL_GamepadHasSensor() +* SDL_GameControllerIsSensorEnabled() => SDL_GamepadSensorEnabled() +* SDL_GameControllerMapping() => SDL_GetGamepadMapping() +* SDL_GameControllerMappingForGUID() => SDL_GetGamepadMappingForGUID() +* SDL_GameControllerName() => SDL_GetGamepadName() +* SDL_GameControllerOpen() => SDL_OpenGamepad() +* SDL_GameControllerPath() => SDL_GetGamepadPath() +* SDL_GameControllerRumble() => SDL_RumbleGamepad() +* SDL_GameControllerRumbleTriggers() => SDL_RumbleGamepadTriggers() +* SDL_GameControllerSendEffect() => SDL_SendGamepadEffect() +* SDL_GameControllerSetLED() => SDL_SetGamepadLED() +* SDL_GameControllerSetPlayerIndex() => SDL_SetGamepadPlayerIndex() +* SDL_GameControllerSetSensorEnabled() => SDL_SetGamepadSensorEnabled() +* SDL_GameControllerUpdate() => SDL_UpdateGamepads() +* SDL_IsGameController() => SDL_IsGamepad() + +The following functions have been removed: +* SDL_GameControllerEventState() - replaced with SDL_SetGamepadEventsEnabled() and SDL_GamepadEventsEnabled() +* SDL_GameControllerGetBindForAxis() - replaced with SDL_GetGamepadBindings() +* SDL_GameControllerGetBindForButton() - replaced with SDL_GetGamepadBindings() +* SDL_GameControllerMappingForDeviceIndex() - replaced with SDL_GetGamepadInstanceMapping() +* SDL_GameControllerNameForIndex() - replaced with SDL_GetGamepadInstanceName() +* SDL_GameControllerPathForIndex() - replaced with SDL_GetGamepadInstancePath() +* SDL_GameControllerTypeForIndex() - replaced with SDL_GetGamepadInstanceType() +* SDL_GameControllerNumMappings() - replaced with SDL_GetGamepadMappings() +* SDL_GameControllerMappingForIndex() - replaced with SDL_GetGamepadMappings() + +The following symbols have been renamed: +* SDL_CONTROLLER_AXIS_INVALID => SDL_GAMEPAD_AXIS_INVALID +* SDL_CONTROLLER_AXIS_LEFTX => SDL_GAMEPAD_AXIS_LEFTX +* SDL_CONTROLLER_AXIS_LEFTY => SDL_GAMEPAD_AXIS_LEFTY +* SDL_CONTROLLER_AXIS_MAX => SDL_GAMEPAD_AXIS_MAX +* SDL_CONTROLLER_AXIS_RIGHTX => SDL_GAMEPAD_AXIS_RIGHTX +* SDL_CONTROLLER_AXIS_RIGHTY => SDL_GAMEPAD_AXIS_RIGHTY +* SDL_CONTROLLER_AXIS_TRIGGERLEFT => SDL_GAMEPAD_AXIS_LEFT_TRIGGER +* SDL_CONTROLLER_AXIS_TRIGGERRIGHT => SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +* SDL_CONTROLLER_BINDTYPE_AXIS => SDL_GAMEPAD_BINDTYPE_AXIS +* SDL_CONTROLLER_BINDTYPE_BUTTON => SDL_GAMEPAD_BINDTYPE_BUTTON +* SDL_CONTROLLER_BINDTYPE_HAT => SDL_GAMEPAD_BINDTYPE_HAT +* SDL_CONTROLLER_BINDTYPE_NONE => SDL_GAMEPAD_BINDTYPE_NONE +* SDL_CONTROLLER_BUTTON_A => SDL_GAMEPAD_BUTTON_SOUTH +* SDL_CONTROLLER_BUTTON_B => SDL_GAMEPAD_BUTTON_EAST +* SDL_CONTROLLER_BUTTON_BACK => SDL_GAMEPAD_BUTTON_BACK +* SDL_CONTROLLER_BUTTON_DPAD_DOWN => SDL_GAMEPAD_BUTTON_DPAD_DOWN +* SDL_CONTROLLER_BUTTON_DPAD_LEFT => SDL_GAMEPAD_BUTTON_DPAD_LEFT +* SDL_CONTROLLER_BUTTON_DPAD_RIGHT => SDL_GAMEPAD_BUTTON_DPAD_RIGHT +* SDL_CONTROLLER_BUTTON_DPAD_UP => SDL_GAMEPAD_BUTTON_DPAD_UP +* SDL_CONTROLLER_BUTTON_GUIDE => SDL_GAMEPAD_BUTTON_GUIDE +* SDL_CONTROLLER_BUTTON_INVALID => SDL_GAMEPAD_BUTTON_INVALID +* SDL_CONTROLLER_BUTTON_LEFTSHOULDER => SDL_GAMEPAD_BUTTON_LEFT_SHOULDER +* SDL_CONTROLLER_BUTTON_LEFTSTICK => SDL_GAMEPAD_BUTTON_LEFT_STICK +* SDL_CONTROLLER_BUTTON_MAX => SDL_GAMEPAD_BUTTON_MAX +* SDL_CONTROLLER_BUTTON_MISC1 => SDL_GAMEPAD_BUTTON_MISC1 +* SDL_CONTROLLER_BUTTON_PADDLE1 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 +* SDL_CONTROLLER_BUTTON_PADDLE2 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 +* SDL_CONTROLLER_BUTTON_PADDLE3 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 +* SDL_CONTROLLER_BUTTON_PADDLE4 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 +* SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER +* SDL_CONTROLLER_BUTTON_RIGHTSTICK => SDL_GAMEPAD_BUTTON_RIGHT_STICK +* SDL_CONTROLLER_BUTTON_START => SDL_GAMEPAD_BUTTON_START +* SDL_CONTROLLER_BUTTON_TOUCHPAD => SDL_GAMEPAD_BUTTON_TOUCHPAD +* SDL_CONTROLLER_BUTTON_X => SDL_GAMEPAD_BUTTON_WEST +* SDL_CONTROLLER_BUTTON_Y => SDL_GAMEPAD_BUTTON_NORTH +* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT +* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR +* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT +* SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO +* SDL_CONTROLLER_TYPE_PS3 => SDL_GAMEPAD_TYPE_PS3 +* SDL_CONTROLLER_TYPE_PS4 => SDL_GAMEPAD_TYPE_PS4 +* SDL_CONTROLLER_TYPE_PS5 => SDL_GAMEPAD_TYPE_PS5 +* SDL_CONTROLLER_TYPE_UNKNOWN => SDL_GAMEPAD_TYPE_STANDARD +* SDL_CONTROLLER_TYPE_VIRTUAL => SDL_GAMEPAD_TYPE_VIRTUAL +* SDL_CONTROLLER_TYPE_XBOX360 => SDL_GAMEPAD_TYPE_XBOX360 +* SDL_CONTROLLER_TYPE_XBOXONE => SDL_GAMEPAD_TYPE_XBOXONE + +## SDL_gesture.h + +The gesture API has been removed. There is no replacement planned in SDL3. +However, the SDL2 code has been moved to a single-header library that can +be dropped into an SDL3 or SDL2 program, to continue to provide this +functionality to your app and aid migration. That is located in the +[SDL_gesture GitHub repository](https://github.com/libsdl-org/SDL_gesture). + +## SDL_hints.h + +SDL_AddHintCallback() now returns a standard int result instead of void, returning 0 if the function succeeds or a negative error code if there was an error. + +Calling SDL_GetHint() with the name of the hint being changed from within a hint callback will now return the new value rather than the old value. The old value is still passed as a parameter to the hint callback. + +The following hints have been removed: +* SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS - gamepad buttons are always positional +* SDL_HINT_IDLE_TIMER_DISABLED - use SDL_DisableScreenSaver instead +* SDL_HINT_IME_SUPPORT_EXTENDED_TEXT - the normal text editing event has extended text +* SDL_HINT_MOUSE_RELATIVE_SCALING - mouse coordinates are no longer automatically scaled by the SDL renderer +* SDL_HINT_RENDER_LOGICAL_SIZE_MODE - the logical size mode is explicitly set with SDL_SetRenderLogicalPresentation() +* SDL_HINT_RENDER_BATCHING - Render batching is always enabled, apps should call SDL_FlushRenderer() before calling into a lower-level graphics API. +* SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL - replaced with the "opengl" property in SDL_CreateWindowWithProperties() +* SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN - replaced with the "vulkan" property in SDL_CreateWindowWithProperties() +* SDL_HINT_VIDEO_HIGHDPI_DISABLED - high DPI support is always enabled +* SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT - replaced with the "win32.pixel_format_hwnd" in SDL_CreateWindowWithProperties() +* SDL_HINT_VIDEO_X11_FORCE_EGL - use SDL_HINT_VIDEO_FORCE_EGL instead +* SDL_HINT_VIDEO_X11_XINERAMA - Xinerama no longer supported by the X11 backend +* SDL_HINT_VIDEO_X11_XVIDMODE - Xvidmode no longer supported by the X11 backend + +* Renamed hints SDL_HINT_VIDEODRIVER and SDL_HINT_AUDIODRIVER to SDL_HINT_VIDEO_DRIVER and SDL_HINT_AUDIO_DRIVER +* Renamed environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER +* The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced with the unified hint SDL_HINT_APP_ID + +## SDL_init.h + +The following symbols have been renamed: +* SDL_INIT_GAMECONTROLLER => SDL_INIT_GAMEPAD + +The following symbols have been removed: +* SDL_INIT_NOPARACHUTE + +## SDL_joystick.h + +SDL_JoystickID has changed from Sint32 to Uint32, with an invalid ID being 0. + +Rather than iterating over joysticks using device index, there is a new function SDL_GetJoysticks() to get the current list of joysticks, and new functions to get information about joysticks from their instance ID: +```c +{ + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == 0) { + int i, num_joysticks; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks); + if (joysticks) { + for (i = 0; i < num_joysticks; ++i) { + SDL_JoystickID instance_id = joysticks[i]; + const char *name = SDL_GetJoystickInstanceName(instance_id); + const char *path = SDL_GetJoystickInstancePath(instance_id); + + SDL_Log("Joystick %" SDL_PRIu32 ": %s%s%s VID 0x%.4x, PID 0x%.4x\n", + instance_id, name ? name : "Unknown", path ? ", " : "", path ? path : "", SDL_GetJoystickInstanceVendor(instance_id), SDL_GetJoystickInstanceProduct(instance_id)); + } + SDL_free(joysticks); + } + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} +``` + +The SDL_EVENT_JOYSTICK_ADDED event now provides the joystick instance ID in the `which` member of the jdevice event structure. + +The functions SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() have been added to directly query the list of available joysticks. + +SDL_AttachVirtualJoystick() and SDL_AttachVirtualJoystickEx() now return the joystick instance ID instead of a device index, and return 0 if there was an error. + +The following functions have been renamed: +* SDL_JoystickAttachVirtual() => SDL_AttachVirtualJoystick() +* SDL_JoystickAttachVirtualEx() => SDL_AttachVirtualJoystickEx() +* SDL_JoystickClose() => SDL_CloseJoystick() +* SDL_JoystickCurrentPowerLevel() => SDL_GetJoystickPowerLevel() +* SDL_JoystickDetachVirtual() => SDL_DetachVirtualJoystick() +* SDL_JoystickFromInstanceID() => SDL_GetJoystickFromInstanceID() +* SDL_JoystickFromPlayerIndex() => SDL_GetJoystickFromPlayerIndex() +* SDL_JoystickGetAttached() => SDL_JoystickConnected() +* SDL_JoystickGetAxis() => SDL_GetJoystickAxis() +* SDL_JoystickGetAxisInitialState() => SDL_GetJoystickAxisInitialState() +* SDL_JoystickGetButton() => SDL_GetJoystickButton() +* SDL_JoystickGetFirmwareVersion() => SDL_GetJoystickFirmwareVersion() +* SDL_JoystickGetGUID() => SDL_GetJoystickGUID() +* SDL_JoystickGetGUIDFromString() => SDL_GetJoystickGUIDFromString() +* SDL_JoystickGetGUIDString() => SDL_GetJoystickGUIDString() +* SDL_JoystickGetHat() => SDL_GetJoystickHat() +* SDL_JoystickGetPlayerIndex() => SDL_GetJoystickPlayerIndex() +* SDL_JoystickGetProduct() => SDL_GetJoystickProduct() +* SDL_JoystickGetProductVersion() => SDL_GetJoystickProductVersion() +* SDL_JoystickGetSerial() => SDL_GetJoystickSerial() +* SDL_JoystickGetType() => SDL_GetJoystickType() +* SDL_JoystickGetVendor() => SDL_GetJoystickVendor() +* SDL_JoystickInstanceID() => SDL_GetJoystickInstanceID() +* SDL_JoystickIsVirtual() => SDL_IsJoystickVirtual() +* SDL_JoystickName() => SDL_GetJoystickName() +* SDL_JoystickNumAxes() => SDL_GetNumJoystickAxes() +* SDL_JoystickNumButtons() => SDL_GetNumJoystickButtons() +* SDL_JoystickNumHats() => SDL_GetNumJoystickHats() +* SDL_JoystickOpen() => SDL_OpenJoystick() +* SDL_JoystickPath() => SDL_GetJoystickPath() +* SDL_JoystickRumble() => SDL_RumbleJoystick() +* SDL_JoystickRumbleTriggers() => SDL_RumbleJoystickTriggers() +* SDL_JoystickSendEffect() => SDL_SendJoystickEffect() +* SDL_JoystickSetLED() => SDL_SetJoystickLED() +* SDL_JoystickSetPlayerIndex() => SDL_SetJoystickPlayerIndex() +* SDL_JoystickSetVirtualAxis() => SDL_SetJoystickVirtualAxis() +* SDL_JoystickSetVirtualButton() => SDL_SetJoystickVirtualButton() +* SDL_JoystickSetVirtualHat() => SDL_SetJoystickVirtualHat() +* SDL_JoystickUpdate() => SDL_UpdateJoysticks() + +The following symbols have been renamed: +* SDL_JOYSTICK_TYPE_GAMECONTROLLER => SDL_JOYSTICK_TYPE_GAMEPAD + +The following functions have been removed: +* SDL_JoystickEventState() - replaced with SDL_SetJoystickEventsEnabled() and SDL_JoystickEventsEnabled() +* SDL_JoystickGetDeviceGUID() - replaced with SDL_GetJoystickInstanceGUID() +* SDL_JoystickGetDeviceInstanceID() +* SDL_JoystickGetDevicePlayerIndex() - replaced with SDL_GetJoystickInstancePlayerIndex() +* SDL_JoystickGetDeviceProduct() - replaced with SDL_GetJoystickInstanceProduct() +* SDL_JoystickGetDeviceProductVersion() - replaced with SDL_GetJoystickInstanceProductVersion() +* SDL_JoystickGetDeviceType() - replaced with SDL_GetJoystickInstanceType() +* SDL_JoystickGetDeviceVendor() - replaced with SDL_GetJoystickInstanceVendor() +* SDL_JoystickNameForIndex() - replaced with SDL_GetJoystickInstanceName() +* SDL_JoystickNumBalls() - API has been removed, see https://github.com/libsdl-org/SDL/issues/6766 +* SDL_JoystickPathForIndex() - replaced with SDL_GetJoystickInstancePath() +* SDL_NumJoysticks() - replaced with SDL_GetJoysticks() + +The following symbols have been removed: +* SDL_JOYBALLMOTION + +## SDL_keyboard.h + +The following functions have been renamed: +* SDL_IsScreenKeyboardShown() => SDL_ScreenKeyboardShown() +* SDL_IsTextInputActive() => SDL_TextInputActive() +* SDL_IsTextInputShown() => SDL_TextInputShown() + +## SDL_keycode.h + +The following symbols have been renamed: +* KMOD_ALT => SDL_KMOD_ALT +* KMOD_CAPS => SDL_KMOD_CAPS +* KMOD_CTRL => SDL_KMOD_CTRL +* KMOD_GUI => SDL_KMOD_GUI +* KMOD_LALT => SDL_KMOD_LALT +* KMOD_LCTRL => SDL_KMOD_LCTRL +* KMOD_LGUI => SDL_KMOD_LGUI +* KMOD_LSHIFT => SDL_KMOD_LSHIFT +* KMOD_MODE => SDL_KMOD_MODE +* KMOD_NONE => SDL_KMOD_NONE +* KMOD_NUM => SDL_KMOD_NUM +* KMOD_RALT => SDL_KMOD_RALT +* KMOD_RCTRL => SDL_KMOD_RCTRL +* KMOD_RESERVED => SDL_KMOD_RESERVED +* KMOD_RGUI => SDL_KMOD_RGUI +* KMOD_RSHIFT => SDL_KMOD_RSHIFT +* KMOD_SCROLL => SDL_KMOD_SCROLL +* KMOD_SHIFT => SDL_KMOD_SHIFT + +## SDL_loadso.h + +SDL_LoadFunction() now returns `SDL_FunctionPointer` instead of `void *`, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior. + +## SDL_main.h + +SDL3 doesn't have a static libSDLmain to link against anymore. +Instead SDL_main.h is now a header-only library **and not included by SDL.h anymore**. + +Using it is really simple: Just `#include ` in the source file with your standard +`int main(int argc, char* argv[])` function. + +## SDL_metal.h + +SDL_Metal_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. + +## SDL_mouse.h + +SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() + +SDL_GetMouseState(), SDL_GetGlobalMouseState(), SDL_GetRelativeMouseState(), SDL_WarpMouseInWindow(), and SDL_WarpMouseGlobal() all use floating point mouse positions, to provide sub-pixel precision on platforms that support it. + +The following functions have been renamed: +* SDL_FreeCursor() => SDL_DestroyCursor() + +## SDL_mutex.h + +SDL_MUTEX_MAXWAIT has been removed; it suggested there was a maximum timeout one could outlive, instead of an infinite wait. Instead, pass a -1 to functions that accepted this symbol. + +SDL_LockMutex and SDL_UnlockMutex now return void; if the mutex is valid (including being a NULL pointer, which returns immediately), these functions never fail. If the mutex is invalid or the caller does something illegal, like unlock another thread's mutex, this is considered undefined behavior. + +The following functions have been renamed: +* SDL_CondBroadcast() => SDL_BroadcastCondition() +* SDL_CondSignal() => SDL_SignalCondition() +* SDL_CondWait() => SDL_WaitCondition() +* SDL_CondWaitTimeout() => SDL_WaitConditionTimeout() +* SDL_CreateCond() => SDL_CreateCondition() +* SDL_DestroyCond() => SDL_DestroyCondition() +* SDL_SemPost() => SDL_PostSemaphore() +* SDL_SemTryWait() => SDL_TryWaitSemaphore() +* SDL_SemValue() => SDL_GetSemaphoreValue() +* SDL_SemWait() => SDL_WaitSemaphore() +* SDL_SemWaitTimeout() => SDL_WaitSemaphoreTimeout() + +The following symbols have been renamed: +* SDL_cond => SDL_Condition +* SDL_mutex => SDL_Mutex +* SDL_sem => SDL_Semaphore + +## SDL_pixels.h + +SDL_CalculateGammaRamp has been removed, because SDL_SetWindowGammaRamp has been removed as well due to poor support in modern operating systems (see [SDL_video.h](#sdl_videoh)). + +The following functions have been renamed: +* SDL_AllocFormat() => SDL_CreatePixelFormat() +* SDL_AllocPalette() => SDL_CreatePalette() +* SDL_FreeFormat() => SDL_DestroyPixelFormat() +* SDL_FreePalette() => SDL_DestroyPalette() +* SDL_MasksToPixelFormatEnum() => SDL_GetPixelFormatEnumForMasks() +* SDL_PixelFormatEnumToMasks() => SDL_GetMasksForPixelFormatEnum() + +The following symbols have been renamed: +* SDL_DISPLAYEVENT_DISCONNECTED => SDL_EVENT_DISPLAY_REMOVED +* SDL_DISPLAYEVENT_MOVED => SDL_EVENT_DISPLAY_MOVED +* SDL_DISPLAYEVENT_ORIENTATION => SDL_EVENT_DISPLAY_ORIENTATION +* SDL_WINDOWEVENT_CLOSE => SDL_EVENT_WINDOW_CLOSE_REQUESTED +* SDL_WINDOWEVENT_DISPLAY_CHANGED => SDL_EVENT_WINDOW_DISPLAY_CHANGED +* SDL_WINDOWEVENT_ENTER => SDL_EVENT_WINDOW_ENTER +* SDL_WINDOWEVENT_EXPOSED => SDL_EVENT_WINDOW_EXPOSED +* SDL_WINDOWEVENT_FOCUS_GAINED => SDL_EVENT_WINDOW_FOCUS_GAINED +* SDL_WINDOWEVENT_FOCUS_LOST => SDL_EVENT_WINDOW_FOCUS_LOST +* SDL_WINDOWEVENT_HIDDEN => SDL_EVENT_WINDOW_HIDDEN +* SDL_WINDOWEVENT_HIT_TEST => SDL_EVENT_WINDOW_HIT_TEST +* SDL_WINDOWEVENT_ICCPROF_CHANGED => SDL_EVENT_WINDOW_ICCPROF_CHANGED +* SDL_WINDOWEVENT_LEAVE => SDL_EVENT_WINDOW_LEAVE +* SDL_WINDOWEVENT_MAXIMIZED => SDL_EVENT_WINDOW_MAXIMIZED +* SDL_WINDOWEVENT_MINIMIZED => SDL_EVENT_WINDOW_MINIMIZED +* SDL_WINDOWEVENT_MOVED => SDL_EVENT_WINDOW_MOVED +* SDL_WINDOWEVENT_RESIZED => SDL_EVENT_WINDOW_RESIZED +* SDL_WINDOWEVENT_RESTORED => SDL_EVENT_WINDOW_RESTORED +* SDL_WINDOWEVENT_SHOWN => SDL_EVENT_WINDOW_SHOWN +* SDL_WINDOWEVENT_SIZE_CHANGED => SDL_EVENT_WINDOW_SIZE_CHANGED +* SDL_WINDOWEVENT_TAKE_FOCUS => SDL_EVENT_WINDOW_TAKE_FOCUS + + +## SDL_platform.h + +The preprocessor symbol `__MACOSX__` has been renamed `__MACOS__`, and `__IPHONEOS__` has been renamed `__IOS__` + +## SDL_rect.h + +The following functions have been renamed: +* SDL_EncloseFPoints() => SDL_GetRectEnclosingPointsFloat() +* SDL_EnclosePoints() => SDL_GetRectEnclosingPoints() +* SDL_FRectEmpty() => SDL_RectEmptyFloat() +* SDL_FRectEquals() => SDL_RectsEqualFloat() +* SDL_FRectEqualsEpsilon() => SDL_RectsEqualEpsilon() +* SDL_HasIntersection() => SDL_HasRectIntersection() +* SDL_HasIntersectionF() => SDL_HasRectIntersectionFloat() +* SDL_IntersectFRect() => SDL_GetRectIntersectionFloat() +* SDL_IntersectFRectAndLine() => SDL_GetRectAndLineIntersectionFloat() +* SDL_IntersectRect() => SDL_GetRectIntersection() +* SDL_IntersectRectAndLine() => SDL_GetRectAndLineIntersection() +* SDL_PointInFRect() => SDL_PointInRectFloat() +* SDL_RectEquals() => SDL_RectsEqual() +* SDL_UnionFRect() => SDL_GetRectUnionFloat() +* SDL_UnionRect() => SDL_GetRectUnion() + +## SDL_render.h + +The 2D renderer API always uses batching in SDL3. There is no magic to turn +it on and off; it doesn't matter if you select a specific renderer or try to +use any hint. This means that all apps that use SDL3's 2D renderer and also +want to call directly into the platform's lower-layer graphics API _must_ call +SDL_FlushRenderer() before doing so. This will make sure any pending rendering +work from SDL is done before the app starts directly drawing. + +SDL_GetRenderDriverInfo() has been removed, since most of the information it reported were +estimates and could not be accurate before creating a renderer. Often times this function +was used to figure out the index of a driver, so one would call it in a for-loop, looking +for the driver named "opengl" or whatnot. SDL_GetRenderDriver() has been added for this +functionality, which returns only the name of the driver. + +Additionally, SDL_CreateRenderer()'s second argument is no longer an integer index, but a +`const char *` representing a renderer's name; if you were just using a for-loop to find +which index is the "opengl" or whatnot driver, you can just pass that string directly +here, now. Passing NULL is the same as passing -1 here in SDL2, to signify you want SDL +to decide for you. + +The SDL_RENDERER_TARGETTEXTURE flag has been removed, all current renderers support target texture functionality. + +When a renderer is created, it will automatically set the logical size to the size of +the window in points. For high DPI displays, this will set up scaling from points to +pixels. You can disable this scaling with: +```c + SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED, SDL_SCALEMODE_NEAREST); +``` + +Mouse and touch events are no longer filtered to change their coordinates, instead you +can call SDL_ConvertEventToRenderCoordinates() to explicitly map event coordinates into +the rendering viewport. + +SDL_RenderWindowToLogical() and SDL_RenderLogicalToWindow() have been renamed SDL_RenderCoordinatesFromWindow() and SDL_RenderCoordinatesToWindow() and take floating point coordinates in both directions. + +The viewport, clipping state, and scale for render targets are now persistent and will remain set whenever they are active. + +The following functions have been renamed: +* SDL_GetRendererOutputSize() => SDL_GetCurrentRenderOutputSize() +* SDL_RenderCopy() => SDL_RenderTexture() +* SDL_RenderCopyEx() => SDL_RenderTextureRotated() +* SDL_RenderCopyExF() => SDL_RenderTextureRotated() +* SDL_RenderCopyF() => SDL_RenderTexture() +* SDL_RenderDrawLine() => SDL_RenderLine() +* SDL_RenderDrawLineF() => SDL_RenderLine() +* SDL_RenderDrawLines() => SDL_RenderLines() +* SDL_RenderDrawLinesF() => SDL_RenderLines() +* SDL_RenderDrawPoint() => SDL_RenderPoint() +* SDL_RenderDrawPointF() => SDL_RenderPoint() +* SDL_RenderDrawPoints() => SDL_RenderPoints() +* SDL_RenderDrawPointsF() => SDL_RenderPoints() +* SDL_RenderDrawRect() => SDL_RenderRect() +* SDL_RenderDrawRectF() => SDL_RenderRect() +* SDL_RenderDrawRects() => SDL_RenderRects() +* SDL_RenderDrawRectsF() => SDL_RenderRects() +* SDL_RenderFillRectF() => SDL_RenderFillRect() +* SDL_RenderFillRectsF() => SDL_RenderFillRects() +* SDL_RenderFlush() => SDL_FlushRenderer() +* SDL_RenderGetClipRect() => SDL_GetRenderClipRect() +* SDL_RenderGetIntegerScale() => SDL_GetRenderIntegerScale() +* SDL_RenderGetLogicalSize() => SDL_GetRenderLogicalPresentation() +* SDL_RenderGetMetalCommandEncoder() => SDL_GetRenderMetalCommandEncoder() +* SDL_RenderGetMetalLayer() => SDL_GetRenderMetalLayer() +* SDL_RenderGetScale() => SDL_GetRenderScale() +* SDL_RenderGetViewport() => SDL_GetRenderViewport() +* SDL_RenderGetWindow() => SDL_GetRenderWindow() +* SDL_RenderIsClipEnabled() => SDL_RenderClipEnabled() +* SDL_RenderLogicalToWindow() => SDL_RenderCoordinatesToWindow() +* SDL_RenderSetClipRect() => SDL_SetRenderClipRect() +* SDL_RenderSetIntegerScale() => SDL_SetRenderIntegerScale() +* SDL_RenderSetLogicalSize() => SDL_SetRenderLogicalPresentation() +* SDL_RenderSetScale() => SDL_SetRenderScale() +* SDL_RenderSetVSync() => SDL_SetRenderVSync() +* SDL_RenderSetViewport() => SDL_SetRenderViewport() +* SDL_RenderWindowToLogical() => SDL_RenderCoordinatesFromWindow() + +The following functions have been removed: +* SDL_GL_BindTexture() - use SDL_GetTextureProperties() to get the OpenGL texture ID and bind the texture directly +* SDL_GL_UnbindTexture() - use SDL_GetTextureProperties() to get the OpenGL texture ID and unbind the texture directly +* SDL_GetTextureUserData() - use SDL_GetTextureProperties() instead +* SDL_RenderGetIntegerScale() +* SDL_RenderSetIntegerScale() - this is now explicit with SDL_LOGICAL_PRESENTATION_INTEGER_SCALE +* SDL_RenderTargetSupported() - render targets are always supported +* SDL_SetTextureUserData() - use SDL_GetTextureProperties() instead + +The following symbols have been renamed: +* SDL_ScaleModeBest => SDL_SCALEMODE_BEST +* SDL_ScaleModeLinear => SDL_SCALEMODE_LINEAR +* SDL_ScaleModeNearest => SDL_SCALEMODE_NEAREST + +## SDL_rwops.h + +The following symbols have been renamed: +* RW_SEEK_CUR => SDL_RW_SEEK_CUR +* RW_SEEK_END => SDL_RW_SEEK_END +* RW_SEEK_SET => SDL_RW_SEEK_SET + +SDL_RWread and SDL_RWwrite (and SDL_RWops::read, SDL_RWops::write) have a different function signature in SDL3. + +Previously they looked more like stdio: + +```c +size_t SDL_RWread(SDL_RWops *context, void *ptr, size_t size, size_t maxnum); +size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size, size_t maxnum); +``` + +But now they look more like POSIX: + +```c +size_t SDL_RWread(SDL_RWops *context, void *ptr, size_t size); +size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size); +``` + +Code that used to look like this: +``` +size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_RWops *stream) +{ + return SDL_RWread(stream, ptr, size, nitems); +} +``` +should be changed to: +``` +size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_RWops *stream) +{ + if (size > 0 && nitems > 0) { + return SDL_RWread(stream, ptr, size * nitems) / size; + } + return 0; +} +``` + +SDL_RWFromFP has been removed from the API, due to issues when the SDL library uses a different C runtime from the application. + +You can implement this in your own code easily: +```c +#include + + +static Sint64 SDLCALL stdio_seek(SDL_RWops *context, Sint64 offset, int whence) +{ + int stdiowhence; + + switch (whence) { + case SDL_RW_SEEK_SET: + stdiowhence = SEEK_SET; + break; + case SDL_RW_SEEK_CUR: + stdiowhence = SEEK_CUR; + break; + case SDL_RW_SEEK_END: + stdiowhence = SEEK_END; + break; + default: + return SDL_SetError("Unknown value for 'whence'"); + } + + if (fseek((FILE *)context->hidden.stdio.fp, (fseek_off_t)offset, stdiowhence) == 0) { + Sint64 pos = ftell((FILE *)context->hidden.stdio.fp); + if (pos < 0) { + return SDL_SetError("Couldn't get stream offset"); + } + return pos; + } + return SDL_Error(SDL_EFSEEK); +} + +static size_t SDLCALL stdio_read(SDL_RWops *context, void *ptr, size_t size) +{ + size_t bytes; + + bytes = fread(ptr, 1, size, (FILE *)context->hidden.stdio.fp); + if (bytes == 0 && ferror((FILE *)context->hidden.stdio.fp)) { + SDL_Error(SDL_EFREAD); + } + return bytes; +} + +static size_t SDLCALL stdio_write(SDL_RWops *context, const void *ptr, size_t size) +{ + size_t bytes; + + bytes = fwrite(ptr, 1, size, (FILE *)context->hidden.stdio.fp); + if (bytes == 0 && ferror((FILE *)context->hidden.stdio.fp)) { + SDL_Error(SDL_EFWRITE); + } + return bytes; +} + +static int SDLCALL stdio_close(SDL_RWops *context) +{ + int status = 0; + if (context->hidden.stdio.autoclose) { + if (fclose((FILE *)context->hidden.stdio.fp) != 0) { + status = SDL_Error(SDL_EFWRITE); + } + } + SDL_DestroyRW(context); + return status; +} + +SDL_RWops *SDL_RWFromFP(void *fp, SDL_bool autoclose) +{ + SDL_RWops *rwops = NULL; + + rwops = SDL_CreateRW(); + if (rwops != NULL) { + rwops->seek = stdio_seek; + rwops->read = stdio_read; + rwops->write = stdio_write; + rwops->close = stdio_close; + rwops->hidden.stdio.fp = fp; + rwops->hidden.stdio.autoclose = autoclose; + rwops->type = SDL_RWOPS_STDFILE; + } + return rwops; +} +``` + +The functions SDL_ReadU8(), SDL_ReadU16LE(), SDL_ReadU16BE(), SDL_ReadU32LE(), SDL_ReadU32BE(), SDL_ReadU64LE(), and SDL_ReadU64BE() now return SDL_TRUE if the read succeeded and SDL_FALSE if it didn't, and store the data in a pointer passed in as a parameter. + +The following functions have been renamed: +* SDL_AllocRW() => SDL_CreateRW() +* SDL_FreeRW() => SDL_DestroyRW() +* SDL_ReadBE16() => SDL_ReadU16BE() +* SDL_ReadBE32() => SDL_ReadU32BE() +* SDL_ReadBE64() => SDL_ReadU64BE() +* SDL_ReadLE16() => SDL_ReadU16LE() +* SDL_ReadLE32() => SDL_ReadU32LE() +* SDL_ReadLE64() => SDL_ReadU64LE() +* SDL_WriteBE16() => SDL_WriteU16BE() +* SDL_WriteBE32() => SDL_WriteU32BE() +* SDL_WriteBE64() => SDL_WriteU64BE() +* SDL_WriteLE16() => SDL_WriteU16LE() +* SDL_WriteLE32() => SDL_WriteU32LE() +* SDL_WriteLE64() => SDL_WriteU64LE() + +## SDL_sensor.h + +SDL_SensorID has changed from Sint32 to Uint32, with an invalid ID being 0. + +Rather than iterating over sensors using device index, there is a new function SDL_GetSensors() to get the current list of sensors, and new functions to get information about sensors from their instance ID: +```c +{ + if (SDL_InitSubSystem(SDL_INIT_SENSOR) == 0) { + int i, num_sensors; + SDL_SensorID *sensors = SDL_GetSensors(&num_sensors); + if (sensors) { + for (i = 0; i < num_sensors; ++i) { + SDL_Log("Sensor %" SDL_PRIu32 ": %s, type %d, platform type %d\n", + sensors[i], + SDL_GetSensorInstanceName(sensors[i]), + SDL_GetSensorInstanceType(sensors[i]), + SDL_GetSensorInstanceNonPortableType(sensors[i])); + } + SDL_free(sensors); + } + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } +} +``` + +Removed SDL_SensorGetDataWithTimestamp(), if you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_SENSOR_UPDATE events. + + +The following functions have been renamed: +* SDL_SensorClose() => SDL_CloseSensor() +* SDL_SensorFromInstanceID() => SDL_GetSensorFromInstanceID() +* SDL_SensorGetData() => SDL_GetSensorData() +* SDL_SensorGetInstanceID() => SDL_GetSensorInstanceID() +* SDL_SensorGetName() => SDL_GetSensorName() +* SDL_SensorGetNonPortableType() => SDL_GetSensorNonPortableType() +* SDL_SensorGetType() => SDL_GetSensorType() +* SDL_SensorOpen() => SDL_OpenSensor() +* SDL_SensorUpdate() => SDL_UpdateSensors() + +The following functions have been removed: +* SDL_LockSensors() +* SDL_NumSensors() - replaced with SDL_GetSensors() +* SDL_SensorGetDeviceInstanceID() +* SDL_SensorGetDeviceName() - replaced with SDL_GetSensorInstanceName() +* SDL_SensorGetDeviceNonPortableType() - replaced with SDL_GetSensorInstanceNonPortableType() +* SDL_SensorGetDeviceType() - replaced with SDL_GetSensorInstanceType() +* SDL_UnlockSensors() + +## SDL_shape.h + +This header has been removed. You can create a window with the SDL_WINDOW_TRANSPARENT flag and then render using the alpha channel to achieve a similar effect. You can see an example of this in test/testshape.c + +## SDL_stdinc.h + +The standard C headers like stdio.h and stdlib.h are no longer included, you should include them directly in your project if you use non-SDL C runtime functions. +M_PI is no longer defined in SDL_stdinc.h, you can use the new symbols SDL_PI_D (double) and SDL_PI_F (float) instead. + + +The following functions have been renamed: +* SDL_strtokr() => SDL_strtok_r() + +## SDL_surface.h + +The userdata member of SDL_Surface has been replaced with a more general properties interface, which can be queried with SDL_GetSurfaceProperties() + +Removed unused 'flags' parameter from SDL_ConvertSurface and SDL_ConvertSurfaceFormat. + +SDL_CreateRGBSurface() and SDL_CreateRGBSurfaceWithFormat() have been combined into a new function SDL_CreateSurface(). +SDL_CreateRGBSurfaceFrom() and SDL_CreateRGBSurfaceWithFormatFrom() have been combined into a new function SDL_CreateSurfaceFrom(). + +You can implement the old functions in your own code easily: +```c +SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurface(width, height, + SDL_GetPixelFormatEnumForMasks(depth, Rmask, Gmask, Bmask, Amask)); +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format) +{ + return SDL_CreateSurface(width, height, format); +} + +SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurfaceFrom(pixels, width, height, pitch, + SDL_GetPixelFormatEnumForMasks(depth, Rmask, Gmask, Bmask, Amask)); +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 format) +{ + return SDL_CreateSurfaceFrom(pixels, width, height, pitch, format); +} + +``` + +But if you're migrating your code which uses masks, you probably have a format in mind, possibly one of these: +```c +// Various mask (R, G, B, A) and their corresponding format: +0xFF000000 0x00FF0000 0x0000FF00 0x000000FF => SDL_PIXELFORMAT_RGBA8888 +0x00FF0000 0x0000FF00 0x000000FF 0xFF000000 => SDL_PIXELFORMAT_ARGB8888 +0x0000FF00 0x00FF0000 0xFF000000 0x000000FF => SDL_PIXELFORMAT_BGRA8888 +0x000000FF 0x0000FF00 0x00FF0000 0xFF000000 => SDL_PIXELFORMAT_ABGR8888 +0x0000F800 0x000007E0 0x0000001F 0x00000000 => SDL_PIXELFORMAT_RGB565 +``` + +SDL_BlitSurfaceScaled() and SDL_BlitSurfaceUncheckedScaled() now take a scale paramater. + +SDL_SoftStretch() now takes a scale paramater. + +The following functions have been renamed: +* SDL_FillRect() => SDL_FillSurfaceRect() +* SDL_FillRects() => SDL_FillSurfaceRects() +* SDL_FreeSurface() => SDL_DestroySurface() +* SDL_GetClipRect() => SDL_GetSurfaceClipRect() +* SDL_GetColorKey() => SDL_GetSurfaceColorKey() +* SDL_HasColorKey() => SDL_SurfaceHasColorKey() +* SDL_HasSurfaceRLE() => SDL_SurfaceHasRLE() +* SDL_LowerBlit() => SDL_BlitSurfaceUnchecked() +* SDL_LowerBlitScaled() => SDL_BlitSurfaceUncheckedScaled() +* SDL_SetClipRect() => SDL_SetSurfaceClipRect() +* SDL_SetColorKey() => SDL_SetSurfaceColorKey() +* SDL_UpperBlit() => SDL_BlitSurface() +* SDL_UpperBlitScaled() => SDL_BlitSurfaceScaled() + +The following functions have been removed: +* SDL_SoftStretchLinear() - use SDL_SoftStretch() with SDL_SCALEMODE_LINEAR + +## SDL_system.h + +SDL_WindowsMessageHook has changed signatures so the message may be modified and it can block further message processing. + +SDL_AndroidGetExternalStorageState() takes the state as an output parameter and returns 0 if the function succeeds or a negative error code if there was an error. + +The following functions have been removed: +* SDL_RenderGetD3D11Device() - replaced with the "SDL.renderer.d3d11.device" property +* SDL_RenderGetD3D12Device() - replaced with the "SDL.renderer.d3d12.device" property +* SDL_RenderGetD3D9Device() - replaced with the "SDL.renderer.d3d9.device" property + +## SDL_syswm.h + +This header has been removed. + +The Windows and X11 events are now available via callbacks which you can set with SDL_SetWindowsMessageHook() and SDL_SetX11EventHook(). + +The information previously available in SDL_GetWindowWMInfo() is now available as window properties, e.g. +```c + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + +#if defined(__WIN32__) + HWND hwnd = NULL; + if (SDL_GetWindowWMInfo(window, &info) && info.subsystem == SDL_SYSWM_WINDOWS) { + hwnd = info.info.win.window; + } + if (hwnd) { + ... + } +#elif defined(__MACOSX__) + NSWindow *nswindow = NULL; + if (SDL_GetWindowWMInfo(window, &info) && info.subsystem == SDL_SYSWM_COCOA) { + nswindow = (__bridge NSWindow *)info.info.cocoa.window; + } + if (nswindow) { + ... + } +#elif defined(__LINUX__) + if (SDL_GetWindowWMInfo(window, &info)) { + if (info.subsystem == SDL_SYSWM_X11) { + Display *xdisplay = info.info.x11.display; + Window xwindow = info.info.x11.window; + if (xdisplay && xwindow) { + ... + } + } else if (info.subsystem == SDL_SYSWM_WAYLAND) { + struct wl_display *display = info.info.wl.display; + struct wl_surface *surface = info.info.wl.surface; + if (display && surface) { + ... + } + } + } +#endif +``` +becomes: +```c +#if defined(__WIN32__) + HWND hwnd = (HWND)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER, NULL); + if (hwnd) { + ... + } +#elif defined(__MACOS__) + NSWindow *nswindow = (__bridge NSWindow *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_COCOA_WINDOW_POINTER, NULL); + if (nswindow) { + ... + } +#elif defined(__LINUX__) + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + Display *xdisplay = (Display *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER, NULL); + Window xwindow = (Window)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER, 0); + if (xdisplay && xwindow) { + ... + } + } else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { + struct wl_display *display = (struct wl_display *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + struct wl_surface *surface = (struct wl_surface *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + if (display && surface) { + ... + } + } +#endif +``` + +## SDL_thread.h + +The following functions have been renamed: +* SDL_TLSCleanup() => SDL_CleanupTLS() +* SDL_TLSCreate() => SDL_CreateTLS() +* SDL_TLSGet() => SDL_GetTLS() +* SDL_TLSSet() => SDL_SetTLS() + +## SDL_timer.h + +SDL_GetTicks() now returns a 64-bit value. Instead of using the SDL_TICKS_PASSED macro, you can directly compare tick values, e.g. +```c +Uint32 deadline = SDL_GetTicks() + 1000; +... +if (SDL_TICKS_PASSED(SDL_GetTicks(), deadline)) { + ... +} +``` +becomes: +```c +Uint64 deadline = SDL_GetTicks() + 1000 +... +if (SDL_GetTicks() >= deadline) { + ... +} +``` + +If you were using this macro for other things besides SDL ticks values, you can define it in your own code as: +```c +#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) +``` + +## SDL_touch.h + +SDL_GetNumTouchFingers() returns a negative error code if there was an error. + +SDL_GetTouchName is replaced with SDL_GetTouchDeviceName(), which takes an SDL_TouchID instead of an index. + +The following functions have been removed: +* SDL_GetNumTouchDevices() - replaced with SDL_GetTouchDevices() +* SDL_GetTouchDevice() - replaced with SDL_GetTouchDevices() + + +## SDL_version.h + +SDL_GetRevisionNumber() has been removed from the API, it always returned 0 in SDL 2.0. + + +## SDL_video.h + +SDL_VideoInit() and SDL_VideoQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_VIDEO, which will properly refcount the subsystems. You can choose a specific video driver using SDL_VIDEO_DRIVER hint. + +Rather than iterating over displays using display index, there is a new function SDL_GetDisplays() to get the current list of displays, and functions which used to take a display index now take SDL_DisplayID, with an invalid ID being 0. +```c +{ + if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0) { + int i, num_displays = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&num_displays); + if (displays) { + for (i = 0; i < num_displays; ++i) { + SDL_DisplayID instance_id = displays[i]; + const char *name = SDL_GetDisplayName(instance_id); + + SDL_Log("Display %" SDL_PRIu32 ": %s\n", instance_id, name ? name : "Unknown"); + } + SDL_free(displays); + } + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } +} +``` + +SDL_CreateWindow() has been simplified and no longer takes a window position. You can use SDL_CreateWindowWithProperties() if you need to set the window position when creating it, e.g. +```c + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, title); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_X_NUMBER, x); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER, y); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, height); + SDL_SetNumberProperty(props, "flags", flags); + pWindow = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (window) { + ... + } +``` + +The SDL_WINDOWPOS_UNDEFINED_DISPLAY() and SDL_WINDOWPOS_CENTERED_DISPLAY() macros take a display ID instead of display index. The display ID 0 has a special meaning in this case, and is used to indicate the primary display. + +The SDL_WINDOW_SHOWN flag has been removed. Windows are shown by default and can be created hidden by using the SDL_WINDOW_HIDDEN flag. + +The SDL_WINDOW_SKIP_TASKBAR flag has been replaced by the SDL_WINDOW_UTILITY flag, which has the same functionality. + +SDL_DisplayMode now includes the pixel density which can be greater than 1.0 for display modes that have a higher pixel size than the mode size. You should use SDL_GetWindowSizeInPixels() to get the actual pixel size of the window back buffer. + +The refresh rate in SDL_DisplayMode is now a float. + +Rather than iterating over display modes using an index, there is a new function SDL_GetFullscreenDisplayModes() to get the list of available fullscreen modes on a display. +```c +{ + SDL_DisplayID display = SDL_GetPrimaryDisplay(); + int num_modes = 0; + SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &num_modes); + if (modes) { + for (i = 0; i < num_modes; ++i) { + SDL_DisplayMode *mode = modes[i]; + SDL_Log("Display %" SDL_PRIu32 " mode %d: %dx%d@%gx %gHz\n", + display, i, mode->w, mode->h, mode->pixel_density, mode->refresh_rate); + } + SDL_free(modes); + } +} +``` + +SDL_GetDesktopDisplayMode() and SDL_GetCurrentDisplayMode() return pointers to display modes rather than filling in application memory. + +Windows now have an explicit fullscreen mode that is set, using SDL_SetWindowFullscreenMode(). The fullscreen mode for a window can be queried with SDL_GetWindowFullscreenMode(), which returns a pointer to the mode, or NULL if the window will be fullscreen desktop. SDL_SetWindowFullscreen() just takes a boolean value, setting the correct fullscreen state based on the selected mode. + +SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, and you can call SDL_GetWindowFullscreenMode() to see whether an exclusive fullscreen mode will be used or the fullscreen desktop mode will be used when the window is fullscreen. + +SDL_SetWindowBrightness and SDL_SetWindowGammaRamp have been removed from the API, because they interact poorly with modern operating systems and aren't able to limit their effects to the SDL window. + +Programs which have access to shaders can implement more robust versions of those functions using custom shader code rendered as a post-process effect. + +Removed SDL_GL_CONTEXT_EGL from OpenGL configuration attributes. You can instead use `SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);` + +SDL_GL_GetProcAddress() and SDL_EGL_GetProcAddress() now return `SDL_FunctionPointer` instead of `void *`, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior. + +SDL_GL_SwapWindow() returns 0 if the function succeeds or a negative error code if there was an error. + +SDL_GL_GetSwapInterval() takes the interval as an output parameter and returns 0 if the function succeeds or a negative error code if there was an error. + +SDL_GL_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. + +The SDL_WINDOW_TOOLTIP and SDL_WINDOW_POPUP_MENU window flags are now supported on Windows, Mac (Cocoa), X11, and Wayland. Creating windows with these flags must happen via the `SDL_CreatePopupWindow()` function. This function requires passing in the handle to a valid parent window for the popup, and the popup window is positioned relative to the parent. + +The following functions have been renamed: +* SDL_GetClosestDisplayMode() => SDL_GetClosestFullscreenDisplayMode() +* SDL_GetDisplayOrientation() => SDL_GetCurrentDisplayOrientation() +* SDL_GetPointDisplayIndex() => SDL_GetDisplayForPoint() +* SDL_GetRectDisplayIndex() => SDL_GetDisplayForRect() +* SDL_GetWindowDisplayIndex() => SDL_GetDisplayForWindow() +* SDL_GetWindowDisplayMode() => SDL_GetWindowFullscreenMode() +* SDL_IsScreenSaverEnabled() => SDL_ScreenSaverEnabled() +* SDL_SetWindowDisplayMode() => SDL_SetWindowFullscreenMode() + +The following functions have been removed: +* SDL_GetClosestFullscreenDisplayMode() +* SDL_GetDisplayDPI() - not reliable across platforms, approximately replaced by multiplying `display_scale` in the structure returned by SDL_GetDesktopDisplayMode() times 160 on iPhone and Android, and 96 on other platforms. +* SDL_GetDisplayMode() +* SDL_GetNumDisplayModes() - replaced with SDL_GetFullscreenDisplayModes() +* SDL_GetNumVideoDisplays() - replaced with SDL_GetDisplays() +* SDL_GetWindowData() - use SDL_GetWindowProperties() instead +* SDL_SetWindowData() - use SDL_GetWindowProperties() instead +* SDL_CreateWindowFrom() - use SDL_CreateWindowWithProperties() with the properties that allow you to wrap an existing window + +The SDL_Window id type is named SDL_WindowID +The SDL_WindowFlags enum should be replaced with Uint32 + +The following symbols have been renamed: +* SDL_WINDOW_ALLOW_HIGHDPI => SDL_WINDOW_HIGH_PIXEL_DENSITY +* SDL_WINDOW_INPUT_GRABBED => SDL_WINDOW_MOUSE_GRABBED + +The following window operations are now considered to be asynchronous requests and should not be assumed to succeed unless +a corresponding event has been received: +* SDL_SetWindowSize() (SDL_EVENT_WINDOW_RESIZED) +* SDL_SetWindowPosition() (SDL_EVENT_WINDOW_MOVED) +* SDL_MinimizeWindow() (SDL_EVENT_WINDOW_MINIMIZED) +* SDL_MaximizeWindow() (SDL_EVENT_WINDOW_MAXIMIZED) +* SDL_RestoreWindow() (SDL_EVENT_WINDOW_RESTORED) +* SDL_SetWindowFullscreen() (SDL_EVENT_WINDOW_ENTER_FULLSCREEN / SDL_EVENT_WINDOW_LEAVE_FULLSCREEN) + +If it is required that operations be applied immediately after one of the preceeding calls, the `SDL_SyncWindow()` function +will attempt to wait until all pending window operations have completed. Be aware that this function can potentially block for +long periods of time, as it may have to wait for window animations to complete. Also note that windowing systems can deny or +not precisely obey these requests (e.g. windows may not be allowed to be larger than the usable desktop space or placed +offscreen), so a corresponding event may never arrive or not contain the expected values. + +## SDL_vulkan.h + +SDL_Vulkan_GetInstanceExtensions() no longer takes a window parameter, and no longer makes the app allocate query/allocate space for the result, instead returning a static const internal string. + +SDL_Vulkan_GetVkGetInstanceProcAddr() now returns `SDL_FunctionPointer` instead of `void *`, and should be cast to PFN_vkGetInstanceProcAddr. + +SDL_Vulkan_CreateSurface() now takes a VkAllocationCallbacks pointer as its third parameter. If you don't have an allocator to supply, pass a NULL here to use the system default allocator (SDL2 always used the system default allocator here). + +SDL_Vulkan_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place. diff --git a/docs/README-n3ds.md b/docs/README-n3ds.md index 80da3802..8f954739 100644 --- a/docs/README-n3ds.md +++ b/docs/README-n3ds.md @@ -1,28 +1,28 @@ -# Nintendo 3DS - -SDL port for the Nintendo 3DS [Homebrew toolchain](https://devkitpro.org/) contributed by: - -- [Pierre Wendling](https://github.com/FtZPetruska) - -Credits to: - -- The awesome people who ported SDL to other homebrew platforms. -- The Devkitpro team for making all the tools necessary to achieve this. - -## Building - -To build for the Nintendo 3DS, make sure you have devkitARM and cmake installed and run: - -```bash -cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/3DS.cmake" -DCMAKE_BUILD_TYPE=Release -cmake --build build -cmake --install build -``` - -## Notes - -- Currently only software rendering is supported. -- SDL3_main should be used to ensure ROMFS is enabled - this is done with `#include ` in the source file that contains your main function. -- By default, the extra L2 cache and higher clock speeds of the New 2/3DS lineup are enabled. If you wish to turn it off, use `osSetSpeedupEnable(false)` in your main function. -- `SDL_GetBasePath` returns the romfs root instead of the executable's directory. -- The Nintendo 3DS uses a cooperative threading model on a single core, meaning a thread will never yield unless done manually through the `SDL_Delay` functions, or blocking waits (`SDL_LockMutex`, `SDL_WaitSemaphore`, `SDL_WaitCondition`, `SDL_WaitThread`). To avoid starving other threads, `SDL_TryWaitSemaphore` and `SDL_WaitSemaphoreTimeout` will yield if they fail to acquire the semaphore, see https://github.com/libsdl-org/SDL/pull/6776 for more information. +# Nintendo 3DS + +SDL port for the Nintendo 3DS [Homebrew toolchain](https://devkitpro.org/) contributed by: + +- [Pierre Wendling](https://github.com/FtZPetruska) + +Credits to: + +- The awesome people who ported SDL to other homebrew platforms. +- The Devkitpro team for making all the tools necessary to achieve this. + +## Building + +To build for the Nintendo 3DS, make sure you have devkitARM and cmake installed and run: + +```bash +cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/3DS.cmake" -DCMAKE_BUILD_TYPE=Release +cmake --build build +cmake --install build +``` + +## Notes + +- Currently only software rendering is supported. +- SDL3_main should be used to ensure ROMFS is enabled - this is done with `#include ` in the source file that contains your main function. +- By default, the extra L2 cache and higher clock speeds of the New 2/3DS lineup are enabled. If you wish to turn it off, use `osSetSpeedupEnable(false)` in your main function. +- `SDL_GetBasePath` returns the romfs root instead of the executable's directory. +- The Nintendo 3DS uses a cooperative threading model on a single core, meaning a thread will never yield unless done manually through the `SDL_Delay` functions, or blocking waits (`SDL_LockMutex`, `SDL_WaitSemaphore`, `SDL_WaitCondition`, `SDL_WaitThread`). To avoid starving other threads, `SDL_TryWaitSemaphore` and `SDL_WaitSemaphoreTimeout` will yield if they fail to acquire the semaphore, see https://github.com/libsdl-org/SDL/pull/6776 for more information. diff --git a/docs/README-ngage.md b/docs/README-ngage.md index ec4a7121..363760b9 100644 --- a/docs/README-ngage.md +++ b/docs/README-ngage.md @@ -1,44 +1,44 @@ -Nokia N-Gage -============ - -SDL port for Symbian S60v1 and v2 with a main focus on the Nokia N-Gage -(Classic and QD) by [Michael Fitzmayer](https://github.com/mupfdev). - -Compiling ---------- - -SDL is part of the [N-Gage SDK.](https://github.com/ngagesdk) project. -The library is included in the -[toolchain](https://github.com/ngagesdk/ngage-toolchain) as a -sub-module. - -A complete example project based on SDL can be found in the GitHub -account of the SDK: [Wordle](https://github.com/ngagesdk/wordle). - -Current level of implementation -------------------------------- - -The video driver currently provides full screen video support with -keyboard input. - -At the moment only the software renderer works. - -Audio is not yet implemented. - -Acknowledgements ----------------- - -Thanks to Hannu Viitala, Kimmo Kinnunen and Markus Mertama for the -valuable insight into Symbian programming. Without the SDL 1.2 port -which was specially developed for CDoom (Doom for the Nokia 9210), this -adaptation would not have been possible. - -I would like to thank my friends -[Razvan](https://twitter.com/bewarerazvan) and [Dan -Whelan](https://danwhelan.ie/), for their continuous support. Without -you and the [N-Gage community](https://discord.gg/dbUzqJ26vs), I would -have lost my patience long ago. - -Last but not least, I would like to thank the development team of -[EKA2L1](https://12z1.com/) (an experimental Symbian OS emulator). Your -patience and support in troubleshooting helped me a lot. +Nokia N-Gage +============ + +SDL port for Symbian S60v1 and v2 with a main focus on the Nokia N-Gage +(Classic and QD) by [Michael Fitzmayer](https://github.com/mupfdev). + +Compiling +--------- + +SDL is part of the [N-Gage SDK.](https://github.com/ngagesdk) project. +The library is included in the +[toolchain](https://github.com/ngagesdk/ngage-toolchain) as a +sub-module. + +A complete example project based on SDL can be found in the GitHub +account of the SDK: [Wordle](https://github.com/ngagesdk/wordle). + +Current level of implementation +------------------------------- + +The video driver currently provides full screen video support with +keyboard input. + +At the moment only the software renderer works. + +Audio is not yet implemented. + +Acknowledgements +---------------- + +Thanks to Hannu Viitala, Kimmo Kinnunen and Markus Mertama for the +valuable insight into Symbian programming. Without the SDL 1.2 port +which was specially developed for CDoom (Doom for the Nokia 9210), this +adaptation would not have been possible. + +I would like to thank my friends +[Razvan](https://twitter.com/bewarerazvan) and [Dan +Whelan](https://danwhelan.ie/), for their continuous support. Without +you and the [N-Gage community](https://discord.gg/dbUzqJ26vs), I would +have lost my patience long ago. + +Last but not least, I would like to thank the development team of +[EKA2L1](https://12z1.com/) (an experimental Symbian OS emulator). Your +patience and support in troubleshooting helped me a lot. diff --git a/docs/README-platforms.md b/docs/README-platforms.md index 711557dc..14454ec5 100644 --- a/docs/README-platforms.md +++ b/docs/README-platforms.md @@ -1,8 +1,8 @@ -Platforms -========= - -We maintain the list of supported platforms on our wiki now, and how to -build and install SDL for those platforms: - - https://wiki.libsdl.org/Installation - +Platforms +========= + +We maintain the list of supported platforms on our wiki now, and how to +build and install SDL for those platforms: + + https://wiki.libsdl.org/Installation + diff --git a/docs/README-porting.md b/docs/README-porting.md index e2e4d0fa..db6b6141 100644 --- a/docs/README-porting.md +++ b/docs/README-porting.md @@ -1,65 +1,65 @@ -Porting -======= - -* Porting To A New Platform - - The first thing you have to do when porting to a new platform, is look at -include/SDL_platform.h and create an entry there for your operating system. -The standard format is "__PLATFORM__", where PLATFORM is the name of the OS. -Ideally SDL_platform_defines.h will be able to auto-detect the system it's building -on based on C preprocessor symbols. - -There are two basic ways of building SDL at the moment: - -1. CMake: cmake -S . -B build && cmake --build build && cmake --install install - - If you have a system that supports CMake, then you might try this. Edit CMakeLists.txt, - - take a look at the large section labelled: - - "Platform-specific options and settings!" - - Add a section for your platform, and then re-run 'cmake -S . -B build' and build! - -2. Using an IDE: - - If you're using an IDE or other non-configure build system, you'll probably want to create a custom `SDL_build_config.h` for your platform. Edit `include/build_config/SDL_build_config.h`, add a section for your platform, and create a custom `SDL_build_config_{platform}.h`, based on `SDL_build_config_minimal.h` and `SDL_build_config.h.cmake` - - Add the top level include directory to the header search path, and then add - the following sources to the project: - - src/*.c - src/atomic/*.c - src/audio/*.c - src/cpuinfo/*.c - src/events/*.c - src/file/*.c - src/haptic/*.c - src/joystick/*.c - src/power/*.c - src/render/*.c - src/render/software/*.c - src/stdlib/*.c - src/thread/*.c - src/timer/*.c - src/video/*.c - src/audio/disk/*.c - src/audio/dummy/*.c - src/filesystem/dummy/*.c - src/video/dummy/*.c - src/haptic/dummy/*.c - src/joystick/dummy/*.c - src/thread/generic/*.c - src/timer/dummy/*.c - src/loadso/dummy/*.c - - -Once you have a working library without any drivers, you can go back to each -of the major subsystems and start implementing drivers for your platform. - -If you have any questions, don't hesitate to ask on the SDL mailing list: - http://www.libsdl.org/mailing-list.php - -Enjoy! - Sam Lantinga (slouken@libsdl.org) - +Porting +======= + +* Porting To A New Platform + + The first thing you have to do when porting to a new platform, is look at +include/SDL_platform.h and create an entry there for your operating system. +The standard format is "__PLATFORM__", where PLATFORM is the name of the OS. +Ideally SDL_platform_defines.h will be able to auto-detect the system it's building +on based on C preprocessor symbols. + +There are two basic ways of building SDL at the moment: + +1. CMake: cmake -S . -B build && cmake --build build && cmake --install install + + If you have a system that supports CMake, then you might try this. Edit CMakeLists.txt, + + take a look at the large section labelled: + + "Platform-specific options and settings!" + + Add a section for your platform, and then re-run 'cmake -S . -B build' and build! + +2. Using an IDE: + + If you're using an IDE or other non-configure build system, you'll probably want to create a custom `SDL_build_config.h` for your platform. Edit `include/build_config/SDL_build_config.h`, add a section for your platform, and create a custom `SDL_build_config_{platform}.h`, based on `SDL_build_config_minimal.h` and `SDL_build_config.h.cmake` + + Add the top level include directory to the header search path, and then add + the following sources to the project: + + src/*.c + src/atomic/*.c + src/audio/*.c + src/cpuinfo/*.c + src/events/*.c + src/file/*.c + src/haptic/*.c + src/joystick/*.c + src/power/*.c + src/render/*.c + src/render/software/*.c + src/stdlib/*.c + src/thread/*.c + src/timer/*.c + src/video/*.c + src/audio/disk/*.c + src/audio/dummy/*.c + src/filesystem/dummy/*.c + src/video/dummy/*.c + src/haptic/dummy/*.c + src/joystick/dummy/*.c + src/thread/generic/*.c + src/timer/dummy/*.c + src/loadso/dummy/*.c + + +Once you have a working library without any drivers, you can go back to each +of the major subsystems and start implementing drivers for your platform. + +If you have any questions, don't hesitate to ask on the SDL mailing list: + http://www.libsdl.org/mailing-list.php + +Enjoy! + Sam Lantinga (slouken@libsdl.org) + diff --git a/docs/README-ps2.md b/docs/README-ps2.md index 579ad986..ade0b6d8 100644 --- a/docs/README-ps2.md +++ b/docs/README-ps2.md @@ -1,51 +1,51 @@ -PS2 -====== -SDL port for the Sony Playstation 2 contributed by: -- Francisco Javier Trujillo Mata - - -Credit to - - The guys that ported SDL to PSP & Vita because I'm taking them as reference. - - David G. F. for helping me with several issues and tests. - -## Building -To build SDL library for the PS2, make sure you have the latest PS2Dev status and run: -```bash -cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake -cmake --build build -cmake --install build -``` - -## Hints -The PS2 port has a special Hint for having a dynamic VSYNC. The Hint is `SDL_HINT_PS2_DYNAMIC_VSYNC`. -If you enabled the dynamic vsync having as well `SDL_RENDERER_PRESENTVSYNC` enabled, then if the app is not able to run at 60 FPS, automatically the `vsync` will be disabled having a better performance, instead of dropping FPS to 30. - -## Notes -If you trying to debug a SDL app through [ps2client](https://github.com/ps2dev/ps2client) you need to avoid the IOP reset, otherwise you will lose the connection with your computer. -So to avoid the reset of the IOP CPU, you need to call to the macro `SDL_PS2_SKIP_IOP_RESET();`. -It could be something similar as: -```c -..... - -SDL_PS2_SKIP_IOP_RESET(); - -int main(int argc, char *argv[]) -{ -..... -``` -For a release binary is recommendable to reset the IOP always. - -Remember to do a clean compilation everytime you enable or disable the `SDL_PS2_SKIP_IOP_RESET` otherwise the change won't be reflected. - -## Getting PS2 Dev -[Installing PS2 Dev](https://github.com/ps2dev/ps2dev) - -## Running on PCSX2 Emulator -[PCSX2](https://github.com/PCSX2/pcsx2) - -[More PCSX2 information](https://pcsx2.net/) - -## To Do -- PS2 Screen Keyboard -- Dialogs -- Others +PS2 +====== +SDL port for the Sony Playstation 2 contributed by: +- Francisco Javier Trujillo Mata + + +Credit to + - The guys that ported SDL to PSP & Vita because I'm taking them as reference. + - David G. F. for helping me with several issues and tests. + +## Building +To build SDL library for the PS2, make sure you have the latest PS2Dev status and run: +```bash +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake +cmake --build build +cmake --install build +``` + +## Hints +The PS2 port has a special Hint for having a dynamic VSYNC. The Hint is `SDL_HINT_PS2_DYNAMIC_VSYNC`. +If you enabled the dynamic vsync having as well `SDL_RENDERER_PRESENTVSYNC` enabled, then if the app is not able to run at 60 FPS, automatically the `vsync` will be disabled having a better performance, instead of dropping FPS to 30. + +## Notes +If you trying to debug a SDL app through [ps2client](https://github.com/ps2dev/ps2client) you need to avoid the IOP reset, otherwise you will lose the connection with your computer. +So to avoid the reset of the IOP CPU, you need to call to the macro `SDL_PS2_SKIP_IOP_RESET();`. +It could be something similar as: +```c +..... + +SDL_PS2_SKIP_IOP_RESET(); + +int main(int argc, char *argv[]) +{ +..... +``` +For a release binary is recommendable to reset the IOP always. + +Remember to do a clean compilation everytime you enable or disable the `SDL_PS2_SKIP_IOP_RESET` otherwise the change won't be reflected. + +## Getting PS2 Dev +[Installing PS2 Dev](https://github.com/ps2dev/ps2dev) + +## Running on PCSX2 Emulator +[PCSX2](https://github.com/PCSX2/pcsx2) + +[More PCSX2 information](https://pcsx2.net/) + +## To Do +- PS2 Screen Keyboard +- Dialogs +- Others diff --git a/docs/README-psp.md b/docs/README-psp.md index e10f5c2d..c010c609 100644 --- a/docs/README-psp.md +++ b/docs/README-psp.md @@ -1,36 +1,36 @@ -PSP -====== -SDL port for the Sony PSP contributed by: -- Captian Lex -- Francisco Javier Trujillo Mata -- Wouter Wijsman - - -Credit to - Marcus R.Brown,Jim Paris,Matthew H for the original SDL 1.2 for PSP - Geecko for his PSP GU lib "Glib2d" - -## Building -To build SDL library for the PSP, make sure you have the latest PSPDev status and run: -```bash -cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake -cmake --build build -cmake --install build -``` - - -## Getting PSP Dev -[Installing PSP Dev](https://github.com/pspdev/pspdev) - -## Running on PPSSPP Emulator -[PPSSPP](https://github.com/hrydgard/ppsspp) - -[Build Instructions](https://github.com/hrydgard/ppsspp/wiki/Build-instructions) - - -## Compiling a HelloWorld -[PSP Hello World](https://psp-dev.org/doku.php?id=tutorial:hello_world) - -## To Do -- PSP Screen Keyboard -- Dialogs +PSP +====== +SDL port for the Sony PSP contributed by: +- Captian Lex +- Francisco Javier Trujillo Mata +- Wouter Wijsman + + +Credit to + Marcus R.Brown,Jim Paris,Matthew H for the original SDL 1.2 for PSP + Geecko for his PSP GU lib "Glib2d" + +## Building +To build SDL library for the PSP, make sure you have the latest PSPDev status and run: +```bash +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake +cmake --build build +cmake --install build +``` + + +## Getting PSP Dev +[Installing PSP Dev](https://github.com/pspdev/pspdev) + +## Running on PPSSPP Emulator +[PPSSPP](https://github.com/hrydgard/ppsspp) + +[Build Instructions](https://github.com/hrydgard/ppsspp/wiki/Build-instructions) + + +## Compiling a HelloWorld +[PSP Hello World](https://psp-dev.org/doku.php?id=tutorial:hello_world) + +## To Do +- PSP Screen Keyboard +- Dialogs diff --git a/docs/README-raspberrypi.md b/docs/README-raspberrypi.md index 94093f02..9984ec6d 100644 --- a/docs/README-raspberrypi.md +++ b/docs/README-raspberrypi.md @@ -1,180 +1,180 @@ -Raspberry Pi -============ - -Requirements: - -Raspbian (other Linux distros may work as well). - -Features --------- - -* Works without X11 -* Hardware accelerated OpenGL ES 2.x -* Sound via ALSA -* Input (mouse/keyboard/joystick) via EVDEV -* Hotplugging of input devices via UDEV - - -Raspbian Build Dependencies ---------------------------- - -sudo apt-get install libudev-dev libasound2-dev libdbus-1-dev - -You also need the VideoCore binary stuff that ships in /opt/vc for EGL and -OpenGL ES 2.x, it usually comes pre-installed, but in any case: - -sudo apt-get install libraspberrypi0 libraspberrypi-bin libraspberrypi-dev - - -NEON ----- - -If your Pi has NEON support, make sure you add -mfpu=neon to your CFLAGS so -that SDL will select some otherwise-disabled highly-optimized code. The -original Pi units don't have NEON, the Pi2 probably does, and the Pi3 -definitely does. - - -Cross compiling from x86 Linux ------------------------------- - -To cross compile SDL for Raspbian from your desktop machine, you'll need a -Raspbian system root and the cross compilation tools. We'll assume these tools -will be placed in /opt/rpi-tools - - sudo git clone --depth 1 https://github.com/raspberrypi/tools /opt/rpi-tools - -You'll also need a Raspbian binary image. -Get it from: http://downloads.raspberrypi.org/raspbian_latest -After unzipping, you'll get file with a name like: "-wheezy-raspbian.img" -Let's assume the sysroot will be built in /opt/rpi-sysroot. - - export SYSROOT=/opt/rpi-sysroot - sudo kpartx -a -v .img - sudo mount -o loop /dev/mapper/loop0p2 /mnt - sudo cp -r /mnt $SYSROOT - sudo apt-get install qemu binfmt-support qemu-user-static - sudo cp /usr/bin/qemu-arm-static $SYSROOT/usr/bin - sudo mount --bind /dev $SYSROOT/dev - sudo mount --bind /proc $SYSROOT/proc - sudo mount --bind /sys $SYSROOT/sys - -Now, before chrooting into the ARM sysroot, you'll need to apply a workaround, -edit $SYSROOT/etc/ld.so.preload and comment out all lines in it. - - sudo chroot $SYSROOT - apt-get install libudev-dev libasound2-dev libdbus-1-dev libraspberrypi0 libraspberrypi-bin libraspberrypi-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxss-dev - exit - sudo umount $SYSROOT/dev - sudo umount $SYSROOT/proc - sudo umount $SYSROOT/sys - sudo umount /mnt - -There's one more fix required, as the libdl.so symlink uses an absolute path -which doesn't quite work in our setup. - - sudo rm -rf $SYSROOT/usr/lib/arm-linux-gnueabihf/libdl.so - sudo ln -s ../../../lib/arm-linux-gnueabihf/libdl.so.2 $SYSROOT/usr/lib/arm-linux-gnueabihf/libdl.so - -The final step is compiling SDL itself. - - export CC="/opt/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-gcc --sysroot=$SYSROOT -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/include -I$SYSROOT/opt/vc/include/interface/vcos/pthreads -I$SYSROOT/opt/vc/include/interface/vmcs_host/linux" - cd - mkdir -p build;cd build - LDFLAGS="-L$SYSROOT/opt/vc/lib" ../configure --with-sysroot=$SYSROOT --host=arm-raspberry-linux-gnueabihf --prefix=$PWD/rpi-sdl3-installed --disable-pulseaudio --disable-esd - make - make install - -To be able to deploy this to /usr/local in the Raspbian system you need to fix up a few paths: - - perl -w -pi -e "s#$PWD/rpi-sdl3-installed#/usr/local#g;" ./rpi-sdl3-installed/lib/libSDL3.la ./rpi-sdl3-installed/lib/pkgconfig/sdl3.pc - -Apps don't work or poor video/audio performance ------------------------------------------------ - -If you get sound problems, buffer underruns, etc, run "sudo rpi-update" to -update the RPi's firmware. Note that doing so will fix these problems, but it -will also render the CMA - Dynamic Memory Split functionality useless. - -Also, by default the Raspbian distro configures the GPU RAM at 64MB, this is too -low in general, specially if a 1080p TV is hooked up. - -See here how to configure this setting: http://elinux.org/RPiconfig - -Using a fixed gpu_mem=128 is the best option (specially if you updated the -firmware, using CMA probably won't work, at least it's the current case). - -No input --------- - -Make sure you belong to the "input" group. - - sudo usermod -aG input `whoami` - -No HDMI Audio -------------- - -If you notice that ALSA works but there's no audio over HDMI, try adding: - - hdmi_drive=2 - -to your config.txt file and reboot. - -Reference: http://www.raspberrypi.org/phpBB3/viewtopic.php?t=5062 - -Text Input API support ----------------------- - -The Text Input API is supported, with translation of scan codes done via the -kernel symbol tables. For this to work, SDL needs access to a valid console. -If you notice there's no SDL_EVENT_TEXT_INPUT message being emitted, double check that -your app has read access to one of the following: - -* /proc/self/fd/0 -* /dev/tty -* /dev/tty[0...6] -* /dev/vc/0 -* /dev/console - -This is usually not a problem if you run from the physical terminal (as opposed -to running from a pseudo terminal, such as via SSH). If running from a PTS, a -quick workaround is to run your app as root or add yourself to the tty group, -then re-login to the system. - - sudo usermod -aG tty `whoami` - -The keyboard layout used by SDL is the same as the one the kernel uses. -To configure the layout on Raspbian: - - sudo dpkg-reconfigure keyboard-configuration - -To configure the locale, which controls which keys are interpreted as letters, -this determining the CAPS LOCK behavior: - - sudo dpkg-reconfigure locales - - -OpenGL problems ---------------- - -If you have desktop OpenGL headers installed at build time in your RPi or cross -compilation environment, support for it will be built in. However, the chipset -does not actually have support for it, which causes issues in certain SDL apps -since the presence of OpenGL support supersedes the ES/ES2 variants. -The workaround is to disable OpenGL at configuration time: - - ./configure --disable-video-opengl - -Or if the application uses the Render functions, you can use the SDL_RENDER_DRIVER -environment variable: - - export SDL_RENDER_DRIVER=opengles2 - -Notes ------ - -* When launching apps remotely (via SSH), SDL can prevent local keystrokes from - leaking into the console only if it has root privileges. Launching apps locally - does not suffer from this issue. - - +Raspberry Pi +============ + +Requirements: + +Raspbian (other Linux distros may work as well). + +Features +-------- + +* Works without X11 +* Hardware accelerated OpenGL ES 2.x +* Sound via ALSA +* Input (mouse/keyboard/joystick) via EVDEV +* Hotplugging of input devices via UDEV + + +Raspbian Build Dependencies +--------------------------- + +sudo apt-get install libudev-dev libasound2-dev libdbus-1-dev + +You also need the VideoCore binary stuff that ships in /opt/vc for EGL and +OpenGL ES 2.x, it usually comes pre-installed, but in any case: + +sudo apt-get install libraspberrypi0 libraspberrypi-bin libraspberrypi-dev + + +NEON +---- + +If your Pi has NEON support, make sure you add -mfpu=neon to your CFLAGS so +that SDL will select some otherwise-disabled highly-optimized code. The +original Pi units don't have NEON, the Pi2 probably does, and the Pi3 +definitely does. + + +Cross compiling from x86 Linux +------------------------------ + +To cross compile SDL for Raspbian from your desktop machine, you'll need a +Raspbian system root and the cross compilation tools. We'll assume these tools +will be placed in /opt/rpi-tools + + sudo git clone --depth 1 https://github.com/raspberrypi/tools /opt/rpi-tools + +You'll also need a Raspbian binary image. +Get it from: http://downloads.raspberrypi.org/raspbian_latest +After unzipping, you'll get file with a name like: "-wheezy-raspbian.img" +Let's assume the sysroot will be built in /opt/rpi-sysroot. + + export SYSROOT=/opt/rpi-sysroot + sudo kpartx -a -v .img + sudo mount -o loop /dev/mapper/loop0p2 /mnt + sudo cp -r /mnt $SYSROOT + sudo apt-get install qemu binfmt-support qemu-user-static + sudo cp /usr/bin/qemu-arm-static $SYSROOT/usr/bin + sudo mount --bind /dev $SYSROOT/dev + sudo mount --bind /proc $SYSROOT/proc + sudo mount --bind /sys $SYSROOT/sys + +Now, before chrooting into the ARM sysroot, you'll need to apply a workaround, +edit $SYSROOT/etc/ld.so.preload and comment out all lines in it. + + sudo chroot $SYSROOT + apt-get install libudev-dev libasound2-dev libdbus-1-dev libraspberrypi0 libraspberrypi-bin libraspberrypi-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxss-dev + exit + sudo umount $SYSROOT/dev + sudo umount $SYSROOT/proc + sudo umount $SYSROOT/sys + sudo umount /mnt + +There's one more fix required, as the libdl.so symlink uses an absolute path +which doesn't quite work in our setup. + + sudo rm -rf $SYSROOT/usr/lib/arm-linux-gnueabihf/libdl.so + sudo ln -s ../../../lib/arm-linux-gnueabihf/libdl.so.2 $SYSROOT/usr/lib/arm-linux-gnueabihf/libdl.so + +The final step is compiling SDL itself. + + export CC="/opt/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-gcc --sysroot=$SYSROOT -I$SYSROOT/opt/vc/include -I$SYSROOT/usr/include -I$SYSROOT/opt/vc/include/interface/vcos/pthreads -I$SYSROOT/opt/vc/include/interface/vmcs_host/linux" + cd + mkdir -p build;cd build + LDFLAGS="-L$SYSROOT/opt/vc/lib" ../configure --with-sysroot=$SYSROOT --host=arm-raspberry-linux-gnueabihf --prefix=$PWD/rpi-sdl3-installed --disable-pulseaudio --disable-esd + make + make install + +To be able to deploy this to /usr/local in the Raspbian system you need to fix up a few paths: + + perl -w -pi -e "s#$PWD/rpi-sdl3-installed#/usr/local#g;" ./rpi-sdl3-installed/lib/libSDL3.la ./rpi-sdl3-installed/lib/pkgconfig/sdl3.pc + +Apps don't work or poor video/audio performance +----------------------------------------------- + +If you get sound problems, buffer underruns, etc, run "sudo rpi-update" to +update the RPi's firmware. Note that doing so will fix these problems, but it +will also render the CMA - Dynamic Memory Split functionality useless. + +Also, by default the Raspbian distro configures the GPU RAM at 64MB, this is too +low in general, specially if a 1080p TV is hooked up. + +See here how to configure this setting: http://elinux.org/RPiconfig + +Using a fixed gpu_mem=128 is the best option (specially if you updated the +firmware, using CMA probably won't work, at least it's the current case). + +No input +-------- + +Make sure you belong to the "input" group. + + sudo usermod -aG input `whoami` + +No HDMI Audio +------------- + +If you notice that ALSA works but there's no audio over HDMI, try adding: + + hdmi_drive=2 + +to your config.txt file and reboot. + +Reference: http://www.raspberrypi.org/phpBB3/viewtopic.php?t=5062 + +Text Input API support +---------------------- + +The Text Input API is supported, with translation of scan codes done via the +kernel symbol tables. For this to work, SDL needs access to a valid console. +If you notice there's no SDL_EVENT_TEXT_INPUT message being emitted, double check that +your app has read access to one of the following: + +* /proc/self/fd/0 +* /dev/tty +* /dev/tty[0...6] +* /dev/vc/0 +* /dev/console + +This is usually not a problem if you run from the physical terminal (as opposed +to running from a pseudo terminal, such as via SSH). If running from a PTS, a +quick workaround is to run your app as root or add yourself to the tty group, +then re-login to the system. + + sudo usermod -aG tty `whoami` + +The keyboard layout used by SDL is the same as the one the kernel uses. +To configure the layout on Raspbian: + + sudo dpkg-reconfigure keyboard-configuration + +To configure the locale, which controls which keys are interpreted as letters, +this determining the CAPS LOCK behavior: + + sudo dpkg-reconfigure locales + + +OpenGL problems +--------------- + +If you have desktop OpenGL headers installed at build time in your RPi or cross +compilation environment, support for it will be built in. However, the chipset +does not actually have support for it, which causes issues in certain SDL apps +since the presence of OpenGL support supersedes the ES/ES2 variants. +The workaround is to disable OpenGL at configuration time: + + ./configure --disable-video-opengl + +Or if the application uses the Render functions, you can use the SDL_RENDER_DRIVER +environment variable: + + export SDL_RENDER_DRIVER=opengles2 + +Notes +----- + +* When launching apps remotely (via SSH), SDL can prevent local keystrokes from + leaking into the console only if it has root privileges. Launching apps locally + does not suffer from this issue. + + diff --git a/docs/README-riscos.md b/docs/README-riscos.md index 48304220..5af80a7f 100644 --- a/docs/README-riscos.md +++ b/docs/README-riscos.md @@ -1,35 +1,35 @@ -RISC OS -======= - -Requirements: - -* RISC OS 3.5 or later. -* [SharedUnixLibrary](http://www.riscos.info/packages/LibraryDetails.html#SharedUnixLibraryarm). -* [DigitalRenderer](http://www.riscos.info/packages/LibraryDetails.html#DRendererarm), for audio support. -* [Iconv](http://www.netsurf-browser.org/projects/iconv/), for `SDL_iconv` and related functions. - - -Compiling: ----------- - -Currently, SDL for RISC OS only supports compiling with GCCSDK under Linux. - -The following commands can be used to build SDL for RISC OS using CMake: - - cmake -Bbuild-riscos -DCMAKE_TOOLCHAIN_FILE=$GCCSDK_INSTALL_ENV/toolchain-riscos.cmake -DRISCOS=ON -DCMAKE_INSTALL_PREFIX=$GCCSDK_INSTALL_ENV -DCMAKE_BUILD_TYPE=Release - cmake --build build-riscos - cmake --install build-riscos - -When using GCCSDK 4.7.4 release 6 or earlier versions, the builtin atomic functions are broken, meaning it's currently necessary to compile with `-DSDL_GCC_ATOMICS=OFF` using CMake. Newer versions of GCCSDK don't have this problem. - - -Current level of implementation -------------------------------- - -The video driver currently provides full screen video support with keyboard and mouse input. Windowed mode is not yet supported, but is planned in the future. Only software rendering is supported. - -The filesystem APIs return either Unix-style paths or RISC OS-style paths based on the value of the `__riscosify_control` symbol, as is standard for UnixLib functions. - -The audio, loadso, thread and timer APIs are currently provided by UnixLib. - -The joystick, locale and power APIs are not yet implemented. +RISC OS +======= + +Requirements: + +* RISC OS 3.5 or later. +* [SharedUnixLibrary](http://www.riscos.info/packages/LibraryDetails.html#SharedUnixLibraryarm). +* [DigitalRenderer](http://www.riscos.info/packages/LibraryDetails.html#DRendererarm), for audio support. +* [Iconv](http://www.netsurf-browser.org/projects/iconv/), for `SDL_iconv` and related functions. + + +Compiling: +---------- + +Currently, SDL for RISC OS only supports compiling with GCCSDK under Linux. + +The following commands can be used to build SDL for RISC OS using CMake: + + cmake -Bbuild-riscos -DCMAKE_TOOLCHAIN_FILE=$GCCSDK_INSTALL_ENV/toolchain-riscos.cmake -DRISCOS=ON -DCMAKE_INSTALL_PREFIX=$GCCSDK_INSTALL_ENV -DCMAKE_BUILD_TYPE=Release + cmake --build build-riscos + cmake --install build-riscos + +When using GCCSDK 4.7.4 release 6 or earlier versions, the builtin atomic functions are broken, meaning it's currently necessary to compile with `-DSDL_GCC_ATOMICS=OFF` using CMake. Newer versions of GCCSDK don't have this problem. + + +Current level of implementation +------------------------------- + +The video driver currently provides full screen video support with keyboard and mouse input. Windowed mode is not yet supported, but is planned in the future. Only software rendering is supported. + +The filesystem APIs return either Unix-style paths or RISC OS-style paths based on the value of the `__riscosify_control` symbol, as is standard for UnixLib functions. + +The audio, loadso, thread and timer APIs are currently provided by UnixLib. + +The joystick, locale and power APIs are not yet implemented. diff --git a/docs/README-touch.md b/docs/README-touch.md index 94d4e723..97925279 100644 --- a/docs/README-touch.md +++ b/docs/README-touch.md @@ -1,81 +1,82 @@ -Touch -=========================================================================== -System Specific Notes -=========================================================================== -Linux: -The linux touch system is currently based off event streams, and proc/bus/devices. The active user must be given permissions to read /dev/input/TOUCHDEVICE, where TOUCHDEVICE is the event stream for your device. Currently only Wacom tablets are supported. If you have an unsupported tablet contact me at jim.tla+sdl_touch@gmail.com and I will help you get support for it. - -Mac: -The Mac and iPhone APIs are pretty. If your touch device supports them then you'll be fine. If it doesn't, then there isn't much we can do. - -iPhone: -Works out of box. - -Windows: -Unfortunately there is no windows support as of yet. Support for Windows 7 is planned, but we currently have no way to test. If you have a Windows 7 WM_TOUCH supported device, and are willing to help test please contact me at jim.tla+sdl_touch@gmail.com - -=========================================================================== -Events -=========================================================================== -SDL_EVENT_FINGER_DOWN: -Sent when a finger (or stylus) is placed on a touch device. -Fields: -* event.tfinger.touchId - the Id of the touch device. -* event.tfinger.fingerId - the Id of the finger which just went down. -* event.tfinger.x - the x coordinate of the touch (0..1) -* event.tfinger.y - the y coordinate of the touch (0..1) -* event.tfinger.pressure - the pressure of the touch (0..1) - -SDL_EVENT_FINGER_MOTION: -Sent when a finger (or stylus) is moved on the touch device. -Fields: -Same as SDL_EVENT_FINGER_DOWN but with additional: -* event.tfinger.dx - change in x coordinate during this motion event. -* event.tfinger.dy - change in y coordinate during this motion event. - -SDL_EVENT_FINGER_UP: -Sent when a finger (or stylus) is lifted from the touch device. -Fields: -Same as SDL_EVENT_FINGER_DOWN. - - -=========================================================================== -Functions -=========================================================================== -SDL provides the ability to access the underlying SDL_Finger structures. -These structures should _never_ be modified. - -The following functions are included from SDL_touch.h - -To get a SDL_TouchID call SDL_GetTouchDevice(int index). -This returns a SDL_TouchID. -IMPORTANT: If the touch has been removed, or there is no touch with the given index, SDL_GetTouchDevice() will return 0. Be sure to check for this! - -The number of touch devices can be queried with SDL_GetNumTouchDevices(). - -A SDL_TouchID may be used to get pointers to SDL_Finger. - -SDL_GetNumTouchFingers(touchID) may be used to get the number of fingers currently down on the device. - -The most common reason to access SDL_Finger is to query the fingers outside the event. In most cases accessing the fingers is using the event. This would be accomplished by code like the following: - - float x = event.tfinger.x; - float y = event.tfinger.y; - - - -To get a SDL_Finger, call SDL_GetTouchFinger(SDL_TouchID touchID, int index), where touchID is a SDL_TouchID, and index is the requested finger. -This returns a SDL_Finger *, or NULL if the finger does not exist, or has been removed. -A SDL_Finger is guaranteed to be persistent for the duration of a touch, but it will be de-allocated as soon as the finger is removed. This occurs when the SDL_EVENT_FINGER_UP event is _added_ to the event queue, and thus _before_ the SDL_EVENT_FINGER_UP event is polled. -As a result, be very careful to check for NULL return values. - -A SDL_Finger has the following fields: -* x, y: - The current coordinates of the touch. -* pressure: - The pressure of the touch. - - -Please direct questions/comments to: - jim.tla+sdl_touch@gmail.com - (original author, API was changed since) +Touch +=========================================================================== +System Specific Notes +=========================================================================== +Linux: +The linux touch system is currently based off event streams, and proc/bus/devices. The active user must be given permissions to read /dev/input/TOUCHDEVICE, where TOUCHDEVICE is the event stream for your device. Currently only Wacom tablets are supported. If you have an unsupported tablet contact me at jim.tla+sdl_touch@gmail.com and I will help you get support for it. + +Mac: +The Mac and iPhone APIs are pretty. If your touch device supports them then you'll be fine. If it doesn't, then there isn't much we can do. + +iPhone: +Works out of box. + +Windows: +Unfortunately there is no windows support as of yet. Support for Windows 7 is planned, but we currently have no way to test. If you have a Windows 7 WM_TOUCH supported device, and are willing to help test please contact me at jim.tla+sdl_touch@gmail.com + +=========================================================================== +Events +=========================================================================== +SDL_EVENT_FINGER_DOWN: +Sent when a finger (or stylus) is placed on a touch device. +Fields: +* event.tfinger.touchId - the Id of the touch device. +* event.tfinger.fingerId - the Id of the finger which just went down. +* event.tfinger.x - the x coordinate of the touch (0..1) +* event.tfinger.y - the y coordinate of the touch (0..1) +* event.tfinger.pressure - the pressure of the touch (0..1) + +SDL_EVENT_FINGER_MOTION: +Sent when a finger (or stylus) is moved on the touch device. +Fields: +Same as SDL_EVENT_FINGER_DOWN but with additional: +* event.tfinger.dx - change in x coordinate during this motion event. +* event.tfinger.dy - change in y coordinate during this motion event. + +SDL_EVENT_FINGER_UP: +Sent when a finger (or stylus) is lifted from the touch device. +Fields: +Same as SDL_EVENT_FINGER_DOWN. + + +=========================================================================== +Functions +=========================================================================== +SDL provides the ability to access the underlying SDL_Finger structures. +These structures should _never_ be modified. + +The following functions are included from SDL_touch.h + +Devices are tracked by instance ID, of type SDL_TouchID. + +To get a list of available device SDL_TouchID values, call SDL_GetTouchDevices(). +This returns an array of device IDs, terminated by a zero ID. Optionally, you can +get a count of IDs by passing a non-NULL int* to SDL_GetTouchDevices() if you'd +rather not iterate the whole array to get this number. + +A SDL_TouchID may be used to get pointers to SDL_Finger. + +SDL_GetNumTouchFingers(touchID) may be used to get the number of fingers currently down on the device. + +The most common reason to access SDL_Finger is to query the fingers outside the event. In most cases accessing the fingers is using the event. This would be accomplished by code like the following: + + float x = event.tfinger.x; + float y = event.tfinger.y; + + + +To get a SDL_Finger, call SDL_GetTouchFinger(SDL_TouchID touchID, int index), where touchID is a SDL_TouchID, and index is the requested finger. +This returns a SDL_Finger *, or NULL if the finger does not exist, or has been removed. +A SDL_Finger is guaranteed to be persistent for the duration of a touch, but it will be de-allocated as soon as the finger is removed. This occurs when the SDL_EVENT_FINGER_UP event is _added_ to the event queue, and thus _before_ the SDL_EVENT_FINGER_UP event is polled. +As a result, be very careful to check for NULL return values. + +A SDL_Finger has the following fields: +* x, y: + The current coordinates of the touch. +* pressure: + The pressure of the touch. + + +Please direct questions/comments to: + jim.tla+sdl_touch@gmail.com + (original author, API was changed since) diff --git a/docs/README-versions.md b/docs/README-versions.md index d54bf40c..097dba1c 100644 --- a/docs/README-versions.md +++ b/docs/README-versions.md @@ -1,60 +1,60 @@ -# Versioning - -## Since 2.23.0 - -SDL follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak -and older versions of the Linux kernel: - -* The major version (first part) increases when backwards compatibility - is broken, which will happen infrequently. - -* If the minor version (second part) is divisible by 2 - (for example 2.24.x, 2.26.x), this indicates a version of SDL that - is believed to be stable and suitable for production use. - - * In stable releases, the patchlevel or micro version (third part) - indicates bugfix releases. Bugfix releases should not add or - remove ABI, so the ".0" release (for example 2.24.0) should be - forwards-compatible with all the bugfix releases from the - same cycle (for example 2.24.1). - - * The minor version increases when new API or ABI is added, or when - other significant changes are made. Newer minor versions are - backwards-compatible, but not fully forwards-compatible. - For example, programs built against SDL 2.24.x should work fine - with SDL 2.26.x, but programs built against SDL 2.26.x will not - necessarily work with 2.24.x. - -* If the minor version (second part) is not divisible by 2 - (for example 2.23.x, 2.25.x), this indicates a development prerelease - of SDL that is not suitable for stable software distributions. - Use with caution. - - * The patchlevel or micro version (third part) increases with - each prerelease. - - * Each prerelease might add new API and/or ABI. - - * Prereleases are backwards-compatible with older stable branches. - For example, 2.25.x will be backwards-compatible with 2.24.x. - - * Prereleases are not guaranteed to be backwards-compatible with - each other. For example, new API or ABI added in 2.25.1 - might be removed or changed in 2.25.2. - If this would be a problem for you, please do not use prereleases. - - * Only upgrade to a prerelease if you can guarantee that you will - promptly upgrade to the stable release that follows it. - For example, do not upgrade to 2.23.x unless you will be able to - upgrade to 2.24.0 when it becomes available. - - * Software distributions that have a freeze policy (in particular Linux - distributions with a release cycle, such as Debian and Fedora) - should usually only package stable releases, and not prereleases. - -## Before 2.23.0 - -Older versions of SDL followed a similar policy, but instead of the -odd/even rule applying to the minor version, it applied to the patchlevel -(micro version, third part). For example, 2.0.22 was a stable release -and 2.0.21 was a prerelease. +# Versioning + +## Since 2.23.0 + +SDL follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak +and older versions of the Linux kernel: + +* The major version (first part) increases when backwards compatibility + is broken, which will happen infrequently. + +* If the minor version (second part) is divisible by 2 + (for example 2.24.x, 2.26.x), this indicates a version of SDL that + is believed to be stable and suitable for production use. + + * In stable releases, the patchlevel or micro version (third part) + indicates bugfix releases. Bugfix releases should not add or + remove ABI, so the ".0" release (for example 2.24.0) should be + forwards-compatible with all the bugfix releases from the + same cycle (for example 2.24.1). + + * The minor version increases when new API or ABI is added, or when + other significant changes are made. Newer minor versions are + backwards-compatible, but not fully forwards-compatible. + For example, programs built against SDL 2.24.x should work fine + with SDL 2.26.x, but programs built against SDL 2.26.x will not + necessarily work with 2.24.x. + +* If the minor version (second part) is not divisible by 2 + (for example 2.23.x, 2.25.x), this indicates a development prerelease + of SDL that is not suitable for stable software distributions. + Use with caution. + + * The patchlevel or micro version (third part) increases with + each prerelease. + + * Each prerelease might add new API and/or ABI. + + * Prereleases are backwards-compatible with older stable branches. + For example, 2.25.x will be backwards-compatible with 2.24.x. + + * Prereleases are not guaranteed to be backwards-compatible with + each other. For example, new API or ABI added in 2.25.1 + might be removed or changed in 2.25.2. + If this would be a problem for you, please do not use prereleases. + + * Only upgrade to a prerelease if you can guarantee that you will + promptly upgrade to the stable release that follows it. + For example, do not upgrade to 2.23.x unless you will be able to + upgrade to 2.24.0 when it becomes available. + + * Software distributions that have a freeze policy (in particular Linux + distributions with a release cycle, such as Debian and Fedora) + should usually only package stable releases, and not prereleases. + +## Before 2.23.0 + +Older versions of SDL followed a similar policy, but instead of the +odd/even rule applying to the minor version, it applied to the patchlevel +(micro version, third part). For example, 2.0.22 was a stable release +and 2.0.21 was a prerelease. diff --git a/docs/README-visualc.md b/docs/README-visualc.md index 276c83bc..78904214 100644 --- a/docs/README-visualc.md +++ b/docs/README-visualc.md @@ -1,113 +1,113 @@ -Using SDL with Microsoft Visual C++ -=================================== - -### by Lion Kimbro with additions by James Turk - -You can either use the precompiled libraries from the [SDL](https://www.libsdl.org/download.php) web site, or you can build SDL -yourself. - -### Building SDL - -0. To build SDL, your machine must, at a minimum, have the DirectX9.0c SDK installed. It may or may not be retrievable from -the [Microsoft](https://www.microsoft.com) website, so you might need to locate it [online](https://duckduckgo.com/?q=directx9.0c+sdk+download&t=h_&ia=web). -_Editor's note: I've been able to successfully build SDL using Visual Studio 2019 **without** the DX9.0c SDK_ - -1. Open the Visual Studio solution file at `./VisualC/SDL.sln`. - -2. Your IDE will likely prompt you to upgrade this solution file to whatever later version of the IDE you're using. In the `Retarget Projects` dialog, -all of the affected project files should be checked allowing you to use the latest `Windows SDK Version` you have installed, along with -the `Platform Toolset`. - -If you choose *NOT* to upgrade to use the latest `Windows SDK Version` or `Platform Toolset`, then you'll need the `Visual Studio 2010 Platform Toolset`. - -3. Build the `.dll` and `.lib` files by right clicking on each project in turn (Projects are listed in the _Workspace_ -panel in the _FileView_ tab), and selecting `Build`. - -You may get a few warnings, but you should not get any errors. - -Later, we will refer to the following `.lib` and `.dll` files that have just been generated: - -- `./VisualC/Win32/Debug/SDL3.dll` or `./VisualC/Win32/Release/SDL3.dll` -- `./VisualC/Win32/Debug/SDL3.lib` or `./VisualC/Win32/Release/SDL3.lib` - -_Note for the `x64` versions, just replace `Win32` in the path with `x64`_ - -### Creating a Project with SDL - -- Create a project as a `Win32 Application`. - -- Create a C++ file for your project. - -- Set the C runtime to `Multi-threaded DLL` in the menu: -`Project|Settings|C/C++ tab|Code Generation|Runtime Library `. - -- Add the SDL `include` directory to your list of includes in the menu: -`Project|Settings|C/C++ tab|Preprocessor|Additional include directories ` - -*VC7 Specific: Instead of doing this, I find it easier to add the -include and library directories to the list that VC7 keeps. Do this by -selecting Tools|Options|Projects|VC++ Directories and under the "Show -Directories For:" dropbox select "Include Files", and click the "New -Directory Icon" and add the [SDLROOT]\\include directory (e.g. If you -installed to c:\\SDL\\ add c:\\SDL\\include). Proceed to change the -dropbox selection to "Library Files" and add [SDLROOT]\\lib.* - -The "include directory" I am referring to is the `./include` folder. - -Now we're going to use the files that we had created earlier in the *Build SDL* step. - -Copy the following file into your Project directory: - -- `SDL3.dll` - -Add the following file to your project (It is not necessary to copy it to your project directory): - -- `SDL3.lib` - -To add them to your project, right click on your project, and select -`Add files to project`. - -**Instead of adding the files to your project, it is more desirable to add them to the linker options: Project|Properties|Linker|Command Line -and type the names of the libraries to link with in the "Additional Options:" box. Note: This must be done for each build configuration -(e.g. Release,Debug).** - -### Hello SDL - -Here's a sample SDL snippet to verify everything is setup in your IDE: - -``` - #include - #include // only include this one in the source file with main()! - - int main( int argc, char* argv[] ) - { - const int WIDTH = 640; - const int HEIGHT = 480; - SDL_Window* window = NULL; - SDL_Renderer* renderer = NULL; - - SDL_Init(SDL_INIT_VIDEO); - window = SDL_CreateWindow("Hello SDL", WIDTH, HEIGHT, 0); - renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - return 0; - } - ``` - -### That's it! - -I hope that this document has helped you get through the most difficult part of using the SDL: installing it. -Suggestions for improvements should be posted to the [Github Issues](https://github.com/libsdl-org/SDL/issues). - -### Credits - -Thanks to [Paulus Esterhazy](mailto:pesterhazy@gmx.net), for the work on VC++ port. - -This document was originally called "VisualC.txt", and was written by [Sam Lantinga](mailto:slouken@libsdl.org). - -Later, it was converted to HTML and expanded into the document that you see today by [Lion Kimbro](mailto:snowlion@sprynet.com). - -Minor Fixes and Visual C++ 7 Information (In Green) was added by [James Turk](mailto:james@conceptofzero.net) +Using SDL with Microsoft Visual C++ +=================================== + +### by Lion Kimbro with additions by James Turk + +You can either use the precompiled libraries from the [SDL](https://www.libsdl.org/download.php) web site, or you can build SDL +yourself. + +### Building SDL + +0. To build SDL, your machine must, at a minimum, have the DirectX9.0c SDK installed. It may or may not be retrievable from +the [Microsoft](https://www.microsoft.com) website, so you might need to locate it [online](https://duckduckgo.com/?q=directx9.0c+sdk+download&t=h_&ia=web). +_Editor's note: I've been able to successfully build SDL using Visual Studio 2019 **without** the DX9.0c SDK_ + +1. Open the Visual Studio solution file at `./VisualC/SDL.sln`. + +2. Your IDE will likely prompt you to upgrade this solution file to whatever later version of the IDE you're using. In the `Retarget Projects` dialog, +all of the affected project files should be checked allowing you to use the latest `Windows SDK Version` you have installed, along with +the `Platform Toolset`. + +If you choose *NOT* to upgrade to use the latest `Windows SDK Version` or `Platform Toolset`, then you'll need the `Visual Studio 2010 Platform Toolset`. + +3. Build the `.dll` and `.lib` files by right clicking on each project in turn (Projects are listed in the _Workspace_ +panel in the _FileView_ tab), and selecting `Build`. + +You may get a few warnings, but you should not get any errors. + +Later, we will refer to the following `.lib` and `.dll` files that have just been generated: + +- `./VisualC/Win32/Debug/SDL3.dll` or `./VisualC/Win32/Release/SDL3.dll` +- `./VisualC/Win32/Debug/SDL3.lib` or `./VisualC/Win32/Release/SDL3.lib` + +_Note for the `x64` versions, just replace `Win32` in the path with `x64`_ + +### Creating a Project with SDL + +- Create a project as a `Win32 Application`. + +- Create a C++ file for your project. + +- Set the C runtime to `Multi-threaded DLL` in the menu: +`Project|Settings|C/C++ tab|Code Generation|Runtime Library `. + +- Add the SDL `include` directory to your list of includes in the menu: +`Project|Settings|C/C++ tab|Preprocessor|Additional include directories ` + +*VC7 Specific: Instead of doing this, I find it easier to add the +include and library directories to the list that VC7 keeps. Do this by +selecting Tools|Options|Projects|VC++ Directories and under the "Show +Directories For:" dropbox select "Include Files", and click the "New +Directory Icon" and add the [SDLROOT]\\include directory (e.g. If you +installed to c:\\SDL\\ add c:\\SDL\\include). Proceed to change the +dropbox selection to "Library Files" and add [SDLROOT]\\lib.* + +The "include directory" I am referring to is the `./include` folder. + +Now we're going to use the files that we had created earlier in the *Build SDL* step. + +Copy the following file into your Project directory: + +- `SDL3.dll` + +Add the following file to your project (It is not necessary to copy it to your project directory): + +- `SDL3.lib` + +To add them to your project, right click on your project, and select +`Add files to project`. + +**Instead of adding the files to your project, it is more desirable to add them to the linker options: Project|Properties|Linker|Command Line +and type the names of the libraries to link with in the "Additional Options:" box. Note: This must be done for each build configuration +(e.g. Release,Debug).** + +### Hello SDL + +Here's a sample SDL snippet to verify everything is setup in your IDE: + +``` + #include + #include // only include this one in the source file with main()! + + int main( int argc, char* argv[] ) + { + const int WIDTH = 640; + const int HEIGHT = 480; + SDL_Window* window = NULL; + SDL_Renderer* renderer = NULL; + + SDL_Init(SDL_INIT_VIDEO); + window = SDL_CreateWindow("Hello SDL", WIDTH, HEIGHT, 0); + renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; + } + ``` + +### That's it! + +I hope that this document has helped you get through the most difficult part of using the SDL: installing it. +Suggestions for improvements should be posted to the [Github Issues](https://github.com/libsdl-org/SDL/issues). + +### Credits + +Thanks to [Paulus Esterhazy](mailto:pesterhazy@gmx.net), for the work on VC++ port. + +This document was originally called "VisualC.txt", and was written by [Sam Lantinga](mailto:slouken@libsdl.org). + +Later, it was converted to HTML and expanded into the document that you see today by [Lion Kimbro](mailto:snowlion@sprynet.com). + +Minor Fixes and Visual C++ 7 Information (In Green) was added by [James Turk](mailto:james@conceptofzero.net) diff --git a/docs/README-vita.md b/docs/README-vita.md index 3dbaf1cd..0a11cf80 100644 --- a/docs/README-vita.md +++ b/docs/README-vita.md @@ -1,33 +1,33 @@ -PS Vita -======= -SDL port for the Sony Playstation Vita and Sony Playstation TV - -Credit to -* xerpi, cpasjuste and rsn8887 for initial (vita2d) port -* vitasdk/dolcesdk devs -* CBPS discord (Namely Graphene and SonicMastr) - -Building --------- -To build for the PSVita, make sure you have vitasdk and cmake installed and run: -``` - cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release - cmake --build build - cmake --install build -``` - - -Notes ------ -* gles1/gles2 support and renderers are disabled by default and can be enabled by configuring with `-DVIDEO_VITA_PVR=ON` - These renderers support 720p and 1080i resolutions. These can be specified with: - `SDL_setenv("VITA_RESOLUTION", "720", 1);` and `SDL_setenv("VITA_RESOLUTION", "1080", 1);` -* Desktop GL 1.X and 2.X support and renderers are also disabled by default and also can be enabled with `-DVIDEO_VITA_PVR=ON` as long as gl4es4vita is present in your SDK. - They support the same resolutions as the gles1/gles2 backends and require specifying `SDL_setenv("VITA_PVR_OGL", "1", 1);` - anytime before video subsystem initialization. -* gles2 support via PIB is disabled by default and can be enabled by configuring with `-DVIDEO_VITA_PIB=ON` -* By default SDL emits mouse events for touch events on every touchscreen. - Vita has two touchscreens, so it's recommended to use `SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");` and handle touch events instead. - Individual touchscreens can be disabled with: - `SDL_setenv("VITA_DISABLE_TOUCH_FRONT", "1", 1);` and `SDL_setenv("VITA_DISABLE_TOUCH_BACK", "1", 1);` -* Support for L2/R2/R3/R3 buttons, haptic feedback and gamepad led only available on PSTV, or when using external ds4 gamepad on vita. +PS Vita +======= +SDL port for the Sony Playstation Vita and Sony Playstation TV + +Credit to +* xerpi, cpasjuste and rsn8887 for initial (vita2d) port +* vitasdk/dolcesdk devs +* CBPS discord (Namely Graphene and SonicMastr) + +Building +-------- +To build for the PSVita, make sure you have vitasdk and cmake installed and run: +``` + cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release + cmake --build build + cmake --install build +``` + + +Notes +----- +* gles1/gles2 support and renderers are disabled by default and can be enabled by configuring with `-DVIDEO_VITA_PVR=ON` + These renderers support 720p and 1080i resolutions. These can be specified with: + `SDL_setenv("VITA_RESOLUTION", "720", 1);` and `SDL_setenv("VITA_RESOLUTION", "1080", 1);` +* Desktop GL 1.X and 2.X support and renderers are also disabled by default and also can be enabled with `-DVIDEO_VITA_PVR=ON` as long as gl4es4vita is present in your SDK. + They support the same resolutions as the gles1/gles2 backends and require specifying `SDL_setenv("VITA_PVR_OGL", "1", 1);` + anytime before video subsystem initialization. +* gles2 support via PIB is disabled by default and can be enabled by configuring with `-DVIDEO_VITA_PIB=ON` +* By default SDL emits mouse events for touch events on every touchscreen. + Vita has two touchscreens, so it's recommended to use `SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");` and handle touch events instead. + Individual touchscreens can be disabled with: + `SDL_setenv("VITA_DISABLE_TOUCH_FRONT", "1", 1);` and `SDL_setenv("VITA_DISABLE_TOUCH_BACK", "1", 1);` +* Support for L2/R2/R3/R3 buttons, haptic feedback and gamepad led only available on PSTV, or when using external ds4 gamepad on vita. diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 2bab54f2..efa52a95 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -1,45 +1,223 @@ -Wayland -======= -Wayland is a replacement for the X11 window system protocol and architecture and is favored over X11 by default in SDL3 -for communicating with desktop compositors. It works well for the majority of applications, however, applications may -encounter limitations or behavior that is different from other windowing systems. - -## Common issues: - -### Window decorations are missing, or the decorations look strange - -- On some desktops (i.e. GNOME), Wayland applications use a library - called [libdecor](https://gitlab.freedesktop.org/libdecor/libdecor) to provide window decorations. If this library is - not installed, the decorations will be missing. This library uses plugins to generate different decoration styles, and - if a plugin to generate native-looking decorations is not installed (i.e. the GTK plugin), the decorations will not - appear to be 'native'. - -### Windows do not appear immediately after creation - -- Wayland requires that the application initially present a buffer before the window becomes visible. Additionally, - applications _must_ have an event loop and processes messages on a regular basis, or the application can appear - unresponsive to both the user and desktop compositor. - -### ```SDL_SetWindowPosition()``` doesn't work on non-popup windows - -- Wayland does not allow toplevel windows to position themselves programmatically. - -### Retrieving the global mouse cursor position when the cursor is outside a window doesn't work - -- Wayland only provides applications with the cursor position within the borders of the application windows. Querying - the global position when an application window does not have mouse focus returns 0,0 as the actual cursor position is - unknown. In most cases, applications don't actually need the global cursor position and should use the window-relative - coordinates as provided by the mouse movement event or from ```SDL_GetMouseState()``` instead. - -### Warping the global mouse cursor position via ```SDL_WarpMouseGlobal()``` doesn't work - -- For security reasons, Wayland does not allow warping the global mouse cursor position. - -### The application icon can't be set via ```SDL_SetWindowIcon()``` - -- Wayland doesn't support programmatically setting the application icon. To provide a custom icon for your application, - you must create an associated desktop entry file, aka a `.desktop` file, that points to the icon image. Please see the - [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for more information - on the format of this file. Note that if your application manually sets the application ID via the `SDL_APP_ID` hint - string, the desktop entry file name should match the application ID. For example, if your application ID is set - to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. +Wayland +======= +Wayland is a replacement for the X11 window system protocol and architecture and is favored over X11 by default in SDL3 +for communicating with desktop compositors. It works well for the majority of applications, however, applications may +encounter limitations or behavior that is different from other windowing systems. + +## Common issues: + +### Window decorations are missing, or the decorations look strange + +- On some desktops (i.e. GNOME), Wayland applications use a library + called [libdecor](https://gitlab.freedesktop.org/libdecor/libdecor) to provide window decorations. If this library is + not installed, the decorations will be missing. This library uses plugins to generate different decoration styles, and + if a plugin to generate native-looking decorations is not installed (i.e. the GTK plugin), the decorations will not + appear to be 'native'. + +### Windows do not appear immediately after creation + +- Wayland requires that the application initially present a buffer before the window becomes visible. Additionally, + applications _must_ have an event loop and processes messages on a regular basis, or the application can appear + unresponsive to both the user and desktop compositor. + +### ```SDL_SetWindowPosition()``` doesn't work on non-popup windows + +- Wayland does not allow toplevel windows to position themselves programmatically. + +### Retrieving the global mouse cursor position when the cursor is outside a window doesn't work + +- Wayland only provides applications with the cursor position within the borders of the application windows. Querying + the global position when an application window does not have mouse focus returns 0,0 as the actual cursor position is + unknown. In most cases, applications don't actually need the global cursor position and should use the window-relative + coordinates as provided by the mouse movement event or from ```SDL_GetMouseState()``` instead. + +### Warping the global mouse cursor position via ```SDL_WarpMouseGlobal()``` doesn't work + +- For security reasons, Wayland does not allow warping the global mouse cursor position. + +### The application icon can't be set via ```SDL_SetWindowIcon()``` + +- Wayland doesn't support programmatically setting the application icon. To provide a custom icon for your application, + you must create an associated desktop entry file, aka a `.desktop` file, that points to the icon image. Please see the + [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for more information + on the format of this file. Note that if your application manually sets the application ID via the `SDL_APP_ID` hint + string, the desktop entry file name should match the application ID. For example, if your application ID is set + to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. + +## Using custom Wayland windowing protocols with SDL windows + +Under normal operation, an `SDL_Window` corresponds to an XDG toplevel window, which provides a standard desktop window. +If an application wishes to use a different windowing protocol with an SDL window (e.g. wlr_layer_shell) while still +having SDL handle input and rendering, it needs to create a custom, roleless surface and attach that surface to its own +toplevel window. + +This is done by using `SDL_CreateWindowWithProperties()` and setting the +`SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` property to `SDL_TRUE`. Once the window has been +successfully created, the `wl_display` and `wl_surface` objects can then be retrieved from the +`SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER` and `SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER` properties respectively. + +Surfaces don't receive any size change notifications, so if an application changes the window size, it must inform SDL +that the surface size has changed by calling SDL_SetWindowSize() with the new dimensions. + +Custom surfaces will automatically handle scaling internally if the window was created with the +`SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN` property set to `SDL_TRUE`. In this case, applications should +not manually attach viewports or change the surface scale value, as SDL will handle this internally. Calls +to `SDL_SetWindowSize()` should use the logical size of the window, and `SDL_GetWindowSizeInPixels()` should be used to +query the size of the backbuffer surface in pixels. If this property is not set or is `SDL_FALSE`, applications can +attach their own viewports or change the surface scale manually, and the SDL backend will not interfere or change any +values internally. In this case, calls to `SDL_SetWindowSize()` should pass the requested surface size in pixels, not +the logical window size, as no scaling calculations will be done internally. + +All window functions that control window state aside from `SDL_SetWindowSize()` are no-ops with custom surfaces. + +Please see the minimal example in `tests/testwaylandcustom.c` for an example of how to use a custom, roleless surface +and attach it to an application-managed toplevel window. + +## Importing external surfaces into SDL windows + +Wayland windows and surfaces are more intrinsically tied to the client library than other windowing systems, therefore, +when importing surfaces, it is necessary for both SDL and the application or toolkit to use the same `wl_display` +object. This can be set/queried via the global `SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER` property. To +import an external `wl_display`, set this property before initializing the SDL video subsystem, and read the value to +export the internal `wl_display` after the video subsystem has been initialized. Setting this property after the video +subsystem has been initialized has no effect, and reading it when the video subsystem is uninitialized will either +return the user provided value, if one was set while in the uninitialized state, or NULL. + +Once this is done, and the application has created or obtained the `wl_surface` to be wrapped in an `SDL_Window`, the +window is created with `SDL_CreateWindowWithProperties()` with the +`SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` property to set to the `wl_surface` object that is to be +imported by SDL. + +SDL receives no notification regarding size changes on external surfaces or toplevel windows, so if the external surface +needs to be resized, SDL must be informed by calling SDL_SetWindowSize() with the new dimensions. + +If desired, SDL can automatically handle the scaling for the surface by setting the +`SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN` property to `SDL_TRUE`, however, if the surface being imported +already has, or will have, a viewport/fractional scale manager attached to it by the application or an external toolkit, +a protocol violation will result. Avoid setting this property if importing surfaces from toolkits such as Qt or GTK. + +If the window is flagged as high pixel density, calls to `SDL_SetWindowSize()` should pass the logical size of the +window and `SDL_GetWindowSizeInPixels()` should be used to retrieve the backbuffer size in pixels. Otherwise, calls to +`SDL_SetWindowSize()` should pass the requested surface size in pixels, not the logical window size, as no scaling +calculations will be done internally. + +All window functions that control window state aside from `SDL_SetWindowSize()` are no-ops with external surfaces. + +An example of how to use external surfaces with a `wl_display` owned by SDL can be seen in `tests/testnativewayland.c`, +and the following is a minimal example of interoperation with Qt 6, with Qt owning the `wl_display`: + +```c++ +#include +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + int ret = -1; + int done = 0; + SDL_PropertiesID props; + SDL_Event e; + SDL_Window *sdlWindow = NULL; + SDL_Renderer *sdlRenderer = NULL; + struct wl_display *display = NULL; + struct wl_surface *surface = NULL; + + /* Initialize Qt */ + QApplication qtApp(argc, argv); + QWindow qtWindow; + + /* The windowing system must be Wayland. */ + if (QApplication::platformName() != "wayland") { + goto exit; + } + + { + /* Get the wl_display object from Qt */ + QNativeInterface::QWaylandApplication *qtWlApp = qtApp.nativeInterface(); + display = qtWlApp->display(); + + if (!display) { + goto exit; + } + } + + /* Set SDL to use the existing wl_display object from Qt and initialize. */ + SDL_SetProperty(SDL_GetGlobalProperties(), SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, display); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + + /* Create a basic, frameless QWindow */ + qtWindow.setFlags(Qt::FramelessWindowHint); + qtWindow.setGeometry(0, 0, 640, 480); + qtWindow.show(); + + { + /* Get the native wl_surface backing resource for the window */ + QPlatformNativeInterface *qtNative = qtApp.platformNativeInterface(); + surface = (struct wl_surface *)qtNative->nativeResourceForWindow("surface", &qtWindow); + + if (!surface) { + goto exit; + } + } + + /* Create a window that wraps the wl_surface from the QWindow. + * Qt objects should not be flagged as DPI-aware or protocol violations will result. + */ + props = SDL_CreateProperties(); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, surface); + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_TRUE); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, 640); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, 480); + sdlWindow = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (!sdlWindow) { + goto exit; + } + + /* Create a renderer */ + sdlRenderer = SDL_CreateRenderer(sdlWindow, NULL, 0); + if (!sdlRenderer) { + goto exit; + } + + /* Draw a blue screen for the window until ESC is pressed or the window is no longer visible. */ + while (!done) { + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_KEY_DOWN && e.key.keysym.sym == SDLK_ESCAPE) { + done = 1; + } + } + + qtApp.processEvents(); + + /* Update the backbuffer size if the window scale changed. */ + qreal scale = qtWindow.devicePixelRatio(); + SDL_SetWindowSize(sdlWindow, SDL_lround(640. * scale), SDL_lround(480. * scale)); + + if (qtWindow.isVisible()) { + SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 255, 255); + SDL_RenderClear(sdlRenderer); + SDL_RenderPresent(sdlRenderer); + } else { + done = 1; + } + } + + ret = 0; + +exit: + /* Cleanup */ + if (sdlRenderer) { + SDL_DestroyRenderer(sdlRenderer); + } + if (sdlWindow) { + SDL_DestroyWindow(sdlWindow); + } + + SDL_Quit(); + return ret; +} +``` + diff --git a/docs/README-windows.md b/docs/README-windows.md index 1e6d59c4..daf80647 100644 --- a/docs/README-windows.md +++ b/docs/README-windows.md @@ -1,66 +1,66 @@ -# Windows - -## LLVM and Intel C++ compiler support - -SDL will build with the Visual Studio project files with LLVM-based compilers, such as the Intel oneAPI C++ -compiler, but you'll have to manually add the "-msse3" command line option -to at least the SDL_audiocvt.c source file, and possibly others. This may -not be necessary if you build SDL with CMake instead of the included Visual -Studio solution. - -Details are here: https://github.com/libsdl-org/SDL/issues/5186 - - -## OpenGL ES 2.x support - -SDL has support for OpenGL ES 2.x under Windows via two alternative -implementations. - -The most straightforward method consists in running your app in a system with -a graphic card paired with a relatively recent (as of November of 2013) driver -which supports the WGL_EXT_create_context_es2_profile extension. Vendors known -to ship said extension on Windows currently include nVidia and Intel. - -The other method involves using the -[ANGLE library](https://code.google.com/p/angleproject/). If an OpenGL ES 2.x -context is requested and no WGL_EXT_create_context_es2_profile extension is -found, SDL will try to load the libEGL.dll library provided by ANGLE. - -To obtain the ANGLE binaries, you can either compile from source from -https://chromium.googlesource.com/angle/angle or copy the relevant binaries -from a recent Chrome/Chromium install for Windows. The files you need are: - -- libEGL.dll -- libGLESv2.dll -- d3dcompiler_46.dll (supports Windows Vista or later, better shader - compiler) *or* d3dcompiler_43.dll (supports Windows XP or later) - -If you compile ANGLE from source, you can configure it so it does not need the -d3dcompiler_* DLL at all (for details on this, see their documentation). -However, by default SDL will try to preload the d3dcompiler_46.dll to -comply with ANGLE's requirements. If you wish SDL to preload -d3dcompiler_43.dll (to support Windows XP) or to skip this step at all, you -can use the SDL_HINT_VIDEO_WIN_D3DCOMPILER hint (see SDL_hints.h for more -details). - -Known Bugs: - -- SDL_GL_SetSwapInterval is currently a no op when using ANGLE. It appears - that there's a bug in the library which prevents the window contents from - refreshing if this is set to anything other than the default value. - -## Vulkan Surface Support - -Support for creating Vulkan surfaces is configured on by default. To disable -it change the value of `SDL_VIDEO_VULKAN` to 0 in `SDL_config_windows.h`. You -must install the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) in order to -use Vulkan graphics in your application. - -## Transparent Window Support - -SDL uses the Desktop Window Manager (DWM) to create transparent windows. DWM is -always enabled from Windows 8 and above. Windows 7 only enables DWM with Aero Glass -theme. - -However, it cannot be guaranteed to work on all hardware configurations (an example -is hybrid GPU systems, such as NVIDIA Optimus laptops). +# Windows + +## LLVM and Intel C++ compiler support + +SDL will build with the Visual Studio project files with LLVM-based compilers, such as the Intel oneAPI C++ +compiler, but you'll have to manually add the "-msse3" command line option +to at least the SDL_audiocvt.c source file, and possibly others. This may +not be necessary if you build SDL with CMake instead of the included Visual +Studio solution. + +Details are here: https://github.com/libsdl-org/SDL/issues/5186 + + +## OpenGL ES 2.x support + +SDL has support for OpenGL ES 2.x under Windows via two alternative +implementations. + +The most straightforward method consists in running your app in a system with +a graphic card paired with a relatively recent (as of November of 2013) driver +which supports the WGL_EXT_create_context_es2_profile extension. Vendors known +to ship said extension on Windows currently include nVidia and Intel. + +The other method involves using the +[ANGLE library](https://code.google.com/p/angleproject/). If an OpenGL ES 2.x +context is requested and no WGL_EXT_create_context_es2_profile extension is +found, SDL will try to load the libEGL.dll library provided by ANGLE. + +To obtain the ANGLE binaries, you can either compile from source from +https://chromium.googlesource.com/angle/angle or copy the relevant binaries +from a recent Chrome/Chromium install for Windows. The files you need are: + +- libEGL.dll +- libGLESv2.dll +- d3dcompiler_46.dll (supports Windows Vista or later, better shader + compiler) *or* d3dcompiler_43.dll (supports Windows XP or later) + +If you compile ANGLE from source, you can configure it so it does not need the +d3dcompiler_* DLL at all (for details on this, see their documentation). +However, by default SDL will try to preload the d3dcompiler_46.dll to +comply with ANGLE's requirements. If you wish SDL to preload +d3dcompiler_43.dll (to support Windows XP) or to skip this step at all, you +can use the SDL_HINT_VIDEO_WIN_D3DCOMPILER hint (see SDL_hints.h for more +details). + +Known Bugs: + +- SDL_GL_SetSwapInterval is currently a no op when using ANGLE. It appears + that there's a bug in the library which prevents the window contents from + refreshing if this is set to anything other than the default value. + +## Vulkan Surface Support + +Support for creating Vulkan surfaces is configured on by default. To disable +it change the value of `SDL_VIDEO_VULKAN` to 0 in `SDL_config_windows.h`. You +must install the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) in order to +use Vulkan graphics in your application. + +## Transparent Window Support + +SDL uses the Desktop Window Manager (DWM) to create transparent windows. DWM is +always enabled from Windows 8 and above. Windows 7 only enables DWM with Aero Glass +theme. + +However, it cannot be guaranteed to work on all hardware configurations (an example +is hybrid GPU systems, such as NVIDIA Optimus laptops). diff --git a/docs/README-winrt.md b/docs/README-winrt.md index 3530f673..a41b21eb 100644 --- a/docs/README-winrt.md +++ b/docs/README-winrt.md @@ -1,523 +1,523 @@ -WinRT -===== - -This port allows SDL applications to run on Microsoft's platforms that require -use of "Windows Runtime", aka. "WinRT", APIs. Microsoft may, in some cases, -refer to them as either "Windows Store", or for Windows 10, "UWP" apps. - -In the past, SDL has supported Windows RT 8.x, Windows Phone, etc, but in -modern times this port is focused on UWP apps, which run on Windows 10, -and modern Xbox consoles. - - -Requirements ------------- - -* Microsoft Visual C++ (aka Visual Studio) 2019. - - Free, "Community" or "Express" editions may be used, so long as they - include support for either "Windows Store" or "Windows Phone" apps. - "Express" versions marked as supporting "Windows Desktop" development - typically do not include support for creating WinRT apps, to note. - (The "Community" editions of Visual C++ do, however, support both - desktop/Win32 and WinRT development). -* A valid Microsoft account - This requirement is not imposed by SDL, but - rather by Microsoft's Visual C++ toolchain. This is required to launch or - debug apps. - - -Status ------- - -Here is a rough list of what works, and what doesn't: - -* What works: - * compilation via Visual C++ 2019. - * compile-time platform detection for SDL programs. The C/C++ #define, - `__WINRT__`, will be set to 1 (by SDL) when compiling for WinRT. - * GPU-accelerated 2D rendering, via SDL_Renderer. - * OpenGL ES 2, via the ANGLE library (included separately from SDL) - * software rendering, via either SDL_Surface (optionally in conjunction with - SDL_GetWindowSurface() and SDL_UpdateWindowSurface()) or via the - SDL_Renderer APIs - * threads - * timers (via SDL_GetTicks(), SDL_AddTimer(), SDL_GetPerformanceCounter(), - SDL_GetPerformanceFrequency(), etc.) - * file I/O via SDL_RWops - * mouse input (unsupported on Windows Phone) - * audio, via SDL's WASAPI backend (if you want to record, your app must - have "Microphone" capabilities enabled in its manifest, and the user must - not have blocked access. Otherwise, capture devices will fail to work, - presenting as a device disconnect shortly after opening it.) - * .DLL file loading. Libraries *MUST* be packaged inside applications. Loading - anything outside of the app is not supported. - * system path retrieval via SDL's filesystem APIs - * game controllers. Support is provided via the SDL_Joystick and - SDL_Gamepad APIs, and is backed by Microsoft's XInput API. Please - note, however, that Windows limits game-controller support in UWP apps to, - "Xbox compatible controllers" (many controllers that work in Win32 apps, - do not work in UWP, due to restrictions in UWP itself.) - * multi-touch input - * app events. SDL_APP_WILLENTER* and SDL_APP_DIDENTER* events get sent out as - appropriate. - * window events - * using Direct3D 11.x APIs outside of SDL. Non-XAML / Direct3D-only apps can - choose to render content directly via Direct3D, using SDL to manage the - internal WinRT window, as well as input and audio. (Use - SDL_GetWindowWMInfo() to get the WinRT 'CoreWindow', and pass it into - IDXGIFactory2::CreateSwapChainForCoreWindow() as appropriate.) - -* What partially works: - * keyboard input. Most of WinRT's documented virtual keys are supported, as - well as many keys with documented hardware scancodes. Converting - SDL_Scancodes to or from SDL_Keycodes may not work, due to missing APIs - (MapVirtualKey()) in Microsoft's Windows Store / UWP APIs. - * SDL_main. WinRT uses a different signature for each app's main() function - and requires it to be implemented in C++, so SDL_main.h must be #include'd - in a C++ source file, that also must be compiled with /ZW. - -* What doesn't work: - * compilation with anything other than Visual C++ - * programmatically-created custom cursors. These don't appear to be supported - by WinRT. Different OS-provided cursors can, however, be created via - SDL_CreateSystemCursor() (unsupported on Windows Phone) - * SDL_WarpMouseInWindow() or SDL_WarpMouseGlobal(). This are not currently - supported by WinRT itself. - * joysticks and game controllers that either are not supported by - Microsoft's XInput API, or are not supported within UWP apps (many - controllers that work in Win32, do not work in UWP, due to restrictions in - UWP itself). - * turning off VSync when rendering on Windows Phone. Attempts to turn VSync - off on Windows Phone result either in Direct3D not drawing anything, or it - forcing VSync back on. As such, SDL_RENDERER_PRESENTVSYNC will always get - turned-on on Windows Phone. This limitation is not present in non-Phone - WinRT (such as Windows 8.x), where turning off VSync appears to work. - * probably anything else that's not listed as supported - - - -Upgrade Notes -------------- - -#### SDL_GetPrefPath() usage when upgrading WinRT apps from SDL 2.0.3 - -SDL 2.0.4 fixes two bugs found in the WinRT version of SDL_GetPrefPath(). -The fixes may affect older, SDL 2.0.3-based apps' save data. Please note -that these changes only apply to SDL-based WinRT apps, and not to apps for -any other platform. - -1. SDL_GetPrefPath() would return an invalid path, one in which the path's - directory had not been created. Attempts to create files there - (via fopen(), for example), would fail, unless that directory was - explicitly created beforehand. - -2. SDL_GetPrefPath(), for non-WinPhone-based apps, would return a path inside - a WinRT 'Roaming' folder, the contents of which get automatically - synchronized across multiple devices. This process can occur while an - application runs, and can cause existing save-data to be overwritten - at unexpected times, with data from other devices. (Windows Phone apps - written with SDL 2.0.3 did not utilize a Roaming folder, due to API - restrictions in Windows Phone 8.0). - - -SDL_GetPrefPath(), starting with SDL 2.0.4, addresses these by: - -1. making sure that SDL_GetPrefPath() returns a directory in which data - can be written to immediately, without first needing to create directories. - -2. basing SDL_GetPrefPath() off of a different, non-Roaming folder, the - contents of which do not automatically get synchronized across devices - (and which require less work to use safely, in terms of data integrity). - -Apps that wish to get their Roaming folder's path can do so either by using -SDL_WinRTGetFSPathUTF8(), SDL_WinRTGetFSPathUNICODE() (which returns a -UCS-2/wide-char string), or directly through the WinRT class, -Windows.Storage.ApplicationData. - - - -Setup, High-Level Steps ------------------------ - -The steps for setting up a project for an SDL/WinRT app looks like the -following, at a high-level: - -1. create a new Visual C++ project using Microsoft's template for a, - "Direct3D App". -2. remove most of the files from the project. -3. make your app's project directly reference SDL/WinRT's own Visual C++ - project file, via use of Visual C++'s "References" dialog. This will setup - the linker, and will copy SDL's .dll files to your app's final output. -4. adjust your app's build settings, at minimum, telling it where to find SDL's - header files. -5. add files that contains a WinRT-appropriate main function, along with some - data to make sure mouse-cursor-hiding (via SDL_ShowCursor(SDL_DISABLE) calls) - work properly. -6. add SDL-specific app code. -7. build and run your app. - - -Setup, Detailed Steps ---------------------- - -### 1. Create a new project ### - -Create a new project using one of Visual C++'s templates for a plain, non-XAML, -"Direct3D App" (XAML support for SDL/WinRT is not yet ready for use). If you -don't see one of these templates, in Visual C++'s 'New Project' dialog, try -using the textbox titled, 'Search Installed Templates' to look for one. - - -### 2. Remove unneeded files from the project ### - -In the new project, delete any file that has one of the following extensions: - -- .cpp -- .h -- .hlsl - -When you are done, you should be left with a few files, each of which will be a -necessary part of your app's project. These files will consist of: - -- an .appxmanifest file, which contains metadata on your WinRT app. This is - similar to an Info.plist file on iOS, or an AndroidManifest.xml on Android. -- a few .png files, one of which is a splash screen (displayed when your app - launches), others are app icons. -- a .pfx file, used for code signing purposes. - - -### 3. Add references to SDL's project files ### - -SDL/WinRT can be built in multiple variations, spanning across three different -CPU architectures (x86, x64, and ARM) and two different configurations -(Debug and Release). WinRT and Visual C++ do not currently provide a means -for combining multiple variations of one library into a single file. -Furthermore, it does not provide an easy means for copying pre-built .dll files -into your app's final output (via Post-Build steps, for example). It does, -however, provide a system whereby an app can reference the MSVC projects of -libraries such that, when the app is built: - -1. each library gets built for the appropriate CPU architecture(s) and WinRT - platform(s). -2. each library's output, such as .dll files, get copied to the app's build - output. - -To set this up for SDL/WinRT, you'll need to run through the following steps: - -1. open up the Solution Explorer inside Visual C++ (under the "View" menu, then - "Solution Explorer") -2. right click on your app's solution. -3. navigate to "Add", then to "Existing Project..." -4. find SDL/WinRT's Visual C++ project file and open it, in the `VisualC-WinRT` - directory. -5. once the project has been added, right-click on your app's project and - select, "References..." -6. click on the button titled, "Add New Reference..." -7. check the box next to SDL -8. click OK to close the dialog -9. SDL will now show up in the list of references. Click OK to close that - dialog. - -Your project is now linked to SDL's project, insofar that when the app is -built, SDL will be built as well, with its build output getting included with -your app. - - -### 4. Adjust Your App's Build Settings ### - -Some build settings need to be changed in your app's project. This guide will -outline the following: - -- making sure that the compiler knows where to find SDL's header files -- **Optional for C++, but NECESSARY for compiling C code:** telling the - compiler not to use Microsoft's C++ extensions for WinRT development. -- **Optional:** telling the compiler not generate errors due to missing - precompiled header files. - -To change these settings: - -1. right-click on the project -2. choose "Properties" -3. in the drop-down box next to "Configuration", choose, "All Configurations" -4. in the drop-down box next to "Platform", choose, "All Platforms" -5. in the left-hand list, expand the "C/C++" section - **Note:** If you don't see this section, you may have to add a .c or .cpp - Source file to the Project first. -6. select "General" -7. edit the "Additional Include Directories" setting, and add a path to SDL's - "include" directory -8. **Optional: to enable compilation of C code:** change the setting for - "Consume Windows Runtime Extension" from "Yes (/ZW)" to "No". If you're - working with a completely C++ based project, this step can usually be - omitted. -9. **Optional: to disable precompiled headers (which can produce - 'stdafx.h'-related build errors, if setup incorrectly:** in the left-hand - list, select "Precompiled Headers", then change the setting for "Precompiled - Header" from "Use (/Yu)" to "Not Using Precompiled Headers". -10. close the dialog, saving settings, by clicking the "OK" button - - -### 5. Add a WinRT-appropriate main function, and a blank-cursor image, to the app. ### - -A few files should be included directly in your app's MSVC project, specifically: -1. a WinRT-appropriate main function (which is different than main() functions on - other platforms) -2. a Win32-style cursor resource, used by SDL_ShowCursor() to hide the mouse cursor - (if and when the app needs to do so). *If this cursor resource is not - included, mouse-position reporting may fail if and when the cursor is - hidden, due to possible bugs/design-oddities in Windows itself.* - -To include these files for C/C++ projects: - -1. right-click on your project (again, in Visual C++'s Solution Explorer), - navigate to "Add", then choose "Existing Item...". -2. navigate to the directory containing SDL's source code, then into its - subdirectory, 'src/main/winrt/'. Select, then add, the following files: - - `SDL3-WinRTResources.rc` - - `SDL3-WinRTResource_BlankCursor.cur` -3. For the next step you need a C++ source file. - - If your standard main() function is implemented in a **C++** source file, - use that file. - - If your standard main() function is implemented in a **plain C** source file, - create an empty .cpp source file (e.g. `main.cpp`) that only contains the line - `#include ` and use that file instead. -4. Right click on the C++ source file from step 3 (as listed in your project), - then click on "Properties...". -5. in the drop-down box next to "Configuration", choose, "All Configurations" -6. in the drop-down box next to "Platform", choose, "All Platforms" -7. in the left-hand list, click on "C/C++" -8. change the setting for "Consume Windows Runtime Extension" to "Yes (/ZW)". -9. click the OK button. This will close the dialog. - -**NOTE: C++/CX compilation is currently required in at least one file of your -app's project. This is to make sure that Visual C++'s linker builds a 'Windows -Metadata' file (.winmd) for your app. Not doing so can lead to build errors.** - -For non-C++ projects, you will need to call SDL_RunApp from your language's -main function, and generate SDL3-WinRTResources.res manually by using `rc` via -the Developer Command Prompt and including it as a within the -first block in your Visual Studio project file. - -### 6. Add app code and assets ### - -At this point, you can add in SDL-specific source code. Be sure to include a -C-style main function (ie: `int main(int argc, char *argv[])`). From there you -should be able to create a single `SDL_Window` (WinRT apps can only have one -window, at present), as well as an `SDL_Renderer`. Direct3D will be used to -draw content. Events are received via SDL's usual event functions -(`SDL_PollEvent`, etc.) If you have a set of existing source files and assets, -you can start adding them to the project now. If not, or if you would like to -make sure that you're setup correctly, some short and simple sample code is -provided below. - - -#### 6.A. ... when creating a new app #### - -If you are creating a new app (rather than porting an existing SDL-based app), -or if you would just like a simple app to test SDL/WinRT with before trying to -get existing code working, some working SDL/WinRT code is provided below. To -set this up: - -1. right click on your app's project -2. select Add, then New Item. An "Add New Item" dialog will show up. -3. from the left-hand list, choose "Visual C++" -4. from the middle/main list, choose "C++ File (.cpp)" -5. near the bottom of the dialog, next to "Name:", type in a name for your -source file, such as, "main.cpp". -6. click on the Add button. This will close the dialog, add the new file to -your project, and open the file in Visual C++'s text editor. -7. Copy and paste the following code into the new file, then save it. - -```c -#include -#include - -int main(int argc, char **argv) -{ - SDL_Window *window = NULL; - SDL_Renderer *renderer = NULL; - SDL_Event evt; - SDL_bool keep_going = SDL_TRUE; - - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - return 1; - } else if (SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN, &window, &renderer) != 0) { - return 1; - } - - while (keep_going) { - while (SDL_PollEvent(&evt)) { - if ((evt.type == SDL_EVENT_KEY_DOWN) && (evt.key.keysym.sym == SDLK_ESCAPE)) { - keep_going = SDL_FALSE; - } - } - - SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); - SDL_RenderClear(renderer); - SDL_RenderPresent(renderer); - } - - SDL_Quit(); - return 0; -} -``` - -#### 6.B. Adding code and assets #### - -If you have existing code and assets that you'd like to add, you should be able -to add them now. The process for adding a set of files is as such. - -1. right click on the app's project -2. select Add, then click on "New Item..." -3. open any source, header, or asset files as appropriate. Support for C and -C++ is available. - -Do note that WinRT only supports a subset of the APIs that are available to -Win32-based apps. Many portions of the Win32 API and the C runtime are not -available. - -A list of unsupported C APIs can be found at - - -General information on using the C runtime in WinRT can be found at - - -A list of supported Win32 APIs for WinRT apps can be found at -. To note, -the list of supported Win32 APIs for Windows Phone 8.0 is different. -That list can be found at - - - -### 7. Build and run your app ### - -Your app project should now be setup, and you should be ready to build your app. -To run it on the local machine, open the Debug menu and choose "Start -Debugging". This will build your app, then run your app full-screen. To switch -out of your app, press the Windows key. Alternatively, you can choose to run -your app in a window. To do this, before building and running your app, find -the drop-down menu in Visual C++'s toolbar that says, "Local Machine". Expand -this by clicking on the arrow on the right side of the list, then click on -Simulator. Once you do that, any time you build and run the app, the app will -launch in window, rather than full-screen. - - -#### 7.A. Running apps on older, ARM-based, "Windows RT" devices #### - -**These instructions do not include Windows Phone, despite Windows Phone -typically running on ARM processors.** They are specifically for devices -that use the "Windows RT" operating system, which was a modified version of -Windows 8.x that ran primarily on ARM-based tablet computers. - -To build and run the app on ARM-based, "Windows RT" devices, you'll need to: - -- install Microsoft's "Remote Debugger" on the device. Visual C++ installs and - debugs ARM-based apps via IP networks. -- change a few options on the development machine, both to make sure it builds - for ARM (rather than x86 or x64), and to make sure it knows how to find the - Windows RT device (on the network). - -Microsoft's Remote Debugger can be found at -. Please note -that separate versions of this debugger exist for different versions of Visual -C++, one each for MSVC 2015, 2013, and 2012. - -To setup Visual C++ to launch your app on an ARM device: - -1. make sure the Remote Debugger is running on your ARM device, and that it's on - the same IP network as your development machine. -2. from Visual C++'s toolbar, find a drop-down menu that says, "Win32". Click - it, then change the value to "ARM". -3. make sure Visual C++ knows the hostname or IP address of the ARM device. To - do this: - 1. open the app project's properties - 2. select "Debugging" - 3. next to "Machine Name", enter the hostname or IP address of the ARM - device - 4. if, and only if, you've turned off authentication in the Remote Debugger, - then change the setting for "Require Authentication" to No - 5. click "OK" -4. build and run the app (from Visual C++). The first time you do this, a - prompt will show up on the ARM device, asking for a Microsoft Account. You - do, unfortunately, need to log in here, and will need to follow the - subsequent registration steps in order to launch the app. After you do so, - if the app didn't already launch, try relaunching it again from within Visual - C++. - - -Troubleshooting ---------------- - -#### Build fails with message, "error LNK2038: mismatch detected for 'vccorlib_lib_should_be_specified_before_msvcrt_lib_to_linker'" - -Try adding the following to your linker flags. In MSVC, this can be done by -right-clicking on the app project, navigating to Configuration Properties -> -Linker -> Command Line, then adding them to the Additional Options -section. - -* For Release builds / MSVC-Configurations, add: - - /nodefaultlib:vccorlib /nodefaultlib:msvcrt vccorlib.lib msvcrt.lib - -* For Debug builds / MSVC-Configurations, add: - - /nodefaultlib:vccorlibd /nodefaultlib:msvcrtd vccorlibd.lib msvcrtd.lib - - -#### Mouse-motion events fail to get sent, or SDL_GetMouseState() fails to return updated values - -This may be caused by a bug in Windows itself, whereby hiding the mouse -cursor can cause mouse-position reporting to fail. - -SDL provides a workaround for this, but it requires that an app links to a -set of Win32-style cursor image-resource files. A copy of suitable resource -files can be found in `src/main/winrt/`. Adding them to an app's Visual C++ -project file should be sufficient to get the app to use them. - - -#### SDL's Visual Studio project file fails to open, with message, "The system can't find the file specified." - -This can be caused for any one of a few reasons, which Visual Studio can -report, but won't always do so in an up-front manner. - -To help determine why this error comes up: - -1. open a copy of Visual Studio without opening a project file. This can be - accomplished via Windows' Start Menu, among other means. -2. show Visual Studio's Output window. This can be done by going to VS' - menu bar, then to View, and then to Output. -3. try opening the SDL project file directly by going to VS' menu bar, then - to File, then to Open, then to Project/Solution. When a File-Open dialog - appears, open the SDL project (such as the one in SDL's source code, in its - directory, VisualC-WinRT/UWP_VS2015/). -4. after attempting to open SDL's Visual Studio project file, additional error - information will be output to the Output window. - -If Visual Studio reports (via its Output window) that the project: - -"could not be loaded because it's missing install components. To fix this launch Visual Studio setup with the following selections: -Microsoft.VisualStudio.ComponentGroup.UWP.VC" - -... then you will need to re-launch Visual Studio's installer, and make sure that -the workflow for "Universal Windows Platform development" is checked, and that its -optional component, "C++ Universal Windows Platform tools" is also checked. While -you are there, if you are planning on targeting UWP / Windows 10, also make sure -that you check the optional component, "Windows 10 SDK (10.0.10240.0)". After -making sure these items are checked as-appropriate, install them. - -Once you install these components, try re-launching Visual Studio, and re-opening -the SDL project file. If you still get the error dialog, try using the Output -window, again, seeing what Visual Studio says about it. - - -#### Game controllers / joysticks aren't working! - -Windows only permits certain game controllers and joysticks to work within -WinRT / UWP apps. Even if a game controller or joystick works in a Win32 -app, that device is not guaranteed to work inside a WinRT / UWP app. - -According to Microsoft, "Xbox compatible controllers" should work inside -UWP apps, potentially with more working in the future. This includes, but -may not be limited to, Microsoft-made Xbox controllers and USB adapters. -(Source: https://social.msdn.microsoft.com/Forums/en-US/9064838b-e8c3-4c18-8a83-19bf0dfe150d/xinput-fails-to-detect-game-controllers?forum=wpdevelop) - - +WinRT +===== + +This port allows SDL applications to run on Microsoft's platforms that require +use of "Windows Runtime", aka. "WinRT", APIs. Microsoft may, in some cases, +refer to them as either "Windows Store", or for Windows 10, "UWP" apps. + +In the past, SDL has supported Windows RT 8.x, Windows Phone, etc, but in +modern times this port is focused on UWP apps, which run on Windows 10, +and modern Xbox consoles. + + +Requirements +------------ + +* Microsoft Visual C++ (aka Visual Studio) 2019. + - Free, "Community" or "Express" editions may be used, so long as they + include support for either "Windows Store" or "Windows Phone" apps. + "Express" versions marked as supporting "Windows Desktop" development + typically do not include support for creating WinRT apps, to note. + (The "Community" editions of Visual C++ do, however, support both + desktop/Win32 and WinRT development). +* A valid Microsoft account - This requirement is not imposed by SDL, but + rather by Microsoft's Visual C++ toolchain. This is required to launch or + debug apps. + + +Status +------ + +Here is a rough list of what works, and what doesn't: + +* What works: + * compilation via Visual C++ 2019. + * compile-time platform detection for SDL programs. The C/C++ #define, + `__WINRT__`, will be set to 1 (by SDL) when compiling for WinRT. + * GPU-accelerated 2D rendering, via SDL_Renderer. + * OpenGL ES 2, via the ANGLE library (included separately from SDL) + * software rendering, via either SDL_Surface (optionally in conjunction with + SDL_GetWindowSurface() and SDL_UpdateWindowSurface()) or via the + SDL_Renderer APIs + * threads + * timers (via SDL_GetTicks(), SDL_AddTimer(), SDL_GetPerformanceCounter(), + SDL_GetPerformanceFrequency(), etc.) + * file I/O via SDL_RWops + * mouse input (unsupported on Windows Phone) + * audio, via SDL's WASAPI backend (if you want to record, your app must + have "Microphone" capabilities enabled in its manifest, and the user must + not have blocked access. Otherwise, capture devices will fail to work, + presenting as a device disconnect shortly after opening it.) + * .DLL file loading. Libraries *MUST* be packaged inside applications. Loading + anything outside of the app is not supported. + * system path retrieval via SDL's filesystem APIs + * game controllers. Support is provided via the SDL_Joystick and + SDL_Gamepad APIs, and is backed by Microsoft's XInput API. Please + note, however, that Windows limits game-controller support in UWP apps to, + "Xbox compatible controllers" (many controllers that work in Win32 apps, + do not work in UWP, due to restrictions in UWP itself.) + * multi-touch input + * app events. SDL_APP_WILLENTER* and SDL_APP_DIDENTER* events get sent out as + appropriate. + * window events + * using Direct3D 11.x APIs outside of SDL. Non-XAML / Direct3D-only apps can + choose to render content directly via Direct3D, using SDL to manage the + internal WinRT window, as well as input and audio. (Use + the window properties to get the WinRT 'CoreWindow', and pass it into + IDXGIFactory2::CreateSwapChainForCoreWindow() as appropriate.) + +* What partially works: + * keyboard input. Most of WinRT's documented virtual keys are supported, as + well as many keys with documented hardware scancodes. Converting + SDL_Scancodes to or from SDL_Keycodes may not work, due to missing APIs + (MapVirtualKey()) in Microsoft's Windows Store / UWP APIs. + * SDL_main. WinRT uses a different signature for each app's main() function + and requires it to be implemented in C++, so SDL_main.h must be #include'd + in a C++ source file, that also must be compiled with /ZW. + +* What doesn't work: + * compilation with anything other than Visual C++ + * programmatically-created custom cursors. These don't appear to be supported + by WinRT. Different OS-provided cursors can, however, be created via + SDL_CreateSystemCursor() (unsupported on Windows Phone) + * SDL_WarpMouseInWindow() or SDL_WarpMouseGlobal(). This are not currently + supported by WinRT itself. + * joysticks and game controllers that either are not supported by + Microsoft's XInput API, or are not supported within UWP apps (many + controllers that work in Win32, do not work in UWP, due to restrictions in + UWP itself). + * turning off VSync when rendering on Windows Phone. Attempts to turn VSync + off on Windows Phone result either in Direct3D not drawing anything, or it + forcing VSync back on. As such, SDL_RENDERER_PRESENTVSYNC will always get + turned-on on Windows Phone. This limitation is not present in non-Phone + WinRT (such as Windows 8.x), where turning off VSync appears to work. + * probably anything else that's not listed as supported + + + +Upgrade Notes +------------- + +#### SDL_GetPrefPath() usage when upgrading WinRT apps from SDL 2.0.3 + +SDL 2.0.4 fixes two bugs found in the WinRT version of SDL_GetPrefPath(). +The fixes may affect older, SDL 2.0.3-based apps' save data. Please note +that these changes only apply to SDL-based WinRT apps, and not to apps for +any other platform. + +1. SDL_GetPrefPath() would return an invalid path, one in which the path's + directory had not been created. Attempts to create files there + (via fopen(), for example), would fail, unless that directory was + explicitly created beforehand. + +2. SDL_GetPrefPath(), for non-WinPhone-based apps, would return a path inside + a WinRT 'Roaming' folder, the contents of which get automatically + synchronized across multiple devices. This process can occur while an + application runs, and can cause existing save-data to be overwritten + at unexpected times, with data from other devices. (Windows Phone apps + written with SDL 2.0.3 did not utilize a Roaming folder, due to API + restrictions in Windows Phone 8.0). + + +SDL_GetPrefPath(), starting with SDL 2.0.4, addresses these by: + +1. making sure that SDL_GetPrefPath() returns a directory in which data + can be written to immediately, without first needing to create directories. + +2. basing SDL_GetPrefPath() off of a different, non-Roaming folder, the + contents of which do not automatically get synchronized across devices + (and which require less work to use safely, in terms of data integrity). + +Apps that wish to get their Roaming folder's path can do so either by using +SDL_WinRTGetFSPathUTF8(), SDL_WinRTGetFSPathUNICODE() (which returns a +UCS-2/wide-char string), or directly through the WinRT class, +Windows.Storage.ApplicationData. + + + +Setup, High-Level Steps +----------------------- + +The steps for setting up a project for an SDL/WinRT app looks like the +following, at a high-level: + +1. create a new Visual C++ project using Microsoft's template for a, + "Direct3D App". +2. remove most of the files from the project. +3. make your app's project directly reference SDL/WinRT's own Visual C++ + project file, via use of Visual C++'s "References" dialog. This will setup + the linker, and will copy SDL's .dll files to your app's final output. +4. adjust your app's build settings, at minimum, telling it where to find SDL's + header files. +5. add files that contains a WinRT-appropriate main function, along with some + data to make sure mouse-cursor-hiding (via SDL_ShowCursor(SDL_DISABLE) calls) + work properly. +6. add SDL-specific app code. +7. build and run your app. + + +Setup, Detailed Steps +--------------------- + +### 1. Create a new project ### + +Create a new project using one of Visual C++'s templates for a plain, non-XAML, +"Direct3D App" (XAML support for SDL/WinRT is not yet ready for use). If you +don't see one of these templates, in Visual C++'s 'New Project' dialog, try +using the textbox titled, 'Search Installed Templates' to look for one. + + +### 2. Remove unneeded files from the project ### + +In the new project, delete any file that has one of the following extensions: + +- .cpp +- .h +- .hlsl + +When you are done, you should be left with a few files, each of which will be a +necessary part of your app's project. These files will consist of: + +- an .appxmanifest file, which contains metadata on your WinRT app. This is + similar to an Info.plist file on iOS, or an AndroidManifest.xml on Android. +- a few .png files, one of which is a splash screen (displayed when your app + launches), others are app icons. +- a .pfx file, used for code signing purposes. + + +### 3. Add references to SDL's project files ### + +SDL/WinRT can be built in multiple variations, spanning across three different +CPU architectures (x86, x64, and ARM) and two different configurations +(Debug and Release). WinRT and Visual C++ do not currently provide a means +for combining multiple variations of one library into a single file. +Furthermore, it does not provide an easy means for copying pre-built .dll files +into your app's final output (via Post-Build steps, for example). It does, +however, provide a system whereby an app can reference the MSVC projects of +libraries such that, when the app is built: + +1. each library gets built for the appropriate CPU architecture(s) and WinRT + platform(s). +2. each library's output, such as .dll files, get copied to the app's build + output. + +To set this up for SDL/WinRT, you'll need to run through the following steps: + +1. open up the Solution Explorer inside Visual C++ (under the "View" menu, then + "Solution Explorer") +2. right click on your app's solution. +3. navigate to "Add", then to "Existing Project..." +4. find SDL/WinRT's Visual C++ project file and open it, in the `VisualC-WinRT` + directory. +5. once the project has been added, right-click on your app's project and + select, "References..." +6. click on the button titled, "Add New Reference..." +7. check the box next to SDL +8. click OK to close the dialog +9. SDL will now show up in the list of references. Click OK to close that + dialog. + +Your project is now linked to SDL's project, insofar that when the app is +built, SDL will be built as well, with its build output getting included with +your app. + + +### 4. Adjust Your App's Build Settings ### + +Some build settings need to be changed in your app's project. This guide will +outline the following: + +- making sure that the compiler knows where to find SDL's header files +- **Optional for C++, but NECESSARY for compiling C code:** telling the + compiler not to use Microsoft's C++ extensions for WinRT development. +- **Optional:** telling the compiler not generate errors due to missing + precompiled header files. + +To change these settings: + +1. right-click on the project +2. choose "Properties" +3. in the drop-down box next to "Configuration", choose, "All Configurations" +4. in the drop-down box next to "Platform", choose, "All Platforms" +5. in the left-hand list, expand the "C/C++" section + **Note:** If you don't see this section, you may have to add a .c or .cpp + Source file to the Project first. +6. select "General" +7. edit the "Additional Include Directories" setting, and add a path to SDL's + "include" directory +8. **Optional: to enable compilation of C code:** change the setting for + "Consume Windows Runtime Extension" from "Yes (/ZW)" to "No". If you're + working with a completely C++ based project, this step can usually be + omitted. +9. **Optional: to disable precompiled headers (which can produce + 'stdafx.h'-related build errors, if setup incorrectly:** in the left-hand + list, select "Precompiled Headers", then change the setting for "Precompiled + Header" from "Use (/Yu)" to "Not Using Precompiled Headers". +10. close the dialog, saving settings, by clicking the "OK" button + + +### 5. Add a WinRT-appropriate main function, and a blank-cursor image, to the app. ### + +A few files should be included directly in your app's MSVC project, specifically: +1. a WinRT-appropriate main function (which is different than main() functions on + other platforms) +2. a Win32-style cursor resource, used by SDL_ShowCursor() to hide the mouse cursor + (if and when the app needs to do so). *If this cursor resource is not + included, mouse-position reporting may fail if and when the cursor is + hidden, due to possible bugs/design-oddities in Windows itself.* + +To include these files for C/C++ projects: + +1. right-click on your project (again, in Visual C++'s Solution Explorer), + navigate to "Add", then choose "Existing Item...". +2. navigate to the directory containing SDL's source code, then into its + subdirectory, 'src/main/winrt/'. Select, then add, the following files: + - `SDL3-WinRTResources.rc` + - `SDL3-WinRTResource_BlankCursor.cur` +3. For the next step you need a C++ source file. + - If your standard main() function is implemented in a **C++** source file, + use that file. + - If your standard main() function is implemented in a **plain C** source file, + create an empty .cpp source file (e.g. `main.cpp`) that only contains the line + `#include ` and use that file instead. +4. Right click on the C++ source file from step 3 (as listed in your project), + then click on "Properties...". +5. in the drop-down box next to "Configuration", choose, "All Configurations" +6. in the drop-down box next to "Platform", choose, "All Platforms" +7. in the left-hand list, click on "C/C++" +8. change the setting for "Consume Windows Runtime Extension" to "Yes (/ZW)". +9. click the OK button. This will close the dialog. + +**NOTE: C++/CX compilation is currently required in at least one file of your +app's project. This is to make sure that Visual C++'s linker builds a 'Windows +Metadata' file (.winmd) for your app. Not doing so can lead to build errors.** + +For non-C++ projects, you will need to call SDL_RunApp from your language's +main function, and generate SDL3-WinRTResources.res manually by using `rc` via +the Developer Command Prompt and including it as a within the +first block in your Visual Studio project file. + +### 6. Add app code and assets ### + +At this point, you can add in SDL-specific source code. Be sure to include a +C-style main function (ie: `int main(int argc, char *argv[])`). From there you +should be able to create a single `SDL_Window` (WinRT apps can only have one +window, at present), as well as an `SDL_Renderer`. Direct3D will be used to +draw content. Events are received via SDL's usual event functions +(`SDL_PollEvent`, etc.) If you have a set of existing source files and assets, +you can start adding them to the project now. If not, or if you would like to +make sure that you're setup correctly, some short and simple sample code is +provided below. + + +#### 6.A. ... when creating a new app #### + +If you are creating a new app (rather than porting an existing SDL-based app), +or if you would just like a simple app to test SDL/WinRT with before trying to +get existing code working, some working SDL/WinRT code is provided below. To +set this up: + +1. right click on your app's project +2. select Add, then New Item. An "Add New Item" dialog will show up. +3. from the left-hand list, choose "Visual C++" +4. from the middle/main list, choose "C++ File (.cpp)" +5. near the bottom of the dialog, next to "Name:", type in a name for your +source file, such as, "main.cpp". +6. click on the Add button. This will close the dialog, add the new file to +your project, and open the file in Visual C++'s text editor. +7. Copy and paste the following code into the new file, then save it. + +```c +#include +#include + +int main(int argc, char **argv) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Event evt; + SDL_bool keep_going = SDL_TRUE; + + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + return 1; + } else if (SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN, &window, &renderer) != 0) { + return 1; + } + + while (keep_going) { + while (SDL_PollEvent(&evt)) { + if ((evt.type == SDL_EVENT_KEY_DOWN) && (evt.key.keysym.sym == SDLK_ESCAPE)) { + keep_going = SDL_FALSE; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } + + SDL_Quit(); + return 0; +} +``` + +#### 6.B. Adding code and assets #### + +If you have existing code and assets that you'd like to add, you should be able +to add them now. The process for adding a set of files is as such. + +1. right click on the app's project +2. select Add, then click on "New Item..." +3. open any source, header, or asset files as appropriate. Support for C and +C++ is available. + +Do note that WinRT only supports a subset of the APIs that are available to +Win32-based apps. Many portions of the Win32 API and the C runtime are not +available. + +A list of unsupported C APIs can be found at + + +General information on using the C runtime in WinRT can be found at + + +A list of supported Win32 APIs for WinRT apps can be found at +. To note, +the list of supported Win32 APIs for Windows Phone 8.0 is different. +That list can be found at + + + +### 7. Build and run your app ### + +Your app project should now be setup, and you should be ready to build your app. +To run it on the local machine, open the Debug menu and choose "Start +Debugging". This will build your app, then run your app full-screen. To switch +out of your app, press the Windows key. Alternatively, you can choose to run +your app in a window. To do this, before building and running your app, find +the drop-down menu in Visual C++'s toolbar that says, "Local Machine". Expand +this by clicking on the arrow on the right side of the list, then click on +Simulator. Once you do that, any time you build and run the app, the app will +launch in window, rather than full-screen. + + +#### 7.A. Running apps on older, ARM-based, "Windows RT" devices #### + +**These instructions do not include Windows Phone, despite Windows Phone +typically running on ARM processors.** They are specifically for devices +that use the "Windows RT" operating system, which was a modified version of +Windows 8.x that ran primarily on ARM-based tablet computers. + +To build and run the app on ARM-based, "Windows RT" devices, you'll need to: + +- install Microsoft's "Remote Debugger" on the device. Visual C++ installs and + debugs ARM-based apps via IP networks. +- change a few options on the development machine, both to make sure it builds + for ARM (rather than x86 or x64), and to make sure it knows how to find the + Windows RT device (on the network). + +Microsoft's Remote Debugger can be found at +. Please note +that separate versions of this debugger exist for different versions of Visual +C++, one each for MSVC 2015, 2013, and 2012. + +To setup Visual C++ to launch your app on an ARM device: + +1. make sure the Remote Debugger is running on your ARM device, and that it's on + the same IP network as your development machine. +2. from Visual C++'s toolbar, find a drop-down menu that says, "Win32". Click + it, then change the value to "ARM". +3. make sure Visual C++ knows the hostname or IP address of the ARM device. To + do this: + 1. open the app project's properties + 2. select "Debugging" + 3. next to "Machine Name", enter the hostname or IP address of the ARM + device + 4. if, and only if, you've turned off authentication in the Remote Debugger, + then change the setting for "Require Authentication" to No + 5. click "OK" +4. build and run the app (from Visual C++). The first time you do this, a + prompt will show up on the ARM device, asking for a Microsoft Account. You + do, unfortunately, need to log in here, and will need to follow the + subsequent registration steps in order to launch the app. After you do so, + if the app didn't already launch, try relaunching it again from within Visual + C++. + + +Troubleshooting +--------------- + +#### Build fails with message, "error LNK2038: mismatch detected for 'vccorlib_lib_should_be_specified_before_msvcrt_lib_to_linker'" + +Try adding the following to your linker flags. In MSVC, this can be done by +right-clicking on the app project, navigating to Configuration Properties -> +Linker -> Command Line, then adding them to the Additional Options +section. + +* For Release builds / MSVC-Configurations, add: + + /nodefaultlib:vccorlib /nodefaultlib:msvcrt vccorlib.lib msvcrt.lib + +* For Debug builds / MSVC-Configurations, add: + + /nodefaultlib:vccorlibd /nodefaultlib:msvcrtd vccorlibd.lib msvcrtd.lib + + +#### Mouse-motion events fail to get sent, or SDL_GetMouseState() fails to return updated values + +This may be caused by a bug in Windows itself, whereby hiding the mouse +cursor can cause mouse-position reporting to fail. + +SDL provides a workaround for this, but it requires that an app links to a +set of Win32-style cursor image-resource files. A copy of suitable resource +files can be found in `src/main/winrt/`. Adding them to an app's Visual C++ +project file should be sufficient to get the app to use them. + + +#### SDL's Visual Studio project file fails to open, with message, "The system can't find the file specified." + +This can be caused for any one of a few reasons, which Visual Studio can +report, but won't always do so in an up-front manner. + +To help determine why this error comes up: + +1. open a copy of Visual Studio without opening a project file. This can be + accomplished via Windows' Start Menu, among other means. +2. show Visual Studio's Output window. This can be done by going to VS' + menu bar, then to View, and then to Output. +3. try opening the SDL project file directly by going to VS' menu bar, then + to File, then to Open, then to Project/Solution. When a File-Open dialog + appears, open the SDL project (such as the one in SDL's source code, in its + directory, VisualC-WinRT/UWP_VS2015/). +4. after attempting to open SDL's Visual Studio project file, additional error + information will be output to the Output window. + +If Visual Studio reports (via its Output window) that the project: + +"could not be loaded because it's missing install components. To fix this launch Visual Studio setup with the following selections: +Microsoft.VisualStudio.ComponentGroup.UWP.VC" + +... then you will need to re-launch Visual Studio's installer, and make sure that +the workflow for "Universal Windows Platform development" is checked, and that its +optional component, "C++ Universal Windows Platform tools" is also checked. While +you are there, if you are planning on targeting UWP / Windows 10, also make sure +that you check the optional component, "Windows 10 SDK (10.0.10240.0)". After +making sure these items are checked as-appropriate, install them. + +Once you install these components, try re-launching Visual Studio, and re-opening +the SDL project file. If you still get the error dialog, try using the Output +window, again, seeing what Visual Studio says about it. + + +#### Game controllers / joysticks aren't working! + +Windows only permits certain game controllers and joysticks to work within +WinRT / UWP apps. Even if a game controller or joystick works in a Win32 +app, that device is not guaranteed to work inside a WinRT / UWP app. + +According to Microsoft, "Xbox compatible controllers" should work inside +UWP apps, potentially with more working in the future. This includes, but +may not be limited to, Microsoft-made Xbox controllers and USB adapters. +(Source: https://social.msdn.microsoft.com/Forums/en-US/9064838b-e8c3-4c18-8a83-19bf0dfe150d/xinput-fails-to-detect-game-controllers?forum=wpdevelop) + + diff --git a/docs/README.md b/docs/README.md index 661515c8..469a8394 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,57 +1,57 @@ -# Simple DirectMedia Layer - -https://www.libsdl.org/ - -Simple DirectMedia Layer is a cross-platform development library designed -to provide low level access to audio, keyboard, mouse, joystick, and graphics -hardware via OpenGL and Direct3D. It is used by video playback software, -emulators, and popular games including Valve's award winning catalog -and many Humble Bundle games. - -SDL officially supports Windows, macOS, Linux, iOS, and Android. -Support for other platforms may be found in the source code. - -SDL is written in C, works natively with C++, and there are bindings -available for several other languages, including C# and Python. - -This library is distributed under the zlib license, which can be found -in the file "LICENSE.txt". - -The best way to learn how to use SDL is to check out the header files in -the "include" subdirectory and the programs in the "test" subdirectory. -The header files and test programs are well commented and always up to date. - -More documentation and FAQs are available online at [the wiki](http://wiki.libsdl.org/) - -- [Android](README-android.md) -- [CMake](README-cmake.md) -- [DynAPI](README-dynapi.md) -- [Emscripten](README-emscripten.md) -- [GDK](README-gdk.md) -- [Git](README-git.md) -- [iOS](README-ios.md) -- [Linux](README-linux.md) -- [macOS](README-macos.md) -- [Supported Platforms](README-platforms.md) -- [Porting information](README-porting.md) -- [PSP](README-psp.md) -- [PS2](README-ps2.md) -- [Raspberry Pi](README-raspberrypi.md) -- [Touch](README-touch.md) -- [Versions](README-versions.md) -- [Windows](README-windows.md) -- [WinRT](README-winrt.md) -- [PSVita](README-vita.md) -- [Nokia N-Gage](README-ngage.md) - -If you need help with the library, or just want to discuss SDL related -issues, you can join the [SDL Discourse](https://discourse.libsdl.org/), -which can be used as a web forum or a mailing list, at your preference. - -If you want to report bugs or contribute patches, please submit them to -[our bug tracker](https://github.com/libsdl-org/SDL/issues) - -Enjoy! - - -Sam Lantinga +# Simple DirectMedia Layer + +https://www.libsdl.org/ + +Simple DirectMedia Layer is a cross-platform development library designed +to provide low level access to audio, keyboard, mouse, joystick, and graphics +hardware via OpenGL and Direct3D. It is used by video playback software, +emulators, and popular games including Valve's award winning catalog +and many Humble Bundle games. + +SDL officially supports Windows, macOS, Linux, iOS, and Android. +Support for other platforms may be found in the source code. + +SDL is written in C, works natively with C++, and there are bindings +available for several other languages, including C# and Python. + +This library is distributed under the zlib license, which can be found +in the file "LICENSE.txt". + +The best way to learn how to use SDL is to check out the header files in +the "include" subdirectory and the programs in the "test" subdirectory. +The header files and test programs are well commented and always up to date. + +More documentation and FAQs are available online at [the wiki](http://wiki.libsdl.org/) + +- [Android](README-android.md) +- [CMake](README-cmake.md) +- [DynAPI](README-dynapi.md) +- [Emscripten](README-emscripten.md) +- [GDK](README-gdk.md) +- [Git](README-git.md) +- [iOS](README-ios.md) +- [Linux](README-linux.md) +- [macOS](README-macos.md) +- [Supported Platforms](README-platforms.md) +- [Porting information](README-porting.md) +- [PSP](README-psp.md) +- [PS2](README-ps2.md) +- [Raspberry Pi](README-raspberrypi.md) +- [Touch](README-touch.md) +- [Versions](README-versions.md) +- [Windows](README-windows.md) +- [WinRT](README-winrt.md) +- [PSVita](README-vita.md) +- [Nokia N-Gage](README-ngage.md) + +If you need help with the library, or just want to discuss SDL related +issues, you can join the [SDL Discourse](https://discourse.libsdl.org/), +which can be used as a web forum or a mailing list, at your preference. + +If you want to report bugs or contribute patches, please submit them to +[our bug tracker](https://github.com/libsdl-org/SDL/issues) + +Enjoy! + + +Sam Lantinga diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index cb51cd7a..a0a77e53 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -58,16 +58,17 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -75,6 +76,7 @@ #include #include #include +#include "SDL3/SDL_video_capture.h" #include #endif /* SDL_h_ */ diff --git a/include/SDL3/SDL_assert.h b/include/SDL3/SDL_assert.h index e0addc2e..e4f553e4 100644 --- a/include/SDL3/SDL_assert.h +++ b/include/SDL3/SDL_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_assert.h * - * \brief Header file for assertion SDL API functions + * Header file for assertion SDL API functions */ #ifndef SDL_assert_h_ diff --git a/include/SDL3/SDL_atomic.h b/include/SDL3/SDL_atomic.h index f0c652aa..25541723 100644 --- a/include/SDL3/SDL_atomic.h +++ b/include/SDL3/SDL_atomic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_atomic.h * - * \brief Atomic operations. + * Atomic operations. * * IMPORTANT: * If you are not an expert in concurrent lockless programming, you should @@ -213,7 +213,7 @@ typedef void (*SDL_KernelMemoryBarrierFunc)(); #if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__) #define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory") #define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory") -#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_5TE__) +#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) #ifdef __thumb__ /* The mcr instruction isn't available in thumb mode, use real functions */ #define SDL_MEMORY_BARRIER_USES_FUNCTION @@ -263,8 +263,9 @@ typedef void (*SDL_KernelMemoryBarrierFunc)(); /** - * \brief A type representing an atomic integer value. It is a struct - * so people don't accidentally use numeric operations on it. + * A type representing an atomic integer value. + * + * It is a struct so people don't accidentally use numeric operations on it. */ typedef struct { int value; } SDL_AtomicInt; @@ -340,14 +341,14 @@ extern DECLSPEC int SDLCALL SDL_AtomicGet(SDL_AtomicInt *a); extern DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_AtomicInt *a, int v); /** - * \brief Increment an atomic variable used as a reference count. + * Increment an atomic variable used as a reference count. */ #ifndef SDL_AtomicIncRef #define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1) #endif /** - * \brief Decrement an atomic variable used as a reference count. + * Decrement an atomic variable used as a reference count. * * \return SDL_TRUE if the variable reached zero after decrementing, * SDL_FALSE otherwise diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 9b873791..1dbfcb11 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,18 +22,19 @@ /** * \file SDL_audio.h * - * \brief Audio functionality for the SDL library. + * Audio functionality for the SDL library. */ #ifndef SDL_audio_h_ #define SDL_audio_h_ #include -#include #include +#include #include -#include +#include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -53,7 +54,7 @@ extern "C" { */ /** - * \brief Audio format flags. + * Audio format flags. * * These are what the 16 bits in SDL_AudioFormat currently mean... * (Unspecified bits are always zero). @@ -659,7 +660,7 @@ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetAudioStreamDevice(SDL_AudioStre * * \param src_spec The format details of the input audio * \param dst_spec The format details of the output audio - * \returns 0 on success, or -1 on error. + * \returns a new audio stream on success, or NULL on failure. * * \threadsafety It is safe to call this function from any thread. * @@ -675,6 +676,19 @@ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetAudioStreamDevice(SDL_AudioStre */ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec); +/** + * Get the properties associated with an audio stream. + * + * \param stream the SDL_AudioStream to query + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetAudioStreamProperties(SDL_AudioStream *stream); /** * Query the current format of an audio stream. @@ -1267,8 +1281,7 @@ extern DECLSPEC int SDLCALL SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, * audio data allocated by the function is written to `audio_buf` and its * length in bytes to `audio_len`. The SDL_AudioSpec members `freq`, * `channels`, and `format` are set to the values of the audio data in the - * buffer. The `samples` member is set to a sane default and all others are - * set to zero. + * buffer. * * It's necessary to use SDL_free() to free the audio data returned in * `audio_buf` when it is no longer used. @@ -1414,9 +1427,9 @@ extern DECLSPEC int SDLCALL SDL_LoadWAV(const char *path, SDL_AudioSpec * spec, * \since This function is available since SDL 3.0.0. */ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, - const Uint8 * src, - SDL_AudioFormat format, - Uint32 len, int volume); + const Uint8 * src, + SDL_AudioFormat format, + Uint32 len, int volume); /** * Convert some audio data of one format to another format. diff --git a/include/SDL3/SDL_begin_code.h b/include/SDL3/SDL_begin_code.h index 6f52f9fb..12803825 100644 --- a/include/SDL3/SDL_begin_code.h +++ b/include/SDL3/SDL_begin_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/include/SDL3/SDL_bits.h b/include/SDL3/SDL_bits.h index ffee5ad7..d4a3aff3 100644 --- a/include/SDL3/SDL_bits.h +++ b/include/SDL3/SDL_bits.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_bits.h * - * \brief Functions for fiddling with bits and bitmasks. + * Functions for fiddling with bits and bitmasks. */ #ifndef SDL_bits_h_ diff --git a/include/SDL3/SDL_blendmode.h b/include/SDL3/SDL_blendmode.h index 7276c923..4b1b4ee4 100644 --- a/include/SDL3/SDL_blendmode.h +++ b/include/SDL3/SDL_blendmode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_blendmode.h * - * \brief Header file declaring the SDL_BlendMode enumeration + * Header file declaring the SDL_BlendMode enumeration */ #ifndef SDL_blendmode_h_ @@ -35,7 +35,7 @@ extern "C" { #endif /** - * \brief The blend mode used in SDL_RenderTexture() and drawing operations. + * The blend mode used in SDL_RenderTexture() and drawing operations. */ typedef enum { @@ -60,7 +60,7 @@ typedef enum } SDL_BlendMode; /** - * \brief The blend operation used when combining source and destination pixel components + * The blend operation used when combining source and destination pixel components */ typedef enum { @@ -72,7 +72,7 @@ typedef enum } SDL_BlendOperation; /** - * \brief The normalized factor used to multiply pixel components + * The normalized factor used to multiply pixel components */ typedef enum { diff --git a/include/SDL3/SDL_clipboard.h b/include/SDL3/SDL_clipboard.h index e5979646..84317613 100644 --- a/include/SDL3/SDL_clipboard.h +++ b/include/SDL3/SDL_clipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_clipboard.h * - * \brief Include file for SDL clipboard handling + * Include file for SDL clipboard handling */ #ifndef SDL_clipboard_h_ diff --git a/include/SDL3/SDL_close_code.h b/include/SDL3/SDL_close_code.h index 55cf3ff3..7e51e665 100644 --- a/include/SDL3/SDL_close_code.h +++ b/include/SDL3/SDL_close_code.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/include/SDL3/SDL_copying.h b/include/SDL3/SDL_copying.h index 4b9c1dea..6f6ade5e 100644 --- a/include/SDL3/SDL_copying.h +++ b/include/SDL3/SDL_copying.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,5 +22,5 @@ /** * \file SDL_copying.h * - * \brief Header file containing SDL's license. + * Header file containing SDL's license. */ diff --git a/include/SDL3/SDL_cpuinfo.h b/include/SDL3/SDL_cpuinfo.h index 50b889f6..c4b3d398 100644 --- a/include/SDL3/SDL_cpuinfo.h +++ b/include/SDL3/SDL_cpuinfo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_cpuinfo.h * - * \brief CPU feature detection for SDL. + * CPU feature detection for SDL. */ #ifndef SDL_cpuinfo_h_ diff --git a/include/SDL3/SDL_egl.h b/include/SDL3/SDL_egl.h index d9c5d679..1945a8fd 100644 --- a/include/SDL3/SDL_egl.h +++ b/include/SDL3/SDL_egl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_egl.h * - * \brief This is a simple file to encapsulate the EGL API headers. + * This is a simple file to encapsulate the EGL API headers. */ #if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(SDL_USE_BUILTIN_OPENGL_DEFINITIONS) diff --git a/include/SDL3/SDL_endian.h b/include/SDL3/SDL_endian.h index b77580df..480fe5ff 100644 --- a/include/SDL3/SDL_endian.h +++ b/include/SDL3/SDL_endian.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_endian.h * - * \brief Functions for reading and writing endian-specific values + * Functions for reading and writing endian-specific values */ #ifndef SDL_endian_h_ diff --git a/include/SDL3/SDL_error.h b/include/SDL3/SDL_error.h index 9440f427..b6ae7aec 100644 --- a/include/SDL3/SDL_error.h +++ b/include/SDL3/SDL_error.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -102,23 +102,6 @@ extern DECLSPEC int SDLCALL SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fm */ extern DECLSPEC const char *SDLCALL SDL_GetError(void); -/** - * Get the last error message that was set for the current thread. - * - * This allows the caller to copy the error string into a provided buffer, but - * otherwise operates exactly the same as SDL_GetError(). - * - * \param errstr A buffer to fill with the last error message that was set for - * the current thread - * \param maxlen The size of the buffer pointed to by the errstr parameter - * \returns the pointer passed in as the `errstr` parameter. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetError - */ -extern DECLSPEC char * SDLCALL SDL_GetErrorMsg(char *errstr, int maxlen); - /** * Clear any previous error message for this thread. * diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index a3c5cced..32655f7c 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -92,8 +93,8 @@ typedef enum /* Display events */ /* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */ SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ - SDL_EVENT_DISPLAY_CONNECTED, /**< Display has been added to the system */ - SDL_EVENT_DISPLAY_DISCONNECTED, /**< Display has been removed from the system */ + SDL_EVENT_DISPLAY_ADDED, /**< Display has been added to the system */ + SDL_EVENT_DISPLAY_REMOVED, /**< Display has been removed from the system */ SDL_EVENT_DISPLAY_MOVED, /**< Display has changed position */ SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /**< Display has changed content scale */ SDL_EVENT_DISPLAY_FIRST = SDL_EVENT_DISPLAY_ORIENTATION, @@ -101,8 +102,8 @@ typedef enum /* Window events */ /* 0x200 was SDL_WINDOWEVENT, reserve the number for sdl2-compat */ - SDL_EVENT_SYSWM = 0x201, /**< System specific event */ - SDL_EVENT_WINDOW_SHOWN, /**< Window has been shown */ + /* 0x201 was SDL_EVENT_SYSWM, reserve the number for sdl2-compat */ + SDL_EVENT_WINDOW_SHOWN = 0x202, /**< Window has been shown */ SDL_EVENT_WINDOW_HIDDEN, /**< Window has been hidden */ SDL_EVENT_WINDOW_EXPOSED, /**< Window has been exposed and should be redrawn */ SDL_EVENT_WINDOW_MOVED, /**< Window has been moved to data1, data2 */ @@ -122,12 +123,16 @@ typedef enum SDL_EVENT_WINDOW_DISPLAY_CHANGED, /**< Window has been moved to display data1 */ SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED, /**< Window display scale has been changed */ SDL_EVENT_WINDOW_OCCLUDED, /**< The window has been occluded */ + SDL_EVENT_WINDOW_ENTER_FULLSCREEN, /**< The window has entered fullscreen mode */ + SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, /**< The window has left fullscreen mode */ SDL_EVENT_WINDOW_DESTROYED, /**< The window with the associated ID is being or has been destroyed. If this message is being handled in an event watcher, the window handle is still valid and can still be used to retrieve any userdata associated with the window. Otherwise, the handle has already been destroyed and all resources associated with it are invalid */ + SDL_EVENT_WINDOW_PEN_ENTER, /**< Window has gained focus of the pressure-sensitive pen with ID "data1" */ + SDL_EVENT_WINDOW_PEN_LEAVE, /**< Window has lost focus of the pressure-sensitive pen with ID "data1" */ SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN, - SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_DESTROYED, + SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_PEN_LEAVE, /* Keyboard events */ SDL_EVENT_KEY_DOWN = 0x300, /**< Key pressed */ @@ -136,7 +141,6 @@ typedef enum SDL_EVENT_TEXT_INPUT, /**< Keyboard text input */ SDL_EVENT_KEYMAP_CHANGED, /**< Keymap changed due to a system event such as an input language or keyboard layout change. */ - SDL_EVENT_TEXT_EDITING_EXT, /**< Extended keyboard text editing (composition) */ /* Mouse events */ SDL_EVENT_MOUSE_MOTION = 0x400, /**< Mouse moved */ @@ -166,6 +170,7 @@ typedef enum SDL_EVENT_GAMEPAD_TOUCHPAD_UP, /**< Gamepad touchpad finger was lifted */ SDL_EVENT_GAMEPAD_SENSOR_UPDATE, /**< Gamepad sensor was updated */ SDL_EVENT_GAMEPAD_UPDATE_COMPLETE, /**< Gamepad update is complete */ + SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED, /**< Gamepad Steam handle has changed */ /* Touch events */ SDL_EVENT_FINGER_DOWN = 0x700, @@ -192,6 +197,13 @@ typedef enum /* Sensor events */ SDL_EVENT_SENSOR_UPDATE = 0x1200, /**< A sensor was updated */ + /* Pressure-sensitive pen events */ + SDL_EVENT_PEN_DOWN = 0x1300, /**< Pressure-sensitive pen touched drawing surface */ + SDL_EVENT_PEN_UP, /**< Pressure-sensitive pen stopped touching drawing surface */ + SDL_EVENT_PEN_MOTION, /**< Pressure-sensitive pen moved, or angle/pressure changed */ + SDL_EVENT_PEN_BUTTON_DOWN, /**< Pressure-sensitive pen button pressed */ + SDL_EVENT_PEN_BUTTON_UP, /**< Pressure-sensitive pen button released */ + /* Render events */ SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */ SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */ @@ -211,7 +223,7 @@ typedef enum } SDL_EventType; /** - * \brief Fields shared by every event + * Fields shared by every event */ typedef struct SDL_CommonEvent { @@ -220,7 +232,7 @@ typedef struct SDL_CommonEvent } SDL_CommonEvent; /** - * \brief Display state change event data (event.display.*) + * Display state change event data (event.display.*) */ typedef struct SDL_DisplayEvent { @@ -231,25 +243,25 @@ typedef struct SDL_DisplayEvent } SDL_DisplayEvent; /** - * \brief Window state change event data (event.window.*) + * Window state change event data (event.window.*) */ typedef struct SDL_WindowEvent { Uint32 type; /**< ::SDL_WINDOWEVENT_* */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The associated window */ + SDL_WindowID windowID; /**< The associated window */ Sint32 data1; /**< event dependent data */ Sint32 data2; /**< event dependent data */ } SDL_WindowEvent; /** - * \brief Keyboard button event structure (event.key.*) + * Keyboard button event structure (event.key.*) */ typedef struct SDL_KeyboardEvent { Uint32 type; /**< ::SDL_EVENT_KEY_DOWN or ::SDL_EVENT_KEY_UP */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The window with keyboard focus, if any */ + SDL_WindowID windowID; /**< The window with keyboard focus, if any */ Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ Uint8 repeat; /**< Non-zero if this is a key repeat */ Uint8 padding2; @@ -257,55 +269,47 @@ typedef struct SDL_KeyboardEvent SDL_Keysym keysym; /**< The key that was pressed or released */ } SDL_KeyboardEvent; -#define SDL_TEXTEDITINGEVENT_TEXT_SIZE (32) +#define SDL_TEXTEDITINGEVENT_TEXT_SIZE 64 /** - * \brief Keyboard text editing event structure (event.edit.*) + * Keyboard text editing event structure (event.edit.*) + * + * The `text` is owned by SDL and should be copied if the application + * wants to hold onto it beyond the scope of handling this event. */ typedef struct SDL_TextEditingEvent { - Uint32 type; /**< ::SDL_EVENT_TEXT_EDITING */ - Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID; /**< The window with keyboard focus, if any */ - char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; /**< The editing text */ - Sint32 start; /**< The start cursor of selected editing text */ - Sint32 length; /**< The length of selected editing text */ + Uint32 type; /**< ::SDL_EVENT_TEXT_EDITING */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + SDL_WindowID windowID; /**< The window with keyboard focus, if any */ + char *text; /**< The editing text */ + Sint32 start; /**< The start cursor of selected editing text */ + Sint32 length; /**< The length of selected editing text */ } SDL_TextEditingEvent; +#define SDL_TEXTINPUTEVENT_TEXT_SIZE 64 /** - * \brief Extended keyboard text editing event structure (event.editExt.*) when text would be - * truncated if stored in the text buffer SDL_TextEditingEvent - */ -typedef struct SDL_TextEditingExtEvent -{ - Uint32 type; /**< ::SDL_EVENT_TEXT_EDITING_EXT */ - Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID; /**< The window with keyboard focus, if any */ - char* text; /**< The editing text, which should be freed with SDL_free(), and will not be NULL */ - Sint32 start; /**< The start cursor of selected editing text */ - Sint32 length; /**< The length of selected editing text */ -} SDL_TextEditingExtEvent; - -#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32) -/** - * \brief Keyboard text input event structure (event.text.*) + * Keyboard text input event structure (event.text.*) + * + * The `text` is owned by SDL and should be copied if the application + * wants to hold onto it beyond the scope of handling this event. */ typedef struct SDL_TextInputEvent { - Uint32 type; /**< ::SDL_EVENT_TEXT_INPUT */ - Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID; /**< The window with keyboard focus, if any */ - char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; /**< The input text */ + Uint32 type; /**< ::SDL_EVENT_TEXT_INPUT */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + SDL_WindowID windowID; /**< The window with keyboard focus, if any */ + char *text; /**< The input text */ } SDL_TextInputEvent; /** - * \brief Mouse motion event structure (event.motion.*) + * Mouse motion event structure (event.motion.*) */ typedef struct SDL_MouseMotionEvent { Uint32 type; /**< ::SDL_EVENT_MOUSE_MOTION */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The window with mouse focus, if any */ - SDL_MouseID which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + SDL_WindowID windowID; /**< The window with mouse focus, if any */ + SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */ Uint32 state; /**< The current button state */ float x; /**< X coordinate, relative to window */ float y; /**< Y coordinate, relative to window */ @@ -314,14 +318,14 @@ typedef struct SDL_MouseMotionEvent } SDL_MouseMotionEvent; /** - * \brief Mouse button event structure (event.button.*) + * Mouse button event structure (event.button.*) */ typedef struct SDL_MouseButtonEvent { Uint32 type; /**< ::SDL_EVENT_MOUSE_BUTTON_DOWN or ::SDL_EVENT_MOUSE_BUTTON_UP */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The window with mouse focus, if any */ - SDL_MouseID which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + SDL_WindowID windowID; /**< The window with mouse focus, if any */ + SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */ Uint8 button; /**< The mouse button index */ Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ Uint8 clicks; /**< 1 for single-click, 2 for double-click, etc. */ @@ -331,14 +335,14 @@ typedef struct SDL_MouseButtonEvent } SDL_MouseButtonEvent; /** - * \brief Mouse wheel event structure (event.wheel.*) + * Mouse wheel event structure (event.wheel.*) */ typedef struct SDL_MouseWheelEvent { Uint32 type; /**< ::SDL_EVENT_MOUSE_WHEEL */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The window with mouse focus, if any */ - SDL_MouseID which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + SDL_WindowID windowID; /**< The window with mouse focus, if any */ + SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */ float x; /**< The amount scrolled horizontally, positive to the right and negative to the left */ float y; /**< The amount scrolled vertically, positive away from the user and negative toward the user */ Uint32 direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */ @@ -347,7 +351,7 @@ typedef struct SDL_MouseWheelEvent } SDL_MouseWheelEvent; /** - * \brief Joystick axis motion event structure (event.jaxis.*) + * Joystick axis motion event structure (event.jaxis.*) */ typedef struct SDL_JoyAxisEvent { @@ -363,7 +367,7 @@ typedef struct SDL_JoyAxisEvent } SDL_JoyAxisEvent; /** - * \brief Joystick hat position change event structure (event.jhat.*) + * Joystick hat position change event structure (event.jhat.*) */ typedef struct SDL_JoyHatEvent { @@ -383,7 +387,7 @@ typedef struct SDL_JoyHatEvent } SDL_JoyHatEvent; /** - * \brief Joystick button event structure (event.jbutton.*) + * Joystick button event structure (event.jbutton.*) */ typedef struct SDL_JoyButtonEvent { @@ -397,7 +401,7 @@ typedef struct SDL_JoyButtonEvent } SDL_JoyButtonEvent; /** - * \brief Joystick device event structure (event.jdevice.*) + * Joystick device event structure (event.jdevice.*) */ typedef struct SDL_JoyDeviceEvent { @@ -407,7 +411,7 @@ typedef struct SDL_JoyDeviceEvent } SDL_JoyDeviceEvent; /** - * \brief Joysick battery level change event structure (event.jbattery.*) + * Joysick battery level change event structure (event.jbattery.*) */ typedef struct SDL_JoyBatteryEvent { @@ -418,7 +422,7 @@ typedef struct SDL_JoyBatteryEvent } SDL_JoyBatteryEvent; /** - * \brief Gamepad axis motion event structure (event.gaxis.*) + * Gamepad axis motion event structure (event.gaxis.*) */ typedef struct SDL_GamepadAxisEvent { @@ -435,7 +439,7 @@ typedef struct SDL_GamepadAxisEvent /** - * \brief Gamepad button event structure (event.gbutton.*) + * Gamepad button event structure (event.gbutton.*) */ typedef struct SDL_GamepadButtonEvent { @@ -450,17 +454,17 @@ typedef struct SDL_GamepadButtonEvent /** - * \brief Gamepad device event structure (event.gdevice.*) + * Gamepad device event structure (event.gdevice.*) */ typedef struct SDL_GamepadDeviceEvent { - Uint32 type; /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED or ::SDL_EVENT_GAMEPAD_UPDATE_COMPLETE */ + Uint32 type; /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED, ::SDL_EVENT_GAMEPAD_UPDATE_COMPLETE or ::SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ SDL_JoystickID which; /**< The joystick instance id */ } SDL_GamepadDeviceEvent; /** - * \brief Gamepad touchpad event structure (event.gtouchpad.*) + * Gamepad touchpad event structure (event.gtouchpad.*) */ typedef struct SDL_GamepadTouchpadEvent { @@ -475,7 +479,7 @@ typedef struct SDL_GamepadTouchpadEvent } SDL_GamepadTouchpadEvent; /** - * \brief Gamepad sensor event structure (event.gsensor.*) + * Gamepad sensor event structure (event.gsensor.*) */ typedef struct SDL_GamepadSensorEvent { @@ -488,7 +492,7 @@ typedef struct SDL_GamepadSensorEvent } SDL_GamepadSensorEvent; /** - * \brief Audio device event structure (event.adevice.*) + * Audio device event structure (event.adevice.*) */ typedef struct SDL_AudioDeviceEvent { @@ -503,7 +507,7 @@ typedef struct SDL_AudioDeviceEvent /** - * \brief Touch finger event structure (event.tfinger.*) + * Touch finger event structure (event.tfinger.*) */ typedef struct SDL_TouchFingerEvent { @@ -516,27 +520,87 @@ typedef struct SDL_TouchFingerEvent float dx; /**< Normalized in the range -1...1 */ float dy; /**< Normalized in the range -1...1 */ float pressure; /**< Normalized in the range 0...1 */ - SDL_WindowID windowID;/**< The window underneath the finger, if any */ + SDL_WindowID windowID; /**< The window underneath the finger, if any */ } SDL_TouchFingerEvent; +#define SDL_DROPEVENT_DATA_SIZE 64 /** - * \brief An event used to request a file open by the system (event.drop.*) - * This event is enabled by default, you can disable it with SDL_SetEventEnabled(). - * \note If this event is enabled, you must free the filename in the event. + * Pressure-sensitive pen touched or stopped touching surface (event.ptip.*) + */ +typedef struct SDL_PenTipEvent +{ + Uint32 type; /**< ::SDL_EVENT_PEN_DOWN or ::SDL_EVENT_PEN_UP */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + Uint32 windowID; /**< The window with pen focus, if any */ + SDL_PenID which; /**< The pen instance id */ + Uint8 tip; /**< ::SDL_PEN_TIP_INK when using a regular pen tip, or ::SDL_PEN_TIP_ERASER if the pen is being used as an eraser (e.g., flipped to use the eraser tip) */ + Uint8 state; /**< ::SDL_PRESSED on ::SDL_EVENT_PEN_DOWN and ::SDL_RELEASED on ::SDL_EVENT_PEN_UP */ + Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), + ::SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and + ::SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */ + float x; /**< X coordinate, relative to window */ + float y; /**< Y coordinate, relative to window */ + float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per ::SDL_PenAxis) */ +} SDL_PenTipEvent; + +/** + * Pressure-sensitive pen motion / pressure / angle event structure (event.pmotion.*) + */ +typedef struct SDL_PenMotionEvent +{ + Uint32 type; /**< ::SDL_EVENT_PEN_MOTION */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + Uint32 windowID; /**< The window with pen focus, if any */ + SDL_PenID which; /**< The pen instance id */ + Uint8 padding1; + Uint8 padding2; + Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), + ::SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and + ::SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */ + float x; /**< X coordinate, relative to window */ + float y; /**< Y coordinate, relative to window */ + float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per ::SDL_PenAxis) */ +} SDL_PenMotionEvent; + +/** + * Pressure-sensitive pen button event structure (event.pbutton.*) + */ +typedef struct SDL_PenButtonEvent +{ + Uint32 type; /**< ::SDL_EVENT_PEN_BUTTON_DOWN or ::SDL_EVENT_PEN_BUTTON_UP */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + Uint32 windowID; /**< The window with pen focus, if any */ + SDL_PenID which; /**< The pen instance id */ + Uint8 button; /**< The pen button index (1 represents the pen tip for compatibility with mouse events) */ + Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ + Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), + ::SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and + ::SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */ + float x; /**< X coordinate, relative to window */ + float y; /**< Y coordinate, relative to window */ + float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per ::SDL_PenAxis) */ +} SDL_PenButtonEvent; + +/** + * An event used to drop text or request a file open by the system (event.drop.*) + * + * The `data` is owned by SDL and should be copied if the application + * wants to hold onto it beyond the scope of handling this event. */ typedef struct SDL_DropEvent { Uint32 type; /**< ::SDL_EVENT_DROP_BEGIN or ::SDL_EVENT_DROP_FILE or ::SDL_EVENT_DROP_TEXT or ::SDL_EVENT_DROP_COMPLETE or ::SDL_EVENT_DROP_POSITION */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - char *file; /**< The file name, which should be freed with SDL_free(), is NULL on begin/complete */ SDL_WindowID windowID; /**< The window that was dropped on, if any */ float x; /**< X coordinate, relative to window (not on begin) */ float y; /**< Y coordinate, relative to window (not on begin) */ + char *source; /**< The source app that sent this drop event, or NULL if that isn't available */ + char *data; /**< The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events */ } SDL_DropEvent; /** - * \brief An event triggered when the clipboard contents have changed (event.clipboard.*) + * An event triggered when the clipboard contents have changed (event.clipboard.*) */ typedef struct SDL_ClipboardEvent { @@ -545,19 +609,19 @@ typedef struct SDL_ClipboardEvent } SDL_ClipboardEvent; /** - * \brief Sensor event structure (event.sensor.*) + * Sensor event structure (event.sensor.*) */ typedef struct SDL_SensorEvent { Uint32 type; /**< ::SDL_EVENT_SENSOR_UPDATE */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_SensorID which; /**< The instance ID of the sensor */ + SDL_SensorID which; /**< The instance ID of the sensor */ float data[6]; /**< Up to 6 values from the sensor - additional values can be queried using SDL_GetSensorData() */ Uint64 sensor_timestamp; /**< The timestamp of the sensor reading in nanoseconds, not necessarily synchronized with the system clock */ } SDL_SensorEvent; /** - * \brief The "quit requested" event + * The "quit requested" event */ typedef struct SDL_QuitEvent { @@ -566,46 +630,21 @@ typedef struct SDL_QuitEvent } SDL_QuitEvent; /** - * \brief OS Specific event - */ -typedef struct SDL_OSEvent -{ - Uint32 type; /**< ::SDL_EVENT_QUIT */ - Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ -} SDL_OSEvent; - -/** - * \brief A user-defined event type (event.user.*) + * A user-defined event type (event.user.*) */ typedef struct SDL_UserEvent { Uint32 type; /**< ::SDL_EVENT_USER through ::SDL_EVENT_LAST-1 */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_WindowID windowID;/**< The associated window if any */ + SDL_WindowID windowID; /**< The associated window if any */ Sint32 code; /**< User defined event code */ void *data1; /**< User defined data pointer */ void *data2; /**< User defined data pointer */ } SDL_UserEvent; -struct SDL_SysWMmsg; -typedef struct SDL_SysWMmsg SDL_SysWMmsg; - /** - * \brief A video driver dependent system event (event.syswm.*) - * This event is disabled by default, you can enable it with SDL_SetEventEnabled() - * - * \note If you want to use this event, you should include SDL_syswm.h. - */ -typedef struct SDL_SysWMEvent -{ - Uint32 type; /**< ::SDL_EVENT_SYSWM */ - Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_SysWMmsg *msg; /**< driver dependent data, defined in SDL_syswm.h */ -} SDL_SysWMEvent; - -/** - * \brief General event structure + * General event structure */ typedef union SDL_Event { @@ -615,7 +654,6 @@ typedef union SDL_Event SDL_WindowEvent window; /**< Window event data */ SDL_KeyboardEvent key; /**< Keyboard event data */ SDL_TextEditingEvent edit; /**< Text editing event data */ - SDL_TextEditingExtEvent editExt; /**< Extended text editing event data */ SDL_TextInputEvent text; /**< Text input event data */ SDL_MouseMotionEvent motion; /**< Mouse motion event data */ SDL_MouseButtonEvent button; /**< Mouse button event data */ @@ -634,10 +672,12 @@ typedef union SDL_Event SDL_SensorEvent sensor; /**< Sensor event data */ SDL_QuitEvent quit; /**< Quit request event data */ SDL_UserEvent user; /**< Custom event data */ - SDL_SysWMEvent syswm; /**< System dependent window event data */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_PenTipEvent ptip; /**< Pen tip touching or leaving drawing surface */ + SDL_PenMotionEvent pmotion; /**< Pen change in position, pressure, or angle */ + SDL_PenButtonEvent pbutton; /**< Pen button press */ SDL_DropEvent drop; /**< Drag and drop event data */ - SDL_ClipboardEvent clipboard; /**< Clipboard cancelled event data */ + SDL_ClipboardEvent clipboard; /**< Clipboard event data */ /* This is necessary for ABI compatibility between Visual C++ and GCC. Visual C++ will respect the push pack pragma and use 52 bytes (size of @@ -646,7 +686,7 @@ typedef union SDL_Event largest datatype within the union, which is 8 bytes on 64-bit architectures. - So... we'll add padding to force the size to be 56 bytes for both. + So... we'll add padding to force the size to be the same for both. On architectures where pointers are 16 bytes, this needs rounding up to the next multiple of 16, 64, and on architectures where pointers are @@ -731,9 +771,7 @@ typedef enum * \sa SDL_PumpEvents * \sa SDL_PushEvent */ -extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event * events, int numevents, - SDL_eventaction action, - Uint32 minType, Uint32 maxType); +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event *events, int numevents, SDL_eventaction action, Uint32 minType, Uint32 maxType); /* @} */ /** @@ -852,18 +890,16 @@ extern DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType); * * \param event the SDL_Event structure to be filled with the next event from * the queue, or NULL - * \returns 1 if there is a pending event or 0 if there are none available. + * \returns SDL_TRUE if this got an event or SDL_FALSE if there are none + * available. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_GetEventFilter - * \sa SDL_PeepEvents * \sa SDL_PushEvent - * \sa SDL_SetEventFilter * \sa SDL_WaitEvent * \sa SDL_WaitEventTimeout */ -extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event); +extern DECLSPEC SDL_bool SDLCALL SDL_PollEvent(SDL_Event *event); /** * Wait indefinitely for the next available event. @@ -876,16 +912,16 @@ extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event); * * \param event the SDL_Event structure to be filled in with the next event * from the queue, or NULL - * \returns 1 on success or 0 if there was an error while waiting for events; - * call SDL_GetError() for more information. + * \returns SDL_TRUE on success or SDL_FALSE if there was an error while + * waiting for events; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * * \sa SDL_PollEvent - * \sa SDL_PumpEvents + * \sa SDL_PushEvent * \sa SDL_WaitEventTimeout */ -extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event *event); +extern DECLSPEC SDL_bool SDLCALL SDL_WaitEvent(SDL_Event *event); /** * Wait until the specified timeout (in milliseconds) for the next available @@ -904,17 +940,16 @@ extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event *event); * from the queue, or NULL * \param timeoutMS the maximum number of milliseconds to wait for the next * available event - * \returns 1 on success or 0 if there was an error while waiting for events; - * call SDL_GetError() for more information. This also returns 0 if - * the timeout elapsed without an event arriving. + * \returns SDL_TRUE if this got an event or SDL_FALSE if the timeout elapsed + * without any events available. * * \since This function is available since SDL 3.0.0. * * \sa SDL_PollEvent - * \sa SDL_PumpEvents + * \sa SDL_PushEvent * \sa SDL_WaitEvent */ -extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS); +extern DECLSPEC SDL_bool SDLCALL SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS); /** * Add an event to the event queue. @@ -948,7 +983,7 @@ extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeou * \sa SDL_PollEvent * \sa SDL_RegisterEvents */ -extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event); +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event *event); /** * A function pointer used for callbacks that watch the event queue. @@ -962,7 +997,7 @@ extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event); * \sa SDL_SetEventFilter * \sa SDL_AddEventWatch */ -typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event); +typedef int (SDLCALL *SDL_EventFilter)(void *userdata, SDL_Event *event); /** * Set up a filter to process all events before they change internal state and @@ -1006,8 +1041,7 @@ typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event); * \sa SDL_PeepEvents * \sa SDL_PushEvent */ -extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, - void *userdata); +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, void *userdata); /** * Query the current event filter. @@ -1024,8 +1058,7 @@ extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, * * \sa SDL_SetEventFilter */ -extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter, - void **userdata); +extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata); /** * Add a callback to be triggered when an event is added to the event queue. @@ -1047,14 +1080,15 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter, * * \param filter an SDL_EventFilter function to call when an event happens. * \param userdata a pointer that is passed to `filter` + * \returns 0 on success, or a negative error code on failure; call + * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * * \sa SDL_DelEventWatch * \sa SDL_SetEventFilter */ -extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, - void *userdata); +extern DECLSPEC int SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, void *userdata); /** * Remove an event watch callback added with SDL_AddEventWatch(). @@ -1069,8 +1103,7 @@ extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, * * \sa SDL_AddEventWatch */ -extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, - void *userdata); +extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, void *userdata); /** * Run a specific filter function on the current event queue, removing any @@ -1088,8 +1121,7 @@ extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, * \sa SDL_GetEventFilter * \sa SDL_SetEventFilter */ -extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter, - void *userdata); +extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter, void *userdata); /** * Set the state of processing events by type. @@ -1135,6 +1167,20 @@ extern DECLSPEC SDL_bool SDLCALL SDL_EventEnabled(Uint32 type); */ extern DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); +/** + * Allocate dynamic memory for an SDL event + * + * You can use this to allocate memory for user events that will be + * automatically freed after the event is processed. + * + * \param size the amount of memory to allocate + * \returns a pointer to the memory allocated or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC void * SDLCALL SDL_AllocateEventMemory(size_t size); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h index ae8139c1..8ee4d2c2 100644 --- a/include/SDL3/SDL_filesystem.h +++ b/include/SDL3/SDL_filesystem.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_filesystem.h * - * \brief Include file for filesystem SDL API functions + * Include file for filesystem SDL API functions */ #ifndef SDL_filesystem_h_ @@ -64,7 +64,7 @@ extern "C" { * directory of the application as it is uncommon to store resources outside * the executable. As such it is not a writable directory. * - * The returned path is guaranteed to end with a path separator ('\' on + * The returned path is guaranteed to end with a path separator ('\\' on * Windows, '/' on most other platforms). * * The pointer returned is owned by the caller. Please call SDL_free() on the @@ -120,7 +120,7 @@ extern DECLSPEC char *SDLCALL SDL_GetBasePath(void); * - ...only use letters, numbers, and spaces. Avoid punctuation like "Game * Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. * - * The returned path is guaranteed to end with a path separator ('\' on + * The returned path is guaranteed to end with a path separator ('\\' on * Windows, '/' on most other platforms). * * The pointer returned is owned by the caller. Please call SDL_free() on the diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h index b9c31e8d..98af9a17 100644 --- a/include/SDL3/SDL_gamepad.h +++ b/include/SDL3/SDL_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_gamepad.h * - * \brief Include file for SDL gamepad event handling + * Include file for SDL gamepad event handling */ #ifndef SDL_gamepad_h_ @@ -30,9 +30,10 @@ #include #include +#include +#include #include #include -#include #include /* Set up for C function definitions, even when using C++ */ @@ -76,14 +77,31 @@ typedef enum /** * The list of buttons available on a gamepad + * + * For controllers that use a diamond pattern for the face buttons, + * the south/east/west/north buttons below correspond to the locations + * in the diamond pattern. For Xbox controllers, this would be A/B/X/Y, + * for Nintendo Switch controllers, this would be B/A/Y/X, for + * PlayStation controllers this would be Cross/Circle/Square/Triangle. + * + * For controllers that don't use a diamond pattern for the face buttons, + * the south/east/west/north buttons indicate the buttons labeled A, B, + * C, D, or 1, 2, 3, 4, or for controllers that aren't labeled, they are + * the primary, secondary, etc. buttons. + * + * The activate action is often the south button and the cancel action + * is often the east button, but in some regions this is reversed, so + * your game should allow remapping actions based on user preferences. + * + * You can query the labels for the face buttons using SDL_GetGamepadButtonLabel() */ typedef enum { SDL_GAMEPAD_BUTTON_INVALID = -1, - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_B, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_Y, + SDL_GAMEPAD_BUTTON_SOUTH, /* Bottom face button (e.g. Xbox A button) */ + SDL_GAMEPAD_BUTTON_EAST, /* Right face button (e.g. Xbox B button) */ + SDL_GAMEPAD_BUTTON_WEST, /* Left face button (e.g. Xbox X button) */ + SDL_GAMEPAD_BUTTON_NORTH, /* Top face button (e.g. Xbox Y button) */ SDL_GAMEPAD_BUTTON_BACK, SDL_GAMEPAD_BUTTON_GUIDE, SDL_GAMEPAD_BUTTON_START, @@ -95,15 +113,35 @@ typedef enum SDL_GAMEPAD_BUTTON_DPAD_DOWN, SDL_GAMEPAD_BUTTON_DPAD_LEFT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, - SDL_GAMEPAD_BUTTON_MISC1, /* Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /* Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /* Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */ - SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /* Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */ - SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /* Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */ - SDL_GAMEPAD_BUTTON_TOUCHPAD, /* PS4/PS5 touchpad button */ + SDL_GAMEPAD_BUTTON_MISC1, /* Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, /* Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, /* Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */ + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, /* Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */ + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, /* Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */ + SDL_GAMEPAD_BUTTON_TOUCHPAD, /* PS4/PS5 touchpad button */ SDL_GAMEPAD_BUTTON_MAX } SDL_GamepadButton; +/** + * The set of gamepad button labels + * + * This isn't a complete set, just the face buttons to make it easy to show button prompts. + * + * For a complete set, you should look at the button and gamepad type and have a set of symbols that work well with your art style. + */ +typedef enum +{ + SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN, + SDL_GAMEPAD_BUTTON_LABEL_A, + SDL_GAMEPAD_BUTTON_LABEL_B, + SDL_GAMEPAD_BUTTON_LABEL_X, + SDL_GAMEPAD_BUTTON_LABEL_Y, + SDL_GAMEPAD_BUTTON_LABEL_CROSS, + SDL_GAMEPAD_BUTTON_LABEL_CIRCLE, + SDL_GAMEPAD_BUTTON_LABEL_SQUARE, + SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE +} SDL_GamepadButtonLabel; + /** * The list of axes available on a gamepad * @@ -111,7 +149,9 @@ typedef enum * and are centered within ~8000 of zero, though advanced UI will allow users to set * or autodetect the dead zone, which varies between gamepads. * - * Trigger axis values range from 0 to SDL_JOYSTICK_AXIS_MAX. + * Trigger axis values range from 0 (released) to SDL_JOYSTICK_AXIS_MAX + * (fully pressed) when reported by SDL_GetGamepadAxis(). Note that this is not the + * same range that will be reported by the lower-level SDL_GetJoystickAxis(). */ typedef enum { @@ -273,25 +313,20 @@ extern DECLSPEC int SDLCALL SDL_AddGamepadMappingsFromFile(const char *file); */ extern DECLSPEC int SDLCALL SDL_ReloadGamepadMappings(void); -/** - * Get the number of mappings installed. - * - * \returns the number of mappings. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC int SDLCALL SDL_GetNumGamepadMappings(void); - /** * Get the mapping at a particular index. * - * \param mapping_index mapping index - * \returns the mapping string. Must be freed with SDL_free(). Returns NULL if - * the index is out of range. + * You must free the returned pointer with SDL_free() when you are done with + * it, but you do _not_ free each string in the array. + * + * \param count a pointer filled in with the number of mappings returned, can + * be NULL. + * \returns an array of the mapping strings, NULL-terminated. Must be freed + * with SDL_free(). Returns NULL on error. * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC char * SDLCALL SDL_GetGamepadMappingForIndex(int mapping_index); +extern DECLSPEC char ** SDLCALL SDL_GetGamepadMappings(int *count); /** * Get the gamepad mapping string for a given GUID. @@ -554,6 +589,23 @@ extern DECLSPEC SDL_Gamepad *SDLCALL SDL_GetGamepadFromInstanceID(SDL_JoystickID */ extern DECLSPEC SDL_Gamepad *SDLCALL SDL_GetGamepadFromPlayerIndex(int player_index); +/** + * Get the properties associated with an opened gamepad. + * + * These properties are shared with the underlying joystick object. + * + * \param gamepad a gamepad identifier previously returned by + * SDL_OpenGamepad() + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGamepadProperties(SDL_Gamepad *gamepad); + /** * Get the instance ID of an opened gamepad. * @@ -708,6 +760,19 @@ extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepa */ extern DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamepad); +/** + * Get the Steam Input handle of an opened gamepad, if available. + * + * Returns an InputHandle_t for the gamepad that can be used with Steam Input + * API: https://partner.steamgames.com/doc/api/ISteamInput + * + * \param gamepad the gamepad object to query. + * \returns the gamepad handle, or 0 if unavailable. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad); + /** * Get the battery level of a gamepad, if available. * @@ -898,8 +963,12 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_Ga * * The axis indices start at index 0. * - * The state is a value ranging from -32768 to 32767. Triggers, however, range - * from 0 to 32767 (they never return a negative value). + * For thumbsticks, the state is a value ranging from -32768 (up/left) to + * 32767 (down/right). + * + * Triggers range from 0 when released to 32767 when fully pressed, and never + * return a negative value. Note that this differs from the value reported by + * the lower-level SDL_GetJoystickAxis(), which normally uses the full range. * * \param gamepad a gamepad * \param axis an axis index (one of the SDL_GamepadAxis values) @@ -922,7 +991,7 @@ extern DECLSPEC Sint16 SDLCALL SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_Game * * \param str string representing a SDL_Gamepad axis * \returns the SDL_GamepadButton enum corresponding to the input string, or - * `SDL_GAMEPAD_AXIS_INVALID` if no match was found. + * `SDL_GAMEPAD_BUTTON_INVALID` if no match was found. * * \since This function is available since SDL 3.0.0. */ @@ -972,6 +1041,32 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_ */ extern DECLSPEC Uint8 SDLCALL SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button); +/** + * Get the label of a button on a gamepad. + * + * \param type the type of gamepad to check + * \param button a button index (one of the SDL_GamepadButton values) + * \returns the SDL_GamepadButtonLabel enum corresponding to the button label + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetGamepadButtonLabel + */ +extern DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button); + +/** + * Get the label of a button on a gamepad. + * + * \param gamepad a gamepad + * \param button a button index (one of the SDL_GamepadButton values) + * \returns the SDL_GamepadButtonLabel enum corresponding to the button label + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetGamepadButtonLabelForType + */ +extern DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button); + /** * Get the number of touchpads on a gamepad. * diff --git a/include/SDL3/SDL_guid.h b/include/SDL3/SDL_guid.h index fbd70143..accb4d9a 100644 --- a/include/SDL3/SDL_guid.h +++ b/include/SDL3/SDL_guid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_guid.h * - * \brief Include file for handling ::SDL_GUID values. + * Include file for handling ::SDL_GUID values. */ #ifndef SDL_guid_h_ diff --git a/include/SDL3/SDL_haptic.h b/include/SDL3/SDL_haptic.h index 7dbd3916..75709944 100644 --- a/include/SDL3/SDL_haptic.h +++ b/include/SDL3/SDL_haptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,8 +22,7 @@ /** * \file SDL_haptic.h * - * \brief The SDL haptic subsystem allows you to control haptic (force feedback) - * devices. + * The SDL haptic subsystem manages haptic (force feedback) devices. * * The basic usage is as follows: * - Initialize the subsystem (::SDL_INIT_HAPTIC). @@ -131,7 +130,7 @@ extern "C" { /** * \typedef SDL_Haptic * - * \brief The haptic structure used to identify an SDL haptic. + * The haptic structure used to identify an SDL haptic. * * \sa SDL_HapticOpen * \sa SDL_HapticOpenFromJoystick @@ -154,7 +153,7 @@ typedef struct SDL_Haptic SDL_Haptic; /* @{ */ /** - * \brief Constant effect supported. + * Constant effect supported. * * Constant haptic effect. * @@ -163,7 +162,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_CONSTANT (1u<<0) /** - * \brief Sine wave effect supported. + * Sine wave effect supported. * * Periodic haptic effect that simulates sine waves. * @@ -172,7 +171,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_SINE (1u<<1) /** - * \brief Left/Right effect supported. + * Left/Right effect supported. * * Haptic effect for direct control over high/low frequency motors. * @@ -186,7 +185,7 @@ typedef struct SDL_Haptic SDL_Haptic; /* #define SDL_HAPTIC_SQUARE (1<<2) */ /** - * \brief Triangle wave effect supported. + * Triangle wave effect supported. * * Periodic haptic effect that simulates triangular waves. * @@ -195,7 +194,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_TRIANGLE (1u<<3) /** - * \brief Sawtoothup wave effect supported. + * Sawtoothup wave effect supported. * * Periodic haptic effect that simulates saw tooth up waves. * @@ -204,7 +203,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_SAWTOOTHUP (1u<<4) /** - * \brief Sawtoothdown wave effect supported. + * Sawtoothdown wave effect supported. * * Periodic haptic effect that simulates saw tooth down waves. * @@ -213,7 +212,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_SAWTOOTHDOWN (1u<<5) /** - * \brief Ramp effect supported. + * Ramp effect supported. * * Ramp haptic effect. * @@ -222,7 +221,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_RAMP (1u<<6) /** - * \brief Spring effect supported - uses axes position. + * Spring effect supported - uses axes position. * * Condition haptic effect that simulates a spring. Effect is based on the * axes position. @@ -232,7 +231,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_SPRING (1u<<7) /** - * \brief Damper effect supported - uses axes velocity. + * Damper effect supported - uses axes velocity. * * Condition haptic effect that simulates dampening. Effect is based on the * axes velocity. @@ -242,7 +241,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_DAMPER (1u<<8) /** - * \brief Inertia effect supported - uses axes acceleration. + * Inertia effect supported - uses axes acceleration. * * Condition haptic effect that simulates inertia. Effect is based on the axes * acceleration. @@ -252,7 +251,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_INERTIA (1u<<9) /** - * \brief Friction effect supported - uses axes movement. + * Friction effect supported - uses axes movement. * * Condition haptic effect that simulates friction. Effect is based on the * axes movement. @@ -262,7 +261,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_FRICTION (1u<<10) /** - * \brief Custom effect is supported. + * Custom effect is supported. * * User defined custom haptic effect. */ @@ -273,7 +272,7 @@ typedef struct SDL_Haptic SDL_Haptic; /* These last few are features the device has, not effects */ /** - * \brief Device can set global gain. + * Device can set global gain. * * Device supports setting the global gain. * @@ -282,7 +281,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_GAIN (1u<<12) /** - * \brief Device can set autocenter. + * Device can set autocenter. * * Device supports setting autocenter. * @@ -291,7 +290,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_AUTOCENTER (1u<<13) /** - * \brief Device can be queried for effect status. + * Device can be queried for effect status. * * Device supports querying effect status. * @@ -300,7 +299,7 @@ typedef struct SDL_Haptic SDL_Haptic; #define SDL_HAPTIC_STATUS (1u<<14) /** - * \brief Device can be paused. + * Device can be paused. * * Devices supports being paused. * @@ -316,31 +315,33 @@ typedef struct SDL_Haptic SDL_Haptic; /* @{ */ /** - * \brief Uses polar coordinates for the direction. + * Uses polar coordinates for the direction. * * \sa SDL_HapticDirection */ #define SDL_HAPTIC_POLAR 0 /** - * \brief Uses cartesian coordinates for the direction. + * Uses cartesian coordinates for the direction. * * \sa SDL_HapticDirection */ #define SDL_HAPTIC_CARTESIAN 1 /** - * \brief Uses spherical coordinates for the direction. + * Uses spherical coordinates for the direction. * * \sa SDL_HapticDirection */ #define SDL_HAPTIC_SPHERICAL 2 /** - * \brief Use this value to play an effect on the steering wheel axis. This - * provides better compatibility across platforms and devices as SDL will guess - * the correct axis. - * \sa SDL_HapticDirection + * Use this value to play an effect on the steering wheel axis. + * + * This provides better compatibility across platforms and devices as SDL + * will guess the correct axis. + * + * \sa SDL_HapticDirection */ #define SDL_HAPTIC_STEERING_AXIS 3 @@ -353,7 +354,7 @@ typedef struct SDL_Haptic SDL_Haptic; */ /** - * \brief Used to play a device an infinite number of times. + * Used to play a device an infinite number of times. * * \sa SDL_HapticRunEffect */ @@ -361,7 +362,7 @@ typedef struct SDL_Haptic SDL_Haptic; /** - * \brief Structure that represents a haptic direction. + * Structure that represents a haptic direction. * * This is the direction where the force comes from, * instead of the direction in which the force is exerted. @@ -464,7 +465,7 @@ typedef struct SDL_HapticDirection /** - * \brief A structure containing a template for a Constant effect. + * A structure containing a template for a Constant effect. * * This struct is exclusively for the ::SDL_HAPTIC_CONSTANT effect. * @@ -499,7 +500,7 @@ typedef struct SDL_HapticConstant } SDL_HapticConstant; /** - * \brief A structure containing a template for a Periodic effect. + * A structure containing a template for a Periodic effect. * * The struct handles the following effects: * - ::SDL_HAPTIC_SINE @@ -585,7 +586,7 @@ typedef struct SDL_HapticPeriodic } SDL_HapticPeriodic; /** - * \brief A structure containing a template for a Condition effect. + * A structure containing a template for a Condition effect. * * The struct handles the following effects: * - ::SDL_HAPTIC_SPRING: Effect based on axes position. @@ -633,7 +634,7 @@ typedef struct SDL_HapticCondition } SDL_HapticCondition; /** - * \brief A structure containing a template for a Ramp effect. + * A structure containing a template for a Ramp effect. * * This struct is exclusively for the ::SDL_HAPTIC_RAMP effect. * @@ -671,7 +672,7 @@ typedef struct SDL_HapticRamp } SDL_HapticRamp; /** - * \brief A structure containing a template for a Left/Right effect. + * A structure containing a template for a Left/Right effect. * * This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect. * @@ -696,7 +697,7 @@ typedef struct SDL_HapticLeftRight } SDL_HapticLeftRight; /** - * \brief A structure containing a template for the ::SDL_HAPTIC_CUSTOM effect. + * A structure containing a template for the ::SDL_HAPTIC_CUSTOM effect. * * This struct is exclusively for the ::SDL_HAPTIC_CUSTOM effect. * @@ -738,7 +739,7 @@ typedef struct SDL_HapticCustom } SDL_HapticCustom; /** - * \brief The generic template for any haptic effect. + * The generic template for any haptic effect. * * All values max at 32767 (0x7FFF). Signed values also can be negative. * Time values unless specified otherwise are in milliseconds. diff --git a/include/SDL3/SDL_hidapi.h b/include/SDL3/SDL_hidapi.h index 1d7b2224..422c329a 100644 --- a/include/SDL3/SDL_hidapi.h +++ b/include/SDL3/SDL_hidapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_hidapi.h * - * \brief Header file for SDL HIDAPI functions. + * Header file for SDL HIDAPI functions. * * This is an adaptation of the original HIDAPI interface by Alan Ott, * and includes source code licensed under the following BSD license: @@ -71,13 +71,13 @@ extern "C" { #endif /** - * \brief A handle representing an open HID device + * A handle representing an open HID device */ struct SDL_hid_device_; typedef struct SDL_hid_device_ SDL_hid_device; /**< opaque hidapi structure */ /** - * \brief HID underlying bus types. + * HID underlying bus types. */ typedef enum { /** Unknown bus type */ @@ -109,7 +109,7 @@ typedef enum { /** hidapi info structure */ /** - * \brief Information about a connected HID device + * Information about a connected HID device */ typedef struct SDL_hid_device_info { diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 01193b0c..3319a149 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_hints.h * - * \brief Official documentation for SDL configuration variables + * Official documentation for SDL configuration variables * * This file contains functions to set and get configuration hints, * as well as listing each of them alphabetically. @@ -48,17 +48,16 @@ extern "C" { #endif /** - * \brief A variable controlling whether the Android / iOS built-in - * accelerometer should be listed as a joystick device. + * Set if Android/iOS accelerometers should be listed as joystick devices. * - * This variable can be set to the following values: + * This variable can be set to the following values: * "0" - The accelerometer is not listed as a joystick * "1" - The accelerometer is available as a 3 axis joystick (the default). */ #define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK" /** - * \brief Specify the behavior of Alt+Tab while the keyboard is grabbed. + * Specify the behavior of Alt+Tab while the keyboard is grabbed. * * By default, SDL emulates Alt+Tab functionality while the keyboard is grabbed * and your window is full-screen. This prevents the user from getting stuck in @@ -72,7 +71,7 @@ extern "C" { #define SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED "SDL_ALLOW_ALT_TAB_WHILE_GRABBED" /** - * \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it. + * If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it. * This is a debugging aid for developers and not expected to be used by end users. The default is "1" * * This variable can be set to the following values: @@ -82,7 +81,7 @@ extern "C" { #define SDL_HINT_ALLOW_TOPMOST "SDL_ALLOW_TOPMOST" /** - * \brief A variable to control whether the event loop will block itself when the app is paused. + * A variable to control whether the event loop will block itself when the app is paused. * * The variable can be set to the following values: * "0" - Non blocking. @@ -93,7 +92,7 @@ extern "C" { #define SDL_HINT_ANDROID_BLOCK_ON_PAUSE "SDL_ANDROID_BLOCK_ON_PAUSE" /** - * \brief A variable to control whether SDL will pause audio in background + * A variable to control whether SDL will pause audio in background * (Requires SDL_ANDROID_BLOCK_ON_PAUSE as "Non blocking") * * The variable can be set to the following values: @@ -105,7 +104,7 @@ extern "C" { #define SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO "SDL_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO" /** - * \brief A variable to control whether we trap the Android back button to handle it manually. + * A variable to control whether we trap the Android back button to handle it manually. * This is necessary for the right mouse button to work on some Android devices, or * to be able to trap the back button for use in your code reliably. If set to true, * the back button will show up as an SDL_EVENT_KEY_DOWN / SDL_EVENT_KEY_UP pair with a keycode of @@ -122,7 +121,7 @@ extern "C" { #define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON" /** - * \brief A variable to control whether SDL activity is allowed to be re-created. + * A variable to control whether SDL activity is allowed to be re-created. * If so, java static datas and static datas from native libraries remain with their current values. * When not allowed, the activity terminates with exit(0) to be fully re-initialized afterward. * @@ -135,7 +134,7 @@ extern "C" { #define SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY" /** - * \brief A variable setting the app ID string. + * A variable setting the app ID string. * This string is used by desktop compositors to identify and group windows * together, as well as match applications with associated desktop settings * and icons. @@ -182,7 +181,7 @@ extern "C" { #define SDL_HINT_APP_ID "SDL_APP_ID" /** - * \brief Specify an application name. + * Specify an application name. * * This hint lets you specify the application name sent to the OS when * required. For example, this will often appear in volume control applets for @@ -202,7 +201,7 @@ extern "C" { #define SDL_HINT_APP_NAME "SDL_APP_NAME" /** - * \brief A variable controlling whether controllers used with the Apple TV + * A variable controlling whether controllers used with the Apple TV * generate UI events. * * When UI events are generated by controller input, the app will be @@ -220,7 +219,7 @@ extern "C" { #define SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS "SDL_APPLE_TV_CONTROLLER_UI_EVENTS" /** - * \brief A variable controlling whether the Apple TV remote's joystick axes + * A variable controlling whether the Apple TV remote's joystick axes * will automatically match the rotation of the remote. * * This variable can be set to the following values: @@ -230,7 +229,7 @@ extern "C" { #define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION" /** - * \brief A variable controlling the audio category on iOS and macOS + * A variable controlling the audio category on iOS and macOS * * This variable can be set to the following values: * @@ -243,7 +242,7 @@ extern "C" { #define SDL_HINT_AUDIO_CATEGORY "SDL_AUDIO_CATEGORY" /** - * \brief Specify an application name for an audio device. + * Specify an application name for an audio device. * * Some audio backends (such as PulseAudio) allow you to describe your audio * stream. Among other things, this description might show up in a system @@ -264,7 +263,7 @@ extern "C" { #define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME" /** - * \brief Specify an application name for an audio device. + * Specify an application name for an audio device. * * Some audio backends (such as PulseAudio) allow you to describe your audio * stream. Among other things, this description might show up in a system @@ -285,7 +284,7 @@ extern "C" { #define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME" /** - * \brief Specify an application role for an audio device. + * Specify an application role for an audio device. * * Some audio backends (such as Pipewire) allow you to describe the role of * your audio stream. Among other things, this description might show up in @@ -305,7 +304,7 @@ extern "C" { #define SDL_HINT_AUDIO_DEVICE_STREAM_ROLE "SDL_AUDIO_DEVICE_STREAM_ROLE" /** - * \brief A variable controlling whether SDL updates joystick state when getting input events + * A variable controlling whether SDL updates joystick state when getting input events * * This variable can be set to the following values: * @@ -317,7 +316,7 @@ extern "C" { #define SDL_HINT_AUTO_UPDATE_JOYSTICKS "SDL_AUTO_UPDATE_JOYSTICKS" /** - * \brief A variable controlling whether SDL updates sensor state when getting input events + * A variable controlling whether SDL updates sensor state when getting input events * * This variable can be set to the following values: * @@ -329,7 +328,7 @@ extern "C" { #define SDL_HINT_AUTO_UPDATE_SENSORS "SDL_AUTO_UPDATE_SENSORS" /** - * \brief Prevent SDL from using version 4 of the bitmap header when saving BMPs. + * Prevent SDL from using version 4 of the bitmap header when saving BMPs. * * The bitmap header version 4 is required for proper alpha channel support and * SDL will use it when required. Should this not be desired, this hint can @@ -348,7 +347,7 @@ extern "C" { #define SDL_HINT_BMP_SAVE_LEGACY_FORMAT "SDL_BMP_SAVE_LEGACY_FORMAT" /** - * \brief Override for SDL_GetDisplayUsableBounds() + * Override for SDL_GetDisplayUsableBounds() * * If set, this hint will override the expected results for * SDL_GetDisplayUsableBounds() for display index 0. Generally you don't want @@ -362,7 +361,7 @@ extern "C" { #define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" /** - * \brief Disable giving back control to the browser automatically + * Disable giving back control to the browser automatically * when running with asyncify * * With -s ASYNCIFY, SDL calls emscripten_sleep during operations @@ -377,7 +376,7 @@ extern "C" { #define SDL_HINT_EMSCRIPTEN_ASYNCIFY "SDL_EMSCRIPTEN_ASYNCIFY" /** - * \brief Specify the CSS selector used for the "default" window/canvas + * Specify the CSS selector used for the "default" window/canvas * * This hint only applies to the emscripten platform * @@ -386,7 +385,7 @@ extern "C" { #define SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR "SDL_EMSCRIPTEN_CANVAS_SELECTOR" /** - * \brief override the binding element for keyboard inputs for Emscripten builds + * override the binding element for keyboard inputs for Emscripten builds * * This hint only applies to the emscripten platform * @@ -400,7 +399,7 @@ extern "C" { #define SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT" /** - * \brief A variable that controls whether the on-screen keyboard should be shown when text input is active + * A variable that controls whether the on-screen keyboard should be shown when text input is active * * The variable can be set to the following values: * "0" - Do not show the on-screen keyboard @@ -411,14 +410,13 @@ extern "C" { #define SDL_HINT_ENABLE_SCREEN_KEYBOARD "SDL_ENABLE_SCREEN_KEYBOARD" /** - * \brief A variable controlling verbosity of the logging of SDL events pushed onto the internal queue. + * A variable controlling verbosity of the logging of SDL events pushed onto the internal queue. * * This variable can be set to the following values, from least to most verbose: * * "0" - Don't log any events (default) * "1" - Log most events (other than the really spammy ones). * "2" - Include mouse and finger motion events. - * "3" - Include SDL_SysWMEvent events. * * This is generally meant to be used to debug SDL itself, but can be useful * for application developers that need better visibility into what is going @@ -433,7 +431,7 @@ extern "C" { #define SDL_HINT_EVENT_LOGGING "SDL_EVENT_LOGGING" /** - * \brief A variable controlling whether raising the window should be done more forcefully + * A variable controlling whether raising the window should be done more forcefully * * This variable can be set to the following values: * "0" - No forcing (the default) @@ -446,7 +444,7 @@ extern "C" { #define SDL_HINT_FORCE_RAISEWINDOW "SDL_HINT_FORCE_RAISEWINDOW" /** -* \brief A variable controlling whether the window is activated when the SDL_RaiseWindow function is called +* A variable controlling whether the window is activated when the SDL_RaiseWindow function is called * * This variable can be set to the following values: * "0" - The window is not activated when the SDL_RaiseWindow function is called @@ -458,7 +456,7 @@ extern "C" { #define SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED "SDL_WINDOW_ACTIVATE_WHEN_RAISED" /** - * \brief A variable controlling how 3D acceleration is used to accelerate the SDL screen surface. + * A variable controlling how 3D acceleration is used to accelerate the SDL screen surface. * * SDL can try to accelerate the SDL screen surface by using streaming * textures with a 3D rendering engine. This variable controls whether and @@ -475,7 +473,7 @@ extern "C" { #define SDL_HINT_FRAMEBUFFER_ACCELERATION "SDL_FRAMEBUFFER_ACCELERATION" /** - * \brief A variable that lets you manually hint extra gamecontroller db entries. + * A variable that lets you manually hint extra gamecontroller db entries. * * The variable should be newline delimited rows of gamecontroller config data, see SDL_gamepad.h * @@ -485,7 +483,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG" /** - * \brief A variable that lets you provide a file with extra gamecontroller db entries. + * A variable that lets you provide a file with extra gamecontroller db entries. * * The file should contain lines of gamecontroller config data, see SDL_gamepad.h * @@ -495,7 +493,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLERCONFIG_FILE "SDL_GAMECONTROLLERCONFIG_FILE" /** - * \brief A variable that overrides the automatic controller type detection + * A variable that overrides the automatic controller type detection * * The variable should be comma separated entries, in the form: VID/PID=type * @@ -514,7 +512,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLERTYPE "SDL_GAMECONTROLLERTYPE" /** - * \brief A variable containing a list of devices to skip when scanning for game controllers. + * A variable containing a list of devices to skip when scanning for game controllers. * * The format of the string is a comma separated list of USB VID/PID pairs * in hexadecimal form, e.g. @@ -527,7 +525,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES "SDL_GAMECONTROLLER_IGNORE_DEVICES" /** - * \brief If set, all devices will be skipped when scanning for game controllers except for the ones listed in this variable. + * If set, all devices will be skipped when scanning for game controllers except for the ones listed in this variable. * * The format of the string is a comma separated list of USB VID/PID pairs * in hexadecimal form, e.g. @@ -540,30 +538,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT" /** - * \brief If set, game controller face buttons report their values according to their labels instead of their positional layout. - * - * For example, on Nintendo Switch controllers, normally you'd get: - * - * (Y) - * (X) (B) - * (A) - * - * but if this hint is set, you'll get: - * - * (X) - * (Y) (A) - * (B) - * - * The variable can be set to the following values: - * "0" - Report the face buttons by position, as though they were on an Xbox controller. - * "1" - Report the face buttons by label instead of position - * - * The default value is "1". This hint may be set at any time. - */ -#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" - -/** - * \brief Controls whether the device's built-in accelerometer and gyro should be used as sensors for gamepads. + * Controls whether the device's built-in accelerometer and gyro should be used as sensors for gamepads. * * The variable can be set to the following values: * "0" - Sensor fusion is disabled @@ -582,7 +557,7 @@ extern "C" { #define SDL_HINT_GAMECONTROLLER_SENSOR_FUSION "SDL_GAMECONTROLLER_SENSOR_FUSION" /** - * \brief A variable controlling whether grabbing input grabs the keyboard + * A variable controlling whether grabbing input grabs the keyboard * * This variable can be set to the following values: * "0" - Grab will affect only the mouse @@ -593,7 +568,7 @@ extern "C" { #define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD" /** - * \brief A variable to control whether SDL_hid_enumerate() enumerates all HID devices or only controllers. + * A variable to control whether SDL_hid_enumerate() enumerates all HID devices or only controllers. * * This variable can be set to the following values: * "0" - SDL_hid_enumerate() will enumerate all HID devices @@ -604,7 +579,7 @@ extern "C" { #define SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS "SDL_HIDAPI_ENUMERATE_ONLY_CONTROLLERS" /** - * \brief A variable containing a list of devices to ignore in SDL_hid_enumerate() + * A variable containing a list of devices to ignore in SDL_hid_enumerate() * * For example, to ignore the Shanwan DS3 controller and any Valve controller, you might * have the string "0x2563/0x0523,0x28de/0x0000" @@ -612,7 +587,7 @@ extern "C" { #define SDL_HINT_HIDAPI_IGNORE_DEVICES "SDL_HIDAPI_IGNORE_DEVICES" /** - * \brief A variable to control whether certain IMEs should handle text editing internally instead of sending SDL_EVENT_TEXT_EDITING events. + * A variable to control whether certain IMEs should handle text editing internally instead of sending SDL_EVENT_TEXT_EDITING events. * * The variable can be set to the following values: * "0" - SDL_EVENT_TEXT_EDITING events are sent, and it is the application's @@ -624,7 +599,7 @@ extern "C" { #define SDL_HINT_IME_INTERNAL_EDITING "SDL_IME_INTERNAL_EDITING" /** - * \brief A variable to control whether certain IMEs should show native UI components (such as the Candidate List) instead of suppressing them. + * A variable to control whether certain IMEs should show native UI components (such as the Candidate List) instead of suppressing them. * * The variable can be set to the following values: * "0" - Native UI components are not display. (default) @@ -633,18 +608,7 @@ extern "C" { #define SDL_HINT_IME_SHOW_UI "SDL_IME_SHOW_UI" /** - * \brief A variable to control if extended IME text support is enabled. - * If enabled then SDL_TextEditingExtEvent will be issued if the text would be truncated otherwise. - * Additionally SDL_TextInputEvent will be dispatched multiple times so that it is not truncated. - * - * The variable can be set to the following values: - * "0" - Legacy behavior. Text can be truncated, no heap allocations. (default) - * "1" - Modern behavior. - */ -#define SDL_HINT_IME_SUPPORT_EXTENDED_TEXT "SDL_IME_SUPPORT_EXTENDED_TEXT" - -/** - * \brief A variable controlling whether the home indicator bar on iPhone X + * A variable controlling whether the home indicator bar on iPhone X * should be hidden. * * This variable can be set to the following values: @@ -655,7 +619,7 @@ extern "C" { #define SDL_HINT_IOS_HIDE_HOME_INDICATOR "SDL_IOS_HIDE_HOME_INDICATOR" /** - * \brief A variable that lets you enable joystick (and gamecontroller) events even when your app is in the background. + * A variable that lets you enable joystick (and gamecontroller) events even when your app is in the background. * * The variable can be set to the following values: * "0" - Disable joystick & gamecontroller input events when the @@ -668,7 +632,111 @@ extern "C" { #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" /** - * \brief A variable controlling whether the HIDAPI joystick drivers should be used. + * A variable containing a list of arcade stick style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES "SDL_JOYSTICK_ARCADESTICK_DEVICES" + +/** + * A variable containing a list of devices that are not arcade stick style controllers. This will override SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED "SDL_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices that should not be considerd joysticks. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_BLACKLIST_DEVICES "SDL_JOYSTICK_BLACKLIST_DEVICES" + +/** + * A variable containing a list of devices that should be considered joysticks. This will override SDL_HINT_JOYSTICK_BLACKLIST_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED "SDL_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED" + +/** + * A variable containing a list of flightstick style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES "SDL_JOYSTICK_FLIGHTSTICK_DEVICES" + +/** + * A variable containing a list of devices that are not flightstick style controllers. This will override SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED "SDL_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices known to have a GameCube form factor. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_GAMECUBE_DEVICES "SDL_JOYSTICK_GAMECUBE_DEVICES" + +/** + * A variable containing a list of devices known not to have a GameCube form factor. This will override SDL_HINT_JOYSTICK_GAMECUBE_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED "SDL_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED" + +/** + * A variable controlling whether the HIDAPI joystick drivers should be used. * * This variable can be set to the following values: * "0" - HIDAPI drivers are not used @@ -679,7 +747,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI" /** - * \brief A variable controlling whether the HIDAPI driver for Nintendo GameCube controllers should be used. + * A variable controlling whether the HIDAPI driver for Nintendo GameCube controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -690,7 +758,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE "SDL_JOYSTICK_HIDAPI_GAMECUBE" /** - * \brief A variable controlling whether "low_frequency_rumble" and "high_frequency_rumble" is used to implement + * A variable controlling whether "low_frequency_rumble" and "high_frequency_rumble" is used to implement * the GameCube controller's 3 rumble modes, Stop(0), Rumble(1), and StopHard(2) * this is useful for applications that need full compatibility for things like ADSR envelopes. * Stop is implemented by setting "low_frequency_rumble" to "0" and "high_frequency_rumble" ">0" @@ -705,7 +773,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE "SDL_JOYSTICK_GAMECUBE_RUMBLE_BRAKE" /** - * \brief A variable controlling whether the HIDAPI driver for Nintendo Switch Joy-Cons should be used. + * A variable controlling whether the HIDAPI driver for Nintendo Switch Joy-Cons should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -716,7 +784,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS "SDL_JOYSTICK_HIDAPI_JOY_CONS" /** - * \brief A variable controlling whether Nintendo Switch Joy-Con controllers will be combined into a single Pro-like controller when using the HIDAPI driver + * A variable controlling whether Nintendo Switch Joy-Con controllers will be combined into a single Pro-like controller when using the HIDAPI driver * * This variable can be set to the following values: * "0" - Left and right Joy-Con controllers will not be combined and each will be a mini-gamepad @@ -725,7 +793,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS" /** - * \brief A variable controlling whether Nintendo Switch Joy-Con controllers will be in vertical mode when using the HIDAPI driver + * A variable controlling whether Nintendo Switch Joy-Con controllers will be in vertical mode when using the HIDAPI driver * * This variable can be set to the following values: * "0" - Left and right Joy-Con controllers will not be in vertical mode (the default) @@ -736,7 +804,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS" /** - * \brief A variable controlling whether the HIDAPI driver for Amazon Luna controllers connected via Bluetooth should be used. + * A variable controlling whether the HIDAPI driver for Amazon Luna controllers connected via Bluetooth should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -747,7 +815,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_LUNA "SDL_JOYSTICK_HIDAPI_LUNA" /** - * \brief A variable controlling whether the HIDAPI driver for Nintendo Online classic controllers should be used. + * A variable controlling whether the HIDAPI driver for Nintendo Online classic controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -758,7 +826,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC "SDL_JOYSTICK_HIDAPI_NINTENDO_CLASSIC" /** - * \brief A variable controlling whether the HIDAPI driver for NVIDIA SHIELD controllers should be used. + * A variable controlling whether the HIDAPI driver for NVIDIA SHIELD controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -769,7 +837,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_SHIELD "SDL_JOYSTICK_HIDAPI_SHIELD" /** - * \brief A variable controlling whether the HIDAPI driver for PS3 controllers should be used. + * A variable controlling whether the HIDAPI driver for PS3 controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -783,7 +851,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS3 "SDL_JOYSTICK_HIDAPI_PS3" /** - * \brief A variable controlling whether the HIDAPI driver for PS4 controllers should be used. + * A variable controlling whether the HIDAPI driver for PS4 controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -794,7 +862,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4" /** - * \brief A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver. + * A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver. * * This variable can be set to the following values: * "0" - extended reports are not enabled (the default) @@ -814,7 +882,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE" /** - * \brief A variable controlling whether the HIDAPI driver for PS5 controllers should be used. + * A variable controlling whether the HIDAPI driver for PS5 controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -825,7 +893,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS5 "SDL_JOYSTICK_HIDAPI_PS5" /** - * \brief A variable controlling whether the player LEDs should be lit to indicate which player is associated with a PS5 controller. + * A variable controlling whether the player LEDs should be lit to indicate which player is associated with a PS5 controller. * * This variable can be set to the following values: * "0" - player LEDs are not enabled @@ -834,7 +902,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED" /** - * \brief A variable controlling whether extended input reports should be used for PS5 controllers when using the HIDAPI driver. + * A variable controlling whether extended input reports should be used for PS5 controllers when using the HIDAPI driver. * * This variable can be set to the following values: * "0" - extended reports are not enabled (the default) @@ -853,7 +921,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE" /** - * \brief A variable controlling whether the HIDAPI driver for Google Stadia controllers should be used. + * A variable controlling whether the HIDAPI driver for Google Stadia controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -864,7 +932,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_STADIA "SDL_JOYSTICK_HIDAPI_STADIA" /** - * \brief A variable controlling whether the HIDAPI driver for Bluetooth Steam Controllers should be used. + * A variable controlling whether the HIDAPI driver for Bluetooth Steam Controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -876,7 +944,18 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM" /** - * \brief A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used. + * A variable controlling whether the HIDAPI driver for the Steam Deck builtin controller should be used. + * + * This 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 + */ +#define SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK "SDL_JOYSTICK_HIDAPI_STEAMDECK" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -887,7 +966,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH" /** - * \brief A variable controlling whether the Home button LED should be turned on when a Nintendo Switch Pro controller is opened + * A variable controlling whether the Home button LED should be turned on when a Nintendo Switch Pro controller is opened * * This variable can be set to the following values: * "0" - home button LED is turned off @@ -898,7 +977,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED "SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED" /** - * \brief A variable controlling whether the Home button LED should be turned on when a Nintendo Switch Joy-Con controller is opened + * A variable controlling whether the Home button LED should be turned on when a Nintendo Switch Joy-Con controller is opened * * This variable can be set to the following values: * "0" - home button LED is turned off @@ -909,7 +988,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED "SDL_JOYSTICK_HIDAPI_JOYCON_HOME_LED" /** - * \brief A variable controlling whether the player LEDs should be lit to indicate which player is associated with a Nintendo Switch controller. + * A variable controlling whether the player LEDs should be lit to indicate which player is associated with a Nintendo Switch controller. * * This variable can be set to the following values: * "0" - player LEDs are not enabled @@ -918,7 +997,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" /** - * \brief A variable controlling whether the HIDAPI driver for Nintendo Wii and Wii U controllers should be used. + * A variable controlling whether the HIDAPI driver for Nintendo Wii and Wii U controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -929,7 +1008,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_WII "SDL_JOYSTICK_HIDAPI_WII" /** - * \brief A variable controlling whether the player LEDs should be lit to indicate which player is associated with a Wii controller. + * A variable controlling whether the player LEDs should be lit to indicate which player is associated with a Wii controller. * * This variable can be set to the following values: * "0" - player LEDs are not enabled @@ -938,7 +1017,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED "SDL_JOYSTICK_HIDAPI_WII_PLAYER_LED" /** - * \brief A variable controlling whether the HIDAPI driver for XBox controllers should be used. + * A variable controlling whether the HIDAPI driver for XBox controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -949,7 +1028,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX "SDL_JOYSTICK_HIDAPI_XBOX" /** - * \brief A variable controlling whether the HIDAPI driver for XBox 360 controllers should be used. + * A variable controlling whether the HIDAPI driver for XBox 360 controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -960,7 +1039,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360 "SDL_JOYSTICK_HIDAPI_XBOX_360" /** - * \brief A variable controlling whether the player LEDs should be lit to indicate which player is associated with an Xbox 360 controller. + * A variable controlling whether the player LEDs should be lit to indicate which player is associated with an Xbox 360 controller. * * This variable can be set to the following values: * "0" - player LEDs are not enabled @@ -969,7 +1048,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED "SDL_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED" /** - * \brief A variable controlling whether the HIDAPI driver for XBox 360 wireless controllers should be used. + * A variable controlling whether the HIDAPI driver for XBox 360 wireless controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -980,7 +1059,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS "SDL_JOYSTICK_HIDAPI_XBOX_360_WIRELESS" /** - * \brief A variable controlling whether the HIDAPI driver for XBox One controllers should be used. + * A variable controlling whether the HIDAPI driver for XBox One controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used @@ -991,7 +1070,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE "SDL_JOYSTICK_HIDAPI_XBOX_ONE" /** - * \brief A variable controlling whether the Home button LED should be turned on when an Xbox One controller is opened + * A variable controlling whether the Home button LED should be turned on when an Xbox One controller is opened * * This variable can be set to the following values: * "0" - home button LED is turned off @@ -1002,7 +1081,25 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED" /** - * \brief A variable controlling whether the RAWINPUT joystick drivers should be used for better handling XInput-capable devices. + * A variable controlling whether IOKit should be used for controller handling. + * + * This variable can be set to the following values: + * "0" - IOKit is not used + * "1" - IOKit is used (the default) + */ +#define SDL_HINT_JOYSTICK_IOKIT "SDL_JOYSTICK_IOKIT" + +/** + * A variable controlling whether GCController should be used for controller handling. + * + * This variable can be set to the following values: + * "0" - GCController is not used + * "1" - GCController is used (the default) + */ +#define SDL_HINT_JOYSTICK_MFI "SDL_JOYSTICK_MFI" + +/** + * A variable controlling whether the RAWINPUT joystick drivers should be used for better handling XInput-capable devices. * * This variable can be set to the following values: * "0" - RAWINPUT drivers are not used @@ -1011,7 +1108,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_RAWINPUT "SDL_JOYSTICK_RAWINPUT" /** - * \brief A variable controlling whether the RAWINPUT driver should pull correlated data from XInput. + * A variable controlling whether the RAWINPUT driver should pull correlated data from XInput. * * This variable can be set to the following values: * "0" - RAWINPUT driver will only use data from raw input APIs @@ -1024,7 +1121,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT "SDL_JOYSTICK_RAWINPUT_CORRELATE_XINPUT" /** - * \brief A variable controlling whether the ROG Chakram mice should show up as joysticks + * A variable controlling whether the ROG Chakram mice should show up as joysticks * * This variable can be set to the following values: * "0" - ROG Chakram mice do not show up as joysticks (the default) @@ -1033,7 +1130,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_ROG_CHAKRAM "SDL_JOYSTICK_ROG_CHAKRAM" /** - * \brief A variable controlling whether a separate thread should be used + * A variable controlling whether a separate thread should be used * for handling joystick detection and raw input messages on Windows * * This variable can be set to the following values: @@ -1044,7 +1141,33 @@ extern "C" { #define SDL_HINT_JOYSTICK_THREAD "SDL_JOYSTICK_THREAD" /** - * \brief A variable controlling whether Windows.Gaming.Input should be used for controller handling. + * A variable containing a list of throttle style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_THROTTLE_DEVICES "SDL_JOYSTICK_THROTTLE_DEVICES" + +/** + * A variable containing a list of devices that are not throttle style controllers. This will override SDL_HINT_JOYSTICK_THROTTLE_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED "SDL_JOYSTICK_THROTTLE_DEVICES_EXCLUDED" + +/** + * A variable controlling whether Windows.Gaming.Input should be used for controller handling. * * This variable can be set to the following values: * "0" - WGI is not used @@ -1053,7 +1176,46 @@ extern "C" { #define SDL_HINT_JOYSTICK_WGI "SDL_JOYSTICK_WGI" /** - * \brief Determines whether SDL enforces that DRM master is required in order + * A variable containing a list of wheel style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_WHEEL_DEVICES "SDL_JOYSTICK_WHEEL_DEVICES" + +/** + * A variable containing a list of devices that are not wheel style controllers. This will override SDL_HINT_JOYSTICK_WHEEL_DEVICES and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED "SDL_JOYSTICK_WHEEL_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices known to have all axes centered at zero. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" + +/** + * Determines whether SDL enforces that DRM master is required in order * to initialize the KMSDRM video backend. * * The DRM subsystem has a concept of a "DRM master" which is a DRM client that @@ -1076,14 +1238,14 @@ extern "C" { #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" /** - * \brief A comma separated list of devices to open as joysticks + * A comma separated list of devices to open as joysticks * * This variable is currently only used by the Linux joystick driver. */ #define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE" /** - * \brief A variable controlling whether joysticks on Linux will always treat 'hat' axis inputs (ABS_HAT0X - ABS_HAT3Y) as 8-way digital hats without checking whether they may be analog. + * A variable controlling whether joysticks on Linux will always treat 'hat' axis inputs (ABS_HAT0X - ABS_HAT3Y) as 8-way digital hats without checking whether they may be analog. * * This variable can be set to the following values: * "0" - Only map hat axis inputs to digital hat outputs if the input axes appear to actually be digital (the default) @@ -1092,7 +1254,7 @@ extern "C" { #define SDL_HINT_LINUX_DIGITAL_HATS "SDL_LINUX_DIGITAL_HATS" /** - * \brief A variable controlling whether digital hats on Linux will apply deadzones to their underlying input axes or use unfiltered values. + * A variable controlling whether digital hats on Linux will apply deadzones to their underlying input axes or use unfiltered values. * * This variable can be set to the following values: * "0" - Return digital hat values based on unfiltered input axis values @@ -1101,7 +1263,7 @@ extern "C" { #define SDL_HINT_LINUX_HAT_DEADZONES "SDL_LINUX_HAT_DEADZONES" /** - * \brief A variable controlling whether to use the classic /dev/input/js* joystick interface or the newer /dev/input/event* joystick interface on Linux + * A variable controlling whether to use the classic /dev/input/js* joystick interface or the newer /dev/input/event* joystick interface on Linux * * This variable can be set to the following values: * "0" - Use /dev/input/event* @@ -1112,7 +1274,7 @@ extern "C" { #define SDL_HINT_LINUX_JOYSTICK_CLASSIC "SDL_LINUX_JOYSTICK_CLASSIC" /** - * \brief A variable controlling whether joysticks on Linux adhere to their HID-defined deadzones or return unfiltered values. + * A variable controlling whether joysticks on Linux adhere to their HID-defined deadzones or return unfiltered values. * * This variable can be set to the following values: * "0" - Return unfiltered joystick axis values (the default) @@ -1121,7 +1283,7 @@ extern "C" { #define SDL_HINT_LINUX_JOYSTICK_DEADZONES "SDL_LINUX_JOYSTICK_DEADZONES" /** -* \brief When set don't force the SDL app to become a foreground process +* When set don't force the SDL app to become a foreground process * * This hint only applies to macOS. * @@ -1129,7 +1291,7 @@ extern "C" { #define SDL_HINT_MAC_BACKGROUND_APP "SDL_MAC_BACKGROUND_APP" /** - * \brief A variable that determines whether ctrl+click should generate a right-click event on Mac + * A variable that determines whether ctrl+click should generate a right-click event on Mac * * If present, holding ctrl while left clicking will generate a right click * event when on Mac. @@ -1137,7 +1299,7 @@ extern "C" { #define SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK "SDL_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK" /** - * \brief A variable controlling whether dispatching OpenGL context updates should block the dispatching thread until the main thread finishes processing + * A variable controlling whether dispatching OpenGL context updates should block the dispatching thread until the main thread finishes processing * * This variable can be set to the following values: * "0" - Dispatching OpenGL context updates will block the dispatching thread until the main thread finishes processing (default). @@ -1155,17 +1317,17 @@ extern "C" { #define SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH "SDL_MAC_OPENGL_ASYNC_DISPATCH" /** - * \brief A variable setting the double click radius, in pixels. + * A variable setting the double click radius, in pixels. */ #define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS "SDL_MOUSE_DOUBLE_CLICK_RADIUS" /** - * \brief A variable setting the double click time, in milliseconds. + * A variable setting the double click time, in milliseconds. */ #define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME "SDL_MOUSE_DOUBLE_CLICK_TIME" /** - * \brief Allow mouse click events when clicking to focus an SDL window + * Allow mouse click events when clicking to focus an SDL window * * This variable can be set to the following values: * "0" - Ignore mouse clicks that activate a window @@ -1176,12 +1338,12 @@ extern "C" { #define SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH "SDL_MOUSE_FOCUS_CLICKTHROUGH" /** - * \brief A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode + * A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode */ #define SDL_HINT_MOUSE_NORMAL_SPEED_SCALE "SDL_MOUSE_NORMAL_SPEED_SCALE" /** - * \brief A variable controlling whether relative mouse mode constrains the mouse to the center of the window + * A variable controlling whether relative mouse mode constrains the mouse to the center of the window * * This variable can be set to the following values: * "0" - Relative mouse mode constrains the mouse to the window @@ -1197,7 +1359,7 @@ extern "C" { #define SDL_HINT_MOUSE_RELATIVE_MODE_CENTER "SDL_MOUSE_RELATIVE_MODE_CENTER" /** - * \brief A variable controlling whether relative mouse mode is implemented using mouse warping + * A variable controlling whether relative mouse mode is implemented using mouse warping * * This variable can be set to the following values: * "0" - Relative mouse mode uses raw input @@ -1208,12 +1370,12 @@ extern "C" { #define SDL_HINT_MOUSE_RELATIVE_MODE_WARP "SDL_MOUSE_RELATIVE_MODE_WARP" /** - * \brief A variable setting the scale for mouse motion, in floating point, when the mouse is in relative mode + * A variable setting the scale for mouse motion, in floating point, when the mouse is in relative mode */ #define SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE "SDL_MOUSE_RELATIVE_SPEED_SCALE" /** - * \brief A variable controlling whether the system mouse acceleration curve is used for relative mouse motion. + * A variable controlling whether the system mouse acceleration curve is used for relative mouse motion. * * This variable can be set to the following values: * "0" - Relative mouse motion will be unscaled (the default) @@ -1224,7 +1386,7 @@ extern "C" { #define SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE "SDL_MOUSE_RELATIVE_SYSTEM_SCALE" /** - * \brief A variable controlling whether a motion event should be generated for mouse warping in relative mode. + * A variable controlling whether a motion event should be generated for mouse warping in relative mode. * * This variable can be set to the following values: * "0" - Warping the mouse will not generate a motion event in relative mode @@ -1235,7 +1397,7 @@ extern "C" { #define SDL_HINT_MOUSE_RELATIVE_WARP_MOTION "SDL_MOUSE_RELATIVE_WARP_MOTION" /** - * \brief A variable controlling whether mouse events should generate synthetic touch events + * A variable controlling whether mouse events should generate synthetic touch events * * This variable can be set to the following values: * "0" - Mouse events will not generate touch events (default for desktop platforms) @@ -1244,7 +1406,7 @@ extern "C" { #define SDL_HINT_MOUSE_TOUCH_EVENTS "SDL_MOUSE_TOUCH_EVENTS" /** - * \brief A variable controlling whether the mouse is captured while mouse buttons are pressed + * A variable controlling whether the mouse is captured while mouse buttons are pressed * * This variable can be set to the following values: * "0" - The mouse is not captured while mouse buttons are pressed @@ -1257,7 +1419,41 @@ extern "C" { #define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE" /** - * \brief Tell SDL not to catch the SIGINT or SIGTERM signals. + * Treat pen movement as separate from mouse movement + * + * By default, pens report both ::SDL_MouseMotionEvent and ::SDL_PenMotionEvent updates + * (analogously for button presses). This hint allows decoupling mouse and pen updates. + * + * This variable toggles between the following behaviour: + * "0" - (Default) Pen acts as a mouse with mouse ID ::SDL_PEN_MOUSEID. + * Use case: client application is not pen aware, user wants to + * use pen instead of mouse to interact. + * "1" - Pen reports mouse clicks and movement events but does not update + * SDL-internal mouse state (buttons pressed, current mouse location). + * Use case: client application is not pen aware, user frequently + * alternates between pen and "real" mouse. + * "2" - Pen reports no mouse events. + * Use case: pen-aware client application uses this hint to allow user to + * toggle between pen+mouse mode ("2") and pen-only mode ("1" or "0"). + */ +#define SDL_HINT_PEN_NOT_MOUSE "SDL_HINT_PEN_NOT_MOUSE" + +/** + * Pen mouse button emulation triggers only when the pen touches the tablet surface + * + * "0" - The pen reports mouse button press/release immediately when the pen + * button is pressed/released, and the pen tip touching the surface counts + * as left mouse button press. + * "1" - (Default) Mouse button presses are sent when the pen first touches + * the tablet (analogously for releases). Not pressing a pen button + * simulates mouse button 1, pressing the first pen button simulates + * mouse button 2 etc.; it is not possible to report multiple buttons + * as pressed at the same time. + */ +#define SDL_HINT_PEN_DELAY_MOUSE_BUTTON "SDL_HINT_PEN_DELAY_MOUSE_BUTTON" + +/** + * Tell SDL not to catch the SIGINT or SIGTERM signals. * * This hint only applies to Unix-like platforms, and should set before * any calls to SDL_Init() @@ -1270,7 +1466,7 @@ extern "C" { #define SDL_HINT_NO_SIGNAL_HANDLERS "SDL_NO_SIGNAL_HANDLERS" /** - * \brief A variable controlling what driver to use for OpenGL ES contexts. + * A variable controlling what driver to use for OpenGL ES contexts. * * On some platforms, currently Windows and X11, OpenGL drivers may support * creating contexts with an OpenGL ES profile. By default SDL uses these @@ -1301,7 +1497,7 @@ extern "C" { #define SDL_HINT_OPENGL_ES_DRIVER "SDL_OPENGL_ES_DRIVER" /** - * \brief A variable controlling which orientations are allowed on iOS/Android. + * A variable controlling which orientations are allowed on iOS/Android. * * In some circumstances it is necessary to be able to explicitly control * which UI orientations are allowed. @@ -1312,7 +1508,7 @@ extern "C" { #define SDL_HINT_ORIENTATIONS "SDL_IOS_ORIENTATIONS" /** - * \brief A variable controlling the use of a sentinel event when polling the event queue + * A variable controlling the use of a sentinel event when polling the event queue * * This variable can be set to the following values: * "0" - Disable poll sentinels @@ -1328,7 +1524,7 @@ extern "C" { #define SDL_HINT_POLL_SENTINEL "SDL_POLL_SENTINEL" /** - * \brief Override for SDL_GetPreferredLocales() + * Override for SDL_GetPreferredLocales() * * If set, this will be favored over anything the OS might report for the * user's preferred locales. Changing this hint at runtime will not generate @@ -1342,7 +1538,7 @@ extern "C" { #define SDL_HINT_PREFERRED_LOCALES "SDL_PREFERRED_LOCALES" /** - * \brief A variable describing the content orientation on QtWayland-based platforms. + * A variable describing the content orientation on QtWayland-based platforms. * * On QtWayland platforms, windows are rotated client-side to allow for custom * transitions. In order to correctly position overlays (e.g. volume bar) and @@ -1361,7 +1557,7 @@ extern "C" { #define SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION "SDL_QTWAYLAND_CONTENT_ORIENTATION" /** - * \brief Flags to set on QtWayland windows to integrate with the native window manager. + * Flags to set on QtWayland windows to integrate with the native window manager. * * On QtWayland platforms, this hint controls the flags to set on the windows. * For example, on Sailfish OS "OverridesSystemGestures" disables swipe gestures. @@ -1372,31 +1568,7 @@ extern "C" { #define SDL_HINT_QTWAYLAND_WINDOW_FLAGS "SDL_QTWAYLAND_WINDOW_FLAGS" /** - * \brief A variable controlling whether the 2D render API is compatible or efficient. - * - * This variable can be set to the following values: - * - * "0" - Don't use batching to make rendering more efficient. - * "1" - Use batching, but might cause problems if app makes its own direct OpenGL calls. - * - * Up to SDL 2.0.9, the render API would draw immediately when requested. Now - * it batches up draw requests and sends them all to the GPU only when forced - * to (during SDL_RenderPresent, when changing render targets, by updating a - * texture that the batch needs, etc). This is significantly more efficient, - * but it can cause problems for apps that expect to render on top of the - * render API's output. As such, SDL will disable batching if a specific - * render backend is requested (since this might indicate that the app is - * planning to use the underlying graphics API directly). This hint can - * be used to explicitly request batching in this instance. It is a contract - * that you will either never use the underlying graphics API directly, or - * if you do, you will call SDL_RenderFlush() before you do so any current - * batch goes to the GPU before your work begins. Not following this contract - * will result in undefined behavior. - */ -#define SDL_HINT_RENDER_BATCHING "SDL_RENDER_BATCHING" - -/** - * \brief A variable controlling how the 2D render API renders lines + * A variable controlling how the 2D render API renders lines * * This variable can be set to the following values: * "0" - Use the default line drawing method (Bresenham's line algorithm as of SDL 2.0.20) @@ -1409,7 +1581,7 @@ extern "C" { #define SDL_HINT_RENDER_LINE_METHOD "SDL_RENDER_LINE_METHOD" /** - * \brief A variable controlling whether to enable Direct3D 11+'s Debug Layer. + * A variable controlling whether to enable Direct3D 11+'s Debug Layer. * * This variable does not have any effect on the Direct3D 9 based renderer. * @@ -1422,7 +1594,7 @@ extern "C" { #define SDL_HINT_RENDER_DIRECT3D11_DEBUG "SDL_RENDER_DIRECT3D11_DEBUG" /** - * \brief A variable controlling whether the Direct3D device is initialized for thread-safe operations. + * A variable controlling whether the Direct3D device is initialized for thread-safe operations. * * This variable can be set to the following values: * "0" - Thread-safety is not enabled (faster) @@ -1433,7 +1605,7 @@ extern "C" { #define SDL_HINT_RENDER_DIRECT3D_THREADSAFE "SDL_RENDER_DIRECT3D_THREADSAFE" /** - * \brief A variable specifying which render driver to use. + * A variable specifying which render driver to use. * * If the application doesn't pick a specific renderer to use, this variable * specifies the name of the preferred renderer. If the preferred renderer @@ -1455,7 +1627,7 @@ extern "C" { #define SDL_HINT_RENDER_DRIVER "SDL_RENDER_DRIVER" /** - * \brief A variable controlling whether the OpenGL render driver uses shaders if they are available. + * A variable controlling whether the OpenGL render driver uses shaders if they are available. * * This variable can be set to the following values: * "0" - Disable shaders @@ -1466,7 +1638,7 @@ extern "C" { #define SDL_HINT_RENDER_OPENGL_SHADERS "SDL_RENDER_OPENGL_SHADERS" /** - * \brief A variable controlling the scaling quality + * A variable controlling the scaling quality * * This variable can be set to the following values: * "0" or "nearest" - Nearest pixel sampling @@ -1478,7 +1650,7 @@ extern "C" { #define SDL_HINT_RENDER_SCALE_QUALITY "SDL_RENDER_SCALE_QUALITY" /** - * \brief A variable controlling whether updates to the SDL screen surface should be synchronized with the vertical refresh, to avoid tearing. + * A variable controlling whether updates to the SDL screen surface should be synchronized with the vertical refresh, to avoid tearing. * * This variable can be set to the following values: * "0" - Disable vsync @@ -1489,7 +1661,7 @@ extern "C" { #define SDL_HINT_RENDER_VSYNC "SDL_RENDER_VSYNC" /** - * \brief A variable controlling whether the Metal render driver select low power device over default one + * A variable controlling whether the Metal render driver select low power device over default one * * This variable can be set to the following values: * "0" - Use the prefered OS device @@ -1500,7 +1672,33 @@ extern "C" { #define SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE "SDL_RENDER_METAL_PREFER_LOW_POWER_DEVICE" /** - * \brief A variable controlling if VSYNC is automatically disable if doesn't reach the enough FPS + * A variable containing a list of ROG gamepad capable mice. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_ROG_GAMEPAD_MICE "SDL_ROG_GAMEPAD_MICE" + +/** + * A variable containing a list of devices that are not ROG gamepad capable mice. This will override SDL_HINT_ROG_GAMEPAD_MICE and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs + * in hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named + * file will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED "SDL_ROG_GAMEPAD_MICE_EXCLUDED" + +/** + * A variable controlling if VSYNC is automatically disable if doesn't reach the enough FPS * * This variable can be set to the following values: * "0" - It will be using VSYNC as defined in the main flag. Default @@ -1511,7 +1709,7 @@ extern "C" { #define SDL_HINT_PS2_DYNAMIC_VSYNC "SDL_PS2_DYNAMIC_VSYNC" /** - * \brief A variable to control whether the return key on the soft keyboard + * A variable to control whether the return key on the soft keyboard * should hide the soft keyboard on Android and iOS. * * The variable can be set to the following values: @@ -1523,7 +1721,7 @@ extern "C" { #define SDL_HINT_RETURN_KEY_HIDES_IME "SDL_RETURN_KEY_HIDES_IME" /** - * \brief Tell SDL which Dispmanx layer to use on a Raspberry PI + * Tell SDL which Dispmanx layer to use on a Raspberry PI * * Also known as Z-order. The variable can take a negative or positive value. * The default is 10000. @@ -1531,7 +1729,7 @@ extern "C" { #define SDL_HINT_RPI_VIDEO_LAYER "SDL_RPI_VIDEO_LAYER" /** - * \brief Specify an "activity name" for screensaver inhibition. + * Specify an "activity name" for screensaver inhibition. * * Some platforms, notably Linux desktops, list the applications which are * inhibiting the screensaver or other power-saving features. @@ -1551,7 +1749,7 @@ extern "C" { #define SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME "SDL_SCREENSAVER_INHIBIT_ACTIVITY_NAME" /** - * \brief Specifies whether SDL_THREAD_PRIORITY_TIME_CRITICAL should be treated as realtime. + * Specifies whether SDL_THREAD_PRIORITY_TIME_CRITICAL should be treated as realtime. * * On some platforms, like Linux, a realtime priority thread may be subject to restrictions * that require special handling by the application. This hint exists to let SDL know that @@ -1570,7 +1768,7 @@ extern "C" { #define SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL "SDL_THREAD_FORCE_REALTIME_TIME_CRITICAL" /** -* \brief A string specifying additional information to use with SDL_SetThreadPriority. +* A string specifying additional information to use with SDL_SetThreadPriority. * * By default SDL_SetThreadPriority will make appropriate system changes in order to * apply a thread priority. For example on systems using pthreads the scheduler policy @@ -1587,7 +1785,7 @@ extern "C" { #define SDL_HINT_THREAD_PRIORITY_POLICY "SDL_THREAD_PRIORITY_POLICY" /** -* \brief A string specifying SDL's threads stack size in bytes or "0" for the backend's default size +* A string specifying SDL's threads stack size in bytes or "0" for the backend's default size * * Use this hint in case you need to set SDL's threads stack size to other than the default. * This is specially useful if you build SDL against a non glibc libc library (such as musl) which @@ -1601,7 +1799,7 @@ extern "C" { #define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE" /** - * \brief A variable that controls the timer resolution, in milliseconds. + * A variable that controls the timer resolution, in milliseconds. * * The higher resolution the timer, the more frequently the CPU services * timer interrupts, and the more precise delays are, but this takes up @@ -1617,7 +1815,7 @@ extern "C" { #define SDL_HINT_TIMER_RESOLUTION "SDL_TIMER_RESOLUTION" /** - * \brief A variable controlling whether touch events should generate synthetic mouse events + * A variable controlling whether touch events should generate synthetic mouse events * * This variable can be set to the following values: * "0" - Touch events will not generate mouse events @@ -1628,7 +1826,7 @@ extern "C" { #define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS" /** - * \brief A variable controlling which touchpad should generate synthetic mouse events + * A variable controlling which touchpad should generate synthetic mouse events * * This variable can be set to the following values: * "0" - Only front touchpad should generate mouse events. Default @@ -1640,7 +1838,7 @@ extern "C" { #define SDL_HINT_VITA_TOUCH_MOUSE_DEVICE "SDL_HINT_VITA_TOUCH_MOUSE_DEVICE" /** - * \brief A variable controlling whether the Android / tvOS remotes + * A variable controlling whether the Android / tvOS remotes * should be listed as joystick devices, instead of sending keyboard events. * * This variable can be set to the following values: @@ -1650,7 +1848,7 @@ extern "C" { #define SDL_HINT_TV_REMOTE_AS_JOYSTICK "SDL_TV_REMOTE_AS_JOYSTICK" /** - * \brief A variable controlling whether the screensaver is enabled. + * A variable controlling whether the screensaver is enabled. * * This variable can be set to the following values: * "0" - Disable screensaver @@ -1661,7 +1859,7 @@ extern "C" { #define SDL_HINT_VIDEO_ALLOW_SCREENSAVER "SDL_VIDEO_ALLOW_SCREENSAVER" /** - * \brief Tell the video driver that we only want a double buffer. + * Tell the video driver that we only want a double buffer. * * By default, most lowlevel 2D APIs will use a triple buffer scheme that * wastes no CPU time on waiting for vsync after issuing a flip, but @@ -1680,7 +1878,7 @@ extern "C" { #define SDL_HINT_VIDEO_DOUBLE_BUFFER "SDL_VIDEO_DOUBLE_BUFFER" /** - * \brief If eglGetPlatformDisplay fails, fall back to calling eglGetDisplay. + * If eglGetPlatformDisplay fails, fall back to calling eglGetDisplay. * * This variable can be set to one of the following values: * "0" - Do not fall back to eglGetDisplay @@ -1692,7 +1890,7 @@ extern "C" { #define SDL_HINT_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK "SDL_VIDEO_EGL_GETDISPLAY_FALLBACK" /** - * \brief A variable controlling whether the graphics context is externally managed. + * A variable controlling whether the graphics context is externally managed. * * This variable can be set to the following values: * "0" - SDL will manage graphics contexts that are attached to windows. @@ -1707,7 +1905,7 @@ extern "C" { #define SDL_HINT_VIDEO_EXTERNAL_CONTEXT "SDL_VIDEO_EXTERNAL_CONTEXT" /** - * \brief A variable that dictates policy for fullscreen Spaces on macOS. + * A variable that dictates policy for fullscreen Spaces on macOS. * * This hint only applies to macOS. * @@ -1724,7 +1922,7 @@ extern "C" { #define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" /** - * \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to false. + * Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to false. * \warning Before SDL 2.0.14, this defaulted to true! In 2.0.14, we're * seeing if "true" causes more problems than it solves in modern times. * @@ -1732,7 +1930,7 @@ extern "C" { #define SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS" /** - * \brief A variable controlling whether the libdecor Wayland backend is allowed to be used. + * A variable controlling whether the libdecor Wayland backend is allowed to be used. * * This variable can be set to the following values: * "0" - libdecor use is disabled. @@ -1743,7 +1941,7 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR" /** - * \brief A variable controlling whether the libdecor Wayland backend is preferred over native decrations. + * A variable controlling whether the libdecor Wayland backend is preferred over native decrations. * * When this hint is set, libdecor will be used to provide window decorations, even if xdg-decoration is * available. (Note that, by default, libdecor will use xdg-decoration itself if available). @@ -1757,7 +1955,7 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR "SDL_VIDEO_WAYLAND_PREFER_LIBDECOR" /** - * \brief A variable controlling whether video mode emulation is enabled under Wayland. + * A variable controlling whether video mode emulation is enabled under Wayland. * * When this hint is set, a standard set of emulated CVT video modes will be exposed for use by the application. * If it is disabled, the only modes exposed will be the logical desktop size and, in the case of a scaled @@ -1772,7 +1970,7 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION "SDL_VIDEO_WAYLAND_MODE_EMULATION" /** - * \brief A variable controlling how modes with a non-native aspect ratio are displayed under Wayland. + * A variable controlling how modes with a non-native aspect ratio are displayed under Wayland. * * When this hint is set, the requested scaling will be used when displaying fullscreen video modes * that don't match the display's native aspect ratio. This is contingent on compositor viewport support. @@ -1787,7 +1985,7 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_MODE_SCALING "SDL_VIDEO_WAYLAND_MODE_SCALING" /** - * \brief Enable or disable mouse pointer warp emulation, needed by some older games. + * Enable or disable mouse pointer warp emulation, needed by some older games. * * When this hint is set, any SDL will emulate mouse warps using relative mouse mode. * This is required for some older games (such as Source engine games), which warp the @@ -1804,48 +2002,26 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP" /** -* \brief A variable that is the address of another SDL_Window* (as a hex string formatted with "%p"). +* Set whether all window operations will block until complete. * -* If this hint is set before SDL_CreateWindowFrom() and the SDL_Window* it is set to has -* SDL_WINDOW_OPENGL set (and running on WGL only, currently), then two things will occur on the newly -* created SDL_Window: +* Window systems that run asynchronously may not have the results of window operations that resize or move the window +* applied immediately upon the return of the requesting function. Setting this hint will cause such operations to block +* after every call until the pending operation has completed. Setting this to '1' is the equivalent of calling +* SDL_SyncWindow() after every function call. * -* 1. Its pixel format will be set to the same pixel format as this SDL_Window. This is -* needed for example when sharing an OpenGL context across multiple windows. -* -* 2. The flag SDL_WINDOW_OPENGL will be set on the new window so it can be used for -* OpenGL rendering. +* Be aware that amount of time spent blocking while waiting for window operations to complete can be quite lengthy, as +* animations may have to complete, which can take upwards of multiple seconds in some cases. * * This variable can be set to the following values: -* The address (as a string "%p") of the SDL_Window* that new windows created with SDL_CreateWindowFrom() should -* share a pixel format with. -*/ -#define SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT "SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT" - -/** - * \brief When calling SDL_CreateWindowFrom(), make the window compatible with OpenGL. - * - * This variable can be set to the following values: - * "0" - Don't add any graphics flags to the SDL_WindowFlags - * "1" - Add SDL_WINDOW_OPENGL to the SDL_WindowFlags - * - * By default SDL will not make the foreign window compatible with OpenGL. +* "0" - Window operations are non-blocking +* "1" - Window operations will block until completed +* +* By default SDL will run in non-blocking mode */ -#define SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL "SDL_VIDEO_FOREIGN_WINDOW_OPENGL" +#define SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS "SDL_VIDEO_SYNC_WINDOW_OPERATIONS" /** - * \brief When calling SDL_CreateWindowFrom(), make the window compatible with Vulkan. - * - * This variable can be set to the following values: - * "0" - Don't add any graphics flags to the SDL_WindowFlags - * "1" - Add SDL_WINDOW_VULKAN to the SDL_WindowFlags - * - * By default SDL will not make the foreign window compatible with Vulkan. - */ -#define SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN "SDL_VIDEO_FOREIGN_WINDOW_VULKAN" - -/** -* \brief A variable specifying which shader compiler to preload when using the Chrome ANGLE binaries +* A variable specifying which shader compiler to preload when using the Chrome ANGLE binaries * * SDL has EGL and OpenGL ES2 support on Windows via the ANGLE project. It * can use two different sets of binaries, those compiled by the user from source @@ -1861,8 +2037,7 @@ extern "C" { #define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER" /** - * \brief A variable controlling whether the OpenGL context should be created - * with EGL by default + * Set whether the OpenGL context should be created with EGL by default * * This variable can be set to the following values: * "0" - Use platform-specific GL context creation API (GLX, WGL, CGL, etc) @@ -1873,7 +2048,7 @@ extern "C" { #define SDL_HINT_VIDEO_FORCE_EGL "SDL_VIDEO_FORCE_EGL" /** - * \brief A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint should be used. + * A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint should be used. * * This variable can be set to the following values: * "0" - Disable _NET_WM_BYPASS_COMPOSITOR @@ -1885,7 +2060,7 @@ extern "C" { #define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR" /** - * \brief A variable controlling whether the X11 _NET_WM_PING protocol should be supported. + * A variable controlling whether the X11 _NET_WM_PING protocol should be supported. * * This variable can be set to the following values: * "0" - Disable _NET_WM_PING @@ -1899,20 +2074,20 @@ extern "C" { #define SDL_HINT_VIDEO_X11_NET_WM_PING "SDL_VIDEO_X11_NET_WM_PING" /** - * \brief A variable forcing the visual ID chosen for new X11 windows + * A variable forcing the visual ID chosen for new X11 windows * */ #define SDL_HINT_VIDEO_X11_WINDOW_VISUALID "SDL_VIDEO_X11_WINDOW_VISUALID" /** - * \brief A variable forcing the scaling factor for X11 windows + * A variable forcing the scaling factor for X11 windows * * This variable can be set to a floating point value in the range 1.0-10.0f */ #define SDL_HINT_VIDEO_X11_SCALING_FACTOR "SDL_VIDEO_X11_SCALING_FACTOR" /** - * \brief A variable controlling whether the X11 XRandR extension should be used. + * A variable controlling whether the X11 XRandR extension should be used. * * This variable can be set to the following values: * "0" - Disable XRandR @@ -1923,7 +2098,7 @@ extern "C" { #define SDL_HINT_VIDEO_X11_XRANDR "SDL_VIDEO_X11_XRANDR" /** - * \brief Controls how the fact chunk affects the loading of a WAVE file. + * Controls how the fact chunk affects the loading of a WAVE file. * * The fact chunk stores information about the number of samples of a WAVE * file. The Standards Update from Microsoft notes that this value can be used @@ -1950,7 +2125,7 @@ extern "C" { #define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK" /** - * \brief Controls how the size of the RIFF chunk affects the loading of a WAVE file. + * Controls how the size of the RIFF chunk affects the loading of a WAVE file. * * The size of the RIFF chunk (which includes all the sub-chunks of the WAVE * file) is not always reliable. In case the size is wrong, it's possible to @@ -1971,7 +2146,7 @@ extern "C" { #define SDL_HINT_WAVE_RIFF_CHUNK_SIZE "SDL_WAVE_RIFF_CHUNK_SIZE" /** - * \brief Controls how a truncated WAVE file is handled. + * Controls how a truncated WAVE file is handled. * * A WAVE file is considered truncated if any of the chunks are incomplete or * the data chunk size is not a multiple of the block size. By default, SDL @@ -1987,7 +2162,7 @@ extern "C" { #define SDL_HINT_WAVE_TRUNCATION "SDL_WAVE_TRUNCATION" /** - * \brief Tell SDL not to name threads on Windows with the 0x406D1388 Exception. + * Tell SDL not to name threads on Windows with the 0x406D1388 Exception. * The 0x406D1388 Exception is a trick used to inform Visual Studio of a * thread's name, but it tends to cause problems with other debuggers, * and the .NET runtime. Note that SDL 2.0.6 and later will still use @@ -2003,7 +2178,7 @@ extern "C" { #define SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING "SDL_WINDOWS_DISABLE_THREAD_NAMING" /** - * \brief Controls whether menus can be opened with their keyboard shortcut (Alt+mnemonic). + * Controls whether menus can be opened with their keyboard shortcut (Alt+mnemonic). * * If the mnemonics are enabled, then menus can be opened by pressing the Alt * key and the corresponding mnemonic (for example, Alt+F opens the File menu). @@ -2025,7 +2200,7 @@ extern "C" { #define SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS "SDL_WINDOWS_ENABLE_MENU_MNEMONICS" /** - * \brief A variable controlling whether the windows message loop is processed by SDL + * A variable controlling whether the windows message loop is processed by SDL * * This variable can be set to the following values: * "0" - The window message loop is not run @@ -2036,7 +2211,7 @@ extern "C" { #define SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP "SDL_WINDOWS_ENABLE_MESSAGELOOP" /** - * \brief Force SDL to use Critical Sections for mutexes on Windows. + * Force SDL to use Critical Sections for mutexes on Windows. * On Windows 7 and newer, Slim Reader/Writer Locks are available. * They offer better performance, allocate no kernel resources and * use less memory. SDL will fall back to Critical Sections on older @@ -2050,7 +2225,7 @@ extern "C" { #define SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS "SDL_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS" /** - * \brief Force SDL to use Kernel Semaphores on Windows. + * Force SDL to use Kernel Semaphores on Windows. * Kernel Semaphores are inter-process and require a context * switch on every interaction. On Windows 8 and newer, the * WaitOnAddress API is available. Using that and atomics to @@ -2066,13 +2241,13 @@ extern "C" { #define SDL_HINT_WINDOWS_FORCE_SEMAPHORE_KERNEL "SDL_WINDOWS_FORCE_SEMAPHORE_KERNEL" /** - * \brief A variable to specify custom icon resource id from RC file on Windows platform + * A variable to specify custom icon resource id from RC file on Windows platform */ #define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" #define SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL "SDL_WINDOWS_INTRESOURCE_ICON_SMALL" /** - * \brief Tell SDL not to generate window-close events for Alt+F4 on Windows. + * Tell SDL not to generate window-close events for Alt+F4 on Windows. * * The variable can be set to the following values: * "0" - SDL will generate a window-close event when it sees Alt+F4. @@ -2081,7 +2256,7 @@ extern "C" { #define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4" /** - * \brief Use the D3D9Ex API introduced in Windows Vista, instead of normal D3D9. + * Use the D3D9Ex API introduced in Windows Vista, instead of normal D3D9. * Direct3D 9Ex contains changes to state management that can eliminate device * loss errors during scenarios like Alt+Tab or UAC prompts. D3D9Ex may require * some changes to your application to cope with the new behavior, so this @@ -2101,7 +2276,7 @@ extern "C" { #define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX" /** - * \brief A variable controlling whether the window frame and title bar are interactive when the cursor is hidden + * A variable controlling whether the window frame and title bar are interactive when the cursor is hidden * * This variable can be set to the following values: * "0" - The window frame is not interactive when the cursor is hidden (no move, resize, etc) @@ -2112,7 +2287,7 @@ extern "C" { #define SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN "SDL_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN" /** -* \brief A variable controlling whether the window is activated when the SDL_ShowWindow function is called +* A variable controlling whether the window is activated when the SDL_ShowWindow function is called * * This variable can be set to the following values: * "0" - The window is not activated when the SDL_ShowWindow function is called @@ -2122,7 +2297,7 @@ extern "C" { */ #define SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN "SDL_WINDOW_ACTIVATE_WHEN_SHOWN" -/** \brief Allows back-button-press events on Windows Phone to be marked as handled +/** Allows back-button-press events on Windows Phone to be marked as handled * * Windows Phone devices typically feature a Back button. When pressed, * the OS will emit back-button-press events, which apps are expected to @@ -2174,7 +2349,7 @@ extern "C" { */ #define SDL_HINT_WINRT_HANDLE_BACK_BUTTON "SDL_WINRT_HANDLE_BACK_BUTTON" -/** \brief Label text for a WinRT app's privacy policy link +/** Label text for a WinRT app's privacy policy link * * Network-enabled WinRT apps must include a privacy policy. On Windows 8, 8.1, and RT, * Microsoft mandates that this policy be available via the Windows Settings charm. @@ -2196,7 +2371,7 @@ extern "C" { #define SDL_HINT_WINRT_PRIVACY_POLICY_LABEL "SDL_WINRT_PRIVACY_POLICY_LABEL" /** - * \brief A URL to a WinRT app's privacy policy + * A URL to a WinRT app's privacy policy * * All network-enabled WinRT apps must make a privacy policy available to its * users. On Windows 8, 8.1, and RT, Microsoft mandates that this policy be @@ -2222,7 +2397,7 @@ extern "C" { #define SDL_HINT_WINRT_PRIVACY_POLICY_URL "SDL_WINRT_PRIVACY_POLICY_URL" /** - * \brief Mark X11 windows as override-redirect. + * Mark X11 windows as override-redirect. * * If set, this _might_ increase framerate at the expense of the desktop * not working as expected. Override-redirect windows aren't noticed by the @@ -2234,7 +2409,7 @@ extern "C" { #define SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT "SDL_X11_FORCE_OVERRIDE_REDIRECT" /** - * \brief A variable that lets you disable the detection and use of Xinput gamepad devices + * A variable that lets you disable the detection and use of Xinput gamepad devices * * The variable can be set to the following values: * "0" - Disable XInput detection (only uses direct input) @@ -2243,7 +2418,7 @@ extern "C" { #define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED" /** - * \brief A variable that lets you disable the detection and use of DirectInput gamepad devices + * A variable that lets you disable the detection and use of DirectInput gamepad devices * * The variable can be set to the following values: * "0" - Disable DirectInput detection (only uses XInput) @@ -2252,7 +2427,7 @@ extern "C" { #define SDL_HINT_DIRECTINPUT_ENABLED "SDL_DIRECTINPUT_ENABLED" /** - * \brief A variable that causes SDL to use the old axis and button mapping for XInput devices. + * A variable that causes SDL to use the old axis and button mapping for XInput devices. * * This hint is for backwards compatibility only and will be removed in SDL 2.1 * @@ -2261,7 +2436,7 @@ extern "C" { #define SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING "SDL_XINPUT_USE_OLD_JOYSTICK_MAPPING" /** - * \brief A variable that causes SDL to not ignore audio "monitors" + * A variable that causes SDL to not ignore audio "monitors" * * This is currently only used for PulseAudio and ignored elsewhere. * @@ -2280,7 +2455,7 @@ extern "C" { #define SDL_HINT_AUDIO_INCLUDE_MONITORS "SDL_AUDIO_INCLUDE_MONITORS" /** - * \brief A variable that forces X11 windows to create as a custom type. + * A variable that forces X11 windows to create as a custom type. * * This is currently only used for X11 and ignored elsewhere. * @@ -2300,7 +2475,7 @@ extern "C" { #define SDL_HINT_X11_WINDOW_TYPE "SDL_X11_WINDOW_TYPE" /** - * \brief A variable that decides whether to send SDL_EVENT_QUIT when closing the final window. + * A variable that decides whether to send SDL_EVENT_QUIT when closing the final window. * * By default, SDL sends an SDL_EVENT_QUIT event when there is only one window * and it receives an SDL_EVENT_WINDOW_CLOSE_REQUESTED event, under the assumption most @@ -2325,7 +2500,7 @@ extern "C" { /** - * \brief A variable that decides what video backend to use. + * A variable that decides what video backend to use. * * By default, SDL will try all available video backends in a reasonable * order until it finds one that can work, but this hint allows the app @@ -2348,7 +2523,7 @@ extern "C" { #define SDL_HINT_VIDEO_DRIVER "SDL_VIDEO_DRIVER" /** - * \brief A variable that decides what audio backend to use. + * A variable that decides what audio backend to use. * * By default, SDL will try all available audio backends in a reasonable * order until it finds one that can work, but this hint allows the app @@ -2371,7 +2546,7 @@ extern "C" { #define SDL_HINT_AUDIO_DRIVER "SDL_AUDIO_DRIVER" /** - * \brief A variable that decides what KMSDRM device to use. + * A variable that decides what KMSDRM device to use. * * Internally, SDL might open something like "/dev/dri/cardNN" to * access KMSDRM functionality, where "NN" is a device index number. @@ -2386,7 +2561,7 @@ extern "C" { /** - * \brief A variable that treats trackpads as touch devices. + * A variable that treats trackpads as touch devices. * * On macOS (and possibly other platforms in the future), SDL will report * touches on a trackpad as mouse input, which is generally what users @@ -2408,7 +2583,7 @@ extern "C" { /** - * \brief Sets the title of the TextInput window on GDK platforms. + * Sets the title of the TextInput window on GDK platforms. * * On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the * standard SDL text input and virtual keyboard capabilities @@ -2427,7 +2602,7 @@ extern "C" { #define SDL_HINT_GDK_TEXTINPUT_TITLE "SDL_GDK_TEXTINPUT_TITLE" /** - * \brief Sets the description of the TextInput window on GDK platforms. + * Sets the description of the TextInput window on GDK platforms. * * On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the * standard SDL text input and virtual keyboard capabilities @@ -2446,7 +2621,7 @@ extern "C" { #define SDL_HINT_GDK_TEXTINPUT_DESCRIPTION "SDL_GDK_TEXTINPUT_DESCRIPTION" /** - * \brief Sets the default text of the TextInput window on GDK platforms. + * Sets the default text of the TextInput window on GDK platforms. * * On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the * standard SDL text input and virtual keyboard capabilities @@ -2465,7 +2640,7 @@ extern "C" { #define SDL_HINT_GDK_TEXTINPUT_DEFAULT "SDL_GDK_TEXTINPUT_DEFAULT" /** - * \brief Sets the input scope of the TextInput window on GDK platforms. + * Sets the input scope of the TextInput window on GDK platforms. * * On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the * standard SDL text input and virtual keyboard capabilities @@ -2488,7 +2663,7 @@ extern "C" { #define SDL_HINT_GDK_TEXTINPUT_SCOPE "SDL_GDK_TEXTINPUT_SCOPE" /** - * \brief Sets the maximum input length of the TextInput window on GDK platforms. + * Sets the maximum input length of the TextInput window on GDK platforms. * * On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the * standard SDL text input and virtual keyboard capabilities @@ -2532,7 +2707,38 @@ extern "C" { #define SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES "SDL_AUDIO_DEVICE_SAMPLE_FRAMES" /** - * \brief An enumeration of hint priorities + * Request SDL_AppIterate() be called at a specific rate. + * + * This number is in Hz, so "60" means try to iterate 60 times per second. + * + * On some platforms, or if you are using SDL_main instead of SDL_AppIterate, + * this hint is ignored. When the hint can be used, it is allowed to be + * changed at any time. + * + * This defaults to 60, and specifying NULL for the hint's value will restore + * the default. + */ +#define SDL_HINT_MAIN_CALLBACK_RATE "SDL_MAIN_CALLBACK_RATE" + +/** + * Cause SDL to call dbus_shutdown() on quit. + * + * This is useful as a debug tool to validate memory leaks, but shouldn't ever + * be set in production applications, as other libraries used by the application + * might use dbus under the hood and this cause cause crashes if they continue + * after SDL_Quit(). + * + * This variable can be set to the following values: + * "0" - SDL will not call dbus_shutdown() on quit (default) + * "1" - SDL will call dbus_shutdown() on quit + * + * This hint is available since SDL 3.0.0. + */ +#define SDL_HINT_SHUTDOWN_DBUS_ON_QUIT "SDL_SHUTDOWN_DBUS_ON_QUIT" + + +/** + * An enumeration of hint priorities */ typedef enum { diff --git a/include/SDL3/SDL_init.h b/include/SDL3/SDL_init.h index 361c6534..472afc99 100644 --- a/include/SDL3/SDL_init.h +++ b/include/SDL3/SDL_init.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_init.h * - * \brief Init and quit header for the SDL library + * Init and quit header for the SDL library */ #ifndef SDL_init_h_ @@ -39,7 +39,7 @@ extern "C" { /* As of version 0.5, SDL is loaded dynamically into the application */ /** - * \brief Initialization flags for SDL_Init and/or SDL_InitSubSystem + * Initialization flags for SDL_Init and/or SDL_InitSubSystem * * These are the flags which may be passed to SDL_Init(). You should * specify the subsystems which you will be using in your application. diff --git a/include/SDL3/SDL_intrin.h b/include/SDL3/SDL_intrin.h index addda91e..d75ce788 100644 --- a/include/SDL3/SDL_intrin.h +++ b/include/SDL3/SDL_intrin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_intrin.h * - * \brief Header file for CPU intrinsics for SDL + * Header file for CPU intrinsics for SDL */ #ifndef SDL_intrin_h_ diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h index c7985a92..80f210d6 100644 --- a/include/SDL3/SDL_joystick.h +++ b/include/SDL3/SDL_joystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_joystick.h * - * \brief Include file for SDL joystick event handling + * Include file for SDL joystick event handling * * The term "instance_id" is the current instantiation of a joystick device in the system, if the joystick is removed and then re-inserted * then it will get a new instance_id, instance_id's are monotonically increasing identifiers of a joystick plugged in. @@ -42,6 +42,7 @@ #include #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -342,7 +343,7 @@ typedef struct SDL_VirtualJoystickDesc Uint16 product_id; /**< the USB product ID of this joystick */ Uint16 padding; /**< unused */ Uint32 button_mask; /**< A mask of which buttons are valid for this controller - e.g. (1 << SDL_GAMEPAD_BUTTON_A) */ + e.g. (1 << SDL_GAMEPAD_BUTTON_SOUTH) */ Uint32 axis_mask; /**< A mask of which axes are valid for this controller e.g. (1 << SDL_GAMEPAD_AXIS_LEFTX) */ const char *name; /**< the name of the joystick */ @@ -358,7 +359,7 @@ typedef struct SDL_VirtualJoystickDesc } SDL_VirtualJoystickDesc; /** - * \brief The current version of the SDL_VirtualJoystickDesc structure + * The current version of the SDL_VirtualJoystickDesc structure */ #define SDL_VIRTUAL_JOYSTICK_DESC_VERSION 1 @@ -456,6 +457,20 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, */ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value); +/** + * Get the properties associated with a joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_OpenJoystick() + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetJoystickProperties(SDL_Joystick *joystick); + /** * Get the implementation dependent name of a joystick. * diff --git a/include/SDL3/SDL_keyboard.h b/include/SDL3/SDL_keyboard.h index 5b2df215..042ae43d 100644 --- a/include/SDL3/SDL_keyboard.h +++ b/include/SDL3/SDL_keyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_keyboard.h * - * \brief Include file for SDL keyboard event handling + * Include file for SDL keyboard event handling */ #ifndef SDL_keyboard_h_ @@ -40,7 +40,7 @@ extern "C" { #endif /** - * \brief The SDL keysym structure, used in key events. + * The SDL keysym structure, used in key events. * * \note If you are looking for translated character input, see the ::SDL_EVENT_TEXT_INPUT event. */ @@ -302,6 +302,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_TextInputShown(void); /** * Set the rectangle used to type Unicode text inputs. * + * Native input methods will place a window with word suggestions near it, + * without covering the text being inputted. + * * To start text input in a given location, this function is intended to be * called before SDL_StartTextInput, although some platforms support moving * the rectangle even while text input (and a composition) is active. diff --git a/include/SDL3/SDL_keycode.h b/include/SDL3/SDL_keycode.h index 9f4953c0..e94ace90 100644 --- a/include/SDL3/SDL_keycode.h +++ b/include/SDL3/SDL_keycode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_keycode.h * - * \brief Defines constants which identify keyboard keys and modifiers. + * Defines constants which identify keyboard keys and modifiers. */ #ifndef SDL_keycode_h_ @@ -32,7 +32,7 @@ #include /** - * \brief The SDL virtual key representation. + * The SDL virtual key representation. * * Values of this type are used to represent keyboard keys using the current * layout of the keyboard. These values include Unicode values representing @@ -327,7 +327,7 @@ typedef enum } SDL_KeyCode; /** - * \brief Enumeration of valid key mods (possibly OR'd together). + * Enumeration of valid key mods (possibly OR'd together). */ typedef enum { diff --git a/include/SDL3/SDL_loadso.h b/include/SDL3/SDL_loadso.h index f7ed6d2c..58e14d37 100644 --- a/include/SDL3/SDL_loadso.h +++ b/include/SDL3/SDL_loadso.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_loadso.h * - * \brief System dependent library loading routines + * System dependent library loading routines * * Some things to keep in mind: * \li These functions only work on C function names. Other languages may diff --git a/include/SDL3/SDL_locale.h b/include/SDL3/SDL_locale.h index ebf965ec..7ac803ca 100644 --- a/include/SDL3/SDL_locale.h +++ b/include/SDL3/SDL_locale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_locale.h * - * \brief Include file for SDL locale services + * Include file for SDL locale services */ #ifndef SDL_locale_h diff --git a/include/SDL3/SDL_log.h b/include/SDL3/SDL_log.h index d56b0590..dd33ad94 100644 --- a/include/SDL3/SDL_log.h +++ b/include/SDL3/SDL_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_log.h * - * \brief Simple log messages with categories and priorities. + * Simple log messages with categories and priorities. * * By default logs are quiet, but if you're debugging SDL you might want: * @@ -47,14 +47,14 @@ extern "C" { /** - * \brief The maximum size of a log message prior to SDL 2.0.24 + * The maximum size of a log message prior to SDL 2.0.24 * * As of 2.0.24 there is no limit to the length of SDL log messages. */ #define SDL_MAX_LOG_MESSAGE 4096 /** - * \brief The predefined log categories + * The predefined log categories * * By default the application category is enabled at the INFO level, * the assert category is enabled at the WARN level, test is enabled @@ -97,7 +97,7 @@ typedef enum } SDL_LogCategory; /** - * \brief The predefined log priorities + * The predefined log priorities */ typedef enum { @@ -163,8 +163,7 @@ extern DECLSPEC void SDLCALL SDL_LogResetPriorities(void); /** * Log a message with SDL_LOG_CATEGORY_APPLICATION and SDL_LOG_PRIORITY_INFO. * - * = * \param fmt a printf() style message format string - * + * \param fmt a printf() style message format string * \param ... additional parameters matching % tokens in the `fmt` string, if * any * @@ -352,7 +351,7 @@ extern DECLSPEC void SDLCALL SDL_LogMessage(int category, */ extern DECLSPEC void SDLCALL SDL_LogMessageV(int category, SDL_LogPriority priority, - const char *fmt, va_list ap); + SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(3); /** * The prototype for the log output callback function. diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 6181c0c0..76f08f10 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,11 +23,20 @@ #define SDL_main_h_ #include +#include + +/* + * For details on how SDL_main works, and how to use it, please refer to: + * + * https://wiki.libsdl.org/SDL3/README/main-functions + * + * (or docs/README-main-functions.md in the SDL source tree) + */ /** * \file SDL_main.h * - * \brief Redefine main() on some platforms so that it is called by SDL. + * Redefine main() on some platforms so that it is called by SDL. */ #ifndef SDL_MAIN_HANDLED @@ -140,7 +149,7 @@ * \endcode */ -#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) +#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) || defined(SDL_MAIN_USE_CALLBACKS) #define main SDL_main #endif @@ -149,11 +158,198 @@ extern "C" { #endif +typedef int (SDLCALL *SDL_AppInit_func)(int argc, char *argv[]); +typedef int (SDLCALL *SDL_AppIterate_func)(void); +typedef int (SDLCALL *SDL_AppEvent_func)(const SDL_Event *event); +typedef void (SDLCALL *SDL_AppQuit_func)(void); + +/** + * You can (optionally!) define SDL_MAIN_USE_CALLBACKS before including + * SDL_main.h, and then your application will _not_ have a standard + * "main" entry point. Instead, it will operate as a collection of + * functions that are called as necessary by the system. On some + * platforms, this is just a layer where SDL drives your program + * instead of your program driving SDL, on other platforms this might + * hook into the OS to manage the lifecycle. Programs on most platforms + * can use whichever approach they prefer, but the decision boils down + * to: + * + * - Using a standard "main" function: this works like it always has for + * the past 50+ years in C programming, and your app is in control. + * - Using the callback functions: this might clean up some code, + * avoid some #ifdef blocks in your program for some platforms, be more + * resource-friendly to the system, and possibly be the primary way to + * access some future platforms (but none require this at the moment). + * + * This is up to the app; both approaches are considered valid and supported + * ways to write SDL apps. + * + * If using the callbacks, don't define a "main" function. Instead, implement + * the functions listed below in your program. + */ +#ifdef SDL_MAIN_USE_CALLBACKS + +/** + * App-implemented initial entry point for SDL_MAIN_USE_CALLBACKS apps. + * + * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If + * using a standard "main" function, you should not supply this. + * + * This function is called by SDL once, at startup. The function should + * initialize whatever is necessary, possibly create windows and open + * audio devices, etc. The `argc` and `argv` parameters work like they would + * with a standard "main" function. + * + * This function should not go into an infinite mainloop; it should do any + * one-time setup it requires and then return. + * + * If this function returns 0, the app will proceed to normal operation, + * and will begin receiving repeated calls to SDL_AppIterate and SDL_AppEvent + * for the life of the program. If this function returns < 0, SDL will + * call SDL_AppQuit and terminate the process with an exit code that reports + * an error to the platform. If it returns > 0, the SDL calls SDL_AppQuit + * and terminates with an exit code that reports success to the platform. + * + * \param argc The standard ANSI C main's argc; number of elements in `argv` + * \param argv The standard ANSI C main's argv; array of command line arguments. + * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AppIterate + * \sa SDL_AppEvent + * \sa SDL_AppQuit + */ +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]); + +/** + * App-implemented iteration entry point for SDL_MAIN_USE_CALLBACKS apps. + * + * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If + * using a standard "main" function, you should not supply this. + * + * This function is called repeatedly by SDL after SDL_AppInit returns 0. + * The function should operate as a single iteration the program's primary + * loop; it should update whatever state it needs and draw a new frame of + * video, usually. + * + * On some platforms, this function will be called at the refresh rate of + * the display (which might change during the life of your app!). There are + * no promises made about what frequency this function might run at. You + * should use SDL's timer functions if you need to see how much time has + * passed since the last iteration. + * + * There is no need to process the SDL event queue during this function; + * SDL will send events as they arrive in SDL_AppEvent, and in most cases + * the event queue will be empty when this function runs anyhow. + * + * This function should not go into an infinite mainloop; it should do one + * iteration of whatever the program does and return. + * + * If this function returns 0, the app will continue normal operation, + * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life + * of the program. If this function returns < 0, SDL will call SDL_AppQuit + * and terminate the process with an exit code that reports an error to the + * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with + * an exit code that reports success to the platform. + * + * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AppInit + * \sa SDL_AppEvent + * \sa SDL_AppQuit + */ +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void); + +/** + * App-implemented event entry point for SDL_MAIN_USE_CALLBACKS apps. + * + * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If + * using a standard "main" function, you should not supply this. + * + * This function is called as needed by SDL after SDL_AppInit returns 0; + * It is called once for each new event. + * + * There is (currently) no guarantee about what thread this will be called + * from; whatever thread pushes an event onto SDL's queue will trigger this + * function. SDL is responsible for pumping the event queue between + * each call to SDL_AppIterate, so in normal operation one should only + * get events in a serial fashion, but be careful if you have a thread that + * explicitly calls SDL_PushEvent. + * + * Events sent to this function are not owned by the app; if you need to + * save the data, you should copy it. + * + * You do not need to free event data (such as the `file` string in + * SDL_EVENT_DROP_FILE), as SDL will free it once this function returns. + * Note that this is different than one might expect when using a standard + * "main" function! + * + * This function should not go into an infinite mainloop; it should handle + * the provided event appropriately and return. + * + * If this function returns 0, the app will continue normal operation, + * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life + * of the program. If this function returns < 0, SDL will call SDL_AppQuit + * and terminate the process with an exit code that reports an error to the + * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with + * an exit code that reports success to the platform. + * + * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AppInit + * \sa SDL_AppIterate + * \sa SDL_AppQuit + */ +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(const SDL_Event *event); + +/** + * App-implemented deinit entry point for SDL_MAIN_USE_CALLBACKS apps. + * + * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If + * using a standard "main" function, you should not supply this. + * + * This function is called once by SDL before terminating the program. + * + * This function will be called no matter what, even if SDL_AppInit + * requests termination. + * + * This function should not go into an infinite mainloop; it should + * deinitialize any resources necessary, perform whatever shutdown + * activities, and return. + * + * You do not need to call SDL_Quit() in this function, as SDL will call + * it after this function returns and before the process terminates, but + * it is safe to do so. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AppInit + * \sa SDL_AppIterate + * \sa SDL_AppEvent + */ +extern SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void); + +#endif /* SDL_MAIN_USE_CALLBACKS */ + + /** * The prototype for the application's main() function */ -typedef int (*SDL_main_func)(int argc, char *argv[]); -extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]); +typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]); +extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]); /** @@ -198,6 +394,34 @@ extern DECLSPEC void SDLCALL SDL_SetMainReady(void); */ extern DECLSPEC int SDLCALL SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved); +/** + * An entry point for SDL's use in SDL_MAIN_USE_CALLBACKS. + * + * Generally, you should not call this function directly. This only exists to + * hand off work into SDL as soon as possible, where it has a lot more control + * and functionality available, and make the inline code in SDL_main.h as + * small as possible. + * + * Not all platforms use this, it's actual use is hidden in a magic + * header-only library, and you should not call this directly unless you + * _really_ know what you're doing. + * + * \param argc standard Unix main argc + * \param argv standard Unix main argv + * \param appinit The application's SDL_AppInit function + * \param appiter The application's SDL_AppIterate function + * \param appevent The application's SDL_AppEvent function + * \param appquit The application's SDL_AppQuit function + * \returns standard Unix main return value + * + * \threadsafety It is not safe to call this anywhere except as the only + * function call in SDL_main. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit); + + #if defined(__WIN32__) || defined(__GDK__) /** @@ -282,7 +506,8 @@ extern DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); #if !defined(SDL_MAIN_HANDLED) && !defined(SDL_MAIN_NOIMPL) /* include header-only SDL_main implementations */ -#if defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \ +#if defined(SDL_MAIN_USE_CALLBACKS) \ + || defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \ || defined(__3DS__) || defined(__NGAGE__) || defined(__PS2__) || defined(__PSP__) /* platforms which main (-equivalent) can be implemented in plain C */ diff --git a/include/SDL3/SDL_main_impl.h b/include/SDL3/SDL_main_impl.h index 6b73bbb3..2ba34271 100644 --- a/include/SDL3/SDL_main_impl.h +++ b/include/SDL3/SDL_main_impl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,6 +41,30 @@ # undef main #endif /* main */ +#ifdef SDL_MAIN_USE_CALLBACKS + +#if 0 + /* currently there are no platforms that _need_ a magic entry point here + for callbacks, but if one shows up, implement it here. */ + +#else /* use a standard SDL_main, which the app SHOULD NOT ALSO SUPPLY. */ + +/* this define makes the normal SDL_main entry point stuff work...we just provide SDL_main() instead of the app. */ +#define SDL_MAIN_CALLBACK_STANDARD 1 + +int SDL_main(int argc, char **argv) +{ + return SDL_EnterAppMainCallbacks(argc, argv, SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit); +} + +#endif /* platform-specific tests */ + +#endif /* SDL_MAIN_USE_CALLBACKS */ + + +/* set up the usual SDL_main stuff if we're not using callbacks or if we are but need the normal entry point. */ +#if !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD) + #if defined(__WIN32__) || defined(__GDK__) /* these defines/typedefs are needed for the WinMain() definition */ @@ -48,12 +72,6 @@ #define WINAPI __stdcall #endif -#include - -#ifdef __cplusplus -extern "C" { -#endif - typedef struct HINSTANCE__ * HINSTANCE; typedef char* LPSTR; typedef wchar_t* PWSTR; @@ -82,6 +100,10 @@ int main(int argc, char *argv[]) #endif /* _MSC_VER && ! __GDK__ */ /* This is where execution begins [windowed apps and GDK] */ + +#ifdef __cplusplus +extern "C" { +#endif #if defined( UNICODE ) && UNICODE int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, PWSTR szCmdLine, int sw) #else /* ANSI */ @@ -94,13 +116,10 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) (void)sw; return SDL_RunApp(0, NULL, SDL_main, NULL); } - #ifdef __cplusplus } /* extern "C" */ #endif -#include - /* end of __WIN32__ and __GDK__ impls */ #elif defined(__WINRT__) @@ -151,10 +170,16 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) #pragma comment(lib, "runtimeobject.lib") #endif +#ifdef __cplusplus +extern "C" { +#endif int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return SDL_RunApp(0, NULL, SDL_main, NULL); } +#ifdef __cplusplus +} /* extern "C" */ +#endif /* end of WinRT impl */ #elif defined(__NGAGE__) @@ -172,27 +197,17 @@ TInt E32Main() #else /* platforms that use a standard main() and just call SDL_RunApp(), like iOS and 3DS */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - int main(int argc, char *argv[]) { return SDL_RunApp(argc, argv, SDL_main, NULL); } -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#include - /* end of impls for standard-conforming platforms */ #endif /* __WIN32__ etc */ +#endif /* !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD) */ + /* rename users main() function to SDL_main() so it can be called from the wrappers above */ #define main SDL_main diff --git a/include/SDL3/SDL_messagebox.h b/include/SDL3/SDL_messagebox.h index e1ee10ae..17696c64 100644 --- a/include/SDL3/SDL_messagebox.h +++ b/include/SDL3/SDL_messagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/include/SDL3/SDL_metal.h b/include/SDL3/SDL_metal.h index 42ef3c86..016ee947 100644 --- a/include/SDL3/SDL_metal.h +++ b/include/SDL3/SDL_metal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_metal.h * - * \brief Header file for functions to creating Metal layers and views on SDL windows. + * Header file for functions to creating Metal layers and views on SDL windows. */ #ifndef SDL_metal_h_ @@ -37,7 +37,7 @@ extern "C" { #endif /** - * \brief A handle to a CAMetalLayer-backed NSView (macOS) or UIView (iOS/tvOS). + * A handle to a CAMetalLayer-backed NSView (macOS) or UIView (iOS/tvOS). * * \note This can be cast directly to an NSView or UIView. */ diff --git a/include/SDL3/SDL_misc.h b/include/SDL3/SDL_misc.h index 42426b52..e50d91bd 100644 --- a/include/SDL3/SDL_misc.h +++ b/include/SDL3/SDL_misc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_misc.h * - * \brief Include file for SDL API functions that don't fit elsewhere. + * Include file for SDL API functions that don't fit elsewhere. */ #ifndef SDL_misc_h_ diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h index f20d832e..d9df80df 100644 --- a/include/SDL3/SDL_mouse.h +++ b/include/SDL3/SDL_mouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_mouse.h * - * \brief Include file for SDL mouse event handling. + * Include file for SDL mouse event handling. */ #ifndef SDL_mouse_h_ @@ -43,7 +43,7 @@ typedef Uint32 SDL_MouseID; typedef struct SDL_Cursor SDL_Cursor; /**< Implementation dependent */ /** - * \brief Cursor types for SDL_CreateSystemCursor(). + * Cursor types for SDL_CreateSystemCursor(). */ typedef enum { @@ -59,11 +59,19 @@ typedef enum SDL_SYSTEM_CURSOR_SIZEALL, /**< Four pointed arrow pointing north, south, east, and west */ SDL_SYSTEM_CURSOR_NO, /**< Slashed circle or crossbones */ SDL_SYSTEM_CURSOR_HAND, /**< Hand */ + SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT, /**< Window resize top-left (or SIZENWSE) */ + SDL_SYSTEM_CURSOR_WINDOW_TOP, /**< Window resize top (or SIZENS) */ + SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT, /**< Window resize top-right (or SIZENESW) */ + SDL_SYSTEM_CURSOR_WINDOW_RIGHT, /**< Window resize right (or SIZEWE) */ + SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT, /**< Window resize bottom-right (or SIZENWSE) */ + SDL_SYSTEM_CURSOR_WINDOW_BOTTOM, /**< Window resize bottom (or SIZENS) */ + SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT, /**< Window resize bottom-left (or SIZENESW) */ + SDL_SYSTEM_CURSOR_WINDOW_LEFT, /**< Window resize left (or SIZEWE) */ SDL_NUM_SYSTEM_CURSORS } SDL_SystemCursor; /** - * \brief Scroll direction types for the Scroll event + * Scroll direction types for the Scroll event */ typedef enum { diff --git a/include/SDL3/SDL_mutex.h b/include/SDL3/SDL_mutex.h index 19c6b555..d4610cb4 100644 --- a/include/SDL3/SDL_mutex.h +++ b/include/SDL3/SDL_mutex.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ /** * \file SDL_mutex.h * - * \brief Functions to provide thread synchronization primitives. + * Functions to provide thread synchronization primitives. */ #include @@ -121,11 +121,6 @@ extern "C" { */ #define SDL_MUTEX_TIMEDOUT 1 -/** - * This is the timeout value which corresponds to never time out. - */ -#define SDL_MUTEX_MAXWAIT -1 - /** * \name Mutex functions @@ -169,13 +164,15 @@ extern DECLSPEC SDL_Mutex *SDLCALL SDL_CreateMutex(void); * unlock it the same number of times before it is actually made available for * other threads in the system (this is known as a "recursive mutex"). * + * This function does not fail; if mutex is NULL, it will return immediately + * having locked nothing. If the mutex is valid, this function will always + * block until it can lock the mutex, and return with it locked. + * * \param mutex the mutex to lock - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC int SDLCALL SDL_LockMutex(SDL_Mutex *mutex) SDL_ACQUIRE(mutex); +extern DECLSPEC void SDLCALL SDL_LockMutex(SDL_Mutex *mutex) SDL_ACQUIRE(mutex); /** * Try to lock a mutex without blocking. @@ -186,9 +183,13 @@ extern DECLSPEC int SDLCALL SDL_LockMutex(SDL_Mutex *mutex) SDL_ACQUIRE(mutex); * This technique is useful if you need exclusive access to a resource but * don't want to wait for it, and will return to it to try again later. * + * This function does not fail; if mutex is NULL, it will return 0 immediately + * having locked nothing. If the mutex is valid, this function will always + * either lock the mutex and return 0, or return SDL_MUTEX_TIMEOUT and lock + * nothing. + * * \param mutex the mutex to try to lock - * \returns 0, `SDL_MUTEX_TIMEDOUT`, or -1 on error; call SDL_GetError() for - * more information. + * \returns 0 or `SDL_MUTEX_TIMEDOUT` * * \since This function is available since SDL 3.0.0. * @@ -210,12 +211,10 @@ extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_Mutex *mutex) SDL_TRY_ACQUIRE(0 * thread, and doing so results in undefined behavior. * * \param mutex the mutex to unlock. - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC int SDLCALL SDL_UnlockMutex(SDL_Mutex *mutex) SDL_RELEASE(mutex); +extern DECLSPEC void SDLCALL SDL_UnlockMutex(SDL_Mutex *mutex) SDL_RELEASE(mutex); /** * Destroy a mutex created with SDL_CreateMutex(). @@ -321,15 +320,17 @@ extern DECLSPEC SDL_RWLock *SDLCALL SDL_CreateRWLock(void); * lock before requesting a read-only lock. (But, of course, if you have the * write lock, you don't need further locks to read in any case.) * + * This function does not fail; if rwlock is NULL, it will return immediately + * having locked nothing. If the rwlock is valid, this function will always + * block until it can lock the mutex, and return with it locked. + * * \param rwlock the read/write lock to lock - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * * \sa SDL_UnlockRWLock */ -extern DECLSPEC int SDLCALL SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_ACQUIRE_SHARED(rwlock); +extern DECLSPEC void SDLCALL SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_ACQUIRE_SHARED(rwlock); /** * Lock the read/write lock for _write_ operations. @@ -348,15 +349,17 @@ extern DECLSPEC int SDLCALL SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_ACQ * read-only lock. Doing so results in undefined behavior. Unlock the * read-only lock before requesting a write lock. * + * This function does not fail; if rwlock is NULL, it will return immediately + * having locked nothing. If the rwlock is valid, this function will always + * block until it can lock the mutex, and return with it locked. + * * \param rwlock the read/write lock to lock - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * * \sa SDL_UnlockRWLock */ -extern DECLSPEC int SDLCALL SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_ACQUIRE(rwlock); +extern DECLSPEC void SDLCALL SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_ACQUIRE(rwlock); /** * Try to lock a read/write lock _for reading_ without blocking. @@ -370,9 +373,13 @@ extern DECLSPEC int SDLCALL SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_ACQ * Trying to lock for read-only access can succeed if other threads are * holding read-only locks, as this won't prevent access. * + * This function does not fail; if rwlock is NULL, it will return 0 + * immediately having locked nothing. If rwlock is valid, this function will + * always either lock the rwlock and return 0, or return SDL_RWLOCK_TIMEOUT + * and lock nothing. + * * \param rwlock the rwlock to try to lock - * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for - * more information. + * \returns 0 or `SDL_RWLOCK_TIMEDOUT` * * \since This function is available since SDL 3.0.0. * @@ -400,9 +407,13 @@ extern DECLSPEC int SDLCALL SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) SDL_ * read-only lock. Doing so results in undefined behavior. Unlock the * read-only lock before requesting a write lock. * + * This function does not fail; if rwlock is NULL, it will return 0 + * immediately having locked nothing. If rwlock is valid, this function will + * always either lock the rwlock and return 0, or return SDL_RWLOCK_TIMEOUT + * and lock nothing. + * * \param rwlock the rwlock to try to lock - * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for - * more information. + * \returns 0 or `SDL_RWLOCK_TIMEDOUT` * * \since This function is available since SDL 3.0.0. * @@ -428,12 +439,10 @@ extern DECLSPEC int SDLCALL SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) SDL_ * thread, and doing so results in undefined behavior. * * \param rwlock the rwlock to unlock. - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC int SDLCALL SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_RELEASE_SHARED(rwlock); +extern DECLSPEC void SDLCALL SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_RELEASE_GENERIC(rwlock); /** * Destroy a read/write lock created with SDL_CreateRWLock(). @@ -521,7 +530,7 @@ extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_Semaphore *sem); * semaphore value. * * This function is the equivalent of calling SDL_WaitSemaphoreTimeout() with - * a time length of `SDL_MUTEX_MAXWAIT`. + * a time length of -1. * * \param sem the semaphore wait on * \returns 0 on success or a negative error code on failure; call @@ -708,7 +717,7 @@ extern DECLSPEC int SDLCALL SDL_BroadcastCondition(SDL_Condition *cond); * behavior. * * This function is the equivalent of calling SDL_WaitConditionTimeout() with - * a time length of `SDL_MUTEX_MAXWAIT`. + * a time length of -1. * * \param cond the condition variable to wait on * \param mutex the mutex used to coordinate thread access @@ -740,8 +749,8 @@ extern DECLSPEC int SDLCALL SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mu * * \param cond the condition variable to wait on * \param mutex the mutex used to coordinate thread access - * \param timeoutMS the maximum time to wait, in milliseconds, or - * `SDL_MUTEX_MAXWAIT` to wait indefinitely + * \param timeoutMS the maximum time to wait, in milliseconds, or -1 to wait + * indefinitely * \returns 0 if the condition variable is signaled, `SDL_MUTEX_TIMEDOUT` if * the condition is not signaled in the allotted time, or a negative * error code on failure; call SDL_GetError() for more information. diff --git a/include/SDL3/SDL_oldnames.h b/include/SDL3/SDL_oldnames.h index e5b5c9b5..bc5366ee 100644 --- a/include/SDL3/SDL_oldnames.h +++ b/include/SDL3/SDL_oldnames.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_oldnames.h * - * \brief Definitions to ease transition from SDL2 code + * Definitions to ease transition from SDL2 code */ #ifndef SDL_oldnames_h_ @@ -83,6 +83,7 @@ #define SDL_CONTROLLERDEVICEREMAPPED SDL_EVENT_GAMEPAD_REMAPPED #define SDL_CONTROLLERDEVICEREMOVED SDL_EVENT_GAMEPAD_REMOVED #define SDL_CONTROLLERSENSORUPDATE SDL_EVENT_GAMEPAD_SENSOR_UPDATE +#define SDL_CONTROLLERSTEAMHANDLEUPDATED SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED #define SDL_CONTROLLERTOUCHPADDOWN SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN #define SDL_CONTROLLERTOUCHPADMOTION SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION #define SDL_CONTROLLERTOUCHPADUP SDL_EVENT_GAMEPAD_TOUCHPAD_UP @@ -91,8 +92,8 @@ #define SDL_ControllerDeviceEvent SDL_GamepadDeviceEvent #define SDL_ControllerSensorEvent SDL_GamepadSensorEvent #define SDL_ControllerTouchpadEvent SDL_GamepadTouchpadEvent -#define SDL_DISPLAYEVENT_CONNECTED SDL_EVENT_DISPLAY_CONNECTED -#define SDL_DISPLAYEVENT_DISCONNECTED SDL_EVENT_DISPLAY_DISCONNECTED +#define SDL_DISPLAYEVENT_CONNECTED SDL_EVENT_DISPLAY_ADDED +#define SDL_DISPLAYEVENT_DISCONNECTED SDL_EVENT_DISPLAY_REMOVED #define SDL_DISPLAYEVENT_MOVED SDL_EVENT_DISPLAY_MOVED #define SDL_DISPLAYEVENT_ORIENTATION SDL_EVENT_DISPLAY_ORIENTATION #define SDL_DROPBEGIN SDL_EVENT_DROP_BEGIN @@ -124,7 +125,6 @@ #define SDL_RENDER_DEVICE_RESET SDL_EVENT_RENDER_DEVICE_RESET #define SDL_RENDER_TARGETS_RESET SDL_EVENT_RENDER_TARGETS_RESET #define SDL_SENSORUPDATE SDL_EVENT_SENSOR_UPDATE -#define SDL_SYSWMEVENT SDL_EVENT_SYSWM #define SDL_TEXTEDITING SDL_EVENT_TEXT_EDITING #define SDL_TEXTEDITING_EXT SDL_EVENT_TEXT_EDITING_EXT #define SDL_TEXTINPUT SDL_EVENT_TEXT_INPUT @@ -148,7 +148,7 @@ #define SDL_WINDOWEVENT_SIZE_CHANGED SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED #define SDL_WINDOWEVENT_TAKE_FOCUS SDL_EVENT_WINDOW_TAKE_FOCUS -/* ##SDL_gamepad.h */ +/* ##SDL_gamecontroller.h */ #define SDL_CONTROLLER_AXIS_INVALID SDL_GAMEPAD_AXIS_INVALID #define SDL_CONTROLLER_AXIS_LEFTX SDL_GAMEPAD_AXIS_LEFTX #define SDL_CONTROLLER_AXIS_LEFTY SDL_GAMEPAD_AXIS_LEFTY @@ -161,8 +161,8 @@ #define SDL_CONTROLLER_BINDTYPE_BUTTON SDL_GAMEPAD_BINDTYPE_BUTTON #define SDL_CONTROLLER_BINDTYPE_HAT SDL_GAMEPAD_BINDTYPE_HAT #define SDL_CONTROLLER_BINDTYPE_NONE SDL_GAMEPAD_BINDTYPE_NONE -#define SDL_CONTROLLER_BUTTON_A SDL_GAMEPAD_BUTTON_A -#define SDL_CONTROLLER_BUTTON_B SDL_GAMEPAD_BUTTON_B +#define SDL_CONTROLLER_BUTTON_A SDL_GAMEPAD_BUTTON_SOUTH +#define SDL_CONTROLLER_BUTTON_B SDL_GAMEPAD_BUTTON_EAST #define SDL_CONTROLLER_BUTTON_BACK SDL_GAMEPAD_BUTTON_BACK #define SDL_CONTROLLER_BUTTON_DPAD_DOWN SDL_GAMEPAD_BUTTON_DPAD_DOWN #define SDL_CONTROLLER_BUTTON_DPAD_LEFT SDL_GAMEPAD_BUTTON_DPAD_LEFT @@ -182,8 +182,8 @@ #define SDL_CONTROLLER_BUTTON_RIGHTSTICK SDL_GAMEPAD_BUTTON_RIGHT_STICK #define SDL_CONTROLLER_BUTTON_START SDL_GAMEPAD_BUTTON_START #define SDL_CONTROLLER_BUTTON_TOUCHPAD SDL_GAMEPAD_BUTTON_TOUCHPAD -#define SDL_CONTROLLER_BUTTON_X SDL_GAMEPAD_BUTTON_X -#define SDL_CONTROLLER_BUTTON_Y SDL_GAMEPAD_BUTTON_Y +#define SDL_CONTROLLER_BUTTON_X SDL_GAMEPAD_BUTTON_WEST +#define SDL_CONTROLLER_BUTTON_Y SDL_GAMEPAD_BUTTON_NORTH #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT @@ -222,6 +222,7 @@ #define SDL_GameControllerGetSensorData SDL_GetGamepadSensorData #define SDL_GameControllerGetSensorDataRate SDL_GetGamepadSensorDataRate #define SDL_GameControllerGetSerial SDL_GetGamepadSerial +#define SDL_GameControllerGetSteamHandle SDL_GetGamepadSteamHandle #define SDL_GameControllerGetStringForAxis SDL_GetGamepadStringForAxis #define SDL_GameControllerGetStringForButton SDL_GetGamepadStringForButton #define SDL_GameControllerGetTouchpadFinger SDL_GetGamepadTouchpadFinger @@ -236,9 +237,7 @@ #define SDL_GameControllerIsSensorEnabled SDL_GamepadSensorEnabled #define SDL_GameControllerMapping SDL_GetGamepadMapping #define SDL_GameControllerMappingForGUID SDL_GetGamepadMappingForGUID -#define SDL_GameControllerMappingForIndex SDL_GetGamepadMappingForIndex #define SDL_GameControllerName SDL_GetGamepadName -#define SDL_GameControllerNumMappings SDL_GetNumGamepadMappings #define SDL_GameControllerOpen SDL_OpenGamepad #define SDL_GameControllerPath SDL_GetGamepadPath #define SDL_GameControllerRumble SDL_RumbleGamepad @@ -395,6 +394,7 @@ #define SDL_RenderDrawRectsF SDL_RenderRects #define SDL_RenderFillRectF SDL_RenderFillRect #define SDL_RenderFillRectsF SDL_RenderFillRects +#define SDL_RenderFlush SDL_FlushRenderer #define SDL_RenderGetClipRect SDL_GetRenderClipRect #define SDL_RenderGetLogicalSize SDL_GetRenderLogicalPresentation #define SDL_RenderGetMetalCommandEncoder SDL_GetRenderMetalCommandEncoder @@ -462,10 +462,6 @@ #define SDL_UpperBlit SDL_BlitSurface #define SDL_UpperBlitScaled SDL_BlitSurfaceScaled -/* ##SDL_system.h */ -#define SDL_RenderGetD3D11Device SDL_GetRenderD3D11Device -#define SDL_RenderGetD3D9Device SDL_GetRenderD3D9Device - /* ##SDL_thread.h */ #define SDL_TLSCleanup SDL_CleanupTLS #define SDL_TLSCreate SDL_CreateTLS @@ -524,23 +520,24 @@ #define SDL_AUDIODEVICEADDED SDL_AUDIODEVICEADDED_renamed_SDL_EVENT_AUDIO_DEVICE_ADDED #define SDL_AUDIODEVICEREMOVED SDL_AUDIODEVICEREMOVED_renamed_SDL_EVENT_AUDIO_DEVICE_REMOVED #define SDL_CLIPBOARDUPDATE SDL_CLIPBOARDUPDATE_renamed_SDL_EVENT_CLIPBOARD_UPDATE -#define SDL_CONTROLLERAXISMOTION SDL_CONTROLLERAXISMOTION_renamed_SDL_GAMEPADAXISMOTION -#define SDL_CONTROLLERBUTTONDOWN SDL_CONTROLLERBUTTONDOWN_renamed_SDL_GAMEPADBUTTONDOWN -#define SDL_CONTROLLERBUTTONUP SDL_CONTROLLERBUTTONUP_renamed_SDL_GAMEPADBUTTONUP +#define SDL_CONTROLLERAXISMOTION SDL_CONTROLLERAXISMOTION_renamed_SDL_EVENT_GAMEPAD_AXIS_MOTION +#define SDL_CONTROLLERBUTTONDOWN SDL_CONTROLLERBUTTONDOWN_renamed_SDL_EVENT_GAMEPAD_BUTTON_DOWN +#define SDL_CONTROLLERBUTTONUP SDL_CONTROLLERBUTTONUP_renamed_SDL_EVENT_GAMEPAD_BUTTON_UP #define SDL_CONTROLLERDEVICEADDED SDL_CONTROLLERDEVICEADDED_renamed_SDL_EVENT_GAMEPAD_ADDED #define SDL_CONTROLLERDEVICEREMAPPED SDL_CONTROLLERDEVICEREMAPPED_renamed_SDL_EVENT_GAMEPAD_REMAPPED #define SDL_CONTROLLERDEVICEREMOVED SDL_CONTROLLERDEVICEREMOVED_renamed_SDL_EVENT_GAMEPAD_REMOVED -#define SDL_CONTROLLERSENSORUPDATE SDL_CONTROLLERSENSORUPDATE_renamed_SDL_GAMEPADSENSORUPDATE -#define SDL_CONTROLLERTOUCHPADDOWN SDL_CONTROLLERTOUCHPADDOWN_renamed_SDL_GAMEPADTOUCHPADDOWN -#define SDL_CONTROLLERTOUCHPADMOTION SDL_CONTROLLERTOUCHPADMOTION_renamed_SDL_GAMEPADTOUCHPADMOTION -#define SDL_CONTROLLERTOUCHPADUP SDL_CONTROLLERTOUCHPADUP_renamed_SDL_GAMEPADTOUCHPADUP +#define SDL_CONTROLLERSENSORUPDATE SDL_CONTROLLERSENSORUPDATE_renamed_SDL_EVENT_GAMEPAD_SENSOR_UPDATE +#define SDL_CONTROLLERSTEAMHANDLEUPDATED SDL_CONTROLLERSTEAMHANDLEUPDATED_renamed_SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED +#define SDL_CONTROLLERTOUCHPADDOWN SDL_CONTROLLERTOUCHPADDOWN_renamed_SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN +#define SDL_CONTROLLERTOUCHPADMOTION SDL_CONTROLLERTOUCHPADMOTION_renamed_SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION +#define SDL_CONTROLLERTOUCHPADUP SDL_CONTROLLERTOUCHPADUP_renamed_SDL_EVENT_GAMEPAD_TOUCHPAD_UP #define SDL_ControllerAxisEvent SDL_ControllerAxisEvent_renamed_SDL_GamepadAxisEvent #define SDL_ControllerButtonEvent SDL_ControllerButtonEvent_renamed_SDL_GamepadButtonEvent #define SDL_ControllerDeviceEvent SDL_ControllerDeviceEvent_renamed_SDL_GamepadDeviceEvent #define SDL_ControllerSensorEvent SDL_ControllerSensorEvent_renamed_SDL_GamepadSensorEvent #define SDL_ControllerTouchpadEvent SDL_ControllerTouchpadEvent_renamed_SDL_GamepadTouchpadEvent -#define SDL_DISPLAYEVENT_CONNECTED SDL_DISPLAYEVENT_CONNECTED_renamed_SDL_EVENT_DISPLAY_CONNECTED -#define SDL_DISPLAYEVENT_DISCONNECTED SDL_DISPLAYEVENT_DISCONNECTED_renamed_SDL_EVENT_DISPLAY_DISCONNECTED +#define SDL_DISPLAYEVENT_CONNECTED SDL_DISPLAYEVENT_CONNECTED_renamed_SDL_EVENT_DISPLAY_ADDED +#define SDL_DISPLAYEVENT_DISCONNECTED SDL_DISPLAYEVENT_DISCONNECTED_renamed_SDL_EVENT_DISPLAY_REMOVED #define SDL_DISPLAYEVENT_MOVED SDL_DISPLAYEVENT_MOVED_renamed_SDL_EVENT_DISPLAY_MOVED #define SDL_DISPLAYEVENT_ORIENTATION SDL_DISPLAYEVENT_ORIENTATION_renamed_SDL_EVENT_DISPLAY_ORIENTATION #define SDL_DROPBEGIN SDL_DROPBEGIN_renamed_SDL_EVENT_DROP_BEGIN @@ -572,7 +569,6 @@ #define SDL_RENDER_DEVICE_RESET SDL_RENDER_DEVICE_RESET_renamed_SDL_EVENT_RENDER_DEVICE_RESET #define SDL_RENDER_TARGETS_RESET SDL_RENDER_TARGETS_RESET_renamed_SDL_EVENT_RENDER_TARGETS_RESET #define SDL_SENSORUPDATE SDL_SENSORUPDATE_renamed_SDL_EVENT_SENSOR_UPDATE -#define SDL_SYSWMEVENT SDL_SYSWMEVENT_renamed_SDL_EVENT_SYSWM #define SDL_TEXTEDITING SDL_TEXTEDITING_renamed_SDL_EVENT_TEXT_EDITING #define SDL_TEXTEDITING_EXT SDL_TEXTEDITING_EXT_renamed_SDL_EVENT_TEXT_EDITING_EXT #define SDL_TEXTINPUT SDL_TEXTINPUT_renamed_SDL_EVENT_TEXT_INPUT @@ -596,7 +592,7 @@ #define SDL_WINDOWEVENT_SIZE_CHANGED SDL_WINDOWEVENT_SIZE_CHANGED_renamed_SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED #define SDL_WINDOWEVENT_TAKE_FOCUS SDL_WINDOWEVENT_TAKE_FOCUS_renamed_SDL_EVENT_WINDOW_TAKE_FOCUS -/* ##SDL_gamepad.h */ +/* ##SDL_gamecontroller.h */ #define SDL_CONTROLLER_AXIS_INVALID SDL_CONTROLLER_AXIS_INVALID_renamed_SDL_GAMEPAD_AXIS_INVALID #define SDL_CONTROLLER_AXIS_LEFTX SDL_CONTROLLER_AXIS_LEFTX_renamed_SDL_GAMEPAD_AXIS_LEFTX #define SDL_CONTROLLER_AXIS_LEFTY SDL_CONTROLLER_AXIS_LEFTY_renamed_SDL_GAMEPAD_AXIS_LEFTY @@ -609,8 +605,8 @@ #define SDL_CONTROLLER_BINDTYPE_BUTTON SDL_CONTROLLER_BINDTYPE_BUTTON_renamed_SDL_GAMEPAD_BINDTYPE_BUTTON #define SDL_CONTROLLER_BINDTYPE_HAT SDL_CONTROLLER_BINDTYPE_HAT_renamed_SDL_GAMEPAD_BINDTYPE_HAT #define SDL_CONTROLLER_BINDTYPE_NONE SDL_CONTROLLER_BINDTYPE_NONE_renamed_SDL_GAMEPAD_BINDTYPE_NONE -#define SDL_CONTROLLER_BUTTON_A SDL_CONTROLLER_BUTTON_A_renamed_SDL_GAMEPAD_BUTTON_A -#define SDL_CONTROLLER_BUTTON_B SDL_CONTROLLER_BUTTON_B_renamed_SDL_GAMEPAD_BUTTON_B +#define SDL_CONTROLLER_BUTTON_A SDL_CONTROLLER_BUTTON_A_renamed_SDL_GAMEPAD_BUTTON_SOUTH +#define SDL_CONTROLLER_BUTTON_B SDL_CONTROLLER_BUTTON_B_renamed_SDL_GAMEPAD_BUTTON_EAST #define SDL_CONTROLLER_BUTTON_BACK SDL_CONTROLLER_BUTTON_BACK_renamed_SDL_GAMEPAD_BUTTON_BACK #define SDL_CONTROLLER_BUTTON_DPAD_DOWN SDL_CONTROLLER_BUTTON_DPAD_DOWN_renamed_SDL_GAMEPAD_BUTTON_DPAD_DOWN #define SDL_CONTROLLER_BUTTON_DPAD_LEFT SDL_CONTROLLER_BUTTON_DPAD_LEFT_renamed_SDL_GAMEPAD_BUTTON_DPAD_LEFT @@ -630,8 +626,8 @@ #define SDL_CONTROLLER_BUTTON_RIGHTSTICK SDL_CONTROLLER_BUTTON_RIGHTSTICK_renamed_SDL_GAMEPAD_BUTTON_RIGHT_STICK #define SDL_CONTROLLER_BUTTON_START SDL_CONTROLLER_BUTTON_START_renamed_SDL_GAMEPAD_BUTTON_START #define SDL_CONTROLLER_BUTTON_TOUCHPAD SDL_CONTROLLER_BUTTON_TOUCHPAD_renamed_SDL_GAMEPAD_BUTTON_TOUCHPAD -#define SDL_CONTROLLER_BUTTON_X SDL_CONTROLLER_BUTTON_X_renamed_SDL_GAMEPAD_BUTTON_X -#define SDL_CONTROLLER_BUTTON_Y SDL_CONTROLLER_BUTTON_Y_renamed_SDL_GAMEPAD_BUTTON_Y +#define SDL_CONTROLLER_BUTTON_X SDL_CONTROLLER_BUTTON_X_renamed_SDL_GAMEPAD_BUTTON_WEST +#define SDL_CONTROLLER_BUTTON_Y SDL_CONTROLLER_BUTTON_Y_renamed_SDL_GAMEPAD_BUTTON_NORTH #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT @@ -670,6 +666,7 @@ #define SDL_GameControllerGetSensorData SDL_GameControllerGetSensorData_renamed_SDL_GetGamepadSensorData #define SDL_GameControllerGetSensorDataRate SDL_GameControllerGetSensorDataRate_renamed_SDL_GetGamepadSensorDataRate #define SDL_GameControllerGetSerial SDL_GameControllerGetSerial_renamed_SDL_GetGamepadSerial +#define SDL_GameControllerGetSteamHandle SDL_GameControllerGetSteamHandle_renamed_SDL_GetGamepadSteamHandle #define SDL_GameControllerGetStringForAxis SDL_GameControllerGetStringForAxis_renamed_SDL_GetGamepadStringForAxis #define SDL_GameControllerGetStringForButton SDL_GameControllerGetStringForButton_renamed_SDL_GetGamepadStringForButton #define SDL_GameControllerGetTouchpadFinger SDL_GameControllerGetTouchpadFinger_renamed_SDL_GetGamepadTouchpadFinger @@ -685,9 +682,7 @@ #define SDL_GameControllerMapping SDL_GameControllerMapping_renamed_SDL_GetGamepadMapping #define SDL_GameControllerMappingForDeviceIndex SDL_GameControllerMappingForDeviceIndex_renamed_SDL_GetGamepadMappingForDeviceIndex #define SDL_GameControllerMappingForGUID SDL_GameControllerMappingForGUID_renamed_SDL_GetGamepadMappingForGUID -#define SDL_GameControllerMappingForIndex SDL_GameControllerMappingForIndex_renamed_SDL_GetGamepadMappingForIndex #define SDL_GameControllerName SDL_GameControllerName_renamed_SDL_GetGamepadName -#define SDL_GameControllerNumMappings SDL_GameControllerNumMappings_renamed_SDL_GetNumGamepadMappings #define SDL_GameControllerOpen SDL_GameControllerOpen_renamed_SDL_OpenGamepad #define SDL_GameControllerPath SDL_GameControllerPath_renamed_SDL_GetGamepadPath #define SDL_GameControllerRumble SDL_GameControllerRumble_renamed_SDL_RumbleGamepad @@ -844,6 +839,7 @@ #define SDL_RenderDrawRectsF SDL_RenderDrawRectsF_renamed_SDL_RenderRects #define SDL_RenderFillRectF SDL_RenderFillRectF_renamed_SDL_RenderFillRect #define SDL_RenderFillRectsF SDL_RenderFillRectsF_renamed_SDL_RenderFillRects +#define SDL_RenderFlush SDL_RenderFlush_renamed_SDL_FlushRenderer #define SDL_RenderGetClipRect SDL_RenderGetClipRect_renamed_SDL_GetRenderClipRect #define SDL_RenderGetLogicalSize SDL_RenderGetLogicalSize_renamed_SDL_GetRenderLogicalPresentation #define SDL_RenderGetMetalCommandEncoder SDL_RenderGetMetalCommandEncoder_renamed_SDL_GetRenderMetalCommandEncoder @@ -911,10 +907,6 @@ #define SDL_UpperBlit SDL_UpperBlit_renamed_SDL_BlitSurface #define SDL_UpperBlitScaled SDL_UpperBlitScaled_renamed_SDL_BlitSurfaceScaled -/* ##SDL_system.h */ -#define SDL_RenderGetD3D11Device SDL_RenderGetD3D11Device_renamed_SDL_GetRenderD3D11Device -#define SDL_RenderGetD3D9Device SDL_RenderGetD3D9Device_renamed_SDL_GetRenderD3D9Device - /* ##SDL_thread.h */ #define SDL_TLSCleanup SDL_TLSCleanup_renamed_SDL_CleanupTLS #define SDL_TLSCreate SDL_TLSCreate_renamed_SDL_CreateTLS diff --git a/include/SDL3/SDL_opengl.h b/include/SDL3/SDL_opengl.h index f771f3ea..9fb298f9 100644 --- a/include/SDL3/SDL_opengl.h +++ b/include/SDL3/SDL_opengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_opengl.h * - * \brief This is a simple file to encapsulate the OpenGL API headers. + * This is a simple file to encapsulate the OpenGL API headers. */ /** diff --git a/include/SDL3/SDL_opengles.h b/include/SDL3/SDL_opengles.h index 11f57868..8f4b6676 100644 --- a/include/SDL3/SDL_opengles.h +++ b/include/SDL3/SDL_opengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_opengles.h * - * \brief This is a simple file to encapsulate the OpenGL ES 1.X API headers. + * This is a simple file to encapsulate the OpenGL ES 1.X API headers. */ #include diff --git a/include/SDL3/SDL_opengles2.h b/include/SDL3/SDL_opengles2.h index eee6162f..a3b37885 100644 --- a/include/SDL3/SDL_opengles2.h +++ b/include/SDL3/SDL_opengles2.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_opengles2.h * - * \brief This is a simple file to encapsulate the OpenGL ES 2.0 API headers. + * This is a simple file to encapsulate the OpenGL ES 2.0 API headers. */ #include diff --git a/include/SDL3/SDL_pen.h b/include/SDL3/SDL_pen.h new file mode 100644 index 00000000..36c67b7c --- /dev/null +++ b/include/SDL3/SDL_pen.h @@ -0,0 +1,285 @@ +/* + Simple DirectMedia Layer + 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 + 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. +*/ + +/** + * \file SDL_pen.h + * + * Include file for SDL pen event handling. + * + * This file describes operations for pressure-sensitive pen (stylus and/or eraser) handling, e.g., for input + * and drawing tablets or suitably equipped mobile / tablet devices. + * + * To get started with pens: + * - Listen to ::SDL_PenMotionEvent and ::SDL_PenButtonEvent + * - To avoid treating pen events as mouse events, ignore ::SDL_MouseMotionEvent and ::SDL_MouseButtonEvent + * whenever "which" == ::SDL_PEN_MOUSEID. + * + * This header file describes advanced functionality that can be useful for managing user configuration + * and understanding the capabilities of the attached pens. + * + * We primarily identify pens by ::SDL_PenID. The implementation makes a best effort to relate each :SDL_PenID + * to the same physical device during a session. Formerly valid ::SDL_PenID values remain valid + * even if a device disappears. + * + * For identifying pens across sessions, the API provides the type ::SDL_GUID . + */ + +#ifndef SDL_pen_h_ +#define SDL_pen_h_ + +#include "SDL_error.h" +#include "SDL_guid.h" +#include "SDL_stdinc.h" + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef Uint32 SDL_PenID; /**< SDL_PenIDs identify pens uniquely within a session */ + +#define SDL_PEN_INVALID ((Uint32)0) /**< Reserved invalid ::SDL_PenID is valid */ + +#define SDL_PEN_MOUSEID ((Uint32)-2) /**< Device ID for mouse events triggered by pen events */ + +#define SDL_PEN_INFO_UNKNOWN (-1) /**< Marks unknown information when querying the pen */ + +/** + * Pen axis indices + * + * Below are the valid indices to the "axis" array from ::SDL_PenMotionEvent and ::SDL_PenButtonEvent. + * The axis indices form a contiguous range of ints from 0 to ::SDL_PEN_AXIS_LAST, inclusive. + * All "axis[]" entries are either normalised to 0..1 or report a (positive or negative) + * angle in degrees, with 0.0 representing the centre. + * Not all pens/backends support all axes: unsupported entries are always "0.0f". + * + * To convert angles for tilt and rotation into vector representation, use + * SDL_sinf on the XTILT, YTILT, or ROTATION component, e.g., "SDL_sinf(xtilt * SDL_PI_F / 180.0)". + */ +typedef enum +{ + SDL_PEN_AXIS_PRESSURE = 0, /**< Pen pressure. Unidirectional: 0..1.0 */ + SDL_PEN_AXIS_XTILT, /**< Pen horizontal tilt angle. Bidirectional: -90.0..90.0 (left-to-right). + The physical max/min tilt may be smaller than -90.0 / 90.0, cf. SDL_PenCapabilityInfo */ + SDL_PEN_AXIS_YTILT, /**< Pen vertical tilt angle. Bidirectional: -90.0..90.0 (top-to-down). + The physical max/min tilt may be smaller than -90.0 / 90.0, cf. SDL_PenCapabilityInfo */ + SDL_PEN_AXIS_DISTANCE, /**< Pen distance to drawing surface. Unidirectional: 0.0..1.0 */ + SDL_PEN_AXIS_ROTATION, /**< Pen barrel rotation. Bidirectional: -180..179.9 (clockwise, 0 is facing up, -180.0 is facing down). */ + SDL_PEN_AXIS_SLIDER, /**< Pen finger wheel or slider (e.g., Airbrush Pen). Unidirectional: 0..1.0 */ + SDL_PEN_NUM_AXES, /**< Last valid axis index */ + SDL_PEN_AXIS_LAST = SDL_PEN_NUM_AXES - 1 /**< Last axis index plus 1 */ +} SDL_PenAxis; + +/* Pen flags. These share a bitmask space with ::SDL_BUTTON_LEFT and friends. */ +#define SDL_PEN_FLAG_DOWN_BIT_INDEX 13 /* Bit for storing that pen is touching the surface */ +#define SDL_PEN_FLAG_INK_BIT_INDEX 14 /* Bit for storing has-non-eraser-capability status */ +#define SDL_PEN_FLAG_ERASER_BIT_INDEX 15 /* Bit for storing is-eraser or has-eraser-capability property */ +#define SDL_PEN_FLAG_AXIS_BIT_OFFSET 16 /* Bit for storing has-axis-0 property */ + +#define SDL_PEN_CAPABILITY(capbit) (1ul << (capbit)) +#define SDL_PEN_AXIS_CAPABILITY(axis) SDL_PEN_CAPABILITY((axis) + SDL_PEN_FLAG_AXIS_BIT_OFFSET) + +/** + * Pen tips + * @{ + */ +#define SDL_PEN_TIP_INK SDL_PEN_FLAG_INK_BIT_INDEX /**< Regular pen tip (for drawing) touched the surface */ +#define SDL_PEN_TIP_ERASER SDL_PEN_FLAG_ERASER_BIT_INDEX /**< Eraser pen tip touched the surface */ +/** @} */ + + +/** + * \defgroup SDL_PEN_CAPABILITIES Pen capabilities + * Pen capabilities reported by ::SDL_GetPenCapabilities + * @{ + */ +#define SDL_PEN_DOWN_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_DOWN_BIT_INDEX) /**< Pen tip is currently touching the drawing surface. */ +#define SDL_PEN_INK_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_INK_BIT_INDEX) /**< Pen has a regular drawing tip (::SDL_GetPenCapabilities). For events (::SDL_PenButtonEvent, ::SDL_PenMotionEvent, ::SDL_GetPenStatus) this flag is mutually exclusive with ::SDL_PEN_ERASER_MASK . */ +#define SDL_PEN_ERASER_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_ERASER_BIT_INDEX) /**< Pen has an eraser tip (::SDL_GetPenCapabilities) or is being used as eraser (::SDL_PenButtonEvent , ::SDL_PenMotionEvent , ::SDL_GetPenStatus) */ +#define SDL_PEN_AXIS_PRESSURE_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_PRESSURE) /**< Pen provides pressure information in axis ::SDL_PEN_AXIS_PRESSURE */ +#define SDL_PEN_AXIS_XTILT_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_XTILT) /**< Pen provides horizontal tilt information in axis ::SDL_PEN_AXIS_XTILT */ +#define SDL_PEN_AXIS_YTILT_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_YTILT) /**< Pen provides vertical tilt information in axis ::SDL_PEN_AXIS_YTILT */ +#define SDL_PEN_AXIS_DISTANCE_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_DISTANCE) /**< Pen provides distance to drawing tablet in ::SDL_PEN_AXIS_DISTANCE */ +#define SDL_PEN_AXIS_ROTATION_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_ROTATION) /**< Pen provides barrel rotation information in axis ::SDL_PEN_AXIS_ROTATION */ +#define SDL_PEN_AXIS_SLIDER_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_SLIDER) /**< Pen provides slider / finger wheel or similar in axis ::SDL_PEN_AXIS_SLIDER */ + +#define SDL_PEN_AXIS_BIDIRECTIONAL_MASKS (SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) +/**< Masks for all axes that may be bidirectional */ +/** @} */ + +/** + * Pen types + * + * Some pens identify as a particular type of drawing device (e.g., an airbrush or a pencil). + * + */ +typedef enum +{ + SDL_PEN_TYPE_ERASER = 1, /**< Eraser */ + SDL_PEN_TYPE_PEN, /**< Generic pen; this is the default. */ + SDL_PEN_TYPE_PENCIL, /**< Pencil */ + SDL_PEN_TYPE_BRUSH, /**< Brush-like device */ + SDL_PEN_TYPE_AIRBRUSH, /**< Airbrush device that "sprays" ink */ + SDL_PEN_TYPE_LAST = SDL_PEN_TYPE_AIRBRUSH /**< Last valid pen type */ +} SDL_PenSubtype; + + +/* Function prototypes */ + +/** + * Retrieves all pens that are connected to the system. + * + * Yields an array of ::SDL_PenID values. These identify and track pens + * throughout a session. To track pens across sessions (program restart), use + * ::SDL_GUID . + * + * \param count The number of pens in the array (number of array elements + * minus 1, i.e., not counting the terminator 0). + * \returns A 0 terminated array of ::SDL_PenID values, or NULL on error. The + * array must be freed with ::SDL_free(). On a NULL return, + * ::SDL_GetError() is set. + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC SDL_PenID *SDLCALL SDL_GetPens(int *count); + +/** + * Retrieves the pen's current status. + * + * If the pen is detached (cf. ::SDL_PenConnected), this operation may return + * default values. + * + * \param instance_id The pen to query. + * \param x Out-mode parameter for pen x coordinate. May be NULL. + * \param y Out-mode parameter for pen y coordinate. May be NULL. + * \param axes Out-mode parameter for axis information. May be null. The axes + * are in the same order as ::SDL_PenAxis. + * \param num_axes Maximum number of axes to write to "axes". + * \returns a bit mask with the current pen button states (::SDL_BUTTON_LMASK + * etc.), possibly ::SDL_PEN_DOWN_MASK, and exactly one of + * ::SDL_PEN_INK_MASK or ::SDL_PEN_ERASER_MASK , or 0 on error (see + * ::SDL_GetError()). + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetPenStatus(SDL_PenID instance_id, float *x, float *y, float *axes, size_t num_axes); + +/** + * Retrieves an ::SDL_PenID for the given ::SDL_GUID. + * + * \param guid A pen GUID. + * \returns A valid ::SDL_PenID, or ::SDL_PEN_INVALID if there is no matching + * SDL_PenID. + * + * \since This function is available since SDL 3.0.0 + * + * \sa SDL_GUID + */ +extern DECLSPEC SDL_PenID SDLCALL SDL_GetPenFromGUID(SDL_GUID guid); + +/** + * Retrieves the ::SDL_GUID for a given ::SDL_PenID. + * + * \param instance_id The pen to query. + * \returns The corresponding pen GUID; persistent across multiple sessions. + * If "instance_id" is ::SDL_PEN_INVALID, returns an all-zeroes GUID. + * + * \since This function is available since SDL 3.0.0 + * + * \sa SDL_PenForID + */ +extern DECLSPEC SDL_GUID SDLCALL SDL_GetPenGUID(SDL_PenID instance_id); + +/** + * Checks whether a pen is still attached. + * + * If a pen is detached, it will not show up for ::SDL_GetPens(). Other + * operations will still be available but may return default values. + * + * \param instance_id A pen ID. + * \returns SDL_TRUE if "instance_id" is valid and the corresponding pen is + * attached, or SDL_FALSE otherwise. + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_PenConnected(SDL_PenID instance_id); + +/** + * Retrieves a human-readable description for a ::SDL_PenID. + * + * \param instance_id The pen to query. + * \returns A string that contains the name of the pen, intended for human + * consumption. The string might or might not be localised, depending + * on platform settings. It is not guaranteed to be unique; use + * ::SDL_GetPenGUID() for (best-effort) unique identifiers. The + * pointer is managed by the SDL pen subsystem and must not be + * deallocated. The pointer remains valid until SDL is shut down. + * Returns NULL on error (cf. ::SDL_GetError()) + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC const char *SDLCALL SDL_GetPenName(SDL_PenID instance_id); + +/** + * Pen capabilities, as reported by ::SDL_GetPenCapabilities() + */ +typedef struct SDL_PenCapabilityInfo +{ + float max_tilt; /**< Physical maximum tilt angle, for XTILT and YTILT, or SDL_PEN_INFO_UNKNOWN . Pens cannot typically tilt all the way to 90 degrees, so this value is usually less than 90.0. */ + Uint32 wacom_id; /**< For Wacom devices: wacom tool type ID, otherwise 0 (useful e.g. with libwacom) */ + Sint8 num_buttons; /**< Number of pen buttons (not counting the pen tip), or SDL_PEN_INFO_UNKNOWN */ +} SDL_PenCapabilityInfo; + +/** + * Retrieves capability flags for a given ::SDL_PenID. + * + * \param instance_id The pen to query. + * \param capabilities Detail information about pen capabilities, such as the + * number of buttons + * \returns a set of capability flags, cf. SDL_PEN_CAPABILITIES + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetPenCapabilities(SDL_PenID instance_id, SDL_PenCapabilityInfo *capabilities); + +/** + * Retrieves the pen type for a given ::SDL_PenID. + * + * \param instance_id The pen to query. + * \returns The corresponding pen type (cf. ::SDL_PenSubtype) or 0 on error. + * Note that the pen type does not dictate whether the pen tip is + * ::SDL_PEN_TIP_INK or ::SDL_PEN_TIP_ERASER; to determine whether a + * pen is being used for drawing or in eraser mode, check either the + * pen tip on ::SDL_EVENT_PEN_DOWN, or the flag ::SDL_PEN_ERASER_MASK + * in the pen state. + * + * \since This function is available since SDL 3.0.0 + */ +extern DECLSPEC SDL_PenSubtype SDLCALL SDL_GetPenType(SDL_PenID instance_id); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif /* SDL_pen_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/include/SDL3/SDL_pixels.h b/include/SDL3/SDL_pixels.h index 6da0b93a..383def8c 100644 --- a/include/SDL3/SDL_pixels.h +++ b/include/SDL3/SDL_pixels.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,43 @@ /** * \file SDL_pixels.h * - * \brief Header for the enumerated pixel format definitions. + * Header for the enumerated pixel format definitions. + * + * SDL's pixel formats have the following naming convention: + * + * * Names with a list of components and a single bit count, such as + * RGB24 and ABGR32, define a platform-independent encoding into + * bytes in the order specified. For example, in RGB24 data, each + * pixel is encoded in 3 bytes (red, green, blue) in that order, + * and in ABGR32 data, each pixel is encoded in 4 bytes + * (alpha, blue, green, red) in that order. Use these names if the + * property of a format that is important to you is the order of + * the bytes in memory or on disk. + * + * * Names with a bit count per component, such as ARGB8888 and + * XRGB1555, are "packed" into an appropriately-sized integer in + * the platform's native endianness. For example, ARGB8888 is + * a sequence of 32-bit integers; in each integer, the most + * significant bits are alpha, and the least significant bits are + * blue. On a little-endian CPU such as x86, the least significant + * bits of each integer are arranged first in memory, but on a + * big-endian CPU such as s390x, the most significant bits are + * arranged first. Use these names if the property of a format that + * is important to you is the meaning of each bit position within a + * native-endianness integer. + * + * * In indexed formats such as INDEX4LSB, each pixel is represented + * by encoding an index into the palette into the indicated number + * of bits, with multiple pixels packed into each byte if appropriate. + * In LSB formats, the first (leftmost) pixel is stored in the + * least-significant bits of the byte; in MSB formats, it's stored + * in the most-significant bits. INDEX8 does not need LSB/MSB + * variants, because each pixel exactly fills one byte. + * + * The 32-bit byte-array encodings such as RGBA32 are aliases for the + * appropriate 8888 encoding for the current platform. For example, + * RGBA32 is an alias for ABGR8888 on little-endian CPUs like x86, + * or an alias for RGBA8888 on big-endian CPUs. */ #ifndef SDL_pixels_h_ @@ -61,7 +97,9 @@ typedef enum SDL_PIXELTYPE_ARRAYU16, SDL_PIXELTYPE_ARRAYU32, SDL_PIXELTYPE_ARRAYF16, - SDL_PIXELTYPE_ARRAYF32 + SDL_PIXELTYPE_ARRAYF32, + /* appended at the end for compatibility with sdl2-compat: */ + SDL_PIXELTYPE_INDEX2 } SDL_PixelType; /** Bitmap pixel order, high bit -> low bit. */ @@ -130,6 +168,7 @@ typedef enum #define SDL_ISPIXELFORMAT_INDEXED(format) \ (!SDL_ISPIXELFORMAT_FOURCC(format) && \ ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX1) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX2) || \ (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX4) || \ (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX8))) @@ -154,6 +193,10 @@ typedef enum (SDL_PIXELORDER(format) == SDL_PACKEDORDER_ABGR) || \ (SDL_PIXELORDER(format) == SDL_PACKEDORDER_BGRA)))) +#define SDL_ISPIXELFORMAT_10BIT(format) \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED32) && \ + (SDL_PIXELLAYOUT(format) == SDL_PACKEDLAYOUT_2101010)) + /* The flag is set to 1 because 0x1? is not in the printable ASCII range */ #define SDL_ISPIXELFORMAT_FOURCC(format) \ ((format) && (SDL_PIXELFLAG(format) != 1)) @@ -168,6 +211,12 @@ typedef enum SDL_PIXELFORMAT_INDEX1MSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, 1, 0), + SDL_PIXELFORMAT_INDEX2LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX2, SDL_BITMAPORDER_4321, 0, + 2, 0), + SDL_PIXELFORMAT_INDEX2MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX2, SDL_BITMAPORDER_1234, 0, + 2, 0), SDL_PIXELFORMAT_INDEX4LSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, 4, 0), @@ -255,9 +304,18 @@ typedef enum SDL_PIXELFORMAT_BGRA8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_XRGB2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_2101010, 32, 4), + SDL_PIXELFORMAT_XBGR2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_2101010, 32, 4), SDL_PIXELFORMAT_ARGB2101010 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_2101010, 32, 4), + SDL_PIXELFORMAT_ABGR2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_2101010, 32, 4), /* Aliases for RGBA byte arrays of color data, for the current platform */ #if SDL_BYTEORDER == SDL_BIG_ENDIAN diff --git a/include/SDL3/SDL_platform.h b/include/SDL3/SDL_platform.h index c9776156..289d02da 100644 --- a/include/SDL3/SDL_platform.h +++ b/include/SDL3/SDL_platform.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_platform.h * - * \brief Header file for platform functions. + * Header file for platform functions. */ #ifndef SDL_platform_h_ diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h index 565a0943..55a55c30 100644 --- a/include/SDL3/SDL_platform_defines.h +++ b/include/SDL3/SDL_platform_defines.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_platform_defines.h * - * \brief Try to get a standard set of platform defines. + * Try to get a standard set of platform defines. */ #ifndef SDL_platform_defines_h_ diff --git a/include/SDL3/SDL_power.h b/include/SDL3/SDL_power.h index db83ccf1..d957f237 100644 --- a/include/SDL3/SDL_power.h +++ b/include/SDL3/SDL_power.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ /** * \file SDL_power.h * - * \brief Header for the SDL power management routines. + * Header for the SDL power management routines. */ #include diff --git a/include/SDL3/SDL_properties.h b/include/SDL3/SDL_properties.h new file mode 100644 index 00000000..c5d3daca --- /dev/null +++ b/include/SDL3/SDL_properties.h @@ -0,0 +1,411 @@ +/* + Simple DiretMedia Layer + 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 + 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. +*/ + +/** + * \file SDL_properties.h + * + * Header file for SDL properties. + */ + +#ifndef SDL_properties_h_ +#define SDL_properties_h_ + +#include +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * SDL properties ID + */ +typedef Uint32 SDL_PropertiesID; + +/** + * SDL property type + */ +typedef enum +{ + SDL_PROPERTY_TYPE_INVALID, + SDL_PROPERTY_TYPE_POINTER, + SDL_PROPERTY_TYPE_STRING, + SDL_PROPERTY_TYPE_NUMBER, + SDL_PROPERTY_TYPE_FLOAT, + SDL_PROPERTY_TYPE_BOOLEAN, +} SDL_PropertyType; + +/** + * Get the global SDL properties + * + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGlobalProperties(void); + +/** + * Create a set of properties + * + * All properties are automatically destroyed when SDL_Quit() is called. + * + * \returns an ID for a new set of properties, or 0 on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_DestroyProperties + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_CreateProperties(void); + +/** + * Lock a set of properties + * + * Obtain a multi-threaded lock for these properties. Other threads will wait + * while trying to lock these properties until they are unlocked. Properties + * must be unlocked before they are destroyed. + * + * The lock is automatically taken when setting individual properties, this + * function is only needed when you want to set several properties atomically + * or want to guarantee that properties being queried aren't freed in another + * thread. + * + * \param props the properties to lock + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnlockProperties + */ +extern DECLSPEC int SDLCALL SDL_LockProperties(SDL_PropertiesID props); + +/** + * Unlock a set of properties + * + * \param props the properties to unlock + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_LockProperties + */ +extern DECLSPEC void SDLCALL SDL_UnlockProperties(SDL_PropertiesID props); + +/** + * Set a property on a set of properties with a cleanup function that is + * called when the property is deleted + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property, or NULL to delete the property + * \param cleanup the function to call when this property is deleted, or NULL + * if no cleanup is necessary + * \param userdata a pointer that is passed to the cleanup function + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC int SDLCALL SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata); + +/** + * Set a property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property, or NULL to delete the property + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetPropertyWithCleanup + */ +extern DECLSPEC int SDLCALL SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value); + +/** + * Set a string property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property, or NULL to delete the property + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetStringProperty + */ +extern DECLSPEC int SDLCALL SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value); + +/** + * Set an integer property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetNumberProperty + */ +extern DECLSPEC int SDLCALL SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value); + +/** + * Set a floating point property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetFloatProperty + */ +extern DECLSPEC int SDLCALL SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value); + +/** + * Set a boolean property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to modify + * \param value the new value of the property + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetBooleanProperty + */ +extern DECLSPEC int SDLCALL SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool value); + +/** + * Get the type of a property on a set of properties + * + * \param props the properties to query + * \param name the name of the property to query + * \returns the type of the property, or SDL_PROPERTY_TYPE_INVALID if it is + * not set. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC SDL_PropertyType SDLCALL SDL_GetPropertyType(SDL_PropertiesID props, const char *name); + +/** + * Get a property on a set of properties + * + * By convention, the names of properties that SDL exposes on objects will + * start with "SDL.", and properties that SDL uses internally will start with + * "SDL.internal.". These should be considered read-only and should not be + * modified by applications. + * + * \param props the properties to query + * \param name the name of the property to query + * \param default_value the default value of the property + * \returns the value of the property, or `default_value` if it is not set or + * not a pointer property. + * + * \threadsafety It is safe to call this function from any thread, although + * the data returned is not protected and could potentially be + * freed if you call SDL_SetProperty() or SDL_ClearProperty() on + * these properties from another thread. If you need to avoid + * this, use SDL_LockProperties() and SDL_UnlockProperties(). + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetPropertyType + * \sa SDL_SetProperty + */ +extern DECLSPEC void *SDLCALL SDL_GetProperty(SDL_PropertiesID props, const char *name, void *default_value); + +/** + * Get a string property on a set of properties + * + * \param props the properties to query + * \param name the name of the property to query + * \param default_value the default value of the property + * \returns the value of the property, or `default_value` if it is not set or + * not a string property. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetPropertyType + * \sa SDL_SetStringProperty + */ +extern DECLSPEC const char *SDLCALL SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value); + +/** + * Get a number property on a set of properties + * + * You can use SDL_GetPropertyType() to query whether the property exists and + * is a number property. + * + * \param props the properties to query + * \param name the name of the property to query + * \param default_value the default value of the property + * \returns the value of the property, or `default_value` if it is not set or + * not a number property. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetPropertyType + * \sa SDL_SetNumberProperty + */ +extern DECLSPEC Sint64 SDLCALL SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value); + +/** + * Get a floating point property on a set of properties + * + * You can use SDL_GetPropertyType() to query whether the property exists and + * is a floating point property. + * + * \param props the properties to query + * \param name the name of the property to query + * \param default_value the default value of the property + * \returns the value of the property, or `default_value` if it is not set or + * not a float property. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetPropertyType + * \sa SDL_SetFloatProperty + */ +extern DECLSPEC float SDLCALL SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value); + +/** + * Get a boolean property on a set of properties + * + * You can use SDL_GetPropertyType() to query whether the property exists and + * is a boolean property. + * + * \param props the properties to query + * \param name the name of the property to query + * \param default_value the default value of the property + * \returns the value of the property, or `default_value` if it is not set or + * not a float property. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetPropertyType + * \sa SDL_SetBooleanProperty + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool default_value); + +/** + * Clear a property on a set of properties + * + * \param props the properties to modify + * \param name the name of the property to clear + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetProperty + */ +extern DECLSPEC int SDLCALL SDL_ClearProperty(SDL_PropertiesID props, const char *name); + +typedef void (SDLCALL *SDL_EnumeratePropertiesCallback)(void *userdata, SDL_PropertiesID props, const char *name); + +/** + * Enumerate the properties on a set of properties + * + * The callback function is called for each property on the set of properties. + * The properties are locked during enumeration. + * + * \param props the properties to query + * \param callback the function to call for each property + * \param userdata a pointer that is passed to `callback` + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata); + +/** + * Destroy a set of properties + * + * All properties are deleted and their cleanup functions will be called, if + * any. + * + * \param props the properties to destroy + * + * \threadsafety This function should not be called while these properties are + * locked or other threads might be setting or getting values + * from these properties. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateProperties + */ +extern DECLSPEC void SDLCALL SDL_DestroyProperties(SDL_PropertiesID props); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_properties_h_ */ diff --git a/include/SDL3/SDL_quit.h b/include/SDL3/SDL_quit.h index 4087e601..1361dff4 100644 --- a/include/SDL3/SDL_quit.h +++ b/include/SDL3/SDL_quit.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_quit.h * - * \brief Include file for SDL quit event handling. + * Include file for SDL quit event handling. */ #ifndef SDL_quit_h_ diff --git a/include/SDL3/SDL_rect.h b/include/SDL3/SDL_rect.h index c50f168f..4d03884d 100644 --- a/include/SDL3/SDL_rect.h +++ b/include/SDL3/SDL_rect.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_rect.h * - * \brief Header file for SDL_rect definition and management functions. + * Header file for SDL_rect definition and management functions. */ #ifndef SDL_rect_h_ diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index d38484a1..2a5958df 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_render.h * - * \brief Header file for SDL 2D rendering functions. + * Header file for SDL 2D rendering functions. * * This API supports the following features: * * single pixel points @@ -50,6 +50,7 @@ #include #include +#include #include #include @@ -94,16 +95,6 @@ typedef struct SDL_Vertex SDL_FPoint tex_coord; /**< Normalized texture coordinates, if needed */ } SDL_Vertex; -/** - * The scaling mode for a texture. - */ -typedef enum -{ - SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ - SDL_SCALEMODE_LINEAR, /**< linear filtering */ - SDL_SCALEMODE_BEST /**< anisotropic filtering */ -} SDL_ScaleMode; - /** * The access pattern allowed for a texture. */ @@ -114,16 +105,6 @@ typedef enum SDL_TEXTUREACCESS_TARGET /**< Texture can be used as a render target */ } SDL_TextureAccess; -/** - * The texture channel modulation used in SDL_RenderTexture(). - */ -typedef enum -{ - SDL_TEXTUREMODULATE_NONE = 0x00000000, /**< No modulation */ - SDL_TEXTUREMODULATE_COLOR = 0x00000001, /**< srcC = srcC * color */ - SDL_TEXTUREMODULATE_ALPHA = 0x00000002 /**< srcA = srcA * alpha */ -} SDL_TextureModulate; - /** * Flip constants for SDL_RenderTextureRotated */ @@ -204,7 +185,6 @@ extern DECLSPEC int SDLCALL SDL_GetNumRenderDrivers(void); */ extern DECLSPEC const char *SDLCALL SDL_GetRenderDriver(int index); - /** * Create a window and default renderer. * @@ -224,14 +204,13 @@ extern DECLSPEC const char *SDLCALL SDL_GetRenderDriver(int index); */ extern DECLSPEC int SDLCALL SDL_CreateWindowAndRenderer(int width, int height, Uint32 window_flags, SDL_Window **window, SDL_Renderer **renderer); - /** * Create a 2D rendering context for a window. * * If you want a specific renderer, you can specify its name here. A list of * available renderers can be obtained by calling SDL_GetRenderDriver multiple * times, with indices from 0 to SDL_GetNumRenderDrivers()-1. If you don't - * need a specific renderer, specify NULL and SDL will attempt to chooes the + * need a specific renderer, specify NULL and SDL will attempt to choose the * best option for you, based on what is available on the user's system. * * By default the rendering size matches the window size in pixels, but you @@ -247,13 +226,46 @@ extern DECLSPEC int SDLCALL SDL_CreateWindowAndRenderer(int width, int height, U * * \since This function is available since SDL 3.0.0. * + * \sa SDL_CreateRendererWithProperties * \sa SDL_CreateSoftwareRenderer * \sa SDL_DestroyRenderer * \sa SDL_GetNumRenderDrivers * \sa SDL_GetRenderDriver * \sa SDL_GetRendererInfo */ -extern DECLSPEC SDL_Renderer *SDLCALL SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 flags); +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 flags); + +/** + * Create a 2D rendering context for a window, with the specified properties. + * + * These are the supported properties: + * + * - `SDL_PROPERTY_RENDERER_CREATE_WINDOW_POINTER`: the window where rendering + * is displayed + * - `SDL_PROPERTY_RENDERER_CREATE_SURFACE_POINTER`: the surface where + * rendering is displayed, if you want a software renderer without a window + * - `SDL_PROPERTY_RENDERER_CREATE_NAME_STRING`: the name of the rendering + * driver to use, if a specific one is desired + * - `SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN`: true if you want + * present synchronized with the refresh rate + * + * \param props the properties to use + * \returns a valid rendering context or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_CreateSoftwareRenderer + * \sa SDL_DestroyRenderer + * \sa SDL_GetRendererInfo + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_PropertiesID props); + +#define SDL_PROPERTY_RENDERER_CREATE_WINDOW_POINTER "window" +#define SDL_PROPERTY_RENDERER_CREATE_SURFACE_POINTER "surface" +#define SDL_PROPERTY_RENDERER_CREATE_NAME_STRING "name" +#define SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN "present_vsync" /** * Create a 2D software rendering context for a surface. @@ -315,6 +327,36 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetRenderWindow(SDL_Renderer *renderer); */ extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer *renderer, SDL_RendererInfo *info); +/** + * Get the properties associated with a renderer. + * + * The following read-only properties are provided by SDL: + * + * - `SDL_PROPERTY_RENDERER_D3D9_DEVICE_POINTER`: the IDirect3DDevice9 + * associated with the renderer + * - `SDL_PROPERTY_RENDERER_D3D11_DEVICE_POINTER`: the ID3D11Device associated + * with the renderer + * - `SDL_PROPERTY_RENDERER_D3D12_DEVICE_POINTER`: the ID3D12Device associated + * with the renderer + * - `SDL_PROPERTY_RENDERER_D3D12_COMMAND_QUEUE_POINTER`: the + * ID3D12CommandQueue associated with the renderer + * + * \param renderer the rendering context + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Renderer *renderer); + +#define SDL_PROPERTY_RENDERER_D3D9_DEVICE_POINTER "SDL.renderer.d3d9.device" +#define SDL_PROPERTY_RENDERER_D3D11_DEVICE_POINTER "SDL.renderer.d3d11.device" +#define SDL_PROPERTY_RENDERER_D3D12_DEVICE_POINTER "SDL.renderer.d3d12.device" +#define SDL_PROPERTY_RENDERER_D3D12_COMMAND_QUEUE_POINTER "SDL.renderer.d3d12.command_queue" + /** * Get the output size in pixels of a rendering context. * @@ -372,6 +414,7 @@ extern DECLSPEC int SDLCALL SDL_GetCurrentRenderOutputSize(SDL_Renderer *rendere * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateTextureFromSurface + * \sa SDL_CreateTextureWithProperties * \sa SDL_DestroyTexture * \sa SDL_QueryTexture * \sa SDL_UpdateTexture @@ -399,11 +442,217 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTexture(SDL_Renderer *renderer, U * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateTexture + * \sa SDL_CreateTextureWithProperties * \sa SDL_DestroyTexture * \sa SDL_QueryTexture */ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface); +/** + * Create a texture for a rendering context with the specified properties. + * + * These are the supported properties: + * + * - `SDL_PROPERTY_TEXTURE_CREATE_FORMAT_NUMBER`: one of the enumerated values + * in SDL_PixelFormatEnum, defaults to the best RGBA format for the renderer + * - `SDL_PROPERTY_TEXTURE_CREATE_ACCESS_NUMBER`: one of the enumerated values + * in SDL_TextureAccess, defaults to SDL_TEXTUREACCESS_STATIC + * - `SDL_PROPERTY_TEXTURE_CREATE_WIDTH_NUMBER`: the width of the texture in + * pixels, required + * - `SDL_PROPERTY_TEXTURE_CREATE_HEIGHT_NUMBER`: the height of the texture in + * pixels, required + * + * With the direct3d11 renderer: + * + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_POINTER`: the ID3D11Texture2D + * associated with the texture, if you want to wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER`: the + * ID3D11Texture2D associated with the U plane of a YUV texture, if you want + * to wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER`: the + * ID3D11Texture2D associated with the V plane of a YUV texture, if you want + * to wrap an existing texture. + * + * With the direct3d12 renderer: + * + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_POINTER`: the ID3D12Resource + * associated with the texture, if you want to wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER`: the ID3D12Resource + * associated with the U plane of a YUV texture, if you want to wrap an + * existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER`: the ID3D12Resource + * associated with the V plane of a YUV texture, if you want to wrap an + * existing texture. + * + * With the opengl renderer: + * + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER`: the GLuint texture + * associated with the texture, if you want to wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER`: the GLuint + * texture associated with the UV plane of an NV12 texture, if you want to + * wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER`: the GLuint texture + * associated with the U plane of a YUV texture, if you want to wrap an + * existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER`: the GLuint texture + * associated with the V plane of a YUV texture, if you want to wrap an + * existing texture. + * + * With the opengles2 renderer: + * + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER`: the GLuint + * texture associated with the texture, if you want to wrap an existing + * texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER`: the GLuint + * texture associated with the texture, if you want to wrap an existing + * texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER`: the GLuint + * texture associated with the UV plane of an NV12 texture, if you want to + * wrap an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER`: the GLuint + * texture associated with the U plane of a YUV texture, if you want to wrap + * an existing texture. + * - `SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER`: the GLuint + * texture associated with the V plane of a YUV texture, if you want to wrap + * an existing texture. + * + * \param renderer the rendering context + * \param props the properties to use + * \returns a pointer to the created texture or NULL if no rendering context + * was active, the format was unsupported, or the width or height + * were out of range; call SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateTextureFromSurface + * \sa SDL_CreateTexture + * \sa SDL_DestroyTexture + * \sa SDL_QueryTexture + * \sa SDL_UpdateTexture + */ +extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props); + +#define SDL_PROPERTY_TEXTURE_CREATE_FORMAT_NUMBER "format" +#define SDL_PROPERTY_TEXTURE_CREATE_ACCESS_NUMBER "access" +#define SDL_PROPERTY_TEXTURE_CREATE_WIDTH_NUMBER "width" +#define SDL_PROPERTY_TEXTURE_CREATE_HEIGHT_NUMBER "height" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_POINTER "d3d11.texture" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER "d3d11.texture_u" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER "d3d11.texture_v" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_POINTER "d3d12.texture" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER "d3d12.texture_u" +#define SDL_PROPERTY_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER "d3d12.texture_v" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER "opengl.texture" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER "opengl.texture_uv" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER "opengl.texture_u" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER "opengl.texture_v" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "opengles2.texture" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER "opengles2.texture" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER "opengles2.texture_uv" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "opengles2.texture_u" +#define SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "opengles2.texture_v" + + +/** + * Get the properties associated with a texture. + * + * The following read-only properties are provided by SDL: + * + * With the direct3d11 renderer: + * + * - `SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_POINTER`: the ID3D11Texture2D + * associated with the texture + * - `SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_U_POINTER`: the ID3D11Texture2D + * associated with the U plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_V_POINTER`: the ID3D11Texture2D + * associated with the V plane of a YUV texture + * + * With the direct3d12 renderer: + * + * - `SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_POINTER`: the ID3D12Resource + * associated with the texture + * - `SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_U_POINTER`: the ID3D12Resource + * associated with the U plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_V_POINTER`: the ID3D12Resource + * associated with the V plane of a YUV texture + * + * With the opengl renderer: + * + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_NUMBER`: the GLuint texture + * associated with the texture + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_UV_NUMBER`: the GLuint texture + * associated with the UV plane of an NV12 texture + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_U_NUMBER`: the GLuint texture + * associated with the U plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_V_NUMBER`: the GLuint texture + * associated with the V plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_TARGET`: the GLenum for the texture + * target (`GL_TEXTURE_2D`, `GL_TEXTURE_RECTANGLE_ARB`, etc) + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEX_W_FLOAT`: the texture coordinate width + * of the texture (0.0 - 1.0) + * - `SDL_PROPERTY_TEXTURE_OPENGL_TEX_H_FLOAT`: the texture coordinate height + * of the texture (0.0 - 1.0) + * + * With the opengles2 renderer: + * + * - `SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_NUMBER`: the GLuint texture + * associated with the texture + * - `SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER`: the GLuint texture + * associated with the UV plane of an NV12 texture + * - `SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER`: the GLuint texture + * associated with the U plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER`: the GLuint texture + * associated with the V plane of a YUV texture + * - `SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_TARGET`: the GLenum for the + * texture target (`GL_TEXTURE_2D`, `GL_TEXTURE_EXTERNAL_OES`, etc) + * + * \param texture the texture to query + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetTextureProperties(SDL_Texture *texture); + +#define SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_POINTER "SDL.texture.d3d11.texture" +#define SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_U_POINTER "SDL.texture.d3d11.texture_u" +#define SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_V_POINTER "SDL.texture.d3d11.texture_v" +#define SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_POINTER "SDL.texture.d3d12.texture" +#define SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_U_POINTER "SDL.texture.d3d12.texture_u" +#define SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_V_POINTER "SDL.texture.d3d12.texture_v" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_NUMBER "SDL.texture.opengl.texture" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_UV_NUMBER "SDL.texture.opengl.texture_uv" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_U_NUMBER "SDL.texture.opengl.texture_u" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_V_NUMBER "SDL.texture.opengl.texture_v" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_TARGET "SDL.texture.opengl.target" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEX_W_FLOAT "SDL.texture.opengl.tex_w" +#define SDL_PROPERTY_TEXTURE_OPENGL_TEX_H_FLOAT "SDL.texture.opengl.tex_h" +#define SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_NUMBER "SDL.texture.opengles2.texture" +#define SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER "SDL.texture.opengles2.texture_uv" +#define SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.opengles2.texture_u" +#define SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.opengles2.texture_v" +#define SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_TARGET "SDL.texture.opengles2.target" + +/** + * Get the renderer that created an SDL_Texture. + * + * \param texture the texture to query + * \returns a pointer to the SDL_Renderer that created the texture, or NULL on + * failure; call SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateTexture + * \sa SDL_CreateTextureFromSurface + * \sa SDL_CreateTextureWithProperties + */ +extern DECLSPEC SDL_Renderer *SDLCALL SDL_GetRendererFromTexture(SDL_Texture *texture); + /** * Query the attributes of a texture. * @@ -572,33 +821,6 @@ extern DECLSPEC int SDLCALL SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_Sc */ extern DECLSPEC int SDLCALL SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode); -/** - * Associate a user-specified pointer with a texture. - * - * \param texture the texture to update. - * \param userdata the pointer to associate with the texture. - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetTextureUserData - */ -extern DECLSPEC int SDLCALL SDL_SetTextureUserData(SDL_Texture *texture, void *userdata); - -/** - * Get the user-specified pointer associated with a texture - * - * \param texture the texture to query. - * \returns the pointer associated with the texture, or NULL if the texture is - * not valid. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_SetTextureUserData - */ -extern DECLSPEC void *SDLCALL SDL_GetTextureUserData(SDL_Texture *texture); - /** * Update the given texture rectangle with new pixel data. * @@ -1445,27 +1667,27 @@ extern DECLSPEC void SDLCALL SDL_DestroyTexture(SDL_Texture *texture); extern DECLSPEC void SDLCALL SDL_DestroyRenderer(SDL_Renderer *renderer); /** - * Force the rendering context to flush any pending commands to the underlying - * rendering API. + * Force the rendering context to flush any pending commands and state. * * You do not need to (and in fact, shouldn't) call this function unless you - * are planning to call into OpenGL/Direct3D/Metal/whatever directly in + * are planning to call into OpenGL/Direct3D/Metal/whatever directly, in * addition to using an SDL_Renderer. * - * This is for a very-specific case: if you are using SDL's render API, you - * asked for a specific renderer backend (OpenGL, Direct3D, etc), you set - * SDL_HINT_RENDER_BATCHING to "1", and you plan to make OpenGL/D3D/whatever - * calls in addition to SDL render API calls. If all of this applies, you - * should call SDL_RenderFlush() between calls to SDL's render API and the - * low-level API you're using in cooperation. + * This is for a very-specific case: if you are using SDL's render API, and + * you plan to make OpenGL/D3D/whatever calls in addition to SDL render API + * calls. If this applies, you should call this function between calls to + * SDL's render API and the low-level API you're using in cooperation. * - * In all other cases, you can ignore this function. This is only here to get - * maximum performance out of a specific situation. In all other cases, SDL - * will do the right thing, perhaps at a performance loss. + * In all other cases, you can ignore this function. * - * This function is first available in SDL 2.0.10, and is not needed in 2.0.9 - * and earlier, as earlier versions did not queue rendering commands at all, - * instead flushing them to the OS immediately. + * This call makes SDL flush any pending rendering work it was queueing up to + * do later in a single batch, and marks any internal cached state as invalid, + * so it'll prepare all its state again later, from scratch. + * + * This means you do not need to save state in your rendering code to protect + * the SDL renderer. However, there lots of arbitrary pieces of Direct3D and + * OpenGL state that can confuse things; you should use your best judgement + * and be prepared to make changes if specific state needs to be protected. * * \param renderer the rendering context * \returns 0 on success or a negative error code on failure; call @@ -1473,61 +1695,7 @@ extern DECLSPEC void SDLCALL SDL_DestroyRenderer(SDL_Renderer *renderer); * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC int SDLCALL SDL_RenderFlush(SDL_Renderer *renderer); - - -/** - * Bind an OpenGL/ES/ES2 texture to the current context. - * - * This is for use with OpenGL instructions when rendering OpenGL primitives - * directly. - * - * If not NULL, `texw` and `texh` will be filled with the width and height - * values suitable for the provided texture. In most cases, both will be 1.0, - * however, on systems that support the GL_ARB_texture_rectangle extension, - * these values will actually be the pixel width and height used to create the - * texture, so this factor needs to be taken into account when providing - * texture coordinates to OpenGL. - * - * You need a renderer to create an SDL_Texture, therefore you can only use - * this function with an implicit OpenGL context from SDL_CreateRenderer(), - * not with your own OpenGL context. If you need control over your OpenGL - * context, you need to write your own texture-loading methods. - * - * Also note that SDL may upload RGB textures as BGR (or vice-versa), and - * re-order the color channels in the shaders phase, so the uploaded texture - * may have swapped color channels. - * - * \param texture the texture to bind to the current OpenGL/ES/ES2 context - * \param texw a pointer to a float value which will be filled with the - * texture width or NULL if you don't need that value - * \param texh a pointer to a float value which will be filled with the - * texture height or NULL if you don't need that value - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GL_MakeCurrent - * \sa SDL_GL_UnbindTexture - */ -extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh); - -/** - * Unbind an OpenGL/ES/ES2 texture from the current context. - * - * See SDL_GL_BindTexture() for examples on how to use these functions - * - * \param texture the texture to unbind from the current OpenGL/ES/ES2 context - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GL_BindTexture - * \sa SDL_GL_MakeCurrent - */ -extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture); +extern DECLSPEC int SDLCALL SDL_FlushRenderer(SDL_Renderer *renderer); /** * Get the CAMetalLayer associated with the given Metal renderer. diff --git a/include/SDL3/SDL_revision.h b/include/SDL3/SDL_revision.h index 67a2a2b7..badf017d 100644 --- a/include/SDL3/SDL_revision.h +++ b/include/SDL3/SDL_revision.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_revision.h * - * \brief Header file containing the SDL revision + * Header file containing the SDL revision */ #ifndef SDL_revision_h_ diff --git a/include/SDL3/SDL_rwops.h b/include/SDL3/SDL_rwops.h index 6f6c3c01..557bdb81 100644 --- a/include/SDL3/SDL_rwops.h +++ b/include/SDL3/SDL_rwops.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,6 +31,7 @@ #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -99,6 +100,7 @@ typedef struct SDL_RWops Uint32 type; Uint32 status; + SDL_PropertiesID props; union { #ifdef __ANDROID__ @@ -331,6 +333,20 @@ extern DECLSPEC SDL_RWops *SDLCALL SDL_CreateRW(void); */ extern DECLSPEC void SDLCALL SDL_DestroyRW(SDL_RWops *context); +/** + * Get the properties associated with an SDL_RWops. + * + * \param context a pointer to an SDL_RWops structure + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRWProperties(SDL_RWops *context); + #define SDL_RW_SEEK_SET 0 /**< Seek from the beginning of data */ #define SDL_RW_SEEK_CUR 1 /**< Seek relative to current read point */ #define SDL_RW_SEEK_END 2 /**< Seek relative to the end of data */ @@ -474,11 +490,59 @@ extern DECLSPEC size_t SDLCALL SDL_RWread(SDL_RWops *context, void *ptr, size_t * \sa SDL_RWFromConstMem * \sa SDL_RWFromFile * \sa SDL_RWFromMem + * \sa SDL_RWprint * \sa SDL_RWread * \sa SDL_RWseek */ extern DECLSPEC size_t SDLCALL SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size); +/** + * Print to an SDL_RWops data stream. + * + * This function does formatted printing to the stream. + * + * \param context a pointer to an SDL_RWops structure + * \param fmt a printf() style format string + * \param ... additional parameters matching % tokens in the `fmt` string, if + * any + * \returns the number of bytes written, or 0 on error; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWwrite + */ +extern DECLSPEC size_t SDLCALL SDL_RWprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Print to an SDL_RWops data stream. + * + * This function does formatted printing to the stream. + * + * \param context a pointer to an SDL_RWops structure + * \param fmt a printf() style format string + * \param ap a variable argument list + * \returns the number of bytes written, or 0 on error; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWwrite + */ +extern DECLSPEC size_t SDLCALL SDL_RWvprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(2); + /** * Close and free an allocated SDL_RWops structure. * diff --git a/include/SDL3/SDL_scancode.h b/include/SDL3/SDL_scancode.h index 30b0a751..6d3728dd 100644 --- a/include/SDL3/SDL_scancode.h +++ b/include/SDL3/SDL_scancode.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_scancode.h * - * \brief Defines keyboard scancodes. + * Defines keyboard scancodes. */ #ifndef SDL_scancode_h_ @@ -31,7 +31,7 @@ #include /** - * \brief The SDL keyboard scancode representation. + * The SDL keyboard scancode representation. * * Values of this type are used to represent keyboard keys, among other places * in the \link SDL_Keysym::scancode key.keysym.scancode \endlink field of the diff --git a/include/SDL3/SDL_sensor.h b/include/SDL3/SDL_sensor.h index 5715cab9..6db5beb3 100644 --- a/include/SDL3/SDL_sensor.h +++ b/include/SDL3/SDL_sensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_sensor.h * - * \brief Include file for SDL sensor event handling + * Include file for SDL sensor event handling */ #ifndef SDL_sensor_h_ @@ -30,6 +30,7 @@ #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -40,7 +41,7 @@ extern "C" { #endif /** - * \brief SDL_sensor.h + * SDL_sensor.h * * In order to use these functions, SDL_Init() must have been called * with the ::SDL_INIT_SENSOR flag. This causes SDL to scan the system @@ -189,6 +190,20 @@ extern DECLSPEC SDL_Sensor *SDLCALL SDL_OpenSensor(SDL_SensorID instance_id); */ extern DECLSPEC SDL_Sensor *SDLCALL SDL_GetSensorFromInstanceID(SDL_SensorID instance_id); +/** + * Get the properties associated with a sensor. + * + * \param sensor The SDL_Sensor object + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSensorProperties(SDL_Sensor *sensor); + /** * Get the implementation dependent name of a sensor * diff --git a/include/SDL3/SDL_shape.h b/include/SDL3/SDL_shape.h deleted file mode 100644 index 4b7fb661..00000000 --- a/include/SDL3/SDL_shape.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_shape_h_ -#define SDL_shape_h_ - -#include -#include -#include -#include -#include - -#include -/* Set up for C function definitions, even when using C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/** \file SDL_shape.h - * - * \brief Header file for the shaped window API. - */ - -#define SDL_NONSHAPEABLE_WINDOW -1 -#define SDL_INVALID_SHAPE_ARGUMENT -2 -#define SDL_WINDOW_LACKS_SHAPE -3 - -/** - * Create a window that can be shaped with the specified dimensions and flags. - * - * \param title The title of the window, in UTF-8 encoding. - * \param w The width of the window. - * \param h The height of the window. - * \param flags The flags for the window, a mask of SDL_WINDOW_BORDERLESS with - * any of the following: ::SDL_WINDOW_OPENGL, - * ::SDL_WINDOW_MOUSE_GRABBED, ::SDL_WINDOW_HIDDEN, - * ::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED, - * ::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_BORDERLESS is always set, - * and ::SDL_WINDOW_FULLSCREEN is always unset. - * \returns the window created, or NULL if window creation failed. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_DestroyWindow - */ -extern DECLSPEC SDL_Window *SDLCALL SDL_CreateShapedWindow(const char *title, int w, int h, Uint32 flags); - -/** - * Return whether the given window is a shaped window. - * - * \param window The window to query for being shaped. - * \returns SDL_TRUE if the window is a window that can be shaped, SDL_FALSE - * if the window is unshaped or NULL. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_CreateShapedWindow - */ -extern DECLSPEC SDL_bool SDLCALL SDL_IsShapedWindow(const SDL_Window *window); - -/** \brief An enum denoting the specific type of contents present in an SDL_WindowShapeParams union. */ -typedef enum { - /** \brief The default mode, a binarized alpha cutoff of 1. */ - ShapeModeDefault, - /** \brief A binarized alpha cutoff with a given integer value. */ - ShapeModeBinarizeAlpha, - /** \brief A binarized alpha cutoff with a given integer value, but with the opposite comparison. */ - ShapeModeReverseBinarizeAlpha, - /** \brief A color key is applied. */ - ShapeModeColorKey -} WindowShapeMode; - -#define SDL_SHAPEMODEALPHA(mode) (mode == ShapeModeDefault || mode == ShapeModeBinarizeAlpha || mode == ShapeModeReverseBinarizeAlpha) - -/** \brief A union containing parameters for shaped windows. */ -typedef union { - /** \brief A cutoff alpha value for binarization of the window shape's alpha channel. */ - Uint8 binarizationCutoff; - SDL_Color colorKey; -} SDL_WindowShapeParams; - -/** \brief A struct that tags the SDL_WindowShapeParams union with an enum describing the type of its contents. */ -typedef struct SDL_WindowShapeMode { - /** \brief The mode of these window-shape parameters. */ - WindowShapeMode mode; - /** \brief Window-shape parameters. */ - SDL_WindowShapeParams parameters; -} SDL_WindowShapeMode; - -/** - * Set the shape and parameters of a shaped window. - * - * \param window The shaped window whose parameters should be set. - * \param shape A surface encoding the desired shape for the window. - * \param shape_mode The parameters to set for the shaped window. - * \returns 0 on success, SDL_INVALID_SHAPE_ARGUMENT on an invalid shape - * argument, or SDL_NONSHAPEABLE_WINDOW if the SDL_Window given does - * not reference a valid shaped window. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_WindowShapeMode - * \sa SDL_GetShapedWindowMode - */ -extern DECLSPEC int SDLCALL SDL_SetWindowShape(SDL_Window *window,SDL_Surface *shape,SDL_WindowShapeMode *shape_mode); - -/** - * Get the shape parameters of a shaped window. - * - * \param window The shaped window whose parameters should be retrieved. - * \param shape_mode An empty shape-mode structure to fill, or NULL to check - * whether the window has a shape. - * \returns 0 if the window has a shape and, provided shape_mode was not NULL, - * shape_mode has been filled with the mode data, - * SDL_NONSHAPEABLE_WINDOW if the SDL_Window given is not a shaped - * window, or SDL_WINDOW_LACKS_SHAPE if the SDL_Window given is a - * shapeable window currently lacking a shape. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_WindowShapeMode - * \sa SDL_SetWindowShape - */ -extern DECLSPEC int SDLCALL SDL_GetShapedWindowMode(SDL_Window *window,SDL_WindowShapeMode *shape_mode); - -/* Ends C function definitions when using C++ */ -#ifdef __cplusplus -} -#endif -#include - -#endif /* SDL_shape_h_ */ diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index 3643bb60..4f864873 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_stdinc.h * - * \brief This is a general header that includes C language support. + * This is a general header that includes C language support. */ #ifndef SDL_stdinc_h_ @@ -37,26 +37,34 @@ #include #include -#ifndef alloca -# ifdef HAVE_ALLOCA_H -# include -# elif defined(__GNUC__) -# define alloca __builtin_alloca -# elif defined(_MSC_VER) -# include -# define alloca _alloca -# elif defined(__WATCOMC__) -# include -# elif defined(__BORLANDC__) -# include -# elif defined(__DMC__) -# include -# elif defined(__AIX__) -#pragma alloca -# elif defined(__MRC__) +#ifndef SDL_DISABLE_ALLOCA +# ifndef alloca +# ifdef HAVE_ALLOCA_H +# include +# elif defined(__NETBSD__) +# if defined(__STRICT_ANSI__) +# define SDL_DISABLE_ALLOCA +# else +# include +# endif +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) +# pragma alloca +# elif defined(__MRC__) void *alloca(unsigned); -# else +# else char *alloca(); +# endif # endif #endif @@ -120,64 +128,58 @@ char *alloca(); */ /* @{ */ -#ifdef __CC_ARM -/* ARM's compiler throws warnings if we use an enum: like "SDL_bool x = a < b;" */ +/** + * A boolean type. + */ #define SDL_FALSE 0 #define SDL_TRUE 1 typedef int SDL_bool; -#else -typedef enum -{ - SDL_FALSE = 0, - SDL_TRUE = 1 -} SDL_bool; -#endif /** - * \brief A signed 8-bit integer type. + * A signed 8-bit integer type. */ #define SDL_MAX_SINT8 ((Sint8)0x7F) /* 127 */ #define SDL_MIN_SINT8 ((Sint8)(~0x7F)) /* -128 */ typedef int8_t Sint8; /** - * \brief An unsigned 8-bit integer type. + * An unsigned 8-bit integer type. */ #define SDL_MAX_UINT8 ((Uint8)0xFF) /* 255 */ #define SDL_MIN_UINT8 ((Uint8)0x00) /* 0 */ typedef uint8_t Uint8; /** - * \brief A signed 16-bit integer type. + * A signed 16-bit integer type. */ #define SDL_MAX_SINT16 ((Sint16)0x7FFF) /* 32767 */ #define SDL_MIN_SINT16 ((Sint16)(~0x7FFF)) /* -32768 */ typedef int16_t Sint16; /** - * \brief An unsigned 16-bit integer type. + * An unsigned 16-bit integer type. */ #define SDL_MAX_UINT16 ((Uint16)0xFFFF) /* 65535 */ #define SDL_MIN_UINT16 ((Uint16)0x0000) /* 0 */ typedef uint16_t Uint16; /** - * \brief A signed 32-bit integer type. + * A signed 32-bit integer type. */ #define SDL_MAX_SINT32 ((Sint32)0x7FFFFFFF) /* 2147483647 */ #define SDL_MIN_SINT32 ((Sint32)(~0x7FFFFFFF)) /* -2147483648 */ typedef int32_t Sint32; /** - * \brief An unsigned 32-bit integer type. + * An unsigned 32-bit integer type. */ #define SDL_MAX_UINT32 ((Uint32)0xFFFFFFFFu) /* 4294967295 */ #define SDL_MIN_UINT32 ((Uint32)0x00000000) /* 0 */ typedef uint32_t Uint32; /** - * \brief A signed 64-bit integer type. + * A signed 64-bit integer type. */ #define SDL_MAX_SINT64 ((Sint64)0x7FFFFFFFFFFFFFFFll) /* 9223372036854775807 */ #define SDL_MIN_SINT64 ((Sint64)(~0x7FFFFFFFFFFFFFFFll)) /* -9223372036854775808 */ typedef int64_t Sint64; /** - * \brief An unsigned 64-bit integer type. + * An unsigned 64-bit integer type. */ #define SDL_MAX_UINT64 ((Uint64)0xFFFFFFFFFFFFFFFFull) /* 18446744073709551615 */ #define SDL_MIN_UINT64 ((Uint64)(0x0000000000000000ull)) /* 0 */ @@ -285,7 +287,9 @@ typedef uint64_t Uint64; #define SDL_PRINTF_FORMAT_STRING #define SDL_SCANF_FORMAT_STRING #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) #define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) #define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) #else @@ -313,12 +317,16 @@ typedef uint64_t Uint64; #endif #ifdef __GNUC__ #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __printf__, fmtargnumber, fmtargnumber+1 ))) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) __attribute__(( format( __printf__, fmtargnumber, 0 ))) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __scanf__, fmtargnumber, fmtargnumber+1 ))) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) __attribute__(( format( __scanf__, fmtargnumber, 0 ))) #define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) /* __attribute__ (( format( __wprintf__, fmtargnumber, fmtargnumber+1 ))) */ #define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) /* __attribute__ (( format( __wscanf__, fmtargnumber, fmtargnumber+1 ))) */ #else #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) #define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) #define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) #endif @@ -379,7 +387,7 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); extern "C" { #endif -#ifdef HAVE_ALLOCA +#ifndef SDL_DISABLE_ALLOCA #define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) #define SDL_stack_free(data) #else @@ -538,6 +546,7 @@ extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, c extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen); extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr); extern DECLSPEC wchar_t *SDLCALL SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle); +extern DECLSPEC wchar_t *SDLCALL SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen); extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2); extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen); @@ -558,6 +567,7 @@ extern DECLSPEC char *SDLCALL SDL_strlwr(char *str); extern DECLSPEC char *SDLCALL SDL_strchr(const char *str, int c); extern DECLSPEC char *SDLCALL SDL_strrchr(const char *str, int c); extern DECLSPEC char *SDLCALL SDL_strstr(const char *haystack, const char *needle); +extern DECLSPEC char *SDLCALL SDL_strnstr(const char *haystack, const char *needle, size_t maxlen); extern DECLSPEC char *SDLCALL SDL_strcasestr(const char *haystack, const char *needle); extern DECLSPEC char *SDLCALL SDL_strtok_r(char *s1, const char *s2, char **saveptr); extern DECLSPEC size_t SDLCALL SDL_utf8strlen(const char *str); @@ -584,13 +594,13 @@ extern DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str2); extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t len); extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, ...) SDL_SCANF_VARARG_FUNC(2); -extern DECLSPEC int SDLCALL SDL_vsscanf(const char *text, const char *fmt, va_list ap); +extern DECLSPEC int SDLCALL SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_list ap) SDL_SCANF_VARARG_FUNCV(2); extern DECLSPEC int SDLCALL SDL_snprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ... ) SDL_PRINTF_VARARG_FUNC(3); extern DECLSPEC int SDLCALL SDL_swprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ... ) SDL_WPRINTF_VARARG_FUNC(3); -extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap); +extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(3); extern DECLSPEC int SDLCALL SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, const wchar_t *fmt, va_list ap); extern DECLSPEC int SDLCALL SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); -extern DECLSPEC int SDLCALL SDL_vasprintf(char **strp, const char *fmt, va_list ap); +extern DECLSPEC int SDLCALL SDL_vasprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(2); #ifndef SDL_PI_D #define SDL_PI_D 3.141592653589793238462643383279502884 /**< pi (double) */ @@ -684,8 +694,8 @@ extern DECLSPEC char *SDLCALL SDL_iconv_string(const char *tocode, const char *inbuf, size_t inbytesleft); #define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) -#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2-INTERNAL", "UTF-8", S, SDL_strlen(S)+1) -#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4-INTERNAL", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) #define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", (char *)S, (SDL_wcslen(S)+1)*sizeof(wchar_t)) /* force builds using Clang's static analysis tools to use literal C runtime diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index bdba4d07..7ccbda91 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,16 +22,17 @@ /** * \file SDL_surface.h * - * \brief Header file for ::SDL_Surface definition and management functions. + * Header file for ::SDL_Surface definition and management functions. */ #ifndef SDL_surface_h_ #define SDL_surface_h_ #include -#include -#include #include +#include +#include +#include #include #include @@ -49,11 +50,12 @@ extern "C" { * Used internally (read-only). */ /* @{ */ -#define SDL_SWSURFACE 0 /**< Just here for compatibility */ -#define SDL_PREALLOC 0x00000001 /**< Surface uses preallocated memory */ -#define SDL_RLEACCEL 0x00000002 /**< Surface is RLE encoded */ -#define SDL_DONTFREE 0x00000004 /**< Surface is referenced internally */ -#define SDL_SIMD_ALIGNED 0x00000008 /**< Surface uses aligned memory */ +#define SDL_SWSURFACE 0 /**< Just here for compatibility */ +#define SDL_PREALLOC 0x00000001 /**< Surface uses preallocated memory */ +#define SDL_RLEACCEL 0x00000002 /**< Surface is RLE encoded */ +#define SDL_DONTFREE 0x00000004 /**< Surface is referenced internally */ +#define SDL_SIMD_ALIGNED 0x00000008 /**< Surface uses aligned memory */ +#define SDL_SURFACE_USES_PROPERTIES 0x00000010 /**< Surface uses properties */ /* @} *//* Surface flags */ /** @@ -64,10 +66,35 @@ extern "C" { typedef struct SDL_BlitMap SDL_BlitMap; /* this is an opaque type. */ /** - * \brief A collection of pixels used in software blitting. + * The scaling mode + */ +typedef enum +{ + SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ + SDL_SCALEMODE_LINEAR, /**< linear filtering */ + SDL_SCALEMODE_BEST /**< anisotropic filtering */ +} SDL_ScaleMode; + + +/** + * A collection of pixels used in software blitting. + * + * Pixels are arranged in memory in rows, with the top row first. + * Each row occupies an amount of memory given by the pitch (sometimes + * known as the row stride in non-SDL APIs). + * + * Within each row, pixels are arranged from left to right until the + * width is reached. + * Each pixel occupies a number of bits appropriate for its format, with + * most formats representing each pixel as one or more whole bytes + * (in some indexed formats, instead multiple pixels are packed into + * each byte), and a byte order given by the format. + * After encoding all pixels, any remaining bytes to reach the pitch are + * used as padding to reach a desired alignment, and have undefined contents. * * \note This structure should be treated as read-only, except for \c pixels, * which, if not NULL, contains the raw pixel data for the surface. + * \sa SDL_CreateSurfaceFrom */ typedef struct SDL_Surface { @@ -77,8 +104,7 @@ typedef struct SDL_Surface int pitch; /**< Read-only */ void *pixels; /**< Read-write */ - /** Application data associated with the surface */ - void *userdata; /**< Read-write */ + void *reserved; /**< Private */ /** information needed for surfaces requiring locks */ int locked; /**< Read-only */ @@ -97,13 +123,13 @@ typedef struct SDL_Surface } SDL_Surface; /** - * \brief The type of function used for surface blitting functions. + * The type of function used for surface blitting functions. */ typedef int (SDLCALL *SDL_blit) (struct SDL_Surface *src, const SDL_Rect *srcrect, struct SDL_Surface *dst, const SDL_Rect *dstrect); /** - * \brief The formula used for converting between YUV and RGB + * The formula used for converting between YUV and RGB */ typedef enum { @@ -175,6 +201,20 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateSurfaceFrom */ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface); +/** + * Get the properties associated with a surface. + * + * \param surface the SDL_Surface structure to query + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *surface); + /** * Set the palette used by a surface. * @@ -817,10 +857,10 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceUnchecked SDL_Surface *dst, const SDL_Rect *dstrect); /** - * Perform a fast, low quality, stretch blit between two surfaces of the same - * format. + * Perform stretch blit between two surfaces of the same format. * - * **WARNING**: Please use SDL_BlitSurfaceScaled() instead. + * Using SDL_SCALEMODE_NEAREST: fast, low quality. Using SDL_SCALEMODE_LINEAR: + * bilinear scaling, slower, better quality, only 32BPP. * * \param src the SDL_Surface structure to be copied from * \param srcrect the SDL_Rect structure representing the rectangle to be @@ -828,35 +868,19 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceUnchecked * \param dst the SDL_Surface structure that is the blit target * \param dstrect the SDL_Rect structure representing the target rectangle in * the destination surface + * \param scaleMode scale algorithm to be used * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. + * + * \sa SDL_BlitSurfaceScaled */ extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, - const SDL_Rect *dstrect); - -/** - * Perform bilinear scaling between two surfaces of the same format, 32BPP. - * - * \param src the SDL_Surface structure to be copied from - * \param srcrect the SDL_Rect structure representing the rectangle to be - * copied - * \param dst the SDL_Surface structure that is the blit target - * \param dstrect the SDL_Rect structure representing the target rectangle in - * the destination surface - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC int SDLCALL SDL_SoftStretchLinear(SDL_Surface *src, - const SDL_Rect *srcrect, - SDL_Surface *dst, - const SDL_Rect *dstrect); - + const SDL_Rect *dstrect, + SDL_ScaleMode scaleMode); /** * Perform a scaled surface copy to a destination surface. @@ -868,6 +892,7 @@ extern DECLSPEC int SDLCALL SDL_SoftStretchLinear(SDL_Surface *src, * \param dstrect the SDL_Rect structure representing the target rectangle in * the destination surface, filled with the actual rectangle * used after clipping + * \param scaleMode scale algorithm to be used * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * @@ -875,9 +900,11 @@ extern DECLSPEC int SDLCALL SDL_SoftStretchLinear(SDL_Surface *src, * * \sa SDL_BlitSurface */ -extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled - (SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, SDL_Rect *dstrect); +extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled(SDL_Surface *src, + const SDL_Rect *srcrect, + SDL_Surface *dst, + SDL_Rect *dstrect, + SDL_ScaleMode scaleMode); /** * Perform low-level surface scaled blitting only. @@ -891,6 +918,7 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled * \param dst the SDL_Surface structure that is the blit target * \param dstrect the SDL_Rect structure representing the target rectangle in * the destination surface + * \param scaleMode scale algorithm to be used * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * @@ -898,9 +926,11 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled * * \sa SDL_BlitSurfaceScaled */ -extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled - (SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect); +extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, + const SDL_Rect *srcrect, + SDL_Surface *dst, + const SDL_Rect *dstrect, + SDL_ScaleMode scaleMode); /** * Set the YUV conversion mode diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 8b9539b9..2b20dbe7 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_system.h * - * \brief Include file for platform specific SDL API functions + * Include file for platform specific SDL API functions */ #ifndef SDL_system_h_ @@ -40,14 +40,21 @@ extern "C" { #endif -/* Platform specific functions for Windows */ +/* + * Platform specific functions for Windows + */ #if defined(__WIN32__) || defined(__GDK__) -typedef void (SDLCALL * SDL_WindowsMessageHook)(void *userdata, void *hWnd, unsigned int message, Uint64 wParam, Sint64 lParam); +typedef struct tagMSG MSG; +typedef SDL_bool (SDLCALL *SDL_WindowsMessageHook)(void *userdata, MSG *msg); /** * Set a callback for every Windows message, run before TranslateMessage(). * + * The callback may modify the message, and should return SDL_TRUE if the + * message should continue to be processed, or SDL_FALSE to prevent further + * processing. + * * \param callback The SDL_WindowsMessageHook function to call. * \param userdata a pointer to pass to every iteration of `callback` * @@ -73,60 +80,8 @@ extern DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(SDL_WindowsMessageHook ca */ extern DECLSPEC int SDLCALL SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID); -typedef struct IDirect3DDevice9 IDirect3DDevice9; - -/** - * Get the D3D9 device associated with a renderer. - * - * Once you are done using the device, you should release it to avoid a - * resource leak. - * - * \param renderer the renderer from which to get the associated D3D device - * \returns the D3D9 device associated with given renderer or NULL if it is - * not a D3D9 renderer; call SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC IDirect3DDevice9* SDLCALL SDL_GetRenderD3D9Device(SDL_Renderer * renderer); - -typedef struct ID3D11Device ID3D11Device; - -/** - * Get the D3D11 device associated with a renderer. - * - * Once you are done using the device, you should release it to avoid a - * resource leak. - * - * \param renderer the renderer from which to get the associated D3D11 device - * \returns the D3D11 device associated with given renderer or NULL if it is - * not a D3D11 renderer; call SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC ID3D11Device* SDLCALL SDL_GetRenderD3D11Device(SDL_Renderer * renderer); - #endif /* defined(__WIN32__) || defined(__WINGDK__) */ -#if defined(__WIN32__) || defined(__GDK__) - -typedef struct ID3D12Device ID3D12Device; - -/** - * Get the D3D12 device associated with a renderer. - * - * Once you are done using the device, you should release it to avoid a - * resource leak. - * - * \param renderer the renderer from which to get the associated D3D12 device - * \returns the D3D12 device associated with given renderer or NULL if it is - * not a D3D12 renderer; call SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC ID3D12Device* SDLCALL SDL_RenderGetD3D12Device(SDL_Renderer* renderer); - -#endif /* defined(__WIN32__) || defined(__GDK__) */ - #if defined(__WIN32__) || defined(__WINGDK__) /** @@ -148,7 +103,30 @@ extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, #endif /* defined(__WIN32__) || defined(__WINGDK__) */ -/* Platform specific functions for Linux */ +/* + * Platform specific functions for UNIX + */ + +typedef union _XEvent XEvent; +typedef SDL_bool (SDLCALL *SDL_X11EventHook)(void *userdata, XEvent *xevent); + +/** + * Set a callback for every X11 event + * + * The callback may modify the event, and should return SDL_TRUE if the event + * should continue to be processed, or SDL_FALSE to prevent further + * processing. + * + * \param callback The SDL_X11EventHook function to call. + * \param userdata a pointer to pass to every iteration of `callback` + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata); + +/* + * Platform specific functions for Linux + */ #ifdef __LINUX__ /** @@ -182,7 +160,9 @@ extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, #endif /* __LINUX__ */ -/* Platform specific functions for iOS */ +/* + * Platform specific functions for iOS + */ #ifdef __IOS__ #define SDL_iOSSetAnimationCallback(window, interval, callback, callbackParam) SDL_iPhoneSetAnimationCallback(window, interval, callback, callbackParam) @@ -242,7 +222,9 @@ extern DECLSPEC void SDLCALL SDL_iPhoneSetEventPump(SDL_bool enabled); #endif /* __IOS__ */ -/* Platform specific functions for Android */ +/* + * Platform specific functions for Android + */ #ifdef __ANDROID__ /** @@ -289,28 +271,31 @@ extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void); /** * Query Android API level of the current device. * - * - API level 31: Android 12 - * - API level 30: Android 11 - * - API level 29: Android 10 - * - API level 28: Android 9 - * - API level 27: Android 8.1 - * - API level 26: Android 8.0 - * - API level 25: Android 7.1 - * - API level 24: Android 7.0 - * - API level 23: Android 6.0 - * - API level 22: Android 5.1 - * - API level 21: Android 5.0 - * - API level 20: Android 4.4W - * - API level 19: Android 4.4 - * - API level 18: Android 4.3 - * - API level 17: Android 4.2 - * - API level 16: Android 4.1 - * - API level 15: Android 4.0.3 - * - API level 14: Android 4.0 - * - API level 13: Android 3.2 - * - API level 12: Android 3.1 - * - API level 11: Android 3.0 - * - API level 10: Android 2.3.3 + * - API level 34: Android 14 (UPSIDE_DOWN_CAKE) + * - API level 33: Android 13 (TIRAMISU) + * - API level 32: Android 12L (S_V2) + * - API level 31: Android 12 (S) + * - API level 30: Android 11 (R) + * - API level 29: Android 10 (Q) + * - API level 28: Android 9 (P) + * - API level 27: Android 8.1 (O_MR1) + * - API level 26: Android 8.0 (O) + * - API level 25: Android 7.1 (N_MR1) + * - API level 24: Android 7.0 (N) + * - API level 23: Android 6.0 (M) + * - API level 22: Android 5.1 (LOLLIPOP_MR1) + * - API level 21: Android 5.0 (LOLLIPOP, L) + * - API level 20: Android 4.4W (KITKAT_WATCH) + * - API level 19: Android 4.4 (KITKAT) + * - API level 18: Android 4.3 (JELLY_BEAN_MR2) + * - API level 17: Android 4.2 (JELLY_BEAN_MR1) + * - API level 16: Android 4.1 (JELLY_BEAN) + * - API level 15: Android 4.0.3 (ICE_CREAM_SANDWICH_MR1) + * - API level 14: Android 4.0 (ICE_CREAM_SANDWICH) + * - API level 13: Android 3.2 (HONEYCOMB_MR2) + * - API level 12: Android 3.1 (HONEYCOMB_MR1) + * - API level 11: Android 3.0 (HONEYCOMB) + * - API level 10: Android 2.3.3 (GINGERBREAD_MR1) * * \returns the Android API level. * @@ -468,48 +453,50 @@ extern DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param); #endif /* __ANDROID__ */ -/* Platform specific functions for WinRT */ +/* + * Platform specific functions for WinRT + */ #ifdef __WINRT__ /** - * \brief WinRT / Windows Phone path types + * WinRT / Windows Phone path types */ typedef enum { - /** \brief The installed app's root directory. + /** The installed app's root directory. Files here are likely to be read-only. */ SDL_WINRT_PATH_INSTALLED_LOCATION, - /** \brief The app's local data store. Files may be written here */ + /** The app's local data store. Files may be written here */ SDL_WINRT_PATH_LOCAL_FOLDER, - /** \brief The app's roaming data store. Unsupported on Windows Phone. + /** The app's roaming data store. Unsupported on Windows Phone. Files written here may be copied to other machines via a network connection. */ SDL_WINRT_PATH_ROAMING_FOLDER, - /** \brief The app's temporary data store. Unsupported on Windows Phone. + /** The app's temporary data store. Unsupported on Windows Phone. Files written here may be deleted at any time. */ SDL_WINRT_PATH_TEMP_FOLDER } SDL_WinRT_Path; /** - * \brief WinRT Device Family + * WinRT Device Family */ typedef enum { - /** \brief Unknown family */ + /** Unknown family */ SDL_WINRT_DEVICEFAMILY_UNKNOWN, - /** \brief Desktop family*/ + /** Desktop family*/ SDL_WINRT_DEVICEFAMILY_DESKTOP, - /** \brief Mobile family (for example smartphone) */ + /** Mobile family (for example smartphone) */ SDL_WINRT_DEVICEFAMILY_MOBILE, - /** \brief XBox family */ + /** XBox family */ SDL_WINRT_DEVICEFAMILY_XBOX, } SDL_WinRT_DeviceFamily; @@ -630,7 +617,9 @@ extern DECLSPEC void SDLCALL SDL_OnApplicationDidBecomeActive(void); extern DECLSPEC void SDLCALL SDL_OnApplicationDidChangeStatusBarOrientation(void); #endif -/* Functions used only by GDK */ +/* + * Functions used only by GDK + */ #ifdef __GDK__ typedef struct XTaskQueueObject *XTaskQueueHandle; typedef struct XUser *XUserHandle; diff --git a/include/SDL3/SDL_syswm.h b/include/SDL3/SDL_syswm.h deleted file mode 100644 index 9cb03d9e..00000000 --- a/include/SDL3/SDL_syswm.h +++ /dev/null @@ -1,387 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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. -*/ - -/** - * \file SDL_syswm.h - * - * \brief Include file for SDL custom system window manager hooks. - */ - -#ifndef SDL_syswm_h_ -#define SDL_syswm_h_ - -#include -#include -#include -#include - -/** - * \brief SDL_syswm.h - * - * Your application has access to a special type of event ::SDL_EVENT_SYSWM, - * which contains window-manager specific information and arrives whenever - * an unhandled window event occurs. This event is ignored by default, but - * you can enable it with SDL_SetEventEnabled(). - */ - -/** - * The available subsystems based on platform - */ -#ifndef SDL_DISABLE_SYSWM_PLATFORMS - -#ifndef SDL_DISABLE_SYSWM_ANDROID -#ifdef __ANDROID__ -#define SDL_ENABLE_SYSWM_ANDROID -#endif -#endif /* !SDL_DISABLE_SYSWM_ANDROID */ - -#ifndef SDL_DISABLE_SYSWM_COCOA -#ifdef __MACOS__ -#define SDL_ENABLE_SYSWM_COCOA -#endif -#endif /* !SDL_DISABLE_SYSWM_COCOA */ - -#ifndef SDL_DISABLE_SYSWM_HAIKU -#ifdef __HAIKU__ -#define SDL_ENABLE_SYSWM_HAIKU -#endif -#endif /* !SDL_DISABLE_SYSWM_HAIKU */ - -#ifndef SDL_DISABLE_SYSWM_KMSDRM -#if defined(__LINUX__) || defined(__FREEBSD__) || defined(__OPENBSD__) -#define SDL_ENABLE_SYSWM_KMSDRM -#endif -#endif /* !SDL_DISABLE_SYSWM_KMSDRM */ - -#ifndef SDL_DISABLE_SYSWM_RISCOS -#ifdef __RISCOS__ -#define SDL_ENABLE_SYSWM_RISCOS -#endif -#endif /* !SDL_DISABLE_SYSWM_RISCOS */ - -#ifndef SDL_DISABLE_SYSWM_UIKIT -#if defined(__IOS__) || defined(__TVOS__) -#define SDL_ENABLE_SYSWM_UIKIT -#endif -#endif /* !SDL_DISABLE_SYSWM_UIKIT */ - -#ifndef SDL_DISABLE_SYSWM_VIVANTE -/* Not enabled by default */ -#endif /* !SDL_DISABLE_SYSWM_VIVANTE */ - -#ifndef SDL_DISABLE_SYSWM_WAYLAND -#if defined(__LINUX__) || defined(__FREEBSD__) -#define SDL_ENABLE_SYSWM_WAYLAND -#endif -#endif /* !SDL_DISABLE_SYSWM_WAYLAND */ - -#ifndef SDL_DISABLE_SYSWM_WINDOWS -#if defined(__WIN32__) || defined(__GDK__) -#define SDL_ENABLE_SYSWM_WINDOWS -#endif -#endif /* !SDL_DISABLE_SYSWM_WINDOWS */ - -#ifndef SDL_DISABLE_SYSWM_WINRT -#ifdef __WINRT__ -#define SDL_ENABLE_SYSWM_WINRT -#endif -#endif /* !SDL_DISABLE_SYSWM_WINRT */ - -#ifndef SDL_DISABLE_SYSWM_X11 -#if defined(__unix__) && !defined(__WIN32__) && !defined(__ANDROID__) && !defined(__QNX__) -#define SDL_ENABLE_SYSWM_X11 -#endif -#endif /* !SDL_DISABLE_SYSWM_X11 */ - -#endif /* !SDL_DISABLE_SYSWM_PLATFORMS */ - -/** - * Forward declaration of types used by subsystems - */ -#ifndef SDL_DISABLE_SYSWM_TYPES - -#if defined(SDL_ENABLE_SYSWM_ANDROID) && !defined(SDL_DISABLE_SYSWM_ANDROID_TYPES) -typedef struct ANativeWindow ANativeWindow; -typedef void *EGLSurface; -#endif /* SDL_ENABLE_SYSWM_ANDROID */ - -#if defined(SDL_ENABLE_SYSWM_COCOA) && !defined(SDL_DISABLE_SYSWM_COCOA_TYPES) -#ifdef __OBJC__ -@class NSWindow; -#else -typedef struct _NSWindow NSWindow; -#endif -#endif /* SDL_ENABLE_SYSWM_COCOA */ - -#if defined(SDL_ENABLE_SYSWM_KMSDRM) && !defined(SDL_DISABLE_SYSWM_KMSDRM_TYPES) -struct gbm_device; -#endif /* SDL_ENABLE_SYSWM_KMSDRM */ - -#if defined(SDL_ENABLE_SYSWM_UIKIT) && !defined(SDL_DISABLE_SYSWM_UIKIT_TYPES) -#ifdef __OBJC__ -#include -#else -typedef struct _UIWindow UIWindow; -typedef struct _UIViewController UIViewController; -#endif -typedef Uint32 GLuint; -#endif /* SDL_ENABLE_SYSWM_UIKIT */ - -#if defined(SDL_ENABLE_SYSWM_VIVANTE) && !defined(SDL_DISABLE_SYSWM_VIVANTE_TYPES) -#include -#endif /* SDL_ENABLE_SYSWM_VIVANTE */ - -#if defined(SDL_ENABLE_SYSWM_WAYLAND) && !defined(SDL_DISABLE_SYSWM_WAYLAND_TYPES) -struct wl_display; -struct wl_egl_window; -struct wl_surface; -struct xdg_popup; -struct xdg_positioner; -struct xdg_surface; -struct xdg_toplevel; -#endif /* SDL_ENABLE_SYSWM_WAYLAND */ - -#if defined(SDL_ENABLE_SYSWM_WINDOWS) && !defined(SDL_DISABLE_SYSWM_WINDOWS_TYPES) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#ifndef NOMINMAX /* don't define min() and max(). */ -#define NOMINMAX -#endif -#include -#endif /* SDL_ENABLE_SYSWM_WINDOWS */ - -#if defined(SDL_ENABLE_SYSWM_WINRT) && !defined(SDL_DISABLE_SYSWM_WINRT_TYPES) -#include -#endif /* SDL_ENABLE_SYSWM_WINRT */ - -#if defined(SDL_ENABLE_SYSWM_X11) && !defined(SDL_DISABLE_SYSWM_X11_TYPES) -#include -#include -#endif /* SDL_ENABLE_SYSWM_X11 */ - -#endif /* !SDL_DISABLE_SYSWM_TYPES */ - - -#include -/* Set up for C function definitions, even when using C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/* This is the current version of structures in this file */ -#define SDL_SYSWM_CURRENT_VERSION 1 -#define SDL_SYSWM_INFO_SIZE_V1 (16 * (sizeof (void *) >= 8 ? sizeof (void *) : sizeof(Uint64))) -#define SDL_SYSWM_CURRENT_INFO_SIZE SDL_SYSWM_INFO_SIZE_V1 - -/* This is the tag associated with a Metal view so you can find it */ -#define SDL_METALVIEW_TAG 255 - - -/** - * These are the various supported windowing subsystems - */ -typedef enum -{ - SDL_SYSWM_UNKNOWN, - SDL_SYSWM_ANDROID, - SDL_SYSWM_COCOA, - SDL_SYSWM_HAIKU, - SDL_SYSWM_KMSDRM, - SDL_SYSWM_RISCOS, - SDL_SYSWM_UIKIT, - SDL_SYSWM_VIVANTE, - SDL_SYSWM_WAYLAND, - SDL_SYSWM_WINDOWS, - SDL_SYSWM_WINRT, - SDL_SYSWM_X11 -} SDL_SYSWM_TYPE; - -/** - * The custom event structure. - */ -struct SDL_SysWMmsg -{ - Uint32 version; - Uint32 subsystem; /**< SDL_SYSWM_TYPE */ - - Uint32 padding[(2 * (sizeof (void *) >= 8 ? sizeof (void *) : sizeof(Uint64)) - 2 * sizeof(Uint32)) / sizeof(Uint32)]; - - union - { -#ifdef SDL_ENABLE_SYSWM_WINDOWS - struct { - HWND hwnd; /**< The window for the message */ - UINT msg; /**< The type of message */ - WPARAM wParam; /**< WORD message parameter */ - LPARAM lParam; /**< LONG message parameter */ - } win; -#endif -#ifdef SDL_ENABLE_SYSWM_X11 - struct { - XEvent event; - } x11; -#endif - /* Can't have an empty union */ - int dummy; - } msg; -}; - -/** - * The custom window manager information structure. - * - * When this structure is returned, it holds information about which - * low level system it is using, and will be one of SDL_SYSWM_TYPE. - */ -struct SDL_SysWMinfo -{ - Uint32 version; - Uint32 subsystem; /**< SDL_SYSWM_TYPE */ - - Uint32 padding[(2 * (sizeof (void *) >= 8 ? sizeof (void *) : sizeof(Uint64)) - 2 * sizeof(Uint32)) / sizeof(Uint32)]; - - union - { -#ifdef SDL_ENABLE_SYSWM_WINDOWS - struct - { - HWND window; /**< The window handle */ - HDC hdc; /**< The window device context */ - HINSTANCE hinstance; /**< The instance handle */ - } win; -#endif -#ifdef SDL_ENABLE_SYSWM_WINRT - struct - { - IInspectable * window; /**< The WinRT CoreWindow */ - } winrt; -#endif -#ifdef SDL_ENABLE_SYSWM_X11 - struct - { - Display *display; /**< The X11 display */ - int screen; /**< The X11 screen */ - Window window; /**< The X11 window */ - } x11; -#endif -#ifdef SDL_ENABLE_SYSWM_COCOA - struct - { -#if defined(__OBJC__) && defined(__has_feature) - #if __has_feature(objc_arc) - NSWindow __unsafe_unretained *window; /**< The Cocoa window */ - #else - NSWindow *window; /**< The Cocoa window */ - #endif -#else - NSWindow *window; /**< The Cocoa window */ -#endif - } cocoa; -#endif -#ifdef SDL_ENABLE_SYSWM_UIKIT - struct - { -#if defined(__OBJC__) && defined(__has_feature) - #if __has_feature(objc_arc) - UIWindow __unsafe_unretained *window; /**< The UIKit window */ - #else - UIWindow *window; /**< The UIKit window */ - #endif -#else - UIWindow *window; /**< The UIKit window */ -#endif - GLuint framebuffer; /**< The GL view's Framebuffer Object. It must be bound when rendering to the screen using GL. */ - GLuint colorbuffer; /**< The GL view's color Renderbuffer Object. It must be bound when SDL_GL_SwapWindow is called. */ - GLuint resolveFramebuffer; /**< The Framebuffer Object which holds the resolve color Renderbuffer, when MSAA is used. */ - } uikit; -#endif -#ifdef SDL_ENABLE_SYSWM_WAYLAND - struct - { - struct wl_display *display; /**< Wayland display */ - struct wl_surface *surface; /**< Wayland surface */ - struct wl_egl_window *egl_window; /**< Wayland EGL window (native window) */ - struct xdg_surface *xdg_surface; /**< Wayland xdg surface (window manager handle) */ - struct xdg_toplevel *xdg_toplevel; /**< Wayland xdg toplevel role */ - struct xdg_popup *xdg_popup; /**< Wayland xdg popup role */ - struct xdg_positioner *xdg_positioner; /**< Wayland xdg positioner, for popup */ - } wl; -#endif - -#ifdef SDL_ENABLE_SYSWM_ANDROID - struct - { - ANativeWindow *window; - EGLSurface surface; - } android; -#endif - -#ifdef SDL_ENABLE_SYSWM_VIVANTE - struct - { - EGLNativeDisplayType display; - EGLNativeWindowType window; - } vivante; -#endif - -#ifdef SDL_ENABLE_SYSWM_KMSDRM - struct - { - int dev_index; /**< Device index (ex: the X in /dev/dri/cardX) */ - int drm_fd; /**< DRM FD (unavailable on Vulkan windows) */ - struct gbm_device *gbm_dev; /**< GBM device (unavailable on Vulkan windows) */ - } kmsdrm; -#endif - - /* Make sure this union has enough room for 14 pointers */ - void *dummy_ptrs[14]; - Uint64 dummy_ints[14]; - } info; -}; -SDL_COMPILE_TIME_ASSERT(SDL_SysWMinfo_size, sizeof(struct SDL_SysWMinfo) == SDL_SYSWM_CURRENT_INFO_SIZE); - -typedef struct SDL_SysWMinfo SDL_SysWMinfo; - - -/** - * Get driver-specific information about a window. - * - * You must include SDL_syswm.h for the declaration of SDL_SysWMinfo. - * - * \param window the window about which information is being requested - * \param info an SDL_SysWMinfo structure filled in with window information - * \param version the version of info being requested, should be - * SDL_SYSWM_CURRENT_VERSION - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - */ -extern DECLSPEC int SDLCALL SDL_GetWindowWMInfo(SDL_Window *window, SDL_SysWMinfo *info, Uint32 version); - - -/* Ends C function definitions when using C++ */ -#ifdef __cplusplus -} -#endif -#include - -#endif /* SDL_syswm_h_ */ diff --git a/include/SDL3/SDL_test.h b/include/SDL3/SDL_test.h index d66d26b0..634382cf 100644 --- a/include/SDL3/SDL_test.h +++ b/include/SDL3/SDL_test.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test.h * - * \brief Include file for SDL test framework. + * Include file for SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ diff --git a/include/SDL3/SDL_test_assert.h b/include/SDL3/SDL_test_assert.h index b140c9d9..122c355b 100644 --- a/include/SDL3/SDL_test_assert.h +++ b/include/SDL3/SDL_test_assert.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_assert.h * - * \brief Assertion functions of SDL test framework. + * Assertion functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -43,17 +43,17 @@ extern "C" { #endif /** - * \brief Fails the assert. + * Fails the assert. */ #define ASSERT_FAIL 0 /** - * \brief Passes the assert. + * Passes the assert. */ #define ASSERT_PASS 1 /** - * \brief Assert that logs and break execution flow on failures. + * Assert that logs and break execution flow on failures. * * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). * \param assertDescription Message to log with the assert describing it. @@ -61,7 +61,7 @@ extern "C" { void SDLTest_Assert(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(2); /** - * \brief Assert for test cases that logs but does not break execution flow on failures. Updates assertion counters. + * Assert for test cases that logs but does not break execution flow on failures. Updates assertion counters. * * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). * \param assertDescription Message to log with the assert describing it. @@ -71,25 +71,25 @@ void SDLTest_Assert(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *as int SDLTest_AssertCheck(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(2); /** - * \brief Explicitly pass without checking an assertion condition. Updates assertion counter. + * Explicitly pass without checking an assertion condition. Updates assertion counter. * * \param assertDescription Message to log with the assert describing it. */ void SDLTest_AssertPass(SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(1); /** - * \brief Resets the assert summary counters to zero. + * Resets the assert summary counters to zero. */ void SDLTest_ResetAssertSummary(void); /** - * \brief Logs summary of all assertions (total, pass, fail) since last reset as INFO or ERROR. + * Logs summary of all assertions (total, pass, fail) since last reset as INFO or ERROR. */ void SDLTest_LogAssertSummary(void); /** - * \brief Converts the current assert summary state to a test result. + * Converts the current assert summary state to a test result. * * \returns TEST_RESULT_PASSED, TEST_RESULT_FAILED, or TEST_RESULT_NO_ASSERT */ diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h index f2d66e51..0d428e1c 100644 --- a/include/SDL3/SDL_test_common.h +++ b/include/SDL3/SDL_test_common.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_common.h * - * \brief Common functions of SDL test framework. + * Common functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -139,7 +139,7 @@ extern "C" { /* Function prototypes */ /** - * \brief Parse command line parameters and create common state. + * Parse command line parameters and create common state. * * \param argv Array of command line parameters * \param flags Flags indicating which subsystem to initialize (i.e. SDL_INIT_VIDEO | SDL_INIT_AUDIO) @@ -149,14 +149,14 @@ extern "C" { SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, Uint32 flags); /** - * \brief Free the common state object. + * Free the common state object. * * \param state The common state object to destroy */ void SDLTest_CommonDestroyState(SDLTest_CommonState *state); /** - * \brief Process one common argument. + * Process one common argument. * * \param state The common state describing the test window to create. * \param index The index of the argument to process in argv[]. @@ -167,7 +167,7 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index); /** - * \brief Logs command line usage info. + * Logs command line usage info. * * This logs the appropriate command line options for the subsystems in use * plus other common options, and then any application-specific options. @@ -181,7 +181,7 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index); void SDLTest_CommonLogUsage(SDLTest_CommonState *state, const char *argv0, const char **options); /** - * \brief Open test window. + * Open test window. * * \param state The common state describing the test window to create. * @@ -190,7 +190,7 @@ void SDLTest_CommonLogUsage(SDLTest_CommonState *state, const char *argv0, const SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state); /** - * \brief Easy argument handling when test app doesn't need any custom args. + * Easy argument handling when test app doesn't need any custom args. * * \param state The common state describing the test window to create. * \param argc argc, as supplied to SDL_main @@ -201,17 +201,29 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state); SDL_bool SDLTest_CommonDefaultArgs(SDLTest_CommonState *state, const int argc, char **argv); /** - * \brief Common event handler for test windows. + * Common event handler for test windows if you use a standard SDL_main. + * + * This will free data from the event, like the string in a drop event! * * \param state The common state used to create test window. * \param event The event to handle. * \param done Flag indicating we are done. - * */ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done); /** - * \brief Close test window. + * Common event handler for test windows if you use SDL_AppEvent. + * + * This does _not_ free anything in `event`. + * + * \param state The common state used to create test window. + * \param event The event to handle. + * \returns Value suitable for returning from SDL_AppEvent(). + */ +int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event); + +/** + * Close test window. * * \param state The common state used to create test window. * @@ -219,7 +231,7 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done void SDLTest_CommonQuit(SDLTest_CommonState *state); /** - * \brief Draws various window information (position, size, etc.) to the renderer. + * Draws various window information (position, size, etc.) to the renderer. * * \param renderer The renderer to draw to. * \param window The window whose information should be displayed. diff --git a/include/SDL3/SDL_test_compare.h b/include/SDL3/SDL_test_compare.h index 6de16a62..4bd0db31 100644 --- a/include/SDL3/SDL_test_compare.h +++ b/include/SDL3/SDL_test_compare.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_compare.h * - * \brief Comparison function of SDL test framework. + * Comparison function of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -45,7 +45,28 @@ extern "C" { #endif /** - * \brief Compares a surface and with reference image data for equality + * Retrieves a single pixel from a surface. + * + * This function prioritizes correctness over speed: it is suitable for + * unit tests, but is not intended for use in a game engine. + * + * Like SDL_GetRGBA, this uses the entire 0..255 range when converting + * color components from pixel formats with less than 8 bits per RGB + * component. + * + * \param surface The surface + * \param x Horizontal coordinate, 0 <= x < width + * \param y Vertical coordinate, 0 <= y < height + * \param r Pointer to location to store red channel, 0-255 + * \param g Pointer to location to store green channel, 0-255 + * \param b Pointer to location to store blue channel, 0-255 + * \param a Pointer to location to store alpha channel, 0-255 + * \returns 0 if the surface is valid and the coordinates are in-bounds + */ +int SDLTest_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); + +/** + * Compares a surface and with reference image data for equality * * \param surface Surface used in comparison * \param referenceSurface Test Surface used in comparison diff --git a/include/SDL3/SDL_test_crc32.h b/include/SDL3/SDL_test_crc32.h index 8d7f2651..a4103f5c 100644 --- a/include/SDL3/SDL_test_crc32.h +++ b/include/SDL3/SDL_test_crc32.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_crc32.h * - * \brief CRC32 functions of SDL test framework. + * CRC32 functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -70,7 +70,7 @@ extern "C" { /* ---------- Function Prototypes ------------- */ /** - * \brief Initialize the CRC context + * Initialize the CRC context * * Note: The function initializes the crc table required for all crc calculations. * @@ -83,7 +83,7 @@ extern "C" { /** - * \brief calculate a crc32 from a data block + * calculate a crc32 from a data block * * \param crcContext pointer to context variable * \param inBuf input buffer to checksum @@ -102,7 +102,7 @@ int SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, C /** - * \brief clean up CRC context + * clean up CRC context * * \param crcContext pointer to context variable * diff --git a/include/SDL3/SDL_test_font.h b/include/SDL3/SDL_test_font.h index fd6194aa..824f97c2 100644 --- a/include/SDL3/SDL_test_font.h +++ b/include/SDL3/SDL_test_font.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_font.h * - * \brief Font related functions of SDL test framework. + * Font related functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -43,7 +43,7 @@ extern int FONT_CHARACTER_SIZE; #define FONT_LINE_HEIGHT (FONT_CHARACTER_SIZE + 2) /** - * \brief Draw a string in the currently set font. + * Draw a string in the currently set font. * * \param renderer The renderer to draw on. * \param x The X coordinate of the upper left corner of the character. @@ -55,7 +55,7 @@ extern int FONT_CHARACTER_SIZE; int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c); /** - * \brief Draw a UTF-8 string in the currently set font. + * Draw a UTF-8 string in the currently set font. * * The font currently only supports characters in the Basic Latin and Latin-1 Supplement sets. * @@ -69,7 +69,7 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c); int SDLTest_DrawString(SDL_Renderer *renderer, float x, float y, const char *s); /** - * \brief Data used for multi-line text output + * Data used for multi-line text output */ typedef struct SDLTest_TextWindow { @@ -80,7 +80,7 @@ typedef struct SDLTest_TextWindow } SDLTest_TextWindow; /** - * \brief Create a multi-line text output window + * Create a multi-line text output window * * \param x The X coordinate of the upper left corner of the window. * \param y The Y coordinate of the upper left corner of the window. @@ -94,7 +94,7 @@ typedef struct SDLTest_TextWindow SDLTest_TextWindow *SDLTest_TextWindowCreate(float x, float y, float w, float h); /** - * \brief Display a multi-line text output window + * Display a multi-line text output window * * This function should be called every frame to display the text * @@ -106,7 +106,7 @@ SDLTest_TextWindow *SDLTest_TextWindowCreate(float x, float y, float w, float h) void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *renderer); /** - * \brief Add text to a multi-line text output window + * Add text to a multi-line text output window * * Adds UTF-8 text to the end of the current text. The newline character starts a * new line of text. The backspace character deletes the last character or, if the @@ -121,7 +121,7 @@ void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *render void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); /** - * \brief Add text to a multi-line text output window + * Add text to a multi-line text output window * * Adds UTF-8 text to the end of the current text. The newline character starts a * new line of text. The backspace character deletes the last character or, if the @@ -136,7 +136,7 @@ void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, SDL_PRINTF_FORMAT_ST void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char *text, size_t len); /** - * \brief Clear the text in a multi-line text output window + * Clear the text in a multi-line text output window * * \param textwin The text output window * @@ -145,7 +145,7 @@ void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin); /** - * \brief Free the storage associated with a multi-line text output window + * Free the storage associated with a multi-line text output window * * \param textwin The text output window * @@ -154,7 +154,7 @@ void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin); void SDLTest_TextWindowDestroy(SDLTest_TextWindow *textwin); /** - * \brief Cleanup textures used by font drawing functions. + * Cleanup textures used by font drawing functions. */ void SDLTest_CleanupTextDrawing(void); diff --git a/include/SDL3/SDL_test_fuzzer.h b/include/SDL3/SDL_test_fuzzer.h index 2cfdbf53..14c5dc9b 100644 --- a/include/SDL3/SDL_test_fuzzer.h +++ b/include/SDL3/SDL_test_fuzzer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_fuzzer.h * - * \brief Fuzzer functions of SDL test framework. + * Fuzzer functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ diff --git a/include/SDL3/SDL_test_harness.h b/include/SDL3/SDL_test_harness.h index c8a7d120..b17e0b30 100644 --- a/include/SDL3/SDL_test_harness.h +++ b/include/SDL3/SDL_test_harness.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_harness.h * - * \brief Test suite related functions of SDL test framework. + * Test suite related functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -99,7 +99,7 @@ typedef struct SDLTest_TestSuiteReference { /** - * \brief Generates a random run seed string for the harness. The generated seed will contain alphanumeric characters (0-9A-Z). + * Generates a random run seed string for the harness. The generated seed will contain alphanumeric characters (0-9A-Z). * * Note: The returned string needs to be deallocated by the caller. * @@ -110,7 +110,7 @@ typedef struct SDLTest_TestSuiteReference { char *SDLTest_GenerateRunSeed(const int length); /** - * \brief Execute a test suite using the given run seed and execution key. + * Execute a test suite using the given run seed and execution key. * * \param testSuites Suites containing the test case. * \param userRunSeed Custom run seed provided by user, or NULL to autogenerate one. diff --git a/include/SDL3/SDL_test_log.h b/include/SDL3/SDL_test_log.h index fa4be923..315d0ae0 100644 --- a/include/SDL3/SDL_test_log.h +++ b/include/SDL3/SDL_test_log.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_log.h * - * \brief Logging related functions of SDL test framework. + * Logging related functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -43,14 +43,14 @@ extern "C" { #endif /** - * \brief Prints given message with a timestamp in the TEST category and INFO priority. + * Prints given message with a timestamp in the TEST category and INFO priority. * * \param fmt Message to be logged */ void SDLTest_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); /** - * \brief Prints given message with a timestamp in the TEST category and the ERROR priority. + * Prints given message with a timestamp in the TEST category and the ERROR priority. * * \param fmt Message to be logged */ diff --git a/include/SDL3/SDL_test_md5.h b/include/SDL3/SDL_test_md5.h index bf680c15..4aa06340 100644 --- a/include/SDL3/SDL_test_md5.h +++ b/include/SDL3/SDL_test_md5.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_md5.h * - * \brief MD5 related functions of SDL test framework. + * MD5 related functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -78,7 +78,7 @@ extern "C" { /* ---------- Function Prototypes ------------- */ /** - * \brief initialize the context + * initialize the context * * \param mdContext pointer to context variable * @@ -90,7 +90,7 @@ extern "C" { /** - * \brief update digest from variable length data + * update digest from variable length data * * \param mdContext pointer to context variable * \param inBuf pointer to data array/string @@ -106,7 +106,7 @@ extern "C" { /** - * \brief complete digest computation + * complete digest computation * * \param mdContext pointer to context variable * diff --git a/include/SDL3/SDL_test_memory.h b/include/SDL3/SDL_test_memory.h index f2f12881..6d858efd 100644 --- a/include/SDL3/SDL_test_memory.h +++ b/include/SDL3/SDL_test_memory.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_memory.h * - * \brief Memory tracking related functions of SDL test framework. + * Memory tracking related functions of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -38,21 +38,21 @@ extern "C" { /** - * \brief Start tracking SDL memory allocations + * Start tracking SDL memory allocations * * \note This should be called before any other SDL functions for complete tracking coverage */ void SDLTest_TrackAllocations(void); /** - * \brief Fill allocations with random data + * Fill allocations with random data * * \note This implicitly calls SDLTest_TrackAllocations() */ void SDLTest_RandFillAllocations(); /** - * \brief Print a log of any outstanding allocations + * Print a log of any outstanding allocations * * \note This can be called after SDL_Quit() */ diff --git a/include/SDL3/SDL_test_random.h b/include/SDL3/SDL_test_random.h index 50bc116b..5c3e84a8 100644 --- a/include/SDL3/SDL_test_random.h +++ b/include/SDL3/SDL_test_random.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_test_random.h * - * \brief Random number generator related function of SDL test framework. + * Random number generator related function of SDL test framework. * * This code is a part of the SDL test library, not the main SDL library. */ @@ -68,7 +68,7 @@ extern "C" { /* --- Function prototypes */ /** - * \brief Initialize random number generator with two integers. + * Initialize random number generator with two integers. * * Note: The random sequence of numbers returned by ...Random() is the * same for the same two integers and has a period of 2^31. @@ -81,7 +81,7 @@ extern "C" { void SDLTest_RandomInit(SDLTest_RandomContext *rndContext, unsigned int xi, unsigned int ci); /** - * \brief Initialize random number generator based on current system time. + * Initialize random number generator based on current system time. * * \param rndContext pointer to context structure * @@ -90,7 +90,7 @@ extern "C" { /** - * \brief Initialize random number generator based on current system time. + * Initialize random number generator based on current system time. * * Note: ...RandomInit() or ...RandomInitTime() must have been called * before using this function. diff --git a/include/SDL3/SDL_thread.h b/include/SDL3/SDL_thread.h index 313dd3fb..22b91cba 100644 --- a/include/SDL3/SDL_thread.h +++ b/include/SDL3/SDL_thread.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ /** * \file SDL_thread.h * - * \brief Header for the SDL thread management routines. + * Header for the SDL thread management routines. */ #include diff --git a/include/SDL3/SDL_timer.h b/include/SDL3/SDL_timer.h index e8049df8..b8762040 100644 --- a/include/SDL3/SDL_timer.h +++ b/include/SDL3/SDL_timer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ /** * \file SDL_timer.h * - * \brief Header for the SDL time management routines. + * Header for the SDL time management routines. */ #include diff --git a/include/SDL3/SDL_touch.h b/include/SDL3/SDL_touch.h index c374ba6a..4140668f 100644 --- a/include/SDL3/SDL_touch.h +++ b/include/SDL3/SDL_touch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_touch.h * - * \brief Include file for SDL touch event handling. + * Include file for SDL touch event handling. */ #ifndef SDL_touch_h_ @@ -65,46 +65,36 @@ typedef struct SDL_Finger /** - * Get the number of registered touch devices. + * Get a list of registered touch devices. * * On some platforms SDL first sees the touch device if it was actually used. - * Therefore SDL_GetNumTouchDevices() may return 0 although devices are - * available. After using all devices at least once the number will be - * correct. + * Therefore the returned list might be empty, although devices are available. + * After using all devices at least once the number will be correct. * * This was fixed for Android in SDL 2.0.1. * - * \returns the number of registered touch devices. + * \param count a pointer filled in with the number of devices returned, can + * be NULL. + * \returns a 0 terminated array of touch device IDs which should be freed + * with SDL_free(), or NULL on error; call SDL_GetError() for more + * details. * * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetTouchDevice */ -extern DECLSPEC int SDLCALL SDL_GetNumTouchDevices(void); +extern DECLSPEC SDL_TouchID *SDLCALL SDL_GetTouchDevices(int *count); /** - * Get the touch ID with the given index. + * Get the touch device name as reported from the driver. * - * \param index the touch device index - * \returns the touch ID with the given index on success or 0 if the index is - * invalid; call SDL_GetError() for more information. + * You do not own the returned string, do not modify or free it. * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetNumTouchDevices - */ -extern DECLSPEC SDL_TouchID SDLCALL SDL_GetTouchDevice(int index); - -/** - * Get the touch device name as reported from the driver or NULL if the index - * is invalid. - * - * \param index the touch device index - * \returns touch device name + * \param touchID the touch device instance ID. + * \returns touch device name, or NULL on error; call SDL_GetError() for more + * details. * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC const char* SDLCALL SDL_GetTouchName(int index); +extern DECLSPEC const char* SDLCALL SDL_GetTouchDeviceName(SDL_TouchID touchID); /** * Get the type of the given touch device. diff --git a/include/SDL3/SDL_version.h b/include/SDL3/SDL_version.h index 0c7a5989..61ab8548 100644 --- a/include/SDL3/SDL_version.h +++ b/include/SDL3/SDL_version.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_version.h * - * \brief This header defines the current SDL version. + * This header defines the current SDL version. */ #ifndef SDL_version_h_ diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index dd018de1..94942a30 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_video.h * - * \brief Header file for SDL video functions. + * Header file for SDL video functions. */ #ifndef SDL_video_h_ @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -44,7 +45,23 @@ typedef Uint32 SDL_DisplayID; typedef Uint32 SDL_WindowID; /** - * \brief System theme + * Global video properties + * + * - `SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER`: the pointer to + * the global `wl_display` object used by the Wayland video backend. Can be + * set before the video subsystem is initialized to import an external + * `wl_display` object from an application or toolkit for use in SDL, or + * read after initialization to export the `wl_display` used by the + * Wayland video backend. Setting this property after the video subsystem + * has been initialized has no effect, and reading it when the video + * subsystem is uninitialized will either return the user provided value, + * if one was set prior to initialization, or NULL. See + * docs/README-wayland.md for more information. + */ +#define SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "video.wayland.wl_display" + +/** + * System theme */ typedef enum { @@ -54,7 +71,7 @@ typedef enum } SDL_SystemTheme; /** - * \brief The structure that defines a display mode + * The structure that defines a display mode * * \sa SDL_GetFullscreenDisplayModes() * \sa SDL_GetDesktopDisplayMode() @@ -74,7 +91,7 @@ typedef struct } SDL_DisplayMode; /** - * \brief Display orientation + * Display orientation */ typedef enum { @@ -86,77 +103,70 @@ typedef enum } SDL_DisplayOrientation; /** - * \brief The type used to identify a window + * The type used to identify a window * - * \sa SDL_CreateWindow() - * \sa SDL_CreateWindowFrom() - * \sa SDL_CreateWindowWithPosition() - * \sa SDL_DestroyWindow() - * \sa SDL_FlashWindow() - * \sa SDL_GetWindowData() - * \sa SDL_GetWindowFlags() - * \sa SDL_GetWindowGrab() - * \sa SDL_GetWindowKeyboardGrab() - * \sa SDL_GetWindowMouseGrab() - * \sa SDL_GetWindowPosition() - * \sa SDL_GetWindowSize() - * \sa SDL_GetWindowTitle() - * \sa SDL_HideWindow() - * \sa SDL_MaximizeWindow() - * \sa SDL_MinimizeWindow() - * \sa SDL_RaiseWindow() - * \sa SDL_RestoreWindow() - * \sa SDL_SetWindowData() - * \sa SDL_SetWindowFullscreen() - * \sa SDL_SetWindowGrab() - * \sa SDL_SetWindowKeyboardGrab() - * \sa SDL_SetWindowMouseGrab() - * \sa SDL_SetWindowIcon() - * \sa SDL_SetWindowPosition() - * \sa SDL_SetWindowSize() - * \sa SDL_SetWindowBordered() - * \sa SDL_SetWindowResizable() - * \sa SDL_SetWindowTitle() - * \sa SDL_ShowWindow() - * \sa SDL_ShowWindowSystemMenu() + * \sa SDL_CreateWindow + * \sa SDL_CreateWindowWithProperties + * \sa SDL_DestroyWindow + * \sa SDL_FlashWindow + * \sa SDL_GetWindowFlags + * \sa SDL_GetWindowGrab + * \sa SDL_GetWindowKeyboardGrab + * \sa SDL_GetWindowMouseGrab + * \sa SDL_GetWindowPosition + * \sa SDL_GetWindowSize + * \sa SDL_GetWindowTitle + * \sa SDL_HideWindow + * \sa SDL_MaximizeWindow + * \sa SDL_MinimizeWindow + * \sa SDL_RaiseWindow + * \sa SDL_RestoreWindow + * \sa SDL_SetWindowFullscreen + * \sa SDL_SetWindowGrab + * \sa SDL_SetWindowKeyboardGrab + * \sa SDL_SetWindowMouseGrab + * \sa SDL_SetWindowIcon + * \sa SDL_SetWindowPosition + * \sa SDL_SetWindowSize + * \sa SDL_SetWindowBordered + * \sa SDL_SetWindowResizable + * \sa SDL_SetWindowTitle + * \sa SDL_ShowWindow + * \sa SDL_ShowWindowSystemMenu */ typedef struct SDL_Window SDL_Window; /** - * \brief The flags on a window + * The flags on a window * - * \sa SDL_GetWindowFlags() + * \sa SDL_GetWindowFlags */ -typedef enum -{ - SDL_WINDOW_FULLSCREEN = 0x00000001, /**< window is in fullscreen mode */ - SDL_WINDOW_OPENGL = 0x00000002, /**< window usable with OpenGL context */ - SDL_WINDOW_OCCLUDED = 0x00000004, /**< window is occluded */ - SDL_WINDOW_HIDDEN = 0x00000008, /**< window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow() is required for it to become visible */ - SDL_WINDOW_BORDERLESS = 0x00000010, /**< no window decoration */ - SDL_WINDOW_RESIZABLE = 0x00000020, /**< window can be resized */ - SDL_WINDOW_MINIMIZED = 0x00000040, /**< window is minimized */ - SDL_WINDOW_MAXIMIZED = 0x00000080, /**< window is maximized */ - SDL_WINDOW_MOUSE_GRABBED = 0x00000100, /**< window has grabbed mouse input */ - SDL_WINDOW_INPUT_FOCUS = 0x00000200, /**< window has input focus */ - SDL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has mouse focus */ - SDL_WINDOW_FOREIGN = 0x00000800, /**< window not created by SDL */ - SDL_WINDOW_HIGH_PIXEL_DENSITY = 0x00002000, /**< window uses high pixel density back buffer if possible */ - SDL_WINDOW_MOUSE_CAPTURE = 0x00004000, /**< window has mouse captured (unrelated to MOUSE_GRABBED) */ - SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000, /**< window should always be above others */ - SDL_WINDOW_UTILITY = 0x00020000, /**< window should be treated as a utility window, not showing in the task bar and window list */ - SDL_WINDOW_TOOLTIP = 0x00040000, /**< window should be treated as a tooltip and must be created using SDL_CreatePopupWindow() */ - SDL_WINDOW_POPUP_MENU = 0x00080000, /**< window should be treated as a popup menu and must be created using SDL_CreatePopupWindow() */ - SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000, /**< window has grabbed keyboard input */ - SDL_WINDOW_VULKAN = 0x10000000, /**< window usable for Vulkan surface */ - SDL_WINDOW_METAL = 0x20000000, /**< window usable for Metal view */ - SDL_WINDOW_TRANSPARENT = 0x40000000, /**< window with transparent buffer */ - SDL_WINDOW_NOT_FOCUSABLE = 0x80000000, /**< window should not be focusable */ - -} SDL_WindowFlags; +#define SDL_WINDOW_FULLSCREEN 0x00000001U /**< window is in fullscreen mode */ +#define SDL_WINDOW_OPENGL 0x00000002U /**< window usable with OpenGL context */ +#define SDL_WINDOW_OCCLUDED 0x00000004U /**< window is occluded */ +#define SDL_WINDOW_HIDDEN 0x00000008U /**< window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow() is required for it to become visible */ +#define SDL_WINDOW_BORDERLESS 0x00000010U /**< no window decoration */ +#define SDL_WINDOW_RESIZABLE 0x00000020U /**< window can be resized */ +#define SDL_WINDOW_MINIMIZED 0x00000040U /**< window is minimized */ +#define SDL_WINDOW_MAXIMIZED 0x00000080U /**< window is maximized */ +#define SDL_WINDOW_MOUSE_GRABBED 0x00000100U /**< window has grabbed mouse input */ +#define SDL_WINDOW_INPUT_FOCUS 0x00000200U /**< window has input focus */ +#define SDL_WINDOW_MOUSE_FOCUS 0x00000400U /**< window has mouse focus */ +#define SDL_WINDOW_EXTERNAL 0x00000800U /**< window not created by SDL */ +#define SDL_WINDOW_HIGH_PIXEL_DENSITY 0x00002000U /**< window uses high pixel density back buffer if possible */ +#define SDL_WINDOW_MOUSE_CAPTURE 0x00004000U /**< window has mouse captured (unrelated to MOUSE_GRABBED) */ +#define SDL_WINDOW_ALWAYS_ON_TOP 0x00008000U /**< window should always be above others */ +#define SDL_WINDOW_UTILITY 0x00020000U /**< window should be treated as a utility window, not showing in the task bar and window list */ +#define SDL_WINDOW_TOOLTIP 0x00040000U /**< window should be treated as a tooltip */ +#define SDL_WINDOW_POPUP_MENU 0x00080000U /**< window should be treated as a popup menu */ +#define SDL_WINDOW_KEYBOARD_GRABBED 0x00100000U /**< window has grabbed keyboard input */ +#define SDL_WINDOW_VULKAN 0x10000000U /**< window usable for Vulkan surface */ +#define SDL_WINDOW_METAL 0x20000000U /**< window usable for Metal view */ +#define SDL_WINDOW_TRANSPARENT 0x40000000U /**< window with transparent buffer */ +#define SDL_WINDOW_NOT_FOCUSABLE 0x80000000U /**< window should not be focusable */ /** - * \brief Used to indicate that you don't care what the window position is. + * Used to indicate that you don't care what the window position is. */ #define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000u #define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) @@ -165,7 +175,7 @@ typedef enum (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) /** - * \brief Used to indicate that the window position should be centered. + * Used to indicate that the window position should be centered. */ #define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000u #define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) @@ -174,7 +184,7 @@ typedef enum (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) /** - * \brief Window flash operation + * Window flash operation */ typedef enum { @@ -184,12 +194,12 @@ typedef enum } SDL_FlashOperation; /** - * \brief An opaque handle to an OpenGL context. + * An opaque handle to an OpenGL context. */ typedef void *SDL_GLContext; /** - * \brief Opaque EGL types. + * Opaque EGL types. */ typedef void *SDL_EGLDisplay; typedef void *SDL_EGLConfig; @@ -198,13 +208,13 @@ typedef intptr_t SDL_EGLAttrib; typedef int SDL_EGLint; /** - * \brief EGL attribute initialization callback types. + * EGL attribute initialization callback types. */ typedef SDL_EGLAttrib *(SDLCALL *SDL_EGLAttribArrayCallback)(void); typedef SDL_EGLint *(SDLCALL *SDL_EGLIntArrayCallback)(void); /** - * \brief OpenGL configuration attributes + * OpenGL configuration attributes */ typedef enum { @@ -340,6 +350,20 @@ extern DECLSPEC SDL_DisplayID *SDLCALL SDL_GetDisplays(int *count); */ extern DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); +/** + * Get the properties associated with a display. + * + * \param displayID the instance ID of the display to query + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetDisplayProperties(SDL_DisplayID displayID); + /** * Get the name of a display in UTF-8 encoding. * @@ -618,6 +642,16 @@ extern DECLSPEC float SDLCALL SDL_GetWindowDisplayScale(SDL_Window *window); * change the window size when the window is not fullscreen, use * SDL_SetWindowSize(). * + * If the window is currently in the fullscreen state, this request is + * asynchronous on some windowing systems and the new mode dimensions may not + * be applied immediately upon the return of this function. If an immediate + * change is required, call SDL_SyncWindow() to block until the changes have + * taken effect. + * + * When the new mode takes effect, an SDL_EVENT_WINDOW_RESIZED and/or an + * SDL_EVENT_WINDOOW_PIXEL_SIZE_CHANGED event will be emitted with the new + * mode dimensions. + * * \param window the window to affect * \param mode a pointer to the display mode to use, which can be NULL for * desktop mode, or one of the fullscreen modes returned by @@ -629,6 +663,7 @@ extern DECLSPEC float SDLCALL SDL_GetWindowDisplayScale(SDL_Window *window); * * \sa SDL_GetWindowFullscreenMode * \sa SDL_SetWindowFullscreen + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode); @@ -700,11 +735,6 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window *window); * window is created and should be queried again if you get an * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. * - * If the window is set fullscreen, the width and height parameters `w` and - * `h` will not be used. However, invalid size parameters (e.g. too large) may - * still fail. Window size is actually limited to 16384 x 16384 for all - * platforms at window creation. - * * If the window is created with any of the SDL_WINDOW_OPENGL or * SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function * (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the @@ -730,79 +760,11 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window *window); * \since This function is available since SDL 3.0.0. * * \sa SDL_CreatePopupWindow - * \sa SDL_CreateWindowFrom - * \sa SDL_CreateWindowWithPosition + * \sa SDL_CreateWindowWithProperties * \sa SDL_DestroyWindow */ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindow(const char *title, int w, int h, Uint32 flags); -/** - * Create a window with the specified position, dimensions, and flags. - * - * `flags` may be any of the following OR'd together: - * - * - `SDL_WINDOW_FULLSCREEN`: fullscreen window at desktop resolution - * - `SDL_WINDOW_OPENGL`: window usable with an OpenGL context - * - `SDL_WINDOW_VULKAN`: window usable with a Vulkan instance - * - `SDL_WINDOW_METAL`: window usable with a Metal instance - * - `SDL_WINDOW_HIDDEN`: window is not visible - * - `SDL_WINDOW_BORDERLESS`: no window decoration - * - `SDL_WINDOW_RESIZABLE`: window can be resized - * - `SDL_WINDOW_MINIMIZED`: window is minimized - * - `SDL_WINDOW_MAXIMIZED`: window is maximized - * - `SDL_WINDOW_MOUSE_GRABBED`: window has grabbed mouse focus - * - * The SDL_Window is implicitly shown if SDL_WINDOW_HIDDEN is not set. - * - * On Apple's macOS, you **must** set the NSHighResolutionCapable Info.plist - * property to YES, otherwise you will not receive a High-DPI OpenGL canvas. - * - * The window pixel size may differ from its window coordinate size if the - * window is on a high pixel density display. Use SDL_GetWindowSize() to query - * the client area's size in window coordinates, and - * SDL_GetWindowSizeInPixels() or SDL_GetRenderOutputSize() to query the - * drawable size in pixels. Note that the drawable size can vary after the - * window is created and should be queried again if you get an - * SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. - * - * If the window is set fullscreen, the width and height parameters `w` and - * `h` will not be used. However, invalid size parameters (e.g. too large) may - * still fail. Window size is actually limited to 16384 x 16384 for all - * platforms at window creation. - * - * If the window is created with any of the SDL_WINDOW_OPENGL or - * SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function - * (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the - * corresponding UnloadLibrary function is called by SDL_DestroyWindow(). - * - * If SDL_WINDOW_VULKAN is specified and there isn't a working Vulkan driver, - * SDL_CreateWindow() will fail because SDL_Vulkan_LoadLibrary() will fail. - * - * If SDL_WINDOW_METAL is specified on an OS that does not support Metal, - * SDL_CreateWindow() will fail. - * - * On non-Apple devices, SDL requires you to either not link to the Vulkan - * loader or link to a dynamic library version. This limitation may be removed - * in a future version of SDL. - * - * \param title the title of the window, in UTF-8 encoding - * \param x the x position of the window, or `SDL_WINDOWPOS_CENTERED` - * \param y the y position of the window, or `SDL_WINDOWPOS_CENTERED` - * \param w the width of the window - * \param h the height of the window - * \param flags 0, or one or more SDL_WindowFlags OR'd together - * \returns the window that was created or NULL on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_CreatePopupWindow - * \sa SDL_CreateWindow - * \sa SDL_CreateWindowFrom - * \sa SDL_DestroyWindow - */ -extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithPosition(const char *title, int x, int y, int w, int h, Uint32 flags); - /** * Create a child popup window of the specified parent window. * @@ -812,9 +774,12 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithPosition(const char *tit * The topmost popup menu will implicitly gain the keyboard focus. * * The following flags are not relevant to popup window creation and will be - * ignored: - 'SDL_WINDOW_MINIMIZED' - 'SDL_WINDOW_MAXIMIZED' - - * 'SDL_WINDOW_FULLSCREEN' - 'SDL_WINDOW_BORDERLESS' - - * 'SDL_WINDOW_SKIP_TASKBAR' + * ignored: + * + * - 'SDL_WINDOW_MINIMIZED' + * - 'SDL_WINDOW_MAXIMIZED' + * - 'SDL_WINDOW_FULLSCREEN' + * - 'SDL_WINDOW_BORDERLESS' * * The parent parameter **must** be non-null and a valid window. The parent of * a popup window can be either a regular, toplevel window, or another popup @@ -849,20 +814,105 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithPosition(const char *tit * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateWindow + * \sa SDL_CreateWindowWithProperties * \sa SDL_DestroyWindow * \sa SDL_GetWindowParent */ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, Uint32 flags); /** - * Create an SDL window from an existing native window. + * Create a window with the specified properties. * - * In some cases (e.g. OpenGL) and on some platforms (e.g. Microsoft Windows) - * the hint `SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT` needs to be configured - * before using SDL_CreateWindowFrom(). + * These are the supported properties: * - * \param data a pointer to driver-dependent window creation data, typically - * your native window cast to a void* + * - `SDL_PROPERTY_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN`: true if the window + * should be always on top + * - `SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN`: true if the window has + * no window decoration + * - `SDL_PROPERTY_WINDOW_CREATE_FOCUSABLE_BOOLEAN`: true if the window should + * accept keyboard input (defaults true) + * - `SDL_PROPERTY_WINDOW_CREATE_FULLSCREEN_BOOLEAN`: true if the window + * should start in fullscreen mode at desktop resolution + * - `SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER`: the height of the window + * - `SDL_PROPERTY_WINDOW_CREATE_HIDDEN_BOOLEAN`: true if the window should + * start hidden + * - `SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN`: true if the + * window uses a high pixel density buffer if possible + * - `SDL_PROPERTY_WINDOW_CREATE_MAXIMIZED_BOOLEAN`: true if the window should + * start maximized + * - `SDL_PROPERTY_WINDOW_CREATE_MENU_BOOLEAN`: true if the window is a popup + * menu + * - `SDL_PROPERTY_WINDOW_CREATE_METAL_BOOLEAN`: true if the window will be + * used with Metal rendering + * - `SDL_PROPERTY_WINDOW_CREATE_MINIMIZED_BOOLEAN`: true if the window should + * start minimized + * - `SDL_PROPERTY_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN`: true if the window + * starts with grabbed mouse focus + * - `SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN`: true if the window will be + * used with OpenGL rendering + * - `SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER`: an SDL_Window that will be + * the parent of this window, required for windows with the "toolip" and + * "menu" properties + * - `SDL_PROPERTY_WINDOW_CREATE_RESIZABLE_BOOLEAN`: true if the window should + * be resizable + * - `SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING`: the title of the window, in + * UTF-8 encoding + * - `SDL_PROPERTY_WINDOW_CREATE_TRANSPARENT_BOOLEAN`: true if the window show + * transparent in the areas with alpha of 0 + * - `SDL_PROPERTY_WINDOW_CREATE_TOOLTIP_BOOLEAN`: true if the window is a + * tooltip + * - `SDL_PROPERTY_WINDOW_CREATE_UTILITY_BOOLEAN`: true if the window is a + * utility window, not showing in the task bar and window list + * - `SDL_PROPERTY_WINDOW_CREATE_VULKAN_BOOLEAN`: true if the window will be + * used with Vulkan rendering + * - `SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER`: the width of the window + * - `SDL_PROPERTY_WINDOW_CREATE_X_NUMBER`: the x position of the window, or + * `SDL_WINDOWPOS_CENTERED`, defaults to `SDL_WINDOWPOS_UNDEFINED`. This is + * relative to the parent for windows with the "parent" property set. + * - `SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER`: the y position of the window, or + * `SDL_WINDOWPOS_CENTERED`, defaults to `SDL_WINDOWPOS_UNDEFINED`. This is + * relative to the parent for windows with the "parent" property set. + * + * These are additional supported properties on macOS: + * + * - `SDL_PROPERTY_WINDOW_CREATE_COCOA_WINDOW_POINTER`: the + * `(__unsafe_unretained)` NSWindow associated with the window, if you want + * to wrap an existing window. + * - `SDL_PROPERTY_WINDOW_CREATE_COCOA_VIEW_POINTER`: the + * `(__unsafe_unretained)` NSView associated with the window, defaults to + * `[window contentView]` + * + * These are additional supported properties on Wayland: + * + * - `SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true + * if the application wants to use the Wayland surface for a custom role and + * does not want it attached to an XDG toplevel window. See + * docs/README-wayland.md for more information on using custom surfaces. + * - `SDL_PROPERTY_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN - true if + * the application wants an associated `wl_egl_window` object to be created, + * even if the window does not have the OpenGL property or flag set. + * - `SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface + * associated with the window, if you want to wrap an existing window. See + * docs/README-wayland.md for more information. + * + * These are additional supported properties on Windows: + * + * - `SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER`: the HWND associated with + * the window, if you want to wrap an existing window. + * - `SDL_PROPERTY_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER`: optional, + * another window to share pixel format with, useful for OpenGL windows + * + * These are additional supported properties with X11: + * + * - `SDL_PROPERTY_WINDOW_CREATE_X11_WINDOW_NUMBER`: the X11 Window associated + * with the window, if you want to wrap an existing window. + * + * The window is implicitly shown if the "hidden" property is not set. + * + * Windows with the "tooltip" and "menu" properties are popup windows and have + * the behaviors and guidelines outlined in `SDL_CreatePopupWindow()`. + * + * \param props the properties to use * \returns the window that was created or NULL on failure; call * SDL_GetError() for more information. * @@ -871,7 +921,39 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in * \sa SDL_CreateWindow * \sa SDL_DestroyWindow */ -extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowFrom(const void *data); +extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithProperties(SDL_PropertiesID props); + +#define SDL_PROPERTY_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "always-on-top" +#define SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN "borderless" +#define SDL_PROPERTY_WINDOW_CREATE_FOCUSABLE_BOOLEAN "focusable" +#define SDL_PROPERTY_WINDOW_CREATE_FULLSCREEN_BOOLEAN "fullscreen" +#define SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER "height" +#define SDL_PROPERTY_WINDOW_CREATE_HIDDEN_BOOLEAN "hidden" +#define SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN "high-pixel-density" +#define SDL_PROPERTY_WINDOW_CREATE_MAXIMIZED_BOOLEAN "maximized" +#define SDL_PROPERTY_WINDOW_CREATE_MENU_BOOLEAN "menu" +#define SDL_PROPERTY_WINDOW_CREATE_METAL_BOOLEAN "metal" +#define SDL_PROPERTY_WINDOW_CREATE_MINIMIZED_BOOLEAN "minimized" +#define SDL_PROPERTY_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN "mouse-grabbed" +#define SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN "opengl" +#define SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER "parent" +#define SDL_PROPERTY_WINDOW_CREATE_RESIZABLE_BOOLEAN "resizable" +#define SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING "title" +#define SDL_PROPERTY_WINDOW_CREATE_TRANSPARENT_BOOLEAN "transparent" +#define SDL_PROPERTY_WINDOW_CREATE_TOOLTIP_BOOLEAN "tooltip" +#define SDL_PROPERTY_WINDOW_CREATE_UTILITY_BOOLEAN "utility" +#define SDL_PROPERTY_WINDOW_CREATE_VULKAN_BOOLEAN "vulkan" +#define SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER "width" +#define SDL_PROPERTY_WINDOW_CREATE_X_NUMBER "x" +#define SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER "y" +#define SDL_PROPERTY_WINDOW_CREATE_COCOA_WINDOW_POINTER "cocoa.window" +#define SDL_PROPERTY_WINDOW_CREATE_COCOA_VIEW_POINTER "cocoa.view" +#define SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "wayland.surface_role_custom" +#define SDL_PROPERTY_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "wayland.create_egl_window" +#define SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "wayland.wl_surface" +#define SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER "win32.hwnd" +#define SDL_PROPERTY_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "win32.pixel_format_hwnd" +#define SDL_PROPERTY_WINDOW_CREATE_X11_WINDOW_NUMBER "x11.window" /** * Get the numeric ID of a window. @@ -918,6 +1000,132 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowFromID(SDL_WindowID id); */ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window); +/** + * Get the properties associated with a window. + * + * The following read-only properties are provided by SDL: + * + * On Android: + * + * - `SDL_PROPERTY_WINDOW_ANDROID_WINDOW_POINTER`: the ANativeWindow + * associated with the window + * - `SDL_PROPERTY_WINDOW_ANDROID_SURFACE_POINTER`: the EGLSurface associated + * with the window + * + * On iOS: + * + * - `SDL_PROPERTY_WINDOW_UIKIT_WINDOW_POINTER`: the `(__unsafe_unretained)` + * UIWindow associated with the window + * - `SDL_PROPERTY_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER`: the NSInteger tag + * assocated with metal views on the window + * + * On KMS/DRM: + * + * - `SDL_PROPERTY_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER`: the device index + * associated with the window (e.g. the X in /dev/dri/cardX) + * - `SDL_PROPERTY_WINDOW_KMSDRM_DRM_FD_NUMBER`: the DRM FD associated with + * the window + * - `SDL_PROPERTY_WINDOW_KMSDRM_GBM_DEVICE_POINTER`: the GBM device + * associated with the window + * + * On macOS: + * + * - `SDL_PROPERTY_WINDOW_COCOA_WINDOW_POINTER`: the `(__unsafe_unretained)` + * NSWindow associated with the window + * - `SDL_PROPERTY_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER`: the NSInteger tag + * assocated with metal views on the window + * + * On Vivante: + * + * - `SDL_PROPERTY_WINDOW_VIVANTE_DISPLAY_POINTER`: the EGLNativeDisplayType + * associated with the window + * - `SDL_PROPERTY_WINDOW_VIVANTE_WINDOW_POINTER`: the EGLNativeWindowType + * associated with the window + * - `SDL_PROPERTY_WINDOW_VIVANTE_SURFACE_POINTER`: the EGLSurface associated + * with the window + * + * On UWP: + * + * - `SDL_PROPERTY_WINDOW_WINRT_WINDOW_POINTER`: the IInspectable CoreWindow + * associated with the window + * + * On Windows: + * + * - `SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER`: the HWND associated with the + * window + * - `SDL_PROPERTY_WINDOW_WIN32_HDC_POINTER`: the HDC associated with the + * window + * - `SDL_PROPERTY_WINDOW_WIN32_INSTANCE_POINTER`: the HINSTANCE associated + * with the window + * + * On Wayland: + * + * Note: The `xdg_*` window objects do not internally persist across window + * show/hide calls. They will be null if the window is hidden and must be + * queried each time it is shown. + * + * - `SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER`: the wl_display associated + * with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER`: the wl_surface associated + * with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_EGL_WINDOW_POINTER`: the wl_egl_window + * associated with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER`: the xdg_surface + * associated with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER`: the xdg_toplevel role + * associated with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_XDG_POPUP_POINTER`: the xdg_popup role + * associated with the window + * - `SDL_PROPERTY_WINDOW_WAYLAND_XDG_POSITIONER_POINTER`: the xdg_positioner + * associated with the window, in popup mode + * + * On X11: + * + * - `SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER`: the X11 Display associated + * with the window + * - `SDL_PROPERTY_WINDOW_X11_SCREEN_NUMBER`: the screen number associated + * with the window + * - `SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER`: the X11 Window associated with + * the window + * + * \param window the window to query + * \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.0.0. + * + * \sa SDL_GetProperty + * \sa SDL_SetProperty + */ +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window *window); + +#define SDL_PROPERTY_WINDOW_ANDROID_WINDOW_POINTER "SDL.window.android.window" +#define SDL_PROPERTY_WINDOW_ANDROID_SURFACE_POINTER "SDL.window.android.surface" +#define SDL_PROPERTY_WINDOW_UIKIT_WINDOW_POINTER "SDL.window.uikit.window" +#define SDL_PROPERTY_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER "SDL.window.uikit.metal_view_tag" +#define SDL_PROPERTY_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER "SDL.window.kmsdrm.dev_index" +#define SDL_PROPERTY_WINDOW_KMSDRM_DRM_FD_NUMBER "SDL.window.kmsdrm.drm_fd" +#define SDL_PROPERTY_WINDOW_KMSDRM_GBM_DEVICE_POINTER "SDL.window.kmsdrm.gbm_dev" +#define SDL_PROPERTY_WINDOW_COCOA_WINDOW_POINTER "SDL.window.cocoa.window" +#define SDL_PROPERTY_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER "SDL.window.cocoa.metal_view_tag" +#define SDL_PROPERTY_WINDOW_VIVANTE_DISPLAY_POINTER "SDL.window.vivante.display" +#define SDL_PROPERTY_WINDOW_VIVANTE_WINDOW_POINTER "SDL.window.vivante.window" +#define SDL_PROPERTY_WINDOW_VIVANTE_SURFACE_POINTER "SDL.window.vivante.surface" +#define SDL_PROPERTY_WINDOW_WINRT_WINDOW_POINTER "SDL.window.winrt.window" +#define SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER "SDL.window.win32.hwnd" +#define SDL_PROPERTY_WINDOW_WIN32_HDC_POINTER "SDL.window.win32.hdc" +#define SDL_PROPERTY_WINDOW_WIN32_INSTANCE_POINTER "SDL.window.win32.instance" +#define SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER "SDL.window.wayland.display" +#define SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER "SDL.window.wayland.surface" +#define SDL_PROPERTY_WINDOW_WAYLAND_EGL_WINDOW_POINTER "SDL.window.wayland.egl_window" +#define SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER "SDL.window.wayland.xdg_surface" +#define SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER "SDL.window.wayland.xdg_toplevel" +#define SDL_PROPERTY_WINDOW_WAYLAND_XDG_POPUP_POINTER "SDL.window.wayland.xdg_popup" +#define SDL_PROPERTY_WINDOW_WAYLAND_XDG_POSITIONER_POINTER "SDL.window.wayland.xdg_positioner" +#define SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER "SDL.window.x11.display" +#define SDL_PROPERTY_WINDOW_X11_SCREEN_NUMBER "SDL.window.x11.screen" +#define SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window" + /** * Get the window flags. * @@ -978,36 +1186,29 @@ extern DECLSPEC const char *SDLCALL SDL_GetWindowTitle(SDL_Window *window); extern DECLSPEC int SDLCALL SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon); /** - * Associate an arbitrary named pointer with a window. + * Request that the window's position be set. * - * `name` is case-sensitive. + * If, at the time of this request, the window is in a fixed-size state such + * as maximized, this request may be deferred until the window returns to a + * resizable state. * - * \param window the window to associate with the pointer - * \param name the name of the pointer - * \param userdata the associated pointer - * \returns the previous value associated with `name`. + * This can be used to reposition fullscreen-desktop windows onto a different + * display, however, exclusive fullscreen windows are locked to a specific + * display and can only be repositioned programmatically via + * SDL_SetWindowFullscreenMode(). * - * \since This function is available since SDL 3.0.0. + * On some windowing systems this request is asynchronous and the new + * coordinates may not have have been applied immediately upon the return of + * this function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. * - * \sa SDL_GetWindowData - */ -extern DECLSPEC void *SDLCALL SDL_SetWindowData(SDL_Window *window, const char *name, void *userdata); - -/** - * Retrieve the data pointer associated with a window. - * - * \param window the window to query - * \param name the name of the pointer - * \returns the value associated with `name`. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_SetWindowData - */ -extern DECLSPEC void *SDLCALL SDL_GetWindowData(SDL_Window *window, const char *name); - -/** - * Set the position of a window. + * When the window position changes, an SDL_EVENT_WINDOW_MOVED event will be + * emitted with the window's new coordinates. Note that the new coordinates + * may not match the exact coordinates requested, as some windowing systems + * can restrict the position of the window in certain scenarios (e.g. + * constraining the position so the window is always within desktop bounds). + * Additionally, as this is just a request, it can be denied by the windowing + * system. * * \param window the window to reposition * \param x the x coordinate of the window, or `SDL_WINDOWPOS_CENTERED` or @@ -1020,12 +1221,16 @@ extern DECLSPEC void *SDLCALL SDL_GetWindowData(SDL_Window *window, const char * * \since This function is available since SDL 3.0.0. * * \sa SDL_GetWindowPosition + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_SetWindowPosition(SDL_Window *window, int x, int y); /** * Get the position of a window. * + * This is the current position of the window as last reported by the + * windowing system. + * * If you do not need the value for one of the positions a NULL may be passed * in the `x` or `y` parameter. * @@ -1042,10 +1247,29 @@ extern DECLSPEC int SDLCALL SDL_SetWindowPosition(SDL_Window *window, int x, int extern DECLSPEC int SDLCALL SDL_GetWindowPosition(SDL_Window *window, int *x, int *y); /** - * Set the size of a window's client area. + * Request that the size of a window's client area be set. * - * This only affects the size of the window when not in fullscreen mode. To - * change the fullscreen mode of a window, use SDL_SetWindowFullscreenMode() + * NULL can safely be passed as the `w` or `h` parameter if the width or + * height value is not desired. + * + * If, at the time of this request, the window in a fixed-size state, such as + * maximized or fullscreen, the request will be deferred until the window + * exits this state and becomes resizable again. + * + * To change the fullscreen mode of a window, use + * SDL_SetWindowFullscreenMode() + * + * On some windowing systems, this request is asynchronous and the new window + * size may not have have been applied immediately upon the return of this + * function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. + * + * When the window size changes, an SDL_EVENT_WINDOW_RESIZED event will be + * emitted with the new window dimensions. Note that the new dimensions may + * not match the exact size requested, as some windowing systems can restrict + * the window size in certain scenarios (e.g. constraining the size of the + * content area to remain within the usable desktop bounds). Additionally, as + * this is just a request, it can be denied by the windowing system. * * \param window the window to change * \param w the width of the window, must be > 0 @@ -1057,6 +1281,7 @@ extern DECLSPEC int SDLCALL SDL_GetWindowPosition(SDL_Window *window, int *x, in * * \sa SDL_GetWindowSize * \sa SDL_SetWindowFullscreenMode + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, int h); @@ -1300,7 +1525,24 @@ extern DECLSPEC int SDLCALL SDL_HideWindow(SDL_Window *window); extern DECLSPEC int SDLCALL SDL_RaiseWindow(SDL_Window *window); /** - * Make a window as large as possible. + * Request that the window be made as large as possible. + * + * Non-resizable windows can't be maximized. The window must have the + * SDL_WINDOW_RESIZABLE flag set, or this will have no effect. + * + * On some windowing systems this request is asynchronous and the new window + * state may not have have been applied immediately upon the return of this + * function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. + * + * When the window state changes, an SDL_EVENT_WINDOW_MAXIMIZED event will be + * emitted. Note that, as this is just a request, the windowing system can + * deny the state change. + * + * When maximizing a window, whether the constraints set via + * SDL_SetWindowMaximumSize() are honored depends on the policy of the window + * manager. Win32 and macOS enforce the constraints when maximizing, while X11 + * and Wayland window managers may vary. * * \param window the window to maximize * \returns 0 on success or a negative error code on failure; call @@ -1310,11 +1552,21 @@ extern DECLSPEC int SDLCALL SDL_RaiseWindow(SDL_Window *window); * * \sa SDL_MinimizeWindow * \sa SDL_RestoreWindow + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_MaximizeWindow(SDL_Window *window); /** - * Minimize a window to an iconic representation. + * Request that the window be minimized to an iconic representation. + * + * On some windowing systems this request is asynchronous and the new window + * state may not have have been applied immediately upon the return of this + * function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. + * + * When the window state changes, an SDL_EVENT_WINDOW_MINIMIZED event will be + * emitted. Note that, as this is just a request, the windowing system can + * deny the state change. * * \param window the window to minimize * \returns 0 on success or a negative error code on failure; call @@ -1324,11 +1576,22 @@ extern DECLSPEC int SDLCALL SDL_MaximizeWindow(SDL_Window *window); * * \sa SDL_MaximizeWindow * \sa SDL_RestoreWindow + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_MinimizeWindow(SDL_Window *window); /** - * Restore the size and position of a minimized or maximized window. + * Request that the size and position of a minimized or maximized window be + * restored. + * + * On some windowing systems this request is asynchronous and the new window + * state may not have have been applied immediately upon the return of this + * function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. + * + * When the window state changes, an SDL_EVENT_WINDOW_RESTORED event will be + * emitted. Note that, as this is just a request, the windowing system can + * deny the state change. * * \param window the window to restore * \returns 0 on success or a negative error code on failure; call @@ -1338,15 +1601,25 @@ extern DECLSPEC int SDLCALL SDL_MinimizeWindow(SDL_Window *window); * * \sa SDL_MaximizeWindow * \sa SDL_MinimizeWindow + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_RestoreWindow(SDL_Window *window); /** - * Set a window's fullscreen state. + * Request that the window's fullscreen state be changed. * * By default a window in fullscreen state uses fullscreen desktop mode, but a * specific display mode can be set using SDL_SetWindowFullscreenMode(). * + * On some windowing systems this request is asynchronous and the new + * fullscreen state may not have have been applied immediately upon the return + * of this function. If an immediate change is required, call SDL_SyncWindow() + * to block until the changes have taken effect. + * + * When the window state changes, an SDL_EVENT_WINDOW_ENTER_FULLSCREEN or + * SDL_EVENT_WINDOW_LEAVE_FULLSCREEN event will be emitted. Note that, as this + * is just a request, it can be denied by the windowing system. + * * \param window the window to change * \param fullscreen SDL_TRUE for fullscreen mode, SDL_FALSE for windowed mode * \returns 0 on success or a negative error code on failure; call @@ -1356,9 +1629,39 @@ extern DECLSPEC int SDLCALL SDL_RestoreWindow(SDL_Window *window); * * \sa SDL_GetWindowFullscreenMode * \sa SDL_SetWindowFullscreenMode + * \sa SDL_SyncWindow */ extern DECLSPEC int SDLCALL SDL_SetWindowFullscreen(SDL_Window *window, SDL_bool fullscreen); +/** + * Block until any pending window state is finalized. + * + * On asynchronous windowing systems, this acts as a synchronization barrier + * for pending window state. It will attempt to wait until any pending window + * state has been applied and is guaranteed to return within finite time. Note + * that for how long it can potentially block depends on the underlying window + * system, as window state changes may involve somewhat lengthy animations + * that must complete before the window is in its final requested state. + * + * On windowing systems where changes are immediate, this does nothing. + * + * \param window the window for which to wait for the pending state to be + * applied + * \returns 0 on success, a positive value if the operation timed out before + * the window was in the requested state, or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetWindowSize + * \sa SDL_SetWindowPosition + * \sa SDL_SetWindowFullscreen + * \sa SDL_MinimizeWindow + * \sa SDL_MaximizeWindow + * \sa SDL_RestoreWindow + */ +extern DECLSPEC int SDLCALL SDL_SyncWindow(SDL_Window *window); + /** * Return whether the window has a surface associated with it. * @@ -1426,6 +1729,11 @@ extern DECLSPEC int SDLCALL SDL_UpdateWindowSurface(SDL_Window *window); * * This function is equivalent to the SDL 1.2 API SDL_UpdateRects(). * + * Note that this function will update _at least_ the rectangles specified, + * but this is only intended as an optimization; in practice, this might + * update more of the screen (or all of the screen!), depending on what method + * SDL uses to send pixels to the system. + * * \param window the window to update * \param rects an array of SDL_Rect structures representing areas of the * surface to copy, in pixels @@ -1811,8 +2119,9 @@ extern DECLSPEC int SDLCALL SDL_FlashWindow(SDL_Window *window, SDL_FlashOperati * * \since This function is available since SDL 3.0.0. * + * \sa SDL_CreatePopupWindow * \sa SDL_CreateWindow - * \sa SDL_CreateWindowFrom + * \sa SDL_CreateWindowWithProperties */ extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window *window); diff --git a/include/SDL3/SDL_video_capture.h b/include/SDL3/SDL_video_capture.h new file mode 100644 index 00000000..80a21605 --- /dev/null +++ b/include/SDL3/SDL_video_capture.h @@ -0,0 +1,377 @@ +/* + Simple DirectMedia Layer + 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 + 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. +*/ + +/** + * \file SDL_video_capture.h + * + * Video Capture for the SDL library. + */ + +#ifndef SDL_video_capture_h_ +#define SDL_video_capture_h_ + +#include "SDL3/SDL_video.h" + +#include +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This is a unique ID for a video capture device for the time it is connected to the system, + * and is never reused for the lifetime of the application. If the device is + * disconnected and reconnected, it will get a new ID. + * + * The ID value starts at 1 and increments from there. The value 0 is an invalid ID. + * + * \sa SDL_GetVideoCaptureDevices + */ +typedef Uint32 SDL_VideoCaptureDeviceID; + + +/** + * The structure used to identify an SDL video capture device + */ +struct SDL_VideoCaptureDevice; +typedef struct SDL_VideoCaptureDevice SDL_VideoCaptureDevice; + +#define SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE 1 + +/** + * SDL_VideoCaptureSpec structure + * + * Only those field can be 'desired' when configuring the device: + * - format + * - width + * - height + * + * \sa SDL_GetVideoCaptureFormat + * \sa SDL_GetVideoCaptureFrameSize + * + */ +typedef struct SDL_VideoCaptureSpec +{ + Uint32 format; /**< Frame SDL_PixelFormatEnum format */ + int width; /**< Frame width */ + int height; /**< Frame height */ +} SDL_VideoCaptureSpec; + +/** + * SDL Video Capture Status + * + * Change states but calling the function in this order: + * + * SDL_OpenVideoCapture() + * SDL_SetVideoCaptureSpec() -> Init + * SDL_StartVideoCapture() -> Playing + * SDL_StopVideoCapture() -> Stopped + * SDL_CloseVideoCapture() + * + */ +typedef enum +{ + SDL_VIDEO_CAPTURE_FAIL = -1, /**< Failed */ + SDL_VIDEO_CAPTURE_INIT = 0, /**< Init, spec hasn't been set */ + SDL_VIDEO_CAPTURE_STOPPED, /**< Stopped */ + SDL_VIDEO_CAPTURE_PLAYING /**< Playing */ +} SDL_VideoCaptureStatus; + +/** + * SDL Video Capture Status + */ +typedef struct SDL_VideoCaptureFrame +{ + Uint64 timestampNS; /**< Frame timestamp in nanoseconds when read from the driver */ + int num_planes; /**< Number of planes */ + Uint8 *data[3]; /**< Pointer to data of i-th plane */ + int pitch[3]; /**< Pitch of i-th plane */ + void *internal; /**< Private field */ +} SDL_VideoCaptureFrame; + + +/** + * Get a list of currently connected video capture devices. + * + * \param count a pointer filled in with the number of video capture devices + * \returns a 0 terminated array of video capture instance IDs which should be + * freed with SDL_free(), or NULL on error; call SDL_GetError() for + * more details. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenVideoCapture + */ +extern DECLSPEC SDL_VideoCaptureDeviceID *SDLCALL SDL_GetVideoCaptureDevices(int *count); + +/** + * Open a Video Capture device + * + * \param instance_id the video capture device instance ID + * \returns device, or NULL on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetVideoCaptureDeviceName + * \sa SDL_GetVideoCaptureDevices + * \sa SDL_OpenVideoCaptureWithSpec + */ +extern DECLSPEC SDL_VideoCaptureDevice *SDLCALL SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id); + +/** + * Set specification + * + * \param device opened video capture device + * \param desired desired video capture spec + * \param obtained obtained video capture spec + * \param allowed_changes allow changes or not + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenVideoCapture + * \sa SDL_OpenVideoCaptureWithSpec + * \sa SDL_GetVideoCaptureSpec + */ +extern DECLSPEC int SDLCALL SDL_SetVideoCaptureSpec(SDL_VideoCaptureDevice *device, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes); + +/** + * Open a Video Capture device and set specification + * + * \param instance_id the video capture device instance ID + * \param desired desired video capture spec + * \param obtained obtained video capture spec + * \param allowed_changes allow changes or not + * \returns device, or NULL on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenVideoCapture + * \sa SDL_SetVideoCaptureSpec + * \sa SDL_GetVideoCaptureSpec + */ +extern DECLSPEC SDL_VideoCaptureDevice *SDLCALL SDL_OpenVideoCaptureWithSpec(SDL_VideoCaptureDeviceID instance_id, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes); + +/** + * Get device name + * + * \param instance_id the video capture device instance ID + * \returns device name, shouldn't be freed + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetVideoCaptureDevices + */ +extern DECLSPEC const char * SDLCALL SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id); + +/** + * Get the obtained video capture spec + * + * \param device opened video capture device + * \param spec The SDL_VideoCaptureSpec to be initialized by this function. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetVideoCaptureSpec + * \sa SDL_OpenVideoCaptureWithSpec + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureSpec(SDL_VideoCaptureDevice *device, SDL_VideoCaptureSpec *spec); + + +/** + * Get frame format of video capture device. + * + * The value can be used to fill SDL_VideoCaptureSpec structure. + * + * \param device opened video capture device + * \param index format between 0 and num -1 + * \param format pointer output format (SDL_PixelFormatEnum) + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetNumVideoCaptureFormats + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFormat(SDL_VideoCaptureDevice *device, + int index, + Uint32 *format); + +/** + * Number of available formats for the device + * + * \param device opened video capture device + * \returns number of formats or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetVideoCaptureFormat + * \sa SDL_SetVideoCaptureSpec + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDevice *device); + +/** + * Get frame sizes of the device and the specified input format. + * + * The value can be used to fill SDL_VideoCaptureSpec structure. + * + * \param device opened video capture device + * \param format a format that can be used by the device (SDL_PixelFormatEnum) + * \param index framesize between 0 and num -1 + * \param width output width + * \param height output height + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetNumVideoCaptureFrameSizes + */ +extern DECLSPEC int SDLCALL SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDevice *device, Uint32 format, int index, int *width, int *height); + +/** + * Number of different framesizes available for the device and pixel format. + * + * \param device opened video capture device + * \param format frame pixel format (SDL_PixelFormatEnum) + * \returns number of framesizes or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetVideoCaptureFrameSize + * \sa SDL_SetVideoCaptureSpec + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDevice *device, Uint32 format); + + +/** + * Get video capture status + * + * \param device opened video capture device + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_VideoCaptureStatus + */ +extern DECLSPEC SDL_VideoCaptureStatus SDLCALL SDL_GetVideoCaptureStatus(SDL_VideoCaptureDevice *device); + +/** + * Start video capture + * + * \param device opened video capture device + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_StopVideoCapture + */ +extern DECLSPEC int SDLCALL SDL_StartVideoCapture(SDL_VideoCaptureDevice *device); + +/** + * Acquire a frame. + * + * The frame is a memory pointer to the image data, whose size and format are + * given by the the obtained spec. + * + * Non blocking API. If there is a frame available, frame->num_planes is non + * 0. If frame->num_planes is 0 and returned code is 0, there is no frame at + * that time. + * + * After used, the frame should be released with SDL_ReleaseVideoCaptureFrame + * + * \param device opened video capture device + * \param frame pointer to get the frame + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_ReleaseVideoCaptureFrame + */ +extern DECLSPEC int SDLCALL SDL_AcquireVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame); + +/** + * Release a frame. + * + * Let the back-end re-use the internal buffer for video capture. + * + * All acquired frames should be released before closing the device. + * + * \param device opened video capture device + * \param frame frame pointer. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AcquireVideoCaptureFrame + */ +extern DECLSPEC int SDLCALL SDL_ReleaseVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame); + +/** + * Stop Video Capture + * + * \param device opened video capture device + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_StartVideoCapture + */ +extern DECLSPEC int SDLCALL SDL_StopVideoCapture(SDL_VideoCaptureDevice *device); + +/** + * Use this function to shut down video_capture processing and close the + * video_capture device. + * + * \param device opened video capture device + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenVideoCaptureWithSpec + * \sa SDL_OpenVideoCapture + */ +extern DECLSPEC void SDLCALL SDL_CloseVideoCapture(SDL_VideoCaptureDevice *device); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_video_capture_h_ */ diff --git a/include/SDL3/SDL_vulkan.h b/include/SDL3/SDL_vulkan.h index 13047c9d..98590786 100644 --- a/include/SDL3/SDL_vulkan.h +++ b/include/SDL3/SDL_vulkan.h @@ -22,7 +22,7 @@ /** * \file SDL_vulkan.h * - * \brief Header file for functions to creating Vulkan surfaces on SDL windows. + * Header file for functions to creating Vulkan surfaces on SDL windows. */ #ifndef SDL_vulkan_h_ @@ -51,6 +51,7 @@ extern "C" { VK_DEFINE_HANDLE(VkInstance) VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR) +struct VkAllocationCallbacks; #endif /* !NO_SDL_VULKAN_TYPEDEFS */ @@ -135,33 +136,28 @@ extern DECLSPEC SDL_FunctionPointer SDLCALL SDL_Vulkan_GetVkGetInstanceProcAddr( extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void); /** - * Get the names of the Vulkan instance extensions needed to create a surface - * with SDL_Vulkan_CreateSurface. + * Get the Vulkan instance extensions needed for vkCreateInstance. * * This should be called after either calling SDL_Vulkan_LoadLibrary() or * creating an SDL_Window with the `SDL_WINDOW_VULKAN` flag. * - * If `pNames` is NULL, then the number of required Vulkan instance extensions - * is returned in `pCount`. Otherwise, `pCount` must point to a variable set - * to the number of elements in the `pNames` array, and on return the variable - * is overwritten with the number of names actually written to `pNames`. If - * `pCount` is less than the number of required extensions, at most `pCount` - * structures will be written. If `pCount` is smaller than the number of - * required extensions, SDL_FALSE will be returned instead of SDL_TRUE, to - * indicate that not all the required extensions were returned. + * On return, the variable pointed to by `pCount` will be set to the number of + * elements returned, suitable for using with + * VkInstanceCreateInfo::enabledExtensionCount, and the returned array can be + * used with VkInstanceCreateInfo::ppEnabledExtensionNames, for calling + * Vulkan's vkCreateInstance API. * - * \param pCount A pointer to an unsigned int corresponding to the number of - * extensions to be returned - * \param pNames NULL or a pointer to an array to be filled with required - * Vulkan instance extensions - * \returns SDL_TRUE on success, SDL_FALSE on error. + * You should not free the returned array; it is owned by SDL. + * + * \param pCount A pointer to Uint32 that will be filled with the number of + * extensions returned. + * \returns An array of extension name strings on success, NULL on error. * * \since This function is available since SDL 3.0.0. * * \sa SDL_Vulkan_CreateSurface */ -extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(unsigned int *pCount, - const char **pNames); +extern DECLSPEC char const* const* SDLCALL SDL_Vulkan_GetInstanceExtensions(Uint32 *pCount); /** * Create a Vulkan rendering surface for a window. @@ -170,8 +166,13 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(unsigned int * * `instance` must have been created with extensions returned by * SDL_Vulkan_GetInstanceExtensions() enabled. * + * If `allocator` is NULL, Vulkan will use the system default allocator. This + * argument is passed directly to Vulkan and isn't used by SDL itself. + * * \param window The window to which to attach the Vulkan surface * \param instance The Vulkan instance handle + * \param allocator A VkAllocationCallbacks struct, which lets the app set the + * allocator that creates the surface. Can be NULL. * \param surface A pointer to a VkSurfaceKHR handle to output the newly * created surface * \returns SDL_TRUE on success, SDL_FALSE on error. @@ -182,6 +183,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(unsigned int * */ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR* surface); /* @} *//* Vulkan support functions */ diff --git a/include/build_config/SDL_build_config.h b/include/build_config/SDL_build_config.h index 675feae7..6fa6d899 100644 --- a/include/build_config/SDL_build_config.h +++ b/include/build_config/SDL_build_config.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,7 +27,7 @@ /** * \file SDL_build_config.h * - * \brief This is a set of defines to configure the SDL features + * This is a set of defines to configure the SDL features */ /* Add any platform that doesn't build using the configure system. */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 9ce6c437..9deb5555 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_build_config.h * - * \brief This is a set of defines to configure the SDL features + * This is a set of defines to configure the SDL features */ #ifndef SDL_build_config_h_ @@ -42,6 +42,8 @@ #cmakedefine HAVE_GCC_ATOMICS @HAVE_GCC_ATOMICS@ #cmakedefine HAVE_GCC_SYNC_LOCK_TEST_AND_SET @HAVE_GCC_SYNC_LOCK_TEST_AND_SET@ +#cmakedefine SDL_DISABLE_ALLOCA + /* Comment this if you want to build without any C library requirements */ #cmakedefine HAVE_LIBC 1 #ifdef HAVE_LIBC @@ -74,7 +76,6 @@ #cmakedefine HAVE_CALLOC 1 #cmakedefine HAVE_REALLOC 1 #cmakedefine HAVE_FREE 1 -#cmakedefine HAVE_ALLOCA 1 #ifndef __WIN32__ /* Don't use C runtime versions of these on Windows */ #cmakedefine HAVE_GETENV 1 #cmakedefine HAVE_SETENV 1 @@ -115,6 +116,7 @@ #cmakedefine HAVE_STRCHR 1 #cmakedefine HAVE_STRRCHR 1 #cmakedefine HAVE_STRSTR 1 +#cmakedefine HAVE_STRNSTR 1 #cmakedefine HAVE_STRTOK_R 1 #cmakedefine HAVE_ITOA 1 #cmakedefine HAVE__LTOA 1 @@ -195,6 +197,7 @@ #cmakedefine HAVE_CLOCK_GETTIME 1 #cmakedefine HAVE_GETPAGESIZE 1 #cmakedefine HAVE_ICONV 1 +#cmakedefine SDL_USE_LIBICONV 1 #cmakedefine HAVE_PTHREAD_SETNAME_NP 1 #cmakedefine HAVE_PTHREAD_SET_NAME_NP 1 #cmakedefine HAVE_SEM_TIMEDWAIT 1 @@ -222,7 +225,7 @@ #cmakedefine HAVE_LINUX_INPUT_H 1 #cmakedefine HAVE_LIBUDEV_H 1 -#cmakedefine HAVE_LIBDECOR_H 1 +#cmakedefine HAVE_LIBDECOR_H 1 #cmakedefine HAVE_D3D_H @HAVE_D3D_H@ #cmakedefine HAVE_D3D11_H @HAVE_D3D11_H@ @@ -241,16 +244,17 @@ #cmakedefine HAVE_ROAPI_H @HAVE_ROAPI_H@ #cmakedefine HAVE_SHELLSCALINGAPI_H @HAVE_SHELLSCALINGAPI_H@ -#cmakedefine HAVE_XINPUT_GAMEPAD_EX @HAVE_XINPUT_GAMEPAD_EX@ -#cmakedefine HAVE_XINPUT_STATE_EX @HAVE_XINPUT_STATE_EX@ - #cmakedefine USE_POSIX_SPAWN @USE_POSIX_SPAWN@ +#cmakedefine HAVE_COREMEDIA + /* SDL internal assertion support */ #if @SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED@ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL @SDL_DEFAULT_ASSERT_LEVEL@ #endif +#cmakedefine SDL_VIDEO_CAPTURE + /* Allow disabling of core subsystems */ #cmakedefine SDL_ATOMIC_DISABLED @SDL_ATOMIC_DISABLED@ #cmakedefine SDL_AUDIO_DISABLED @SDL_AUDIO_DISABLED@ @@ -264,7 +268,6 @@ #cmakedefine SDL_LOADSO_DISABLED @SDL_LOADSO_DISABLED@ #cmakedefine SDL_RENDER_DISABLED @SDL_RENDER_DISABLED@ #cmakedefine SDL_THREADS_DISABLED @SDL_THREADS_DISABLED@ -#cmakedefine SDL_TIMERS_DISABLED @SDL_TIMERS_DISABLED@ #cmakedefine SDL_VIDEO_DISABLED @SDL_VIDEO_DISABLED@ #cmakedefine SDL_POWER_DISABLED @SDL_POWER_DISABLED@ #cmakedefine SDL_FILESYSTEM_DISABLED @SDL_FILESYSTEM_DISABLED@ @@ -395,7 +398,6 @@ #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL@ #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR@ #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON@ -#cmakedefine SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH @SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH@ #cmakedefine SDL_VIDEO_DRIVER_WINDOWS @SDL_VIDEO_DRIVER_WINDOWS@ #cmakedefine SDL_VIDEO_DRIVER_WINRT @SDL_VIDEO_DRIVER_WINRT@ #cmakedefine SDL_VIDEO_DRIVER_X11 @SDL_VIDEO_DRIVER_X11@ @@ -418,62 +420,6 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE @SDL_VIDEO_DRIVER_X11_XSHAPE@ #cmakedefine SDL_VIDEO_DRIVER_QNX @SDL_VIDEO_DRIVER_QNX@ -#ifdef SDL_VIDEO_DRIVER_ANDROID -#define SDL_ENABLE_SYSWM_ANDROID -#else -#define SDL_DISABLE_SYSWM_ANDROID -#endif -#ifdef SDL_VIDEO_DRIVER_COCOA -#define SDL_ENABLE_SYSWM_COCOA -#else -#define SDL_DISABLE_SYSWM_COCOA -#endif -#ifdef SDL_VIDEO_DRIVER_HAIKU -#define SDL_ENABLE_SYSWM_HAIKU -#else -#define SDL_DISABLE_SYSWM_HAIKU -#endif -#ifdef SDL_VIDEO_DRIVER_KMSDRM -#define SDL_ENABLE_SYSWM_KMSDRM -#else -#define SDL_DISABLE_SYSWM_KMSDRM -#endif -#ifdef SDL_VIDEO_DRIVER_RISCOS -#define SDL_ENABLE_SYSWM_RISCOS -#else -#define SDL_DISABLE_SYSWM_RISCOS -#endif -#ifdef SDL_VIDEO_DRIVER_UIKIT -#define SDL_ENABLE_SYSWM_UIKIT -#else -#define SDL_DISABLE_SYSWM_UIKIT -#endif -#ifdef SDL_VIDEO_DRIVER_VIVANTE -#define SDL_ENABLE_SYSWM_VIVANTE -#else -#define SDL_DISABLE_SYSWM_VIVANTE -#endif -#ifdef SDL_VIDEO_DRIVER_WAYLAND -#define SDL_ENABLE_SYSWM_WAYLAND -#else -#define SDL_DISABLE_SYSWM_WAYLAND -#endif -#ifdef SDL_VIDEO_DRIVER_WINDOWS -#define SDL_ENABLE_SYSWM_WINDOWS -#else -#define SDL_DISABLE_SYSWM_WINDOWS -#endif -#ifdef SDL_VIDEO_DRIVER_WINRT -#define SDL_ENABLE_SYSWM_WINRT -#else -#define SDL_DISABLE_SYSWM_WINRT -#endif -#ifdef SDL_VIDEO_DRIVER_X11 -#define SDL_ENABLE_SYSWM_X11 -#else -#define SDL_DISABLE_SYSWM_X11 -#endif - #cmakedefine SDL_VIDEO_RENDER_D3D @SDL_VIDEO_RENDER_D3D@ #cmakedefine SDL_VIDEO_RENDER_D3D11 @SDL_VIDEO_RENDER_D3D11@ #cmakedefine SDL_VIDEO_RENDER_D3D12 @SDL_VIDEO_RENDER_D3D12@ @@ -542,7 +488,7 @@ #cmakedefine SDL_ARM_NEON_BLITTERS @SDL_ARM_NEON_BLITTERS@ /* Whether SDL_DYNAMIC_API needs dlopen */ -#cmakedefine DYNAPI_NEEDS_DLOPEN @DYNAPI_NEEDS_DLOPEN@ +#cmakedefine DYNAPI_NEEDS_DLOPEN @DYNAPI_NEEDS_DLOPEN@ /* Enable ime support */ #cmakedefine SDL_USE_IME @SDL_USE_IME@ @@ -555,7 +501,7 @@ #cmakedefine SDL_VIDEO_VITA_PVR @SDL_VIDEO_VITA_PVR@ #cmakedefine SDL_VIDEO_VITA_PVR_OGL @SDL_VIDEO_VITA_PVR_OGL@ -#cmakedefine SDL_HAVE_LIBDECOR_VER_0_1_2 @SDL_HAVE_LIBDECOR_VER_0_1_2@ +#cmakedefine SDL_HAVE_LIBDECOR_VER_0_2_0 @SDL_HAVE_LIBDECOR_VER_0_2_0@ #if !defined(HAVE_STDINT_H) && !defined(_STDINT_H_) /* Most everything except Visual Studio 2008 and earlier has stdint.h now */ diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h index 1f8c3ef8..ad75cc1f 100644 --- a/include/build_config/SDL_build_config_android.h +++ b/include/build_config/SDL_build_config_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,7 +57,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_GETENV 1 #define HAVE_SETENV 1 #define HAVE_PUTENV 1 diff --git a/include/build_config/SDL_build_config_emscripten.h b/include/build_config/SDL_build_config_emscripten.h index 2503e98d..fcaa277c 100644 --- a/include/build_config/SDL_build_config_emscripten.h +++ b/include/build_config/SDL_build_config_emscripten.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -60,7 +60,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_GETENV 1 #define HAVE_SETENV 1 #define HAVE_PUTENV 1 diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h index c180ef84..a6691465 100644 --- a/include/build_config/SDL_build_config_ios.h +++ b/include/build_config/SDL_build_config_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -49,7 +49,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_GETENV 1 #define HAVE_SETENV 1 #define HAVE_PUTENV 1 @@ -198,6 +197,8 @@ #define SDL_VIDEO_METAL 1 #endif +#define HAVE_COREMEDIA 1 + /* Enable system power support */ #define SDL_POWER_UIKIT 1 diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h index ed3f83a6..66bdb5ea 100644 --- a/include/build_config/SDL_build_config_macos.h +++ b/include/build_config/SDL_build_config_macos.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -54,7 +54,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_GETENV 1 #define HAVE_SETENV 1 #define HAVE_PUTENV 1 @@ -261,6 +260,8 @@ #endif #endif +#define HAVE_COREMEDIA 1 + /* Enable system power support */ #define SDL_POWER_MACOSX 1 diff --git a/include/build_config/SDL_build_config_minimal.h b/include/build_config/SDL_build_config_minimal.h index b94d0808..276881a7 100644 --- a/include/build_config/SDL_build_config_minimal.h +++ b/include/build_config/SDL_build_config_minimal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -83,9 +83,6 @@ typedef unsigned int uintptr_t; /* Enable the stub thread support (src/thread/generic/\*.c) */ #define SDL_THREADS_DISABLED 1 -/* Enable the stub timer support (src/timer/dummy/\*.c) */ -#define SDL_TIMERS_DISABLED 1 - /* Enable the dummy video driver (src/video/dummy/\*.c) */ #define SDL_VIDEO_DRIVER_DUMMY 1 diff --git a/include/build_config/SDL_build_config_ngage.h b/include/build_config/SDL_build_config_ngage.h index 13d97d22..1942ef46 100644 --- a/include/build_config/SDL_build_config_ngage.h +++ b/include/build_config/SDL_build_config_ngage.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index 8a29bab1..d83857d6 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -131,7 +131,6 @@ typedef unsigned int uintptr_t; #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_QSORT 1 #define HAVE_BSEARCH 1 #define HAVE_ABS 1 diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h index 47adef50..0999b87f 100644 --- a/include/build_config/SDL_build_config_wingdk.h +++ b/include/build_config/SDL_build_config_wingdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -77,7 +77,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_QSORT 1 #define HAVE_BSEARCH 1 #define HAVE_ABS 1 diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h index 73d46714..3b252cec 100644 --- a/include/build_config/SDL_build_config_winrt.h +++ b/include/build_config/SDL_build_config_winrt.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -75,7 +75,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_QSORT 1 #define HAVE_BSEARCH 1 #define HAVE_ABS 1 diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h index 622a8bc4..1a31c0d7 100644 --- a/include/build_config/SDL_build_config_xbox.h +++ b/include/build_config/SDL_build_config_xbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -77,7 +77,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_ALLOCA 1 #define HAVE_QSORT 1 #define HAVE_BSEARCH 1 #define HAVE_ABS 1 diff --git a/include/build_config/SDL_revision.h.cmake b/include/build_config/SDL_revision.h.cmake index b2b96acd..bd94c5f2 100644 --- a/include/build_config/SDL_revision.h.cmake +++ b/include/build_config/SDL_revision.h.cmake @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ /** * \file SDL_revision.h * -* \brief Header file containing the SDL revision. +* Header file containing the SDL revision. */ #ifndef SDL_revision_h_ diff --git a/src/SDL.c b/src/SDL.c index f099b048..ae2ab3bb 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -38,7 +38,8 @@ #include "SDL_assert_c.h" #include "SDL_log_c.h" -#include "audio/SDL_audio_c.h" +#include "SDL_properties_c.h" +#include "audio/SDL_sysaudio.h" #include "video/SDL_video_c.h" #include "events/SDL_events_c.h" #include "haptic/SDL_haptic_c.h" @@ -47,9 +48,7 @@ #include "sensor/SDL_sensor_c.h" /* Initialization/Cleanup routines */ -#ifndef SDL_TIMERS_DISABLED #include "timer/SDL_timer_c.h" -#endif #ifdef SDL_VIDEO_DRIVER_WINDOWS extern int SDL_HelperWindowCreate(void); extern int SDL_HelperWindowDestroy(void); @@ -136,7 +135,7 @@ static SDL_bool SDL_ShouldInitSubsystem(Uint32 subsystem) { const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem); SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255)); - return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) ? SDL_TRUE : SDL_FALSE; + return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)); } /* Private helper to check if a system needs to be quit. */ @@ -150,7 +149,23 @@ static SDL_bool SDL_ShouldQuitSubsystem(Uint32 subsystem) /* If we're in SDL_Quit, we shut down every subsystem, even if refcount * isn't zero. */ - return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit) ? SDL_TRUE : SDL_FALSE; + return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit); +} + +/* Private helper to either increment's existing ref counter, + * or fully init a new subsystem. */ +static SDL_bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) +{ + int subsystem_index = SDL_MostSignificantBitIndex32(subsystem); + SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255)); + if (subsystem_index < 0) { + return SDL_FALSE; + } + if (SDL_SubsystemRefCount[subsystem_index] > 0) { + ++SDL_SubsystemRefCount[subsystem_index]; + return SDL_TRUE; + } + return SDL_InitSubSystem(subsystem) == 0; } void SDL_SetMainReady(void) @@ -167,6 +182,8 @@ int SDL_InitSubSystem(Uint32 flags) } SDL_InitLog(); + SDL_InitProperties(); + SDL_GetGlobalProperties(); /* Clear the error message */ SDL_ClearError(); @@ -175,16 +192,6 @@ int SDL_InitSubSystem(Uint32 flags) SDL_DBus_Init(); #endif - if (flags & SDL_INIT_GAMEPAD) { - /* game controller implies joystick */ - flags |= SDL_INIT_JOYSTICK; - } - - if (flags & (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO)) { - /* video or joystick or audio implies events */ - flags |= SDL_INIT_EVENTS; - } - #ifdef SDL_VIDEO_DRIVER_WINDOWS if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) { if (SDL_HelperWindowCreate() < 0) { @@ -193,9 +200,7 @@ int SDL_InitSubSystem(Uint32 flags) } #endif -#ifndef SDL_TIMERS_DISABLED SDL_InitTicks(); -#endif /* Initialize the event subsystem */ if (flags & SDL_INIT_EVENTS) { @@ -218,7 +223,6 @@ int SDL_InitSubSystem(Uint32 flags) /* Initialize the timer subsystem */ if (flags & SDL_INIT_TIMER) { -#if !defined(SDL_TIMERS_DISABLED) && !defined(SDL_TIMER_DUMMY) if (SDL_ShouldInitSubsystem(SDL_INIT_TIMER)) { SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER); if (SDL_InitTimers() < 0) { @@ -229,16 +233,17 @@ int SDL_InitSubSystem(Uint32 flags) SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER); } flags_initialized |= SDL_INIT_TIMER; -#else - SDL_SetError("SDL not built with timer support"); - goto quit_and_error; -#endif } /* Initialize the video subsystem */ if (flags & SDL_INIT_VIDEO) { #ifndef SDL_VIDEO_DISABLED if (SDL_ShouldInitSubsystem(SDL_INIT_VIDEO)) { + /* video implies events */ + if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) { + goto quit_and_error; + } + SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); if (SDL_VideoInit(NULL) < 0) { SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); @@ -258,6 +263,11 @@ int SDL_InitSubSystem(Uint32 flags) if (flags & SDL_INIT_AUDIO) { #ifndef SDL_AUDIO_DISABLED if (SDL_ShouldInitSubsystem(SDL_INIT_AUDIO)) { + /* audio implies events */ + if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) { + goto quit_and_error; + } + SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO); if (SDL_InitAudio(NULL) < 0) { SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO); @@ -277,6 +287,11 @@ int SDL_InitSubSystem(Uint32 flags) if (flags & SDL_INIT_JOYSTICK) { #ifndef SDL_JOYSTICK_DISABLED if (SDL_ShouldInitSubsystem(SDL_INIT_JOYSTICK)) { + /* joystick implies events */ + if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) { + goto quit_and_error; + } + SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK); if (SDL_InitJoysticks() < 0) { SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK); @@ -295,6 +310,11 @@ int SDL_InitSubSystem(Uint32 flags) if (flags & SDL_INIT_GAMEPAD) { #ifndef SDL_JOYSTICK_DISABLED if (SDL_ShouldInitSubsystem(SDL_INIT_GAMEPAD)) { + /* game controller implies joystick */ + if (!SDL_InitOrIncrementSubsystem(SDL_INIT_JOYSTICK)) { + goto quit_and_error; + } + SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD); if (SDL_InitGamepads() < 0) { SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD); @@ -376,21 +396,19 @@ void SDL_QuitSubSystem(Uint32 flags) #ifndef SDL_JOYSTICK_DISABLED if (flags & SDL_INIT_GAMEPAD) { - /* game controller implies joystick */ - flags |= SDL_INIT_JOYSTICK; - if (SDL_ShouldQuitSubsystem(SDL_INIT_GAMEPAD)) { SDL_QuitGamepads(); + /* game controller implies joystick */ + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD); } if (flags & SDL_INIT_JOYSTICK) { - /* joystick implies events */ - flags |= SDL_INIT_EVENTS; - if (SDL_ShouldQuitSubsystem(SDL_INIT_JOYSTICK)) { SDL_QuitJoysticks(); + /* joystick implies events */ + SDL_QuitSubSystem(SDL_INIT_EVENTS); } SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK); } @@ -407,11 +425,10 @@ void SDL_QuitSubSystem(Uint32 flags) #ifndef SDL_AUDIO_DISABLED if (flags & SDL_INIT_AUDIO) { - /* audio implies events */ - flags |= SDL_INIT_EVENTS; - if (SDL_ShouldQuitSubsystem(SDL_INIT_AUDIO)) { SDL_QuitAudio(); + /* audio implies events */ + SDL_QuitSubSystem(SDL_INIT_EVENTS); } SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO); } @@ -419,24 +436,21 @@ void SDL_QuitSubSystem(Uint32 flags) #ifndef SDL_VIDEO_DISABLED if (flags & SDL_INIT_VIDEO) { - /* video implies events */ - flags |= SDL_INIT_EVENTS; - if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) { SDL_VideoQuit(); + /* video implies events */ + SDL_QuitSubSystem(SDL_INIT_EVENTS); } SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); } #endif -#if !defined(SDL_TIMERS_DISABLED) && !defined(SDL_TIMER_DUMMY) if (flags & SDL_INIT_TIMER) { if (SDL_ShouldQuitSubsystem(SDL_INIT_TIMER)) { SDL_QuitTimers(); } SDL_DecrementSubsystemRefCount(SDL_INIT_TIMER); } -#endif #ifndef SDL_EVENTS_DISABLED if (flags & SDL_INIT_EVENTS) { @@ -488,17 +502,16 @@ void SDL_Quit(void) #endif SDL_QuitSubSystem(SDL_INIT_EVERYTHING); -#ifndef SDL_TIMERS_DISABLED SDL_QuitTicks(); -#endif - - SDL_ClearHints(); - SDL_AssertionsQuit(); #ifdef SDL_USE_LIBDBUS SDL_DBus_Quit(); #endif + SDL_ClearHints(); + SDL_AssertionsQuit(); + + SDL_QuitProperties(); SDL_QuitLog(); /* Now that every subsystem has been quit, we reset the subsystem refcount @@ -511,13 +524,27 @@ void SDL_Quit(void) SDL_bInMainQuit = SDL_FALSE; } +/* Assume we can wrap SDL_AtomicInt values and cast to Uint32 */ +SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32)); + +Uint32 SDL_GetNextObjectID(void) +{ + static SDL_AtomicInt last_id; + + Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1; + if (id == 0) { + id = (Uint32)SDL_AtomicIncRef(&last_id) + 1; + } + return id; +} + /* Get the library version number */ int SDL_GetVersion(SDL_version *ver) { static SDL_bool check_hint = SDL_TRUE; static SDL_bool legacy_version = SDL_FALSE; - if (ver == NULL) { + if (!ver) { return SDL_InvalidParamError("ver"); } diff --git a/src/SDL_assert.c b/src/SDL_assert.c index ba1c194d..fd167430 100644 --- a/src/SDL_assert.c +++ b/src/SDL_assert.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -106,11 +106,11 @@ static void SDL_GenerateAssertionReport(void) const SDL_AssertData *item = triggered_assertions; /* only do this if the app hasn't assigned an assertion handler. */ - if ((item != NULL) && (assertion_handler != SDL_PromptAssertion)) { + if ((item) && (assertion_handler != SDL_PromptAssertion)) { debug_print("\n\nSDL assertion report.\n"); debug_print("All SDL assertions between last init/quit:\n\n"); - while (item != NULL) { + while (item) { debug_print( "'%s'\n" " * %s (%s:%d)\n" @@ -198,7 +198,7 @@ static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, v /* let env. variable override, so unit tests won't block in a GUI. */ envr = SDL_getenv("SDL_ASSERT"); - if (envr != NULL) { + if (envr) { if (message != stack_buf) { SDL_free(message); } @@ -334,9 +334,9 @@ SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, cons #ifndef SDL_THREADS_DISABLED static SDL_SpinLock spinlock = 0; SDL_AtomicLock(&spinlock); - if (assertion_mutex == NULL) { /* never called SDL_Init()? */ + if (!assertion_mutex) { /* never called SDL_Init()? */ assertion_mutex = SDL_CreateMutex(); - if (assertion_mutex == NULL) { + if (!assertion_mutex) { SDL_AtomicUnlock(&spinlock); return SDL_ASSERTION_IGNORE; /* oh well, I guess. */ } @@ -401,7 +401,7 @@ void SDL_AssertionsQuit(void) #if SDL_ASSERT_LEVEL > 0 SDL_GenerateAssertionReport(); #ifndef SDL_THREADS_DISABLED - if (assertion_mutex != NULL) { + if (assertion_mutex) { SDL_DestroyMutex(assertion_mutex); assertion_mutex = NULL; } @@ -429,7 +429,7 @@ void SDL_ResetAssertionReport(void) { SDL_AssertData *next = NULL; SDL_AssertData *item; - for (item = triggered_assertions; item != NULL; item = next) { + for (item = triggered_assertions; item; item = next) { next = (SDL_AssertData *)item->next; item->always_ignore = SDL_FALSE; item->trigger_count = 0; @@ -446,7 +446,7 @@ SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void) SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata) { - if (userdata != NULL) { + if (userdata) { *userdata = assertion_userdata; } return assertion_handler; diff --git a/src/SDL_assert_c.h b/src/SDL_assert_c.h index 37934f31..f4f1d2f8 100644 --- a/src/SDL_assert_c.h +++ b/src/SDL_assert_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/SDL_error.c b/src/SDL_error.c index 00f1bcf7..f01fd3c8 100644 --- a/src/SDL_error.c +++ b/src/SDL_error.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,12 +27,12 @@ int SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) { /* Ignore call if invalid format pointer was passed */ - if (fmt != NULL) { + if (fmt) { va_list ap; int result; - SDL_error *error = SDL_GetErrBuf(); + SDL_error *error = SDL_GetErrBuf(SDL_TRUE); - error->error = 1; /* mark error as valid */ + error->error = SDL_ErrorCodeGeneric; va_start(ap, fmt); result = SDL_vsnprintf(error->str, error->len, fmt, ap); @@ -62,13 +62,29 @@ int SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) /* Available for backwards compatibility */ const char *SDL_GetError(void) { - const SDL_error *error = SDL_GetErrBuf(); - return error->error ? error->str : ""; + const SDL_error *error = SDL_GetErrBuf(SDL_FALSE); + + if (!error) { + return ""; + } + + switch (error->error) { + case SDL_ErrorCodeGeneric: + return error->str; + case SDL_ErrorCodeOutOfMemory: + return "Out of memory"; + default: + return ""; + } } void SDL_ClearError(void) { - SDL_GetErrBuf()->error = 0; + SDL_error *error = SDL_GetErrBuf(SDL_FALSE); + + if (error) { + error->error = SDL_ErrorCodeNone; + } } /* Very common errors go here */ @@ -76,7 +92,8 @@ int SDL_Error(SDL_errorcode code) { switch (code) { case SDL_ENOMEM: - return SDL_SetError("Out of memory"); + SDL_GetErrBuf(SDL_TRUE)->error = SDL_ErrorCodeOutOfMemory; + return -1; case SDL_EFREAD: return SDL_SetError("Error reading from datastream"); case SDL_EFWRITE: @@ -89,16 +106,3 @@ int SDL_Error(SDL_errorcode code) return SDL_SetError("Unknown SDL error"); } } - -char *SDL_GetErrorMsg(char *errstr, int maxlen) -{ - const SDL_error *error = SDL_GetErrBuf(); - - if (error->error) { - SDL_strlcpy(errstr, error->str, maxlen); - } else { - *errstr = '\0'; - } - - return errstr; -} diff --git a/src/SDL_error_c.h b/src/SDL_error_c.h index 4c63ee00..c3e4f782 100644 --- a/src/SDL_error_c.h +++ b/src/SDL_error_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,9 +27,16 @@ #ifndef SDL_error_c_h_ #define SDL_error_c_h_ +typedef enum +{ + SDL_ErrorCodeNone, + SDL_ErrorCodeGeneric, + SDL_ErrorCodeOutOfMemory, +} SDL_ErrorCode; + typedef struct SDL_error { - int error; /* This is a numeric value corresponding to the current error */ + SDL_ErrorCode error; char *str; size_t len; SDL_realloc_func realloc_func; @@ -37,6 +44,6 @@ typedef struct SDL_error } SDL_error; /* Defined in SDL_thread.c */ -extern SDL_error *SDL_GetErrBuf(void); +extern SDL_error *SDL_GetErrBuf(SDL_bool create); #endif /* SDL_error_c_h_ */ diff --git a/src/SDL_guid.c b/src/SDL_guid.c index 61387941..a73852dd 100644 --- a/src/SDL_guid.c +++ b/src/SDL_guid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,7 @@ int SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID) static const char k_rgchHexToASCII[] = "0123456789abcdef"; int i; - if (pszGUID == NULL) { + if (!pszGUID) { return SDL_InvalidParamError("pszGUID"); } if (cbGUID <= 0) { diff --git a/src/SDL_hashtable.c b/src/SDL_hashtable.c new file mode 100644 index 00000000..25b893b6 --- /dev/null +++ b/src/SDL_hashtable.c @@ -0,0 +1,273 @@ +/* + Simple DirectMedia Layer + 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 + 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_hashtable.h" + +typedef struct SDL_HashItem +{ + const void *key; + const void *value; + struct SDL_HashItem *next; +} SDL_HashItem; + +struct SDL_HashTable +{ + SDL_HashItem **table; + Uint32 table_len; + SDL_bool stackable; + void *data; + SDL_HashTable_HashFn hash; + SDL_HashTable_KeyMatchFn keymatch; + SDL_HashTable_NukeFn nuke; +}; + +SDL_HashTable *SDL_CreateHashTable(void *data, const Uint32 num_buckets, const SDL_HashTable_HashFn hashfn, + const SDL_HashTable_KeyMatchFn keymatchfn, + const SDL_HashTable_NukeFn nukefn, + const SDL_bool stackable) +{ + SDL_HashTable *table; + + // num_buckets must be a power of two so we get a solid block of bits to mask hash values against. + if ((num_buckets == 0) || ((num_buckets & (num_buckets - 1)) != 0)) { + SDL_SetError("num_buckets must be a power of two"); + return NULL; + } + + table = (SDL_HashTable *) SDL_calloc(1, sizeof (SDL_HashTable)); + if (!table) { + return NULL; + } + + table->table = (SDL_HashItem **) SDL_calloc(num_buckets, sizeof (SDL_HashItem *)); + if (!table->table) { + SDL_free(table); + return NULL; + } + + table->table_len = num_buckets; + table->stackable = stackable; + table->data = data; + table->hash = hashfn; + table->keymatch = keymatchfn; + table->nuke = nukefn; + return table; +} + +static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key) +{ + return table->hash(key, table->data) & (table->table_len - 1); +} + + +SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value) +{ + SDL_HashItem *item; + const Uint32 hash = calc_hash(table, key); + + if ( (!table->stackable) && (SDL_FindInHashTable(table, key, NULL)) ) { + return SDL_FALSE; + } + + // !!! FIXME: grow and rehash table if it gets too saturated. + item = (SDL_HashItem *) SDL_malloc(sizeof (SDL_HashItem)); + if (!item) { + return SDL_FALSE; + } + + item->key = key; + item->value = value; + item->next = table->table[hash]; + table->table[hash] = item; + + return SDL_TRUE; +} + +SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value) +{ + const Uint32 hash = calc_hash(table, key); + void *data = table->data; + SDL_HashItem *i; + + for (i = table->table[hash]; i; i = i->next) { + if (table->keymatch(key, i->key, data)) { + if (_value) { + *_value = i->value; + } + return SDL_TRUE; + } + } + + return SDL_FALSE; +} + +SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) +{ + const Uint32 hash = calc_hash(table, key); + SDL_HashItem *item = NULL; + SDL_HashItem *prev = NULL; + void *data = table->data; + + for (item = table->table[hash]; item; item = item->next) { + if (table->keymatch(key, item->key, data)) { + if (prev) { + prev->next = item->next; + } else { + table->table[hash] = item->next; + } + + table->nuke(item->key, item->value, data); + SDL_free(item); + return SDL_TRUE; + } + + prev = item; + } + + return SDL_FALSE; +} + +SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter) +{ + SDL_HashItem *item = *iter ? ((SDL_HashItem *) *iter)->next : table->table[calc_hash(table, key)]; + + while (item) { + if (table->keymatch(key, item->key, table->data)) { + *_value = item->value; + *iter = item; + return SDL_TRUE; + } + item = item->next; + } + + // no more matches. + *_value = NULL; + *iter = NULL; + return SDL_FALSE; +} + +SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter) +{ + SDL_HashItem *item = (SDL_HashItem *) *iter; + Uint32 idx = 0; + + if (item) { + const SDL_HashItem *orig = item; + item = item->next; + if (!item) { + idx = calc_hash(table, orig->key) + 1; // !!! FIXME: we probably shouldn't rehash each time. + } + } + + while (!item && (idx < table->table_len)) { + item = table->table[idx++]; // skip empty buckets... + } + + if (!item) { // no more matches? + *_key = NULL; + *iter = NULL; + return SDL_FALSE; + } + + *_key = item->key; + *_value = item->value; + *iter = item; + + return SDL_TRUE; +} + +SDL_bool SDL_HashTableEmpty(SDL_HashTable *table) +{ + if (table) { + Uint32 i; + + for (i = 0; i < table->table_len; i++) { + SDL_HashItem *item = table->table[i]; + if (item) { + return SDL_FALSE; + } + } + } + return SDL_TRUE; +} + +void SDL_DestroyHashTable(SDL_HashTable *table) +{ + if (table) { + void *data = table->data; + Uint32 i; + + for (i = 0; i < table->table_len; i++) { + SDL_HashItem *item = table->table[i]; + while (item) { + SDL_HashItem *next = item->next; + table->nuke(item->key, item->value, data); + SDL_free(item); + item = next; + } + } + + SDL_free(table->table); + SDL_free(table); + } +} + +// this is djb's xor hashing function. +static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len) +{ + Uint32 hash = 5381; + while (len--) { + hash = ((hash << 5) + hash) ^ *(str++); + } + return hash; +} + +Uint32 SDL_HashString(const void *key, void *data) +{ + const char *str = (const char *)key; + return hash_string_djbxor(str, SDL_strlen(str)); +} + +SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *data) +{ + if (a == b) { + return SDL_TRUE; // same pointer, must match. + } else if (!a || !b) { + return SDL_FALSE; // one pointer is NULL (and first test shows they aren't the same pointer), must not match. + } + return (SDL_strcmp((const char *)a, (const char *)b) == 0); // Check against actual string contents. +} + +// We assume we can fit the ID in the key directly +SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *)); + +Uint32 SDL_HashID(const void *key, void *unused) +{ + return (Uint32)(uintptr_t)key; +} + +SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused) +{ + if (a == b) { + return SDL_TRUE; + } + return SDL_FALSE; +} diff --git a/src/SDL_hashtable.h b/src/SDL_hashtable.h new file mode 100644 index 00000000..96614bc1 --- /dev/null +++ b/src/SDL_hashtable.h @@ -0,0 +1,57 @@ +/* + Simple DirectMedia Layer + 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 + 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_hashtable_h_ +#define SDL_hashtable_h_ + +/* this is not (currently) a public API. But maybe it should be! */ + +struct SDL_HashTable; +typedef struct SDL_HashTable SDL_HashTable; +typedef Uint32 (*SDL_HashTable_HashFn)(const void *key, void *data); +typedef SDL_bool (*SDL_HashTable_KeyMatchFn)(const void *a, const void *b, void *data); +typedef void (*SDL_HashTable_NukeFn)(const void *key, const void *value, void *data); + +SDL_HashTable *SDL_CreateHashTable(void *data, + const Uint32 num_buckets, + const SDL_HashTable_HashFn hashfn, + const SDL_HashTable_KeyMatchFn keymatchfn, + const SDL_HashTable_NukeFn nukefn, + const SDL_bool stackable); + +void SDL_DestroyHashTable(SDL_HashTable *table); +SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value); +SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key); +SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value); +SDL_bool SDL_HashTableEmpty(SDL_HashTable *table); + +// iterate all values for a specific key. This only makes sense if the hash is stackable. If not-stackable, just use SDL_FindInHashTable(). +SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter); + +// iterate all key/value pairs in a hash (stackable hashes can have duplicate keys with multiple values). +SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter); + +Uint32 SDL_HashString(const void *key, void *unused); +SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *unused); + +Uint32 SDL_HashID(const void *key, void *unused); +SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused); + +#endif /* SDL_hashtable_h_ */ diff --git a/src/SDL_hints.c b/src/SDL_hints.c index 9ee0bc31..b3cebbf9 100644 --- a/src/SDL_hints.c +++ b/src/SDL_hints.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -49,7 +49,7 @@ SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPr SDL_Hint *hint; SDL_HintWatch *entry; - if (name == NULL) { + if (!name) { return SDL_FALSE; } @@ -64,7 +64,7 @@ SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPr return SDL_FALSE; } if (hint->value != value && - (value == NULL || !hint->value || SDL_strcmp(hint->value, value) != 0)) { + (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) { char *old_value = hint->value; hint->value = value ? SDL_strdup(value) : NULL; @@ -85,7 +85,7 @@ SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPr /* Couldn't find the hint, add a new one */ hint = (SDL_Hint *)SDL_malloc(sizeof(*hint)); - if (hint == NULL) { + if (!hint) { return SDL_FALSE; } hint->name = SDL_strdup(name); @@ -103,16 +103,16 @@ SDL_bool SDL_ResetHint(const char *name) SDL_Hint *hint; SDL_HintWatch *entry; - if (name == NULL) { + if (!name) { return SDL_FALSE; } env = SDL_getenv(name); for (hint = SDL_hints; hint; hint = hint->next) { if (SDL_strcmp(name, hint->name) == 0) { - if ((env == NULL && hint->value != NULL) || - (env != NULL && hint->value == NULL) || - (env != NULL && SDL_strcmp(env, hint->value) != 0)) { + if ((!env && hint->value) || + (env && !hint->value) || + (env && SDL_strcmp(env, hint->value) != 0)) { for (entry = hint->callbacks; entry;) { /* Save the next entry in case this one is deleted */ SDL_HintWatch *next = entry->next; @@ -137,9 +137,9 @@ void SDL_ResetHints(void) for (hint = SDL_hints; hint; hint = hint->next) { env = SDL_getenv(hint->name); - if ((env == NULL && hint->value != NULL) || - (env != NULL && hint->value == NULL) || - (env != NULL && SDL_strcmp(env, hint->value) != 0)) { + if ((!env && hint->value) || + (env && !hint->value) || + (env && SDL_strcmp(env, hint->value) != 0)) { for (entry = hint->callbacks; entry;) { /* Save the next entry in case this one is deleted */ SDL_HintWatch *next = entry->next; @@ -166,7 +166,7 @@ const char *SDL_GetHint(const char *name) env = SDL_getenv(name); for (hint = SDL_hints; hint; hint = hint->next) { if (SDL_strcmp(name, hint->name) == 0) { - if (env == NULL || hint->priority == SDL_HINT_OVERRIDE) { + if (!env || hint->priority == SDL_HINT_OVERRIDE) { return hint->value; } break; @@ -177,7 +177,7 @@ const char *SDL_GetHint(const char *name) int SDL_GetStringInteger(const char *value, int default_value) { - if (value == NULL || !*value) { + if (!value || !*value) { return default_value; } if (*value == '0' || SDL_strcasecmp(value, "false") == 0) { @@ -194,7 +194,7 @@ int SDL_GetStringInteger(const char *value, int default_value) SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value) { - if (value == NULL || !*value) { + if (!value || !*value) { return default_value; } if (*value == '0' || SDL_strcasecmp(value, "false") == 0) { @@ -215,7 +215,7 @@ int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userd SDL_HintWatch *entry; const char *value; - if (name == NULL || !*name) { + if (!name || !*name) { return SDL_InvalidParamError("name"); } if (!callback) { @@ -225,8 +225,8 @@ int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userd SDL_DelHintCallback(name, callback, userdata); entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry)); - if (entry == NULL) { - return SDL_OutOfMemory(); + if (!entry) { + return -1; } entry->callback = callback; entry->userdata = userdata; @@ -236,18 +236,18 @@ int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userd break; } } - if (hint == NULL) { + if (!hint) { /* Need to add a hint entry for this watcher */ hint = (SDL_Hint *)SDL_malloc(sizeof(*hint)); - if (hint == NULL) { + if (!hint) { SDL_free(entry); - return SDL_OutOfMemory(); + return -1; } hint->name = SDL_strdup(name); if (!hint->name) { SDL_free(entry); SDL_free(hint); - return SDL_OutOfMemory(); + return -1; } hint->value = NULL; hint->priority = SDL_HINT_DEFAULT; diff --git a/src/SDL_hints_c.h b/src/SDL_hints_c.h index e7131478..68cceac7 100644 --- a/src/SDL_hints_c.h +++ b/src/SDL_hints_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/SDL_internal.h b/src/SDL_internal.h index 3e2148c3..bfca0782 100644 --- a/src/SDL_internal.h +++ b/src/SDL_internal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,6 +40,17 @@ #define SDL_VARIABLE_LENGTH_ARRAY #endif +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__) +#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1 +#endif + +#ifdef _MSC_VER /* We use constant comparison for generated code */ +#pragma warning(disable : 6326) +#endif + +#ifdef _MSC_VER /* SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe */ +#pragma warning(disable : 6255) +#endif #define SDL_MAX_SMALL_ALLOC_STACKSIZE 128 #define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count)))) #define SDL_small_free(ptr, isstack) \ @@ -197,9 +208,10 @@ extern "C" { #endif +extern DECLSPEC Uint32 SDLCALL SDL_GetNextObjectID(void); extern DECLSPEC int SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS); extern DECLSPEC int SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS); -extern DECLSPEC int SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS); +extern DECLSPEC SDL_bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/SDL_list.c b/src/SDL_list.c index 8c4b509e..4d03e78e 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,8 +27,8 @@ int SDL_ListAdd(SDL_ListNode **head, void *ent) { SDL_ListNode *node = SDL_malloc(sizeof(*node)); - if (node == NULL) { - return SDL_OutOfMemory(); + if (!node) { + return -1; } node->entry = ent; @@ -43,7 +43,7 @@ void SDL_ListPop(SDL_ListNode **head, void **ent) SDL_ListNode **ptr = head; /* Invalid or empty */ - if (head == NULL || *head == NULL) { + if (!head || !*head) { return; } diff --git a/src/SDL_list.h b/src/SDL_list.h index a42897a9..583f78aa 100644 --- a/src/SDL_list.h +++ b/src/SDL_list.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/SDL_log.c b/src/SDL_log.c index 214574e3..198be022 100644 --- a/src/SDL_log.c +++ b/src/SDL_log.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,7 +65,7 @@ static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput; static void *SDL_log_userdata = NULL; static SDL_Mutex *log_function_mutex = NULL; -#ifdef __GNUC__ +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" #endif @@ -80,7 +80,7 @@ static const char *SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = { "CRITICAL" }; -#ifdef __GNUC__ +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop #endif @@ -112,7 +112,7 @@ static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = { void SDL_InitLog(void) { - if (log_function_mutex == NULL) { + if (!log_function_mutex) { /* if this fails we'll try to continue without it. */ log_function_mutex = SDL_CreateMutex(); } @@ -282,7 +282,7 @@ static const char *GetCategoryPrefix(int category) } #endif /* __ANDROID__ */ -void SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va_list ap) +void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) { char *message = NULL; char stack_buf[SDL_MAX_LOG_MESSAGE_STACK]; @@ -305,7 +305,7 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va return; } - if (log_function_mutex == NULL) { + if (!log_function_mutex) { /* this mutex creation can race if you log from two threads at startup. You should have called SDL_Init first! */ log_function_mutex = SDL_CreateMutex(); } @@ -323,7 +323,7 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va if (len >= sizeof(stack_buf) && SDL_size_add_overflow(len, 1, &len_plus_term) == 0) { /* Allocate exactly what we need, including the zero-terminator */ message = (char *)SDL_malloc(len_plus_term); - if (message == NULL) { + if (!message) { return; } va_copy(aq, ap); @@ -459,7 +459,7 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority { FILE *pFile; pFile = fopen("SDL_Log.txt", "a"); - if (pFile != NULL) { + if (pFile) { (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message); (void)fclose(pFile); } @@ -468,7 +468,7 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority { FILE *pFile; pFile = fopen("ux0:/data/SDL_Log.txt", "a"); - if (pFile != NULL) { + if (pFile) { (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message); (void)fclose(pFile); } @@ -477,7 +477,7 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority { FILE *pFile; pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a"); - if (pFile != NULL) { + if (pFile) { (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message); (void)fclose(pFile); } diff --git a/src/SDL_log_c.h b/src/SDL_log_c.h index e62eff4a..3da88d68 100644 --- a/src/SDL_log_c.h +++ b/src/SDL_log_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/SDL_properties.c b/src/SDL_properties.c new file mode 100644 index 00000000..83e5dd68 --- /dev/null +++ b/src/SDL_properties.c @@ -0,0 +1,722 @@ +/* + Simple DiretMedia Layer + 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 + 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_hashtable.h" +#include "SDL_hints_c.h" +#include "SDL_properties_c.h" + + +typedef struct +{ + SDL_PropertyType type; + + union { + void *pointer_value; + char *string_value; + Sint64 number_value; + float float_value; + SDL_bool boolean_value; + } value; + + char *string_storage; + + void (SDLCALL *cleanup)(void *userdata, void *value); + void *userdata; +} SDL_Property; + +typedef struct +{ + SDL_HashTable *props; + SDL_Mutex *lock; +} SDL_Properties; + +static SDL_HashTable *SDL_properties; +static SDL_Mutex *SDL_properties_lock; +static SDL_PropertiesID SDL_last_properties_id; +static SDL_PropertiesID SDL_global_properties; + + +static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, SDL_bool cleanup) +{ + SDL_Property *property = (SDL_Property *)value; + if (property) { + switch (property->type) { + case SDL_PROPERTY_TYPE_POINTER: + if (property->cleanup && cleanup) { + property->cleanup(property->userdata, property->value.pointer_value); + } + break; + case SDL_PROPERTY_TYPE_STRING: + SDL_free(property->value.string_value); + break; + default: + break; + } + if (property->string_storage) { + SDL_free(property->string_storage); + } + } + SDL_free((void *)key); + SDL_free((void *)value); +} + +static void SDL_FreeProperty(const void *key, const void *value, void *data) +{ + SDL_FreePropertyWithCleanup(key, value, data, SDL_TRUE); +} + +static void SDL_FreeProperties(const void *key, const void *value, void *data) +{ + SDL_Properties *properties = (SDL_Properties *)value; + if (properties) { + if (properties->props) { + SDL_DestroyHashTable(properties->props); + properties->props = NULL; + } + if (properties->lock) { + SDL_DestroyMutex(properties->lock); + properties->lock = NULL; + } + SDL_free(properties); + } +} + +int SDL_InitProperties(void) +{ + if (!SDL_properties_lock) { + SDL_properties_lock = SDL_CreateMutex(); + if (!SDL_properties_lock) { + return -1; + } + } + if (!SDL_properties) { + SDL_properties = SDL_CreateHashTable(NULL, 16, SDL_HashID, SDL_KeyMatchID, SDL_FreeProperties, SDL_FALSE); + if (!SDL_properties) { + return -1; + } + } + return 0; +} + +void SDL_QuitProperties(void) +{ + if (SDL_global_properties) { + SDL_DestroyProperties(SDL_global_properties); + SDL_global_properties = 0; + } + if (SDL_properties) { + SDL_DestroyHashTable(SDL_properties); + SDL_properties = NULL; + } + if (SDL_properties_lock) { + SDL_DestroyMutex(SDL_properties_lock); + SDL_properties_lock = NULL; + } +} + +SDL_PropertiesID SDL_GetGlobalProperties(void) +{ + if (!SDL_global_properties) { + SDL_global_properties = SDL_CreateProperties(); + } + return SDL_global_properties; +} + +SDL_PropertiesID SDL_CreateProperties(void) +{ + SDL_PropertiesID props = 0; + SDL_Properties *properties = NULL; + SDL_bool inserted = SDL_FALSE; + + if (!SDL_properties && SDL_InitProperties() < 0) { + return 0; + } + + properties = SDL_calloc(1, sizeof(*properties)); + if (!properties) { + goto error; + } + properties->props = SDL_CreateHashTable(NULL, 4, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, SDL_FALSE); + if (!properties->props) { + goto error; + } + properties->lock = SDL_CreateMutex(); + if (!properties->lock) { + goto error; + } + + if (SDL_InitProperties() < 0) { + goto error; + } + + SDL_LockMutex(SDL_properties_lock); + ++SDL_last_properties_id; + if (SDL_last_properties_id == 0) { + ++SDL_last_properties_id; + } + props = SDL_last_properties_id; + if (SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties)) { + inserted = SDL_TRUE; + } + SDL_UnlockMutex(SDL_properties_lock); + + if (inserted) { + /* All done! */ + return props; + } + +error: + SDL_FreeProperties(NULL, properties, NULL); + return 0; +} + +int SDL_LockProperties(SDL_PropertiesID props) +{ + SDL_Properties *properties = NULL; + + if (!props) { + return SDL_InvalidParamError("props"); + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + return SDL_InvalidParamError("props"); + } + + SDL_LockMutex(properties->lock); + return 0; +} + +void SDL_UnlockProperties(SDL_PropertiesID props) +{ + SDL_Properties *properties = NULL; + + if (!props) { + return; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + return; + } + + SDL_UnlockMutex(properties->lock); +} + +static int SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property) +{ + SDL_Properties *properties = NULL; + int result = 0; + + if (!props) { + SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE); + return SDL_InvalidParamError("props"); + } + if (!name || !*name) { + SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE); + return SDL_InvalidParamError("name"); + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE); + return SDL_InvalidParamError("props"); + } + + SDL_LockMutex(properties->lock); + { + SDL_RemoveFromHashTable(properties->props, name); + if (property) { + char *key = SDL_strdup(name); + if (!SDL_InsertIntoHashTable(properties->props, key, property)) { + SDL_FreePropertyWithCleanup(key, property, NULL, SDL_FALSE); + result = -1; + } + } + } + SDL_UnlockMutex(properties->lock); + + return result; +} + +int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata) +{ + SDL_Property *property; + + if (!value) { + return SDL_ClearProperty(props, name); + } + + property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_POINTER; + property->value.pointer_value = value; + property->cleanup = cleanup; + property->userdata = userdata; + return SDL_PrivateSetProperty(props, name, property); +} + +int SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value) +{ + SDL_Property *property; + + if (!value) { + return SDL_ClearProperty(props, name); + } + + property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_POINTER; + property->value.pointer_value = value; + return SDL_PrivateSetProperty(props, name, property); +} + + +int SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value) +{ + SDL_Property *property; + + if (!value) { + return SDL_ClearProperty(props, name); + } + + property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_STRING; + property->value.string_value = SDL_strdup(value); + if (!property->value.string_value) { + SDL_free(property); + return -1; + } + return SDL_PrivateSetProperty(props, name, property); +} + +int SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value) +{ + SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_NUMBER; + property->value.number_value = value; + return SDL_PrivateSetProperty(props, name, property); +} + +int SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value) +{ + SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_FLOAT; + property->value.float_value = value; + return SDL_PrivateSetProperty(props, name, property); +} + +int SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool value) +{ + SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property)); + if (!property) { + return -1; + } + property->type = SDL_PROPERTY_TYPE_BOOLEAN; + property->value.boolean_value = value ? SDL_TRUE : SDL_FALSE; + return SDL_PrivateSetProperty(props, name, property); +} + +SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name) +{ + SDL_Properties *properties = NULL; + SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID; + + if (!props) { + SDL_InvalidParamError("props"); + return SDL_PROPERTY_TYPE_INVALID; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return SDL_PROPERTY_TYPE_INVALID; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return SDL_PROPERTY_TYPE_INVALID; + } + + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + type = property->type; + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return type; +} + +void *SDL_GetProperty(SDL_PropertiesID props, const char *name, void *default_value) +{ + SDL_Properties *properties = NULL; + void *value = default_value; + + if (!props) { + SDL_InvalidParamError("props"); + return value; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return value; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return value; + } + + /* Note that taking the lock here only guarantees that we won't read the + * hashtable while it's being modified. The value itself can easily be + * freed from another thread after it is returned here. + */ + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + if (property->type == SDL_PROPERTY_TYPE_POINTER) { + value = property->value.pointer_value; + } else { + SDL_SetError("Property %s isn't a pointer value", name); + } + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return value; +} + +const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value) +{ + SDL_Properties *properties = NULL; + const char *value = default_value; + + if (!props) { + SDL_InvalidParamError("props"); + return value; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return value; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return value; + } + + /* Note that taking the lock here only guarantees that we won't read the + * hashtable while it's being modified. The value itself can easily be + * freed from another thread after it is returned here. + * + * FIXME: Should we SDL_strdup() the return value to avoid this? + */ + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + switch (property->type) { + case SDL_PROPERTY_TYPE_STRING: + value = property->value.string_value; + break; + case SDL_PROPERTY_TYPE_NUMBER: + if (property->string_storage) { + value = property->string_storage; + } else { + SDL_asprintf(&property->string_storage, "%" SDL_PRIs64 "", property->value.number_value); + if (property->string_storage) { + value = property->string_storage; + } + } + break; + case SDL_PROPERTY_TYPE_FLOAT: + if (property->string_storage) { + value = property->string_storage; + } else { + SDL_asprintf(&property->string_storage, "%f", property->value.float_value); + if (property->string_storage) { + value = property->string_storage; + } + } + break; + case SDL_PROPERTY_TYPE_BOOLEAN: + value = property->value.boolean_value ? "true" : "false"; + break; + default: + SDL_SetError("Property %s isn't a string value", name); + break; + } + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return value; +} + +Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value) +{ + SDL_Properties *properties = NULL; + Sint64 value = default_value; + + if (!props) { + SDL_InvalidParamError("props"); + return value; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return value; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return value; + } + + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + switch (property->type) { + case SDL_PROPERTY_TYPE_STRING: + value = SDL_strtoll(property->value.string_value, NULL, 0); + break; + case SDL_PROPERTY_TYPE_NUMBER: + value = property->value.number_value; + break; + case SDL_PROPERTY_TYPE_FLOAT: + value = (Sint64)SDL_round((double)property->value.float_value); + break; + case SDL_PROPERTY_TYPE_BOOLEAN: + value = property->value.boolean_value; + break; + default: + SDL_SetError("Property %s isn't a number value", name); + break; + } + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return value; +} + +float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value) +{ + SDL_Properties *properties = NULL; + float value = default_value; + + if (!props) { + SDL_InvalidParamError("props"); + return value; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return value; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return value; + } + + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + switch (property->type) { + case SDL_PROPERTY_TYPE_STRING: + value = (float)SDL_atof(property->value.string_value); + break; + case SDL_PROPERTY_TYPE_NUMBER: + value = (float)property->value.number_value; + break; + case SDL_PROPERTY_TYPE_FLOAT: + value = property->value.float_value; + break; + case SDL_PROPERTY_TYPE_BOOLEAN: + value = (float)property->value.boolean_value; + break; + default: + SDL_SetError("Property %s isn't a float value", name); + break; + } + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return value; +} + +SDL_bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool default_value) +{ + SDL_Properties *properties = NULL; + SDL_bool value = default_value; + + if (!props) { + SDL_InvalidParamError("props"); + return value; + } + if (!name || !*name) { + SDL_InvalidParamError("name"); + return value; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + SDL_InvalidParamError("props"); + return value; + } + + SDL_LockMutex(properties->lock); + { + SDL_Property *property = NULL; + if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) { + switch (property->type) { + case SDL_PROPERTY_TYPE_STRING: + value = SDL_GetStringBoolean(property->value.string_value, default_value); + break; + case SDL_PROPERTY_TYPE_NUMBER: + value = (property->value.number_value != 0); + break; + case SDL_PROPERTY_TYPE_FLOAT: + value = (property->value.float_value != 0.0f); + break; + case SDL_PROPERTY_TYPE_BOOLEAN: + value = property->value.boolean_value; + break; + default: + SDL_SetError("Property %s isn't a boolean value", name); + break; + } + } else { + SDL_SetError("Couldn't find property named %s", name); + } + } + SDL_UnlockMutex(properties->lock); + + return value; +} + +int SDL_ClearProperty(SDL_PropertiesID props, const char *name) +{ + return SDL_PrivateSetProperty(props, name, NULL); +} + +int SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata) +{ + SDL_Properties *properties = NULL; + + if (!props) { + return SDL_InvalidParamError("props"); + } + if (!callback) { + return SDL_InvalidParamError("callback"); + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!properties) { + return SDL_InvalidParamError("props"); + } + + SDL_LockMutex(properties->lock); + { + void *iter; + const void *key, *value; + + iter = NULL; + while (SDL_IterateHashTable(properties->props, &key, &value, &iter)) { + callback(userdata, props, (const char *)key); + } + } + SDL_UnlockMutex(properties->lock); + + return 0; +} + +void SDL_DestroyProperties(SDL_PropertiesID props) +{ + if (!props) { + return; + } + + SDL_LockMutex(SDL_properties_lock); + SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props); + SDL_UnlockMutex(SDL_properties_lock); +} diff --git a/src/SDL_properties_c.h b/src/SDL_properties_c.h new file mode 100644 index 00000000..9b590cde --- /dev/null +++ b/src/SDL_properties_c.h @@ -0,0 +1,23 @@ +/* + Simple DiretMedia Layer + 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 + 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. +*/ + +extern int SDL_InitProperties(void); +extern void SDL_QuitProperties(void); diff --git a/src/SDL_utils.c b/src/SDL_utils.c index 7401d1cb..581964b0 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index d4c8387e..98658c36 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/atomic/SDL_atomic.c b/src/atomic/SDL_atomic.c index 4164a5f7..2919e800 100644 --- a/src/atomic/SDL_atomic.c +++ b/src/atomic/SDL_atomic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -128,13 +128,13 @@ SDL_bool SDL_AtomicCAS(SDL_AtomicInt *a, int oldval, int newval) SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value)); return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval; #elif defined(HAVE_WATCOM_ATOMICS) - return (SDL_bool)_SDL_cmpxchg_watcom(&a->value, newval, oldval); + return _SDL_cmpxchg_watcom(&a->value, newval, oldval); #elif defined(HAVE_GCC_ATOMICS) - return (SDL_bool)__sync_bool_compare_and_swap(&a->value, oldval, newval); + return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(__MACOS__) /* this is deprecated in 10.12 sdk; favor gcc atomics. */ - return (SDL_bool)OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value); + return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value); #elif defined(__SOLARIS__) - return (SDL_bool)((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval); + return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval); #elif defined(EMULATE_CAS) SDL_bool retval = SDL_FALSE; @@ -156,15 +156,15 @@ SDL_bool SDL_AtomicCASPtr(void **a, void *oldval, void *newval) #ifdef HAVE_MSC_ATOMICS return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval; #elif defined(HAVE_WATCOM_ATOMICS) - return (SDL_bool)_SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval); + return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval); #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(a, oldval, newval); #elif defined(__MACOS__) && defined(__LP64__) /* this is deprecated in 10.12 sdk; favor gcc atomics. */ - return (SDL_bool)OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a); + return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a); #elif defined(__MACOS__) && !defined(__LP64__) /* this is deprecated in 10.12 sdk; favor gcc atomics. */ - return (SDL_bool)OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a); + return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a); #elif defined(__SOLARIS__) - return (SDL_bool)(atomic_cas_ptr(a, oldval, newval) == oldval); + return (atomic_cas_ptr(a, oldval, newval) == oldval); #elif defined(EMULATE_CAS) SDL_bool retval = SDL_FALSE; diff --git a/src/atomic/SDL_spinlock.c b/src/atomic/SDL_spinlock.c index d4eaff41..82dcace1 100644 --- a/src/atomic/SDL_spinlock.c +++ b/src/atomic/SDL_spinlock.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,7 +63,7 @@ SDL_bool SDL_AtomicTryLock(SDL_SpinLock *lock) /* Terrible terrible damage */ static SDL_Mutex *_spinlock_mutex; - if (_spinlock_mutex == NULL) { + if (!_spinlock_mutex) { /* Race condition on first lock... */ _spinlock_mutex = SDL_CreateMutex(); } @@ -139,11 +139,11 @@ SDL_bool SDL_AtomicTryLock(SDL_SpinLock *lock) #elif defined(__SOLARIS__) && defined(_LP64) /* Used for Solaris with non-gcc compilers. */ - return (SDL_bool)((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0); + return ((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0); #elif defined(__SOLARIS__) && !defined(_LP64) /* Used for Solaris with non-gcc compilers. */ - return (SDL_bool)((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0); + return ((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0); #elif defined(PS2) uint32_t oldintr; SDL_bool res = SDL_FALSE; diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cfd169ad..b8f3d17b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,6 +30,9 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO &PULSEAUDIO_bootstrap, #endif +#ifdef SDL_AUDIO_DRIVER_PIPEWIRE + &PIPEWIRE_bootstrap, +#endif #ifdef SDL_AUDIO_DRIVER_ALSA &ALSA_bootstrap, #endif @@ -55,7 +58,7 @@ static const AudioBootStrap *const bootstrap[] = { &AAUDIO_bootstrap, #endif #ifdef SDL_AUDIO_DRIVER_OPENSLES - &openslES_bootstrap, + &OPENSLES_bootstrap, #endif #ifdef SDL_AUDIO_DRIVER_ANDROID &ANDROIDAUDIO_bootstrap, @@ -78,9 +81,6 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_JACK &JACK_bootstrap, #endif -#ifdef SDL_AUDIO_DRIVER_PIPEWIRE - &PIPEWIRE_bootstrap, -#endif #ifdef SDL_AUDIO_DRIVER_OSS &DSP_bootstrap, #endif @@ -139,68 +139,47 @@ static int GetDefaultSampleFramesFromFreq(const int freq) void OnAudioStreamCreated(SDL_AudioStream *stream) { - SDL_assert(SDL_GetCurrentAudioDriver() != NULL); SDL_assert(stream != NULL); - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_list_lock); - if (current_audio.existing_streams) { - current_audio.existing_streams->prev = 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.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); } - stream->prev = NULL; - stream->next = current_audio.existing_streams; - current_audio.existing_streams = stream; - SDL_UnlockRWLock(current_audio.device_list_lock); } void OnAudioStreamDestroy(SDL_AudioStream *stream) { - SDL_assert(SDL_GetCurrentAudioDriver() != NULL); SDL_assert(stream != NULL); - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_list_lock); - if (stream->prev) { - stream->prev->next = stream->next; - } - if (stream->next) { - stream->next->prev = stream->prev; - } - if (stream == current_audio.existing_streams) { - current_audio.existing_streams = stream->next; - } - SDL_UnlockRWLock(current_audio.device_list_lock); -} - - -// should hold logdev's physical device's lock before calling. -static void UpdateAudioStreamFormatsLogical(SDL_LogicalAudioDevice *logdev) -{ - const SDL_bool iscapture = logdev->physical_device->iscapture; - SDL_AudioSpec spec; - SDL_copyp(&spec, &logdev->physical_device->spec); - if (logdev->postmix != NULL) { - spec.format = SDL_AUDIO_F32; - } - - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { - // set the proper end of the stream to the device's format. - // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. - SDL_LockMutex(stream->lock); - SDL_copyp(iscapture ? &stream->src_spec : &stream->dst_spec, &spec); - SDL_UnlockMutex(stream->lock); + // 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 (stream->prev) { + stream->prev->next = stream->next; + } + if (stream->next) { + stream->next->prev = stream->prev; + } + if (stream == current_audio.existing_streams) { + current_audio.existing_streams = stream->next; + } + SDL_UnlockRWLock(current_audio.device_hash_lock); } } -// should hold device->lock before calling. -static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device) -{ - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { - UpdateAudioStreamFormatsLogical(logdev); - } -} - - // device should be locked when calling this. static SDL_bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device) { @@ -209,12 +188,74 @@ static SDL_bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device) device->logical_devices && // there's a logical device !device->logical_devices->next && // there's only _ONE_ logical device !device->logical_devices->postmix && // there isn't a postmix callback - !SDL_AtomicGet(&device->logical_devices->paused) && // it isn't paused device->logical_devices->bound_streams && // there's a bound stream !device->logical_devices->bound_streams->next_binding // there's only _ONE_ bound stream. - ) ? SDL_TRUE : SDL_FALSE; + ); } +// should hold device->lock before calling. +static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device) +{ + if (!device->iscapture) { // for capture devices, we only want to move to float32 for postmix, which we'll handle elsewhere. + const SDL_bool simple_copy = AudioDeviceCanUseSimpleCopy(device); + SDL_AudioSpec spec; + + device->simple_copy = simple_copy; + SDL_copyp(&spec, &device->spec); + + if (!simple_copy) { + spec.format = SDL_AUDIO_F32; // mixing and postbuf operates in float32 format. + } + + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { + // set the proper end of the stream to the device's format. + // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. + SDL_LockMutex(stream->lock); + SDL_copyp(&stream->dst_spec, &spec); + SDL_UnlockMutex(stream->lock); + } + } + } +} + + +// Zombie device implementation... + +// These get used when a device is disconnected or fails, so audiostreams don't overflow with data that isn't being +// consumed and apps relying on audio callbacks don't stop making progress. +static int ZombieWaitDevice(SDL_AudioDevice *device) +{ + if (!SDL_AtomicGet(&device->shutdown)) { + const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); + SDL_Delay((frames * 1000) / device->spec.freq); + } + return 0; +} + +static int ZombiePlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + return 0; // no-op, just throw the audio away. +} + +static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + return device->work_buffer; +} + +static int ZombieCaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + // return a full buffer of silence every time. + SDL_memset(buffer, device->silence_value, buflen); + return buflen; +} + +static void ZombieFlushCapture(SDL_AudioDevice *device) +{ + // no-op, this is all imaginary. +} + + // device management and hotplug... @@ -232,28 +273,161 @@ static SDL_bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device) static void ClosePhysicalAudioDevice(SDL_AudioDevice *device); -// the loop in assign_audio_device_instance_id relies on this being true. SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_CAPTURE < SDL_AUDIO_DEVICE_DEFAULT_OUTPUT); -static SDL_AudioDeviceID assign_audio_device_instance_id(SDL_bool iscapture, SDL_bool islogical) +static SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs +static SDL_AudioDeviceID AssignAudioDeviceInstanceId(SDL_bool iscapture, SDL_bool islogical) { /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value. - There's no reasonable scenario where this rolls over, but just in case, we wrap it in a loop. Also, make sure we don't assign SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, etc. */ - // The bottom two bits of the instance id tells you if it's an output device (1<<0), and if it's a physical device (1<<1). Make sure these are right. - const SDL_AudioDeviceID required_mask = (iscapture ? 0 : (1<<0)) | (islogical ? 0 : (1<<1)); + // The bottom two bits of the instance id tells you if it's an output device (1<<0), and if it's a physical device (1<<1). + const SDL_AudioDeviceID flags = (iscapture ? 0 : (1<<0)) | (islogical ? 0 : (1<<1)); - SDL_AudioDeviceID instance_id; - do { - instance_id = (SDL_AudioDeviceID) (SDL_AtomicIncRef(¤t_audio.last_device_instance_id) + 1); - } while ( (instance_id < 2) || (instance_id >= SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) || ((instance_id & 0x3) != required_mask) ); + const SDL_AudioDeviceID instance_id = (((SDL_AudioDeviceID) (SDL_AtomicIncRef(&last_device_instance_id) + 1)) << 2) | flags; + SDL_assert( (instance_id >= 2) && (instance_id < SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) ); return instance_id; } +static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE +{ + if (device) { + RefPhysicalAudioDevice(device); + SDL_LockMutex(device->lock); + } +} + +static void ReleaseAudioDevice(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE +{ + if (device) { + SDL_UnlockMutex(device->lock); + UnrefPhysicalAudioDevice(device); + } +} + +// If found, this locks _the physical device_ this logical device is associated with, before returning. +static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice **device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_ACQUIRE +{ + SDL_assert(device != NULL); + + *device = NULL; + + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + return NULL; + } + + SDL_LogicalAudioDevice *logdev = NULL; + + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_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 (logdev) { + *device = logdev->physical_device; + RefPhysicalAudioDevice(*device); // reference it, in case the logical device migrates to a new default. + } + SDL_UnlockRWLock(current_audio.device_hash_lock); + } + + if (!logdev) { + SDL_SetError("Invalid audio device instance ID"); + } else { + SDL_assert(*device != NULL); + SDL_LockMutex((*device)->lock); + } + + return logdev; +} + + +/* this finds the physical device associated with `devid` and locks it for use. + Note that a logical device instance id will return its associated physical device! */ +static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE +{ + SDL_AudioDevice *device = NULL; + + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_bool islogical = !(devid & (1<<1)); + if (islogical) { + 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_UnlockRWLock(current_audio.device_hash_lock); + + if (!device) { + SDL_SetError("Invalid audio device instance ID"); + } else { + ObtainPhysicalAudioDeviceObj(device); + } + } + + return device; +} + +static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE +{ + const SDL_bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) || (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE)); + if (!wants_default) { + return ObtainPhysicalAudioDevice(devid); + } + + const SDL_AudioDeviceID orig_devid = devid; + + while (SDL_TRUE) { + SDL_LockRWLockForReading(current_audio.device_hash_lock); + if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { + devid = current_audio.default_output_device_id; + } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { + devid = current_audio.default_capture_device_id; + } + SDL_UnlockRWLock(current_audio.device_hash_lock); + + if (devid == 0) { + SDL_SetError("No default audio device available"); + break; + } + + SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); + if (!device) { + break; + } + + // make sure the default didn't change while we were waiting for the lock... + SDL_bool got_it = SDL_FALSE; + SDL_LockRWLockForReading(current_audio.device_hash_lock); + if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) && (devid == current_audio.default_output_device_id)) { + got_it = SDL_TRUE; + } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) && (devid == current_audio.default_capture_device_id)) { + got_it = SDL_TRUE; + } + SDL_UnlockRWLock(current_audio.device_hash_lock); + + if (got_it) { + return device; + } + + ReleaseAudioDevice(device); // let it go and try again. + } + + return NULL; +} + // this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device! +// It also will not unref the physical device, since we might be shutting down; SDL_CloseAudioDevice handles the unref. 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); + } + // remove ourselves from the physical device's list of logical devices. if (logdev->next) { logdev->next->prev = logdev->prev; @@ -267,7 +441,7 @@ static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) // unbind any still-bound streams... SDL_AudioStream *next; - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = next) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = next) { SDL_LockMutex(stream->lock); next = stream->next_binding; stream->next_binding = NULL; @@ -276,12 +450,11 @@ static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) SDL_UnlockMutex(stream->lock); } - logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device); - + UpdateAudioStreamFormatsPhysical(logdev->physical_device); SDL_free(logdev); } -// this must not be called while `device` is still in a device list, or while a device's audio thread is still running (except if the thread calls this while shutting down). */ +// this must not be called while `device` is still in a device list, or while a device's audio thread is still running. static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) { if (!device) { @@ -289,44 +462,62 @@ static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) } // Destroy any logical devices that still exist... - SDL_LockMutex(device->lock); - while (device->logical_devices != NULL) { + SDL_LockMutex(device->lock); // don't use ObtainPhysicalAudioDeviceObj because we don't want to change refcounts while destroying. + while (device->logical_devices) { DestroyLogicalAudioDevice(device->logical_devices); } - SDL_UnlockMutex(device->lock); - // it's safe to not hold the lock for this (we can't anyhow, or the audio thread won't quit), because we shouldn't be in the device list at this point. ClosePhysicalAudioDevice(device); current_audio.impl.FreeDeviceHandle(device); + SDL_UnlockMutex(device->lock); // don't use ReleaseAudioDevice because we don't want to change refcounts while destroying. + SDL_DestroyMutex(device->lock); + SDL_DestroyCondition(device->close_cond); SDL_free(device->work_buffer); SDL_free(device->name); SDL_free(device); } -static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +// Don't hold the device lock when calling this, as we may destroy the device! +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_AtomicAdd(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count, -1); + } + SDL_UnlockRWLock(current_audio.device_hash_lock); + DestroyPhysicalAudioDevice(device); // ...and nuke it. + } +} + +void RefPhysicalAudioDevice(SDL_AudioDevice *device) +{ + SDL_AtomicIncRef(&device->refcount); +} + +static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AtomicInt *device_count) { SDL_assert(name != NULL); - SDL_LockRWLockForReading(current_audio.device_list_lock); + SDL_LockRWLockForReading(current_audio.device_hash_lock); const int shutting_down = SDL_AtomicGet(¤t_audio.shutting_down); - SDL_UnlockRWLock(current_audio.device_list_lock); + SDL_UnlockRWLock(current_audio.device_hash_lock); if (shutting_down) { return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. } SDL_AudioDevice *device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); if (!device) { - SDL_OutOfMemory(); return NULL; } device->name = SDL_strdup(name); if (!device->name) { SDL_free(device); - SDL_OutOfMemory(); return NULL; } @@ -337,8 +528,15 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool isc return NULL; } + device->close_cond = SDL_CreateCondition(); + if (!device->close_cond) { + SDL_DestroyMutex(device->lock); + SDL_free(device->name); + SDL_free(device); + return NULL; + } + SDL_AtomicSet(&device->shutdown, 0); - SDL_AtomicSet(&device->condemned, 0); SDL_AtomicSet(&device->zombie, 0); device->iscapture = iscapture; SDL_copyp(&device->spec, spec); @@ -346,33 +544,34 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool isc device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq); device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); device->handle = handle; - device->prev = NULL; - device->instance_id = assign_audio_device_instance_id(iscapture, /*islogical=*/SDL_FALSE); + device->instance_id = AssignAudioDeviceInstanceId(iscapture, /*islogical=*/SDL_FALSE); - SDL_LockRWLockForWriting(current_audio.device_list_lock); - - if (*devices) { - SDL_assert((*devices)->prev == NULL); - (*devices)->prev = device; + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device)) { + SDL_AtomicAdd(device_count, 1); + } else { + SDL_DestroyCondition(device->close_cond); + SDL_DestroyMutex(device->lock); + SDL_free(device->name); + SDL_free(device); + device = NULL; } - device->next = *devices; - *devices = device; - SDL_AtomicAdd(device_count, 1); - SDL_UnlockRWLock(current_audio.device_list_lock); + SDL_UnlockRWLock(current_audio.device_hash_lock); + RefPhysicalAudioDevice(device); // unref'd on device disconnect. return device; } static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { SDL_assert(current_audio.impl.HasCaptureSupport); - return CreatePhysicalAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); + return CreatePhysicalAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_device_count); } static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { - return CreatePhysicalAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); + return CreatePhysicalAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_device_count); } // The audio backends call this when a new device is plugged in. @@ -394,37 +593,26 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, } SDL_AudioDevice *device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle); + + // Add a device add event to the pending list, to be pushed when the event queue is pumped (away from any of our internal threads). if (device) { - // Post the event, if desired - if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) { - SDL_Event event; - event.type = SDL_EVENT_AUDIO_DEVICE_ADDED; - event.common.timestamp = 0; - event.adevice.which = device->instance_id; - event.adevice.iscapture = iscapture; - SDL_PushEvent(&event); + SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); + if (p) { // if allocation fails, you won't get an event, but we can't help that. + p->type = SDL_EVENT_AUDIO_DEVICE_ADDED; + p->devid = device->instance_id; + p->next = NULL; + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + 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); } } return device; } -// this _also_ destroys the logical device! -static void DisconnectLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) -{ - if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - event.common.timestamp = 0; - event.adevice.which = logdev->instance_id; - event.adevice.iscapture = logdev->physical_device->iscapture ? 1 : 0; - SDL_PushEvent(&event); - } - - DestroyLogicalAudioDevice(logdev); -} - // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) { @@ -432,84 +620,73 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) return; } - // if the current default device is going down, mark it as dead but keep it around until a replacement is decided upon, so we can migrate logical devices to it. - if ((device->instance_id == current_audio.default_output_device_id) || (device->instance_id == current_audio.default_capture_device_id)) { - SDL_LockMutex(device->lock); // make sure nothing else is messing with the device before continuing. - SDL_AtomicSet(&device->zombie, 1); - SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate, but don't mark it condemned, so the thread won't destroy the device. We'll join on the audio thread later. + // Save off removal info in a list so we can send events for each, next + // time the event queue pumps, in case something tries to close a device + // from an event filter, as this would risk deadlocks and other disasters + // if done from the device thread. + SDL_PendingAudioDeviceEvent pending; + pending.next = NULL; + SDL_PendingAudioDeviceEvent *pending_tail = &pending; - // dump any logical devices that explicitly opened this device. Things that opened the system default can stay. - SDL_LogicalAudioDevice *next = NULL; - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = next) { - next = logdev->next; - if (!logdev->opened_as_default) { // if opened as a default, leave it on the zombie device for later migration. - DisconnectLogicalAudioDevice(logdev); + ObtainPhysicalAudioDeviceObj(device); + + SDL_LockRWLockForReading(current_audio.device_hash_lock); + const SDL_AudioDeviceID devid = device->instance_id; + const SDL_bool is_default_device = ((devid == current_audio.default_output_device_id) || (devid == current_audio.default_capture_device_id)); + SDL_UnlockRWLock(current_audio.device_hash_lock); + + const SDL_bool first_disconnect = SDL_AtomicCAS(&device->zombie, 0, 1); + if (first_disconnect) { // if already disconnected this device, don't do it twice. + // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep + // making progress until the app closes it. Otherwise, streams might continue to + // accumulate waste data that never drains, apps that depend on audio callbacks to + // progress will freeze, etc. + device->WaitDevice = ZombieWaitDevice; + device->GetDeviceBuf = ZombieGetDeviceBuf; + device->PlayDevice = ZombiePlayDevice; + device->WaitCaptureDevice = ZombieWaitDevice; + device->CaptureFromDevice = ZombieCaptureFromDevice; + device->FlushCapture = ZombieFlushCapture; + + // on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay. + // on non-default devices, dump everything. + // (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.) + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { + if (!is_default_device || !logdev->opened_as_default) { // if opened as a default, leave it on the zombie device for later migration. + SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED; + p->devid = logdev->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } } } - SDL_UnlockMutex(device->lock); // make sure nothing else is messing with the device before continuing. - return; // done for now. Come back when a new default device is chosen! + + SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED; + p->devid = device->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } } - SDL_bool was_live = SDL_FALSE; + ReleaseAudioDevice(device); - // take it out of the device list. - SDL_LockRWLockForWriting(current_audio.device_list_lock); - SDL_LockMutex(device->lock); // make sure nothing else is messing with the device before continuing. - if (device == current_audio.output_devices) { - SDL_assert(device->prev == NULL); - current_audio.output_devices = device->next; - was_live = SDL_TRUE; - } else if (device == current_audio.capture_devices) { - SDL_assert(device->prev == NULL); - current_audio.capture_devices = device->next; - was_live = SDL_TRUE; - } - if (device->prev != NULL) { - device->prev->next = device->next; - was_live = SDL_TRUE; - } - if (device->next != NULL) { - device->next->prev = device->prev; - was_live = SDL_TRUE; - } + if (first_disconnect) { + if (pending.next) { // NULL if event is disabled or disaster struck. + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + 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); + } - device->next = NULL; - device->prev = NULL; - - if (was_live) { - SDL_AtomicAdd(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count, -1); - } - - SDL_UnlockRWLock(current_audio.device_list_lock); - - // now device is not in the list, and we own it, so no one should be able to find it again, except the audio thread, which holds a pointer! - SDL_AtomicSet(&device->condemned, 1); - SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate. - - // disconnect each attached logical device, so apps won't find their streams still bound if they get the REMOVED event before the device thread cleans up. - SDL_LogicalAudioDevice *next; - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = next) { - next = logdev->next; - DisconnectLogicalAudioDevice(logdev); - } - - // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now. - const SDL_bool should_destroy = SDL_AtomicGet(&device->thread_alive) ? SDL_FALSE : SDL_TRUE; - SDL_UnlockMutex(device->lock); - - // Post the event, if we haven't tried to before and if it's desired - if (was_live && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - event.common.timestamp = 0; - event.adevice.which = device->instance_id; - event.adevice.iscapture = device->iscapture ? 1 : 0; - SDL_PushEvent(&event); - } - - if (should_destroy) { - DestroyPhysicalAudioDevice(device); + UnrefPhysicalAudioDevice(device); } } @@ -517,11 +694,12 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) // stubs for audio drivers that don't need a specific entry point... static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } -static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } +static int SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return 0; /* no-op. */ } static int SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return 0; /* no-op. */ } -static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } +static int SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { return 0; /* no-op. */ } static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ } static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ } @@ -574,23 +752,55 @@ static void CompleteAudioEntryPoints(void) FILL_STUB(FlushCapture); FILL_STUB(CloseDevice); FILL_STUB(FreeDeviceHandle); + FILL_STUB(DeinitializeStart); FILL_STUB(Deinitialize); #undef FILL_STUB } -static SDL_AudioDeviceID GetFirstAddedAudioDeviceID(const SDL_bool iscapture) +static SDL_AudioDevice *GetFirstAddedAudioDevice(const SDL_bool iscapture) { - // (these are pushed to the front of the linked list as added, so the first device added is last in the list.) - SDL_LockRWLockForReading(current_audio.device_list_lock); - SDL_AudioDevice *last = NULL; - for (SDL_AudioDevice *i = iscapture ? current_audio.capture_devices : current_audio.output_devices; i != NULL; i = i->next) { - last = i; + SDL_AudioDeviceID highest = (SDL_AudioDeviceID) SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; // According to AssignAudioDeviceInstanceId, nothing can have a value this large. + SDL_AudioDevice *retval = NULL; + + // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) + SDL_LockRWLockForReading(current_audio.device_hash_lock); + + const void *key; + const void *value; + void *iter = NULL; + while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #0 of devid is set for output devices and unset for capture. + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_bool devid_iscapture = !(devid & (1 << 0)); + const SDL_bool isphysical = (devid & (1 << 1)); + if (isphysical && (devid_iscapture == iscapture) && (devid < highest)) { + highest = devid; + retval = (SDL_AudioDevice *) value; + } } - const SDL_AudioDeviceID retval = last ? last->instance_id : 0; - SDL_UnlockRWLock(current_audio.device_list_lock); + + SDL_UnlockRWLock(current_audio.device_hash_lock); return retval; } +static Uint32 HashAudioDeviceID(const void *key, void *data) +{ + // shift right 2, to dump the first two bits, since these are flags + // (capture vs playback, logical vs physical) and the rest are unique incrementing integers. + return ((Uint32) ((uintptr_t) key)) >> 2; +} + +static SDL_bool MatchAudioDeviceID(const void *a, const void *b, void *data) +{ + return (a == b); +} + +static void NukeAudioDeviceHashItem(const void *key, const void *value, void *data) +{ + // no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here. +} + // !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. int SDL_InitAudio(const char *driver_name) { @@ -598,34 +808,44 @@ int SDL_InitAudio(const char *driver_name) SDL_QuitAudio(); // shutdown driver if already running. } + // make sure device IDs start at 2 (because of SDL2 legacy interface), but don't reset the counter on each init, in case the app is holding an old device ID somewhere. + SDL_AtomicCAS(&last_device_instance_id, 0, 2); + SDL_ChooseAudioConverters(); SDL_SetupAudioResampler(); - SDL_RWLock *device_list_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. - if (!device_list_lock) { + 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) { + return -1; + } + + SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashAudioDeviceID, MatchAudioDeviceID, NukeAudioDeviceHashItem, SDL_FALSE); + if (!device_hash) { + SDL_DestroyRWLock(device_hash_lock); return -1; } // Select the proper audio driver - if (driver_name == NULL) { + if (!driver_name) { driver_name = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); } SDL_bool initialized = SDL_FALSE; SDL_bool tried_to_init = SDL_FALSE; - if (driver_name != NULL && *driver_name != 0) { + if (driver_name && *driver_name != 0) { char *driver_name_copy = SDL_strdup(driver_name); const char *driver_attempt = driver_name_copy; - if (driver_name_copy == NULL) { - SDL_DestroyRWLock(device_list_lock); - return SDL_OutOfMemory(); + if (!driver_name_copy) { + SDL_DestroyRWLock(device_hash_lock); + SDL_DestroyHashTable(device_hash); + return -1; } - while (driver_attempt != NULL && *driver_attempt != 0 && !initialized) { + while (driver_attempt && *driver_attempt != 0 && !initialized) { char *driver_attempt_end = SDL_strchr(driver_attempt, ','); - if (driver_attempt_end != NULL) { + if (driver_attempt_end) { *driver_attempt_end = '\0'; } @@ -640,8 +860,9 @@ int SDL_InitAudio(const char *driver_name) if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { tried_to_init = SDL_TRUE; SDL_zero(current_audio); - SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); // start past 1 because of SDL2's legacy interface. - current_audio.device_list_lock = device_list_lock; + current_audio.pending_events_tail = ¤t_audio.pending_events; + current_audio.device_hash_lock = device_hash_lock; + current_audio.device_hash = device_hash; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -651,7 +872,7 @@ int SDL_InitAudio(const char *driver_name) } } - driver_attempt = (driver_attempt_end != NULL) ? (driver_attempt_end + 1) : NULL; + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; } SDL_free(driver_name_copy); @@ -663,8 +884,9 @@ int SDL_InitAudio(const char *driver_name) tried_to_init = SDL_TRUE; SDL_zero(current_audio); - SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); // start past 1 because of SDL2's legacy interface. - current_audio.device_list_lock = device_list_lock; + current_audio.pending_events_tail = ¤t_audio.pending_events; + current_audio.device_hash_lock = device_hash_lock; + current_audio.device_hash = device_hash; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -683,9 +905,9 @@ int SDL_InitAudio(const char *driver_name) } } + SDL_DestroyRWLock(device_hash_lock); + SDL_DestroyHashTable(device_hash); SDL_zero(current_audio); - SDL_DestroyRWLock(device_list_lock); - current_audio.device_list_lock = NULL; return -1; // No driver was available, so fail. } @@ -696,20 +918,23 @@ int SDL_InitAudio(const char *driver_name) SDL_AudioDevice *default_capture = NULL; current_audio.impl.DetectDevices(&default_output, &default_capture); - // these are only set if default_* is non-NULL, in case the backend just called SDL_DefaultAudioDeviceChanged directly during DetectDevices. - if (default_output) { - current_audio.default_output_device_id = default_output->instance_id; - } - if (default_capture) { - current_audio.default_capture_device_id = default_capture->instance_id; + // If no default was _ever_ specified, just take the first device we see, if any. + if (!default_output) { + default_output = GetFirstAddedAudioDevice(/*iscapture=*/SDL_FALSE); } - // If no default was _ever_ specified, just take the first device we see, if any. - if (!current_audio.default_output_device_id) { - current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_FALSE); + if (!default_capture) { + default_capture = GetFirstAddedAudioDevice(/*iscapture=*/SDL_TRUE); } - if (!current_audio.default_capture_device_id) { - current_audio.default_capture_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_TRUE); + + if (default_output) { + current_audio.default_output_device_id = default_output->instance_id; + RefPhysicalAudioDevice(default_output); // extra ref on default devices. + } + + if (default_capture) { + current_audio.default_capture_device_id = default_capture->instance_id; + RefPhysicalAudioDevice(default_capture); // extra ref on default devices. } return 0; @@ -721,55 +946,46 @@ void SDL_QuitAudio(void) return; } + current_audio.impl.DeinitializeStart(); + // Destroy any audio streams that still exist... - while (current_audio.existing_streams != NULL) { + while (current_audio.existing_streams) { SDL_DestroyAudioStream(current_audio.existing_streams); } - // merge device lists so we don't have to duplicate work below. - SDL_LockRWLockForWriting(current_audio.device_list_lock); + SDL_LockRWLockForWriting(current_audio.device_hash_lock); SDL_AtomicSet(¤t_audio.shutting_down, 1); - SDL_AudioDevice *devices = NULL; - for (SDL_AudioDevice *i = current_audio.output_devices; i != NULL; i = i->next) { - devices = i; - } - if (!devices) { - devices = current_audio.capture_devices; - } else { - SDL_assert(devices->next == NULL); - devices->next = current_audio.capture_devices; - devices = current_audio.output_devices; - } - current_audio.output_devices = NULL; - current_audio.capture_devices = NULL; + SDL_HashTable *device_hash = current_audio.device_hash; + current_audio.device_hash = NULL; + SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; + current_audio.pending_events.next = NULL; SDL_AtomicSet(¤t_audio.output_device_count, 0); SDL_AtomicSet(¤t_audio.capture_device_count, 0); - SDL_UnlockRWLock(current_audio.device_list_lock); + SDL_UnlockRWLock(current_audio.device_hash_lock); - // mark all devices for shutdown so all threads can begin to terminate. - for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { - SDL_AtomicSet(&i->shutdown, 1); + SDL_PendingAudioDeviceEvent *pending_next = NULL; + for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { + pending_next = i->next; + SDL_free(i); } - // now wait on any audio threads... - for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { - if (i->thread) { - SDL_assert(!SDL_AtomicGet(&i->condemned)); // these shouldn't have been in the device list still, and thread should have detached. - SDL_WaitThread(i->thread, NULL); - i->thread = NULL; + const void *key; + const void *value; + void *iter = NULL; + while (SDL_IterateHashTable(device_hash, &key, &value, &iter)) { + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + const SDL_bool isphysical = (devid & (1<<1)); + if (isphysical) { + DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); } } - while (devices) { - SDL_AudioDevice *next = devices->next; - DestroyPhysicalAudioDevice(devices); - devices = next; - } - // Free the driver data current_audio.impl.Deinitialize(); - SDL_DestroyRWLock(current_audio.device_list_lock); + SDL_DestroyRWLock(current_audio.device_hash_lock); + SDL_DestroyHashTable(device_hash); SDL_zero(current_audio); } @@ -777,15 +993,6 @@ void SDL_QuitAudio(void) void SDL_AudioThreadFinalize(SDL_AudioDevice *device) { - if (SDL_AtomicGet(&device->condemned)) { - if (device->thread) { - SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. - device->thread = NULL; - SDL_AtomicSet(&device->thread_alive, 0); - } - DestroyPhysicalAudioDevice(device); - } - SDL_AtomicSet(&device->thread_alive, 0); } static void MixFloat32Audio(float *dst, const float *src, const int buffer_size) @@ -815,11 +1022,13 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) return SDL_FALSE; // we're done, shut it down. } - SDL_bool retval = SDL_TRUE; + SDL_bool failed = SDL_FALSE; int buffer_size = device->buffer_size; - Uint8 *device_buffer = current_audio.impl.GetDeviceBuf(device, &buffer_size); - if (!device_buffer) { - retval = SDL_FALSE; + Uint8 *device_buffer = device->GetDeviceBuf(device, &buffer_size); + if (buffer_size == 0) { + // WASAPI (maybe others, later) does this to say "just abandon this iteration and try again next time." + } else if (!device_buffer) { + failed = SDL_TRUE; } else { SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. SDL_assert(AudioDeviceCanUseSimpleCopy(device) == device->simple_copy); // make sure this hasn't gotten out of sync. @@ -832,9 +1041,9 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(AUDIO_SPECS_EQUAL(stream->dst_spec, device->spec)); - const int br = SDL_GetAudioStreamData(stream, device_buffer, buffer_size); + const int br = SDL_AtomicGet(&logdev->paused) ? 0 : SDL_GetAudioStreamData(stream, device_buffer, buffer_size); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. - retval = SDL_FALSE; + failed = SDL_TRUE; SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. } else if (br < buffer_size) { SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to. @@ -853,7 +1062,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) SDL_memset(final_mix_buffer, '\0', work_buffer_size); // start with silence. - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { if (SDL_AtomicGet(&logdev->paused)) { continue; // paused? Skip this logical device. } @@ -865,7 +1074,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence. } - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // We should have updated this elsewhere if the format changed! SDL_assert(AUDIO_SPECS_EQUAL(stream->dst_spec, outspec)); @@ -875,7 +1084,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) the same stream to different devices at the same time, though.) */ const int br = SDL_GetAudioStreamData(stream, device->work_buffer, work_buffer_size); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. - retval = SDL_FALSE; + failed = SDL_TRUE; break; } else if (br > 0) { // it's okay if we get less than requested, we mix what we have. MixFloat32Audio(mix_buffer, (float *) device->work_buffer, br); @@ -898,26 +1107,28 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) } // PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! - if (current_audio.impl.PlayDevice(device, device_buffer, buffer_size) < 0) { - retval = SDL_FALSE; + if (device->PlayDevice(device, device_buffer, buffer_size) < 0) { + failed = SDL_TRUE; } } SDL_UnlockMutex(device->lock); - if (!retval) { + if (failed) { SDL_AudioDeviceDisconnected(device); // doh. } - return retval; + return SDL_TRUE; // always go on if not shutting down, even if device failed. } void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) { SDL_assert(!device->iscapture); const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); - // Wait for the audio to drain. !!! FIXME: don't bother waiting if device is lost. - SDL_Delay(((frames * 1000) / device->spec.freq) * 2); + // Wait for the audio to drain if device didn't die. + if (!SDL_AtomicGet(&device->zombie)) { + SDL_Delay(((frames * 1000) / device->spec.freq) * 2); + } current_audio.impl.ThreadDeinit(device); SDL_AudioThreadFinalize(device); } @@ -928,8 +1139,11 @@ static int SDLCALL OutputAudioThread(void *devicep) // thread entry point SDL_assert(device != NULL); SDL_assert(!device->iscapture); SDL_OutputAudioThreadSetup(device); + do { - current_audio.impl.WaitDevice(device); + if (device->WaitDevice(device) < 0) { + SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!) + } } while (SDL_OutputAudioThreadIterate(device)); SDL_OutputAudioThreadShutdown(device); @@ -952,19 +1166,22 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) SDL_LockMutex(device->lock); - SDL_bool retval = SDL_TRUE; - if (SDL_AtomicGet(&device->shutdown)) { - retval = SDL_FALSE; // we're done, shut it down. - } else if (device->logical_devices == NULL) { - current_audio.impl.FlushCapture(device); // nothing wants data, dump anything pending. + SDL_UnlockMutex(device->lock); + return SDL_FALSE; // we're done, shut it down. + } + + SDL_bool failed = SDL_FALSE; + + if (!device->logical_devices) { + device->FlushCapture(device); // nothing wants data, dump anything pending. } else { // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitCaptureDevice! - int br = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size); + int br = device->CaptureFromDevice(device, device->work_buffer, device->buffer_size); if (br < 0) { // uhoh, device failed for some reason! - retval = SDL_FALSE; + failed = SDL_TRUE; } else if (br > 0) { // queue the new data to each bound stream. - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { if (SDL_AtomicGet(&logdev->paused)) { continue; // paused? Skip this logical device. } @@ -985,7 +1202,7 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br); } - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // We should have updated this elsewhere if the format changed! SDL_assert(stream->src_spec.format == (logdev->postmix ? SDL_AUDIO_F32 : device->spec.format)); SDL_assert(stream->src_spec.channels == device->spec.channels); @@ -997,7 +1214,7 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) the same stream to different devices at the same time, though.) */ if (SDL_PutAudioStreamData(stream, output_buffer, br) < 0) { // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. - retval = SDL_FALSE; + failed = SDL_TRUE; break; } } @@ -1007,17 +1224,17 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) SDL_UnlockMutex(device->lock); - if (!retval) { + if (failed) { SDL_AudioDeviceDisconnected(device); // doh. } - return retval; + return SDL_TRUE; // always go on if not shutting down, even if device failed. } void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) { SDL_assert(device->iscapture); - current_audio.impl.FlushCapture(device); + device->FlushCapture(device); current_audio.impl.ThreadDeinit(device); SDL_AudioThreadFinalize(device); } @@ -1030,7 +1247,9 @@ static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point SDL_CaptureAudioThreadSetup(device); do { - current_audio.impl.WaitCaptureDevice(device); + if (device->WaitCaptureDevice(device) < 0) { + SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!) + } } while (SDL_CaptureAudioThreadIterate(device)); SDL_CaptureAudioThreadShutdown(device); @@ -1038,130 +1257,64 @@ static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point } -static SDL_AudioDeviceID *GetAudioDevices(int *reqcount, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +static SDL_AudioDeviceID *GetAudioDevices(int *count, SDL_bool iscapture) { - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return NULL; - } + SDL_AudioDeviceID *retval = NULL; + int num_devices = 0; - SDL_LockRWLockForReading(current_audio.device_list_lock); - int num_devices = SDL_AtomicGet(device_count); - SDL_AudioDeviceID *retval = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); - if (retval == NULL) { - num_devices = 0; - SDL_OutOfMemory(); - } else { - const SDL_AudioDevice *dev = *devices; // pointer to a pointer so we can dereference it after the lock is held. - for (int i = 0; i < num_devices; i++) { - SDL_assert(dev != NULL); - SDL_assert(!SDL_AtomicGet((SDL_AtomicInt *) &dev->condemned)); // shouldn't be in the list if pending deletion. - retval[i] = dev->instance_id; - dev = dev->next; + if (SDL_GetCurrentAudioDriver()) { + SDL_LockRWLockForReading(current_audio.device_hash_lock); + { + num_devices = SDL_AtomicGet(iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count); + retval = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); + if (retval) { + int devs_seen = 0; + const void *key; + const void *value; + void *iter = NULL; + while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #0 of devid is set for output devices and unset for capture. + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_bool devid_iscapture = !(devid & (1<<0)); + const SDL_bool isphysical = (devid & (1<<1)); + if (isphysical && (devid_iscapture == iscapture)) { + SDL_assert(devs_seen < num_devices); + retval[devs_seen++] = devid; + } + } + + SDL_assert(devs_seen == num_devices); + retval[devs_seen] = 0; // null-terminated. + } else { + SDL_OutOfMemory(); + } } - SDL_assert(dev == NULL); // did the whole list? - retval[num_devices] = 0; // null-terminated. - } - SDL_UnlockRWLock(current_audio.device_list_lock); - - if (reqcount != NULL) { - *reqcount = num_devices; + SDL_UnlockRWLock(current_audio.device_hash_lock); + } else { + SDL_SetError("Audio subsystem is not initialized"); } + if (count) { + if (retval) { + *count = num_devices; + } else { + *count = 0; + } + } return retval; } SDL_AudioDeviceID *SDL_GetAudioOutputDevices(int *count) { - return GetAudioDevices(count, ¤t_audio.output_devices, ¤t_audio.output_device_count); + return GetAudioDevices(count, SDL_FALSE); } SDL_AudioDeviceID *SDL_GetAudioCaptureDevices(int *count) { - return GetAudioDevices(count, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); + return GetAudioDevices(count, SDL_TRUE); } -// If found, this locks _the physical device_ this logical device is associated with, before returning. -static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid) -{ - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return NULL; - } - - SDL_LogicalAudioDevice *logdev = NULL; - - const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; - if (islogical) { // don't bother looking if it's not a logical device id value. - const SDL_bool iscapture = (devid & (1<<0)) ? SDL_FALSE : SDL_TRUE; - - SDL_LockRWLockForReading(current_audio.device_list_lock); - - for (SDL_AudioDevice *device = iscapture ? current_audio.capture_devices : current_audio.output_devices; device != NULL; device = device->next) { - SDL_LockMutex(device->lock); // caller must unlock if we choose a logical device from this guy. - SDL_assert(!SDL_AtomicGet(&device->condemned)); // shouldn't be in the list if pending deletion. - for (logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { - if (logdev->instance_id == devid) { - break; // found it! - } - } - if (logdev != NULL) { - break; - } - SDL_UnlockMutex(device->lock); // give up this lock and try the next physical device. - } - - SDL_UnlockRWLock(current_audio.device_list_lock); - } - - if (!logdev) { - SDL_SetError("Invalid audio device instance ID"); - } - - return logdev; -} - -/* this finds the physical device associated with `devid` and locks it for use. - Note that a logical device instance id will return its associated physical device! */ -static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) -{ - // bit #1 of devid is set for physical devices and unset for logical. - const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; - if (islogical) { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); - if (logdev) { - return logdev->physical_device; - } - return NULL; - } - - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return NULL; - } - - // bit #0 of devid is set for output devices and unset for capture. - const SDL_bool iscapture = (devid & (1<<0)) ? SDL_FALSE : SDL_TRUE; - SDL_AudioDevice *dev = NULL; - - SDL_LockRWLockForReading(current_audio.device_list_lock); - - for (dev = iscapture ? current_audio.capture_devices : current_audio.output_devices; dev != NULL; dev = dev->next) { - if (dev->instance_id == devid) { // found it? - SDL_LockMutex(dev->lock); // caller must unlock. - SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. - break; - } - } - - SDL_UnlockRWLock(current_audio.device_list_lock); - - if (!dev) { - SDL_SetError("Invalid audio device instance ID"); - } - - return dev; -} SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata) { @@ -1170,33 +1323,27 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_ return NULL; } - SDL_LockRWLockForReading(current_audio.device_list_lock); + const void *key; + const void *value; + void *iter = NULL; - SDL_AudioDevice *dev = NULL; - for (dev = current_audio.output_devices; dev != NULL; dev = dev->next) { - if (callback(dev, userdata)) { // found it? - break; - } - } - - if (!dev) { - // !!! FIXME: code duplication, from above. - for (dev = current_audio.capture_devices; dev != NULL; dev = dev->next) { - if (callback(dev, userdata)) { // found it? - break; + SDL_LockRWLockForReading(current_audio.device_hash_lock); + while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) { + const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_bool isphysical = (devid & (1<<1)); + if (isphysical) { + SDL_AudioDevice *device = (SDL_AudioDevice *) value; + if (callback(device, userdata)) { // found it? + SDL_UnlockRWLock(current_audio.device_hash_lock); + return device; } } } + SDL_UnlockRWLock(current_audio.device_hash_lock); - SDL_UnlockRWLock(current_audio.device_list_lock); - - if (!dev) { - SDL_SetError("Device not found"); - } - - SDL_assert(!dev || !SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. - - return dev; + SDL_SetError("Device not found"); + return NULL; } static SDL_bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle) @@ -1211,17 +1358,12 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + char *retval = NULL; SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (!device) { - return NULL; + if (device) { + retval = SDL_strdup(device->name); } - - char *retval = SDL_strdup(device->name); - if (!retval) { - SDL_OutOfMemory(); - } - - SDL_UnlockMutex(device->lock); + ReleaseAudioDevice(device); return retval; } @@ -1232,45 +1374,46 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int * return SDL_InvalidParamError("spec"); } - SDL_bool wants_default = SDL_FALSE; - if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { - devid = current_audio.default_output_device_id; - wants_default = SDL_TRUE; - } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { - devid = current_audio.default_capture_device_id; - wants_default = SDL_TRUE; + int retval = -1; + SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); + if (device) { + SDL_copyp(spec, &device->spec); + if (sample_frames) { + *sample_frames = device->sample_frames; + } + retval = 0; } + ReleaseAudioDevice(device); - if ((devid == 0) && wants_default) { - return SDL_SetError("No default audio device available"); - } - - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (!device) { - return -1; - } - - SDL_copyp(spec, &device->spec); - if (sample_frames) { - *sample_frames = device->sample_frames; - } - SDL_UnlockMutex(device->lock); - - return 0; + return retval; } -// this expects the device lock to be held. !!! FIXME: no it doesn't...? +// this is awkward, but this makes sure we can release the device lock +// so the device thread can terminate but also not have two things +// race to close or open the device while the lock is unprotected. +// you hold the lock when calling this, it will release the lock and +// wait while the shutdown flag is set. +// BE CAREFUL WITH THIS. +static void SerializePhysicalDeviceClose(SDL_AudioDevice *device) +{ + while (SDL_AtomicGet(&device->shutdown)) { + SDL_WaitCondition(device->close_cond, device->lock); + } +} + +// this expects the device lock to be held. static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) { - SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0))); + SerializePhysicalDeviceClose(device); - if (SDL_AtomicGet(&device->thread_alive)) { - SDL_AtomicSet(&device->shutdown, 1); - if (device->thread != NULL) { - SDL_WaitThread(device->thread, NULL); - device->thread = NULL; - } - SDL_AtomicSet(&device->thread_alive, 0); + SDL_AtomicSet(&device->shutdown, 1); + + // YOU MUST PROTECT KEY POINTS WITH SerializePhysicalDeviceClose() WHILE THE THREAD JOINS + SDL_UnlockMutex(device->lock); + + if (device->thread) { + SDL_WaitThread(device->thread, NULL); + device->thread = NULL; } if (device->currently_opened) { @@ -1279,6 +1422,10 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) device->hidden = NULL; // just in case. } + SDL_LockMutex(device->lock); + SDL_AtomicSet(&device->shutdown, 0); // ready to go again. + SDL_BroadcastCondition(device->close_cond); // release anyone waiting in SerializePhysicalDeviceClose; they'll still block until we release device->lock, though. + SDL_aligned_free(device->work_buffer); device->work_buffer = NULL; @@ -1291,24 +1438,24 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) SDL_copyp(&device->spec, &device->default_spec); device->sample_frames = 0; device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); - SDL_AtomicSet(&device->shutdown, 0); // ready to go again. } void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); - if (logdev) { // if NULL, maybe it was already lost? - SDL_AudioDevice *device = logdev->physical_device; + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + if (logdev) { DestroyLogicalAudioDevice(logdev); - - if (device->logical_devices == NULL) { // no more logical devices? Close the physical device, too. - // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it. - SDL_UnlockMutex(device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it. - ClosePhysicalAudioDevice(device); - } else { - SDL_UnlockMutex(device->lock); // we're set, let everything go again. - } } + + if (device) { + if (!device->logical_devices) { // no more logical devices? Close the physical device, too. + ClosePhysicalAudioDevice(device); + } + UnrefPhysicalAudioDevice(device); // one reference for each logical device. + } + + ReleaseAudioDevice(device); } @@ -1338,7 +1485,7 @@ static void PrepareAudioFormat(SDL_bool iscapture, SDL_AudioSpec *spec) spec->freq = iscapture ? DEFAULT_AUDIO_CAPTURE_FREQUENCY : DEFAULT_AUDIO_OUTPUT_FREQUENCY; const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); // !!! FIXME: should be a hint? - if (env != NULL) { + if (env) { const int val = SDL_atoi(env); if (val > 0) { spec->freq = val; @@ -1349,7 +1496,7 @@ static void PrepareAudioFormat(SDL_bool iscapture, SDL_AudioSpec *spec) if (spec->channels == 0) { spec->channels = iscapture ? DEFAULT_AUDIO_CAPTURE_CHANNELS : DEFAULT_AUDIO_OUTPUT_CHANNELS;; const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); - if (env != NULL) { + if (env) { const int val = SDL_atoi(env); if (val > 0) { spec->channels = val; @@ -1381,14 +1528,25 @@ char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen) // this expects the device lock to be held. static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { - SDL_assert(!device->currently_opened); - SDL_assert(device->logical_devices == NULL); + SerializePhysicalDeviceClose(device); // make sure another thread that's closing didn't release the lock to let the device thread join... - // Just pretend to open a zombie device. It can still collect logical devices on the assumption they will all migrate when the default device is officially changed. + if (device->currently_opened) { + return 0; // we're already good. + } + + // Just pretend to open a zombie device. It can still collect logical devices on a default device under the assumption they will all migrate when the default device is officially changed. if (SDL_AtomicGet(&device->zombie)) { return 0; // Braaaaaaaaains. } + // These start with the backend's implementation, but we might swap them out with zombie versions later. + device->WaitDevice = current_audio.impl.WaitDevice; + device->PlayDevice = current_audio.impl.PlayDevice; + device->GetDeviceBuf = current_audio.impl.GetDeviceBuf; + device->WaitCaptureDevice = current_audio.impl.WaitCaptureDevice; + device->CaptureFromDevice = current_audio.impl.CaptureFromDevice; + device->FlushCapture = current_audio.impl.FlushCapture; + SDL_AudioSpec spec; SDL_copyp(&spec, inspec ? inspec : &device->default_spec); PrepareAudioFormat(device->iscapture, &spec); @@ -1413,29 +1571,27 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec // Allocate a scratch audio buffer device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->work_buffer_size); - if (device->work_buffer == NULL) { + if (!device->work_buffer) { ClosePhysicalAudioDevice(device); - return SDL_OutOfMemory(); + return -1; } if (device->spec.format != SDL_AUDIO_F32) { device->mix_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->work_buffer_size); - if (device->mix_buffer == NULL) { + if (!device->mix_buffer) { ClosePhysicalAudioDevice(device); - return SDL_OutOfMemory(); + return -1; } } // Start the audio thread if necessary - SDL_AtomicSet(&device->thread_alive, 1); if (!current_audio.impl.ProvidesOwnCallbackThread) { const size_t stacksize = 0; // just take the system default, since audio streams might have callbacks. char threadname[64]; SDL_GetAudioThreadName(device, threadname, sizeof (threadname)); device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); - if (device->thread == NULL) { - SDL_AtomicSet(&device->thread_alive, 0); + if (!device->thread) { ClosePhysicalAudioDevice(device); return SDL_SetError("Couldn't create audio thread"); } @@ -1451,30 +1607,17 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp return 0; } - SDL_bool wants_default = SDL_FALSE; - if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { - devid = current_audio.default_output_device_id; - wants_default = SDL_TRUE; - } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { - devid = current_audio.default_capture_device_id; - wants_default = SDL_TRUE; - } - - if ((devid == 0) && wants_default) { - SDL_SetError("No default audio device available"); - return 0; - } + SDL_bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) || (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE)); // 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 SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + const SDL_bool islogical = (!wants_default && !(devid & (1<<1))); if (!islogical) { - device = ObtainPhysicalAudioDevice(devid); + device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); } else { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); // this locks the physical device, too. + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); if (logdev) { wants_default = logdev->opened_as_default; // was the original logical device meant to be a default? Make this one, too. - device = logdev->physical_device; } } @@ -1483,15 +1626,16 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp if (device) { SDL_LogicalAudioDevice *logdev = NULL; if (!wants_default && SDL_AtomicGet(&device->zombie)) { - // uhoh, this device is undead, and just waiting for a new default device to be declared so it can hand off to it. Refuse explicit opens. + // uhoh, this device is undead, and just waiting to be cleaned up. Refuse explicit opens. SDL_SetError("Device was already lost and can't accept new opens"); } else if ((logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice))) == NULL) { - SDL_OutOfMemory(); - } else if (!device->currently_opened && OpenPhysicalAudioDevice(device, spec) == -1) { // first thing using this physical device? Open at the OS level... + /* SDL_calloc already called SDL_OutOfMemory */ + } else if (OpenPhysicalAudioDevice(device, spec) == -1) { // if this is the first thing using this physical device, open at the OS level if necessary... SDL_free(logdev); } else { + RefPhysicalAudioDevice(device); // unref'd on successful SDL_CloseAudioDevice SDL_AtomicSet(&logdev->paused, 0); - retval = logdev->instance_id = assign_audio_device_instance_id(device->iscapture, /*islogical=*/SDL_TRUE); + retval = logdev->instance_id = AssignAudioDeviceInstanceId(device->iscapture, /*islogical=*/SDL_TRUE); logdev->physical_device = device; logdev->opened_as_default = wants_default; logdev->next = device->logical_devices; @@ -1499,9 +1643,19 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp device->logical_devices->prev = logdev; } device->logical_devices = logdev; - device->simple_copy = AudioDeviceCanUseSimpleCopy(device); + UpdateAudioStreamFormatsPhysical(device); + } + ReleaseAudioDevice(device); + + if (retval) { + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + const SDL_bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) retval, logdev); + SDL_UnlockRWLock(current_audio.device_hash_lock); + if (!inserted) { + SDL_CloseAudioDevice(retval); + retval = 0; + } } - SDL_UnlockMutex(device->lock); } return retval; @@ -1509,14 +1663,13 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value) { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); - if (!logdev) { - return -1; // ObtainLogicalAudioDevice will have set an error. + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + if (logdev) { + SDL_AtomicSet(&logdev->paused, value); } - SDL_AtomicSet(&logdev->paused, value); - logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device); - SDL_UnlockMutex(logdev->physical_device->lock); - return 0; + ReleaseAudioDevice(device); + return logdev ? 0 : -1; // ObtainLogicalAudioDevice will have set an error. } int SDL_PauseAudioDevice(SDL_AudioDeviceID devid) @@ -1531,97 +1684,120 @@ int SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID devid) SDL_bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid) { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); SDL_bool retval = SDL_FALSE; - if (logdev) { - if (SDL_AtomicGet(&logdev->paused)) { - retval = SDL_TRUE; - } - SDL_UnlockMutex(logdev->physical_device->lock); + if (logdev && SDL_AtomicGet(&logdev->paused)) { + retval = SDL_TRUE; } + ReleaseAudioDevice(device); return retval; } int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata) { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); int retval = 0; if (logdev) { - SDL_AudioDevice *device = logdev->physical_device; if (callback && !device->postmix_buffer) { device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->work_buffer_size); - if (device->mix_buffer == NULL) { - retval = SDL_OutOfMemory(); + if (!device->postmix_buffer) { + retval = -1; } } if (retval == 0) { logdev->postmix = callback; logdev->postmix_userdata = userdata; + + if (device->iscapture) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { + // set the proper end of the stream to the device's format. + // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. + SDL_LockMutex(stream->lock); + stream->src_spec.format = callback ? SDL_AUDIO_F32 : device->spec.format; + SDL_UnlockMutex(stream->lock); + } + } } - UpdateAudioStreamFormatsLogical(logdev); - device->simple_copy = AudioDeviceCanUseSimpleCopy(device); - - SDL_UnlockMutex(device->lock); + UpdateAudioStreamFormatsPhysical(device); } + ReleaseAudioDevice(device); return retval; } int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams) { - const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; - SDL_LogicalAudioDevice *logdev; + const SDL_bool islogical = !(devid & (1<<1)); + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = NULL; + int retval = 0; if (num_streams == 0) { return 0; // nothing to do } else if (num_streams < 0) { return SDL_InvalidParamError("num_streams"); - } else if (streams == NULL) { + } else if (!streams) { return SDL_InvalidParamError("streams"); } else if (!islogical) { return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); - } else if ((logdev = ObtainLogicalAudioDevice(devid)) == NULL) { - return -1; // ObtainLogicalAudioDevice set the error message. - } else if (logdev->simplified) { - SDL_UnlockMutex(logdev->physical_device->lock); - return SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream"); } - // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet. - // !!! FIXME: Actually, why do we allow there to be an invalid format, again? + logdev = ObtainLogicalAudioDevice(devid, &device); + if (!logdev) { + retval = -1; // ObtainLogicalAudioDevice set the error string. + } else if (logdev->simplified) { + retval = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream"); + } else { - // make sure start of list is sane. - SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); + // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet. + // !!! FIXME: Actually, why do we allow there to be an invalid format, again? - SDL_AudioDevice *device = logdev->physical_device; - int retval = 0; + // make sure start of list is sane. + SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); - // lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing. - for (int i = 0; i < num_streams; i++) { - SDL_AudioStream *stream = streams[i]; - if (stream == NULL) { - retval = SDL_SetError("Stream #%d is NULL", i); - } else { - SDL_LockMutex(stream->lock); - SDL_assert((stream->bound_device == NULL) == ((stream->prev_binding == NULL) || (stream->next_binding == NULL))); - if (stream->bound_device) { - retval = SDL_SetError("Stream #%d is already bound to a device", i); + // lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing. + for (int i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + if (!stream) { + retval = SDL_SetError("Stream #%d is NULL", i); + } else { + SDL_LockMutex(stream->lock); + SDL_assert((stream->bound_device == NULL) == ((stream->prev_binding == NULL) || (stream->next_binding == NULL))); + if (stream->bound_device) { + retval = SDL_SetError("Stream #%d is already bound to a device", i); + } else if (stream->simplified) { // You can get here if you closed the device instead of destroying the stream. + retval = SDL_SetError("Cannot change binding on a stream created with SDL_OpenAudioDeviceStream"); + } } - } - if (retval != 0) { - int j; - for (j = 0; j <= i; j++) { - SDL_UnlockMutex(streams[j]->lock); + if (retval != 0) { + int j; + for (j = 0; j <= i; j++) { +#ifdef _MSC_VER /* Visual Studio analyzer can't tell that we've already verified streams[j] isn't NULL */ +#pragma warning(push) +#pragma warning(disable : 28182) +#endif + SDL_UnlockMutex(streams[j]->lock); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + break; } - break; } } if (retval == 0) { // Now that everything is verified, chain everything together. + const SDL_bool iscapture = device->iscapture; for (int i = 0; i < num_streams; i++) { +#ifdef _MSC_VER /* Visual Studio analyzer can't tell that streams[i] isn't NULL if retval is 0 */ +#pragma warning(push) +#pragma warning(disable : 28182) +#endif SDL_AudioStream *stream = streams[i]; stream->bound_device = logdev; @@ -1632,15 +1808,23 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int } logdev->bound_streams = stream; - SDL_UnlockMutex(stream->lock); - } + if (iscapture) { + SDL_copyp(&stream->src_spec, &device->spec); + if (logdev->postmix) { + stream->src_spec.format = SDL_AUDIO_F32; + } + } - UpdateAudioStreamFormatsLogical(logdev); + SDL_UnlockMutex(stream->lock); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } } - device->simple_copy = AudioDeviceCanUseSimpleCopy(device); + UpdateAudioStreamFormatsPhysical(device); - SDL_UnlockMutex(device->lock); + ReleaseAudioDevice(device); return retval; } @@ -1650,6 +1834,7 @@ int SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream) return SDL_BindAudioStreams(devid, &stream, 1); } +// !!! FIXME: this and BindAudioStreams are mutex nightmares. :/ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) { /* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it. @@ -1689,7 +1874,7 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) // don't allow unbinding from "simplified" devices (opened with SDL_OpenAudioDeviceStream). Just ignore them. if (stream && stream->bound_device && !stream->bound_device->simplified) { if (stream->bound_device->bound_streams == stream) { - SDL_assert(stream->prev_binding == NULL); + SDL_assert(!stream->prev_binding); stream->bound_device->bound_streams = stream->next_binding; } if (stream->prev_binding) { @@ -1710,7 +1895,7 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) stream->bound_device = NULL; SDL_UnlockMutex(stream->lock); if (logdev) { - logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device); + UpdateAudioStreamFormatsPhysical(logdev->physical_device); SDL_UnlockMutex(logdev->physical_device->lock); } } @@ -1742,52 +1927,51 @@ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_Au return NULL; // error string should already be set. } - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid); - if (logdev == NULL) { // this shouldn't happen, but just in case. - SDL_CloseAudioDevice(logdevid); - return NULL; // error string should already be set. - } - - SDL_AudioDevice *physdevice = logdev->physical_device; - SDL_assert(physdevice != NULL); - - SDL_AtomicSet(&logdev->paused, 1); // start the device paused, to match SDL2. - physdevice->simple_copy = AudioDeviceCanUseSimpleCopy(physdevice); - - SDL_UnlockMutex(physdevice->lock); // we don't need to hold the lock for any of this. - const SDL_bool iscapture = physdevice->iscapture; - + SDL_bool failed = SDL_FALSE; SDL_AudioStream *stream = NULL; - if (iscapture) { - stream = SDL_CreateAudioStream(&physdevice->spec, spec); + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid, &device); + if (!logdev) { // this shouldn't happen, but just in case. + failed = SDL_TRUE; } else { - stream = SDL_CreateAudioStream(spec, &physdevice->spec); + SDL_AtomicSet(&logdev->paused, 1); // start the device paused, to match SDL2. + + SDL_assert(device != NULL); + const SDL_bool iscapture = device->iscapture; + + if (iscapture) { + stream = SDL_CreateAudioStream(&device->spec, spec); + } else { + stream = SDL_CreateAudioStream(spec, &device->spec); + } + + if (!stream || (SDL_BindAudioStream(logdevid, stream) == -1)) { + failed = SDL_TRUE; + } else { + logdev->simplified = SDL_TRUE; // forbid further binding changes on this logical device. + stream->simplified = SDL_TRUE; // so we know to close the audio device when this is destroyed. + + if (callback) { + int rc; + if (iscapture) { + rc = SDL_SetAudioStreamPutCallback(stream, callback, userdata); + } else { + rc = SDL_SetAudioStreamGetCallback(stream, callback, userdata); + } + SDL_assert(rc == 0); // should only fail if stream==NULL atm. + } + } } - if (!stream) { - SDL_CloseAudioDevice(logdevid); - return NULL; // error string should already be set. - } - if (SDL_BindAudioStream(logdevid, stream) == -1) { + ReleaseAudioDevice(device); + + if (failed) { SDL_DestroyAudioStream(stream); SDL_CloseAudioDevice(logdevid); - return NULL; // error string should already be set. + stream = NULL; } - logdev->simplified = SDL_TRUE; // forbid further binding changes on this logical device. - stream->simplified = SDL_TRUE; // so we know to close the audio device when this is destroyed. - - if (callback) { - int rc; - if (iscapture) { - rc = SDL_SetAudioStreamPutCallback(stream, callback, userdata); - } else { - rc = SDL_SetAudioStreamGetCallback(stream, callback, userdata); - } - SDL_assert(rc == 0); // should only fail if stream==NULL atm. - } - - return stream; // ready to rock. + return stream; } #define NUM_FORMATS 8 @@ -1820,29 +2004,43 @@ int SDL_GetSilenceValueForFormat(SDL_AudioFormat format) // called internally by backends when the system default device changes. void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) { - if (new_default_device == NULL) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default? + if (!new_default_device) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default? return; // uhoh. } const SDL_bool iscapture = new_default_device->iscapture; - const SDL_AudioDeviceID current_devid = iscapture ? current_audio.default_capture_device_id : current_audio.default_output_device_id; - if (new_default_device->instance_id == current_devid) { + // change the official default over right away, so new opens will go to the new device. + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + const SDL_AudioDeviceID current_devid = iscapture ? current_audio.default_capture_device_id : current_audio.default_output_device_id; + const SDL_bool is_already_default = (new_default_device->instance_id == current_devid); + if (!is_already_default) { + if (iscapture) { + current_audio.default_capture_device_id = new_default_device->instance_id; + } else { + current_audio.default_output_device_id = new_default_device->instance_id; + } + } + SDL_UnlockRWLock(current_audio.device_hash_lock); + + if (is_already_default) { return; // this is already the default. } - SDL_LockMutex(new_default_device->lock); + // Queue up events to push to the queue next time it pumps (presumably + // in a safer thread). + // !!! FIXME: this duplicates some code we could probably refactor. + SDL_PendingAudioDeviceEvent pending; + pending.next = NULL; + SDL_PendingAudioDeviceEvent *pending_tail = &pending; + + // Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected. + RefPhysicalAudioDevice(new_default_device); + + ObtainPhysicalAudioDeviceObj(new_default_device); SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid); - /* change the official default ID over while we have locks on both devices, so if something raced to open the default during - this, it either gets the new device or is ready on the old and can be migrated. */ - if (iscapture) { - current_audio.default_capture_device_id = new_default_device->instance_id; - } else { - current_audio.default_output_device_id = new_default_device->instance_id; - } - if (current_default_device) { // migrate any logical devices that were opened as a default to the new physical device... @@ -1853,10 +2051,10 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) SDL_bool needs_migration = SDL_FALSE; SDL_zero(spec); - for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = logdev->next) { + for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = logdev->next) { if (logdev->opened_as_default) { needs_migration = SDL_TRUE; - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { const SDL_AudioSpec *streamspec = iscapture ? &stream->dst_spec : &stream->src_spec; if (SDL_AUDIO_BITSIZE(streamspec->format) > SDL_AUDIO_BITSIZE(spec.format)) { spec.format = streamspec->format; @@ -1872,25 +2070,24 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } if (needs_migration) { - if (new_default_device->logical_devices == NULL) { // New default physical device not been opened yet? Open at the OS level... - if (OpenPhysicalAudioDevice(new_default_device, &spec) == -1) { - needs_migration = SDL_FALSE; // uhoh, just leave everything on the old default, nothing to be done. - } + // New default physical device not been opened yet? Open at the OS level... + if (OpenPhysicalAudioDevice(new_default_device, &spec) == -1) { + needs_migration = SDL_FALSE; // uhoh, just leave everything on the old default, nothing to be done. } } if (needs_migration) { const SDL_bool spec_changed = !AUDIO_SPECS_EQUAL(current_default_device->spec, new_default_device->spec); - const SDL_bool post_fmt_event = (spec_changed && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED)) ? SDL_TRUE : SDL_FALSE; SDL_LogicalAudioDevice *next = NULL; - for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = next) { + for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = next) { next = logdev->next; if (!logdev->opened_as_default) { continue; // not opened as a default, leave it on the current physical device. } - // now migrate the logical 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); if (logdev->next) { logdev->next->prev = logdev->prev; } @@ -1905,41 +2102,54 @@ 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); - // make sure all our streams are targeting the new device's format. - UpdateAudioStreamFormatsLogical(logdev); + SDL_assert(SDL_AtomicGet(¤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); + UnrefPhysicalAudioDevice(current_default_device); - // Post an event for each logical device we moved. - if (post_fmt_event) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; - event.common.timestamp = 0; - event.adevice.iscapture = iscapture ? 1 : 0; - event.adevice.which = logdev->instance_id; - SDL_PushEvent(&event); + SDL_SetAudioPostmixCallback(logdev->instance_id, logdev->postmix, logdev->postmix_userdata); + + SDL_PendingAudioDeviceEvent *p; + + // Queue an event for each logical device we moved. + if (spec_changed) { + p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; + p->devid = logdev->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } } } - current_default_device->simple_copy = AudioDeviceCanUseSimpleCopy(current_default_device); - new_default_device->simple_copy = AudioDeviceCanUseSimpleCopy(new_default_device); + UpdateAudioStreamFormatsPhysical(current_default_device); + UpdateAudioStreamFormatsPhysical(new_default_device); - if (current_default_device->logical_devices == NULL) { // nothing left on the current physical device, close it. - // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it. - SDL_UnlockMutex(current_default_device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it. + if (!current_default_device->logical_devices) { // nothing left on the current physical device, close it. ClosePhysicalAudioDevice(current_default_device); - SDL_LockMutex(current_default_device->lock); // we're about to unlock this again, so make sure the locks match. } } - SDL_UnlockMutex(current_default_device->lock); + ReleaseAudioDevice(current_default_device); } - SDL_UnlockMutex(new_default_device->lock); + ReleaseAudioDevice(new_default_device); - // was current device already dead and just kept around to migrate to a new default device? Now we can kill it. Aim for the brain. - if (current_default_device && SDL_AtomicGet(¤t_default_device->zombie)) { - SDL_AudioDeviceDisconnected(current_default_device); // Call again, now that we're not the default; this will remove from device list, send removal events, and destroy the SDL_AudioDevice. + // Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected. + if (current_default_device) { // (despite the name, it's no longer current at this point) + UnrefPhysicalAudioDevice(current_default_device); + } + + if (pending.next) { + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + 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); } } @@ -1984,17 +2194,43 @@ int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL } // Post an event for the physical device, and each logical device on this physical device. - if (!kill_device && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED)) { - SDL_Event event; - SDL_zero(event); - event.type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; - event.common.timestamp = 0; - event.adevice.iscapture = device->iscapture ? 1 : 0; - event.adevice.which = device->instance_id; - SDL_PushEvent(&event); - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { - event.adevice.which = logdev->instance_id; - SDL_PushEvent(&event); + if (!kill_device) { + // Queue up events to push to the queue next time it pumps (presumably + // in a safer thread). + // !!! FIXME: this duplicates some code we could probably refactor. + SDL_PendingAudioDeviceEvent pending; + pending.next = NULL; + SDL_PendingAudioDeviceEvent *pending_tail = &pending; + + SDL_PendingAudioDeviceEvent *p; + + p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; + p->devid = device->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } + + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { + p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; + p->devid = logdev->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } + } + + if (pending.next) { + SDL_LockRWLockForWriting(current_audio.device_hash_lock); + 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); } } @@ -2003,9 +2239,43 @@ int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames) { - SDL_LockMutex(device->lock); + ObtainPhysicalAudioDeviceObj(device); const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames); - SDL_UnlockMutex(device->lock); + ReleaseAudioDevice(device); return retval; } +// This is an internal function, so SDL_PumpEvents() can check for pending audio device events. +// ("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_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; + SDL_UnlockRWLock(current_audio.device_hash_lock); + + 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); + 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_PendingAudioDeviceEvent *pending_next = NULL; + for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { + pending_next = i->next; + if (SDL_EventEnabled(i->type)) { + SDL_Event event; + SDL_zero(event); + event.type = i->type; + event.adevice.which = (Uint32) i->devid; + event.adevice.iscapture = (i->devid & (1<<0)) ? 0 : 1; // bit #0 of devid is set for output devices and unset for capture. + SDL_PushEvent(&event); + } + SDL_free(i); + } +} + diff --git a/src/audio/SDL_audio_c.h b/src/audio/SDL_audio_c.h index 7b00ceac..95cac81a 100644 --- a/src/audio/SDL_audio_c.h +++ b/src/audio/SDL_audio_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,8 +22,6 @@ #ifndef SDL_audio_c_h_ #define SDL_audio_c_h_ -/* !!! FIXME: remove this header and have things just include SDL_sysaudio.h directly. */ - -#include "SDL_sysaudio.h" +extern void SDL_UpdateAudio(void); #endif /* SDL_audio_c_h_ */ diff --git a/src/audio/SDL_audio_channel_converters.h b/src/audio/SDL_audio_channel_converters.h index b29046bd..a005a7ee 100644 --- a/src/audio/SDL_audio_channel_converters.h +++ b/src/audio/SDL_audio_channel_converters.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -19,7 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_channel_conversion.c */ +// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_channel_conversion.c typedef void (*SDL_AudioChannelConverter)(float *dst, const float *src, int num_frames); @@ -30,7 +30,7 @@ static void SDL_ConvertMonoToStereo(float *dst, const float *src, int num_frames LOG_DEBUG_AUDIO_CONVERT("mono", "stereo"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 2; for (i = num_frames; i; i--, src--, dst -= 2) { @@ -47,7 +47,7 @@ static void SDL_ConvertMonoTo21(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "2.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 3; for (i = num_frames; i; i--, src--, dst -= 3) { @@ -65,7 +65,7 @@ static void SDL_ConvertMonoToQuad(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "quad"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 4; for (i = num_frames; i; i--, src--, dst -= 4) { @@ -84,7 +84,7 @@ static void SDL_ConvertMonoTo41(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "4.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 5; for (i = num_frames; i; i--, src--, dst -= 5) { @@ -104,7 +104,7 @@ static void SDL_ConvertMonoTo51(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "5.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 6; for (i = num_frames; i; i--, src--, dst -= 6) { @@ -125,7 +125,7 @@ static void SDL_ConvertMonoTo61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src--, dst -= 7) { @@ -147,7 +147,7 @@ static void SDL_ConvertMonoTo71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("mono", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1); dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src--, dst -= 8) { @@ -182,7 +182,7 @@ static void SDL_ConvertStereoTo21(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("stereo", "2.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 3; for (i = num_frames; i; i--, src -= 2, dst -= 3) { @@ -199,7 +199,7 @@ static void SDL_ConvertStereoToQuad(float *dst, const float *src, int num_frames LOG_DEBUG_AUDIO_CONVERT("stereo", "quad"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 4; for (i = num_frames; i; i--, src -= 2, dst -= 4) { @@ -217,7 +217,7 @@ static void SDL_ConvertStereoTo41(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("stereo", "4.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 5; for (i = num_frames; i; i--, src -= 2, dst -= 5) { @@ -236,7 +236,7 @@ static void SDL_ConvertStereoTo51(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("stereo", "5.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 6; for (i = num_frames; i; i--, src -= 2, dst -= 6) { @@ -256,7 +256,7 @@ static void SDL_ConvertStereoTo61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("stereo", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src -= 2, dst -= 7) { @@ -277,7 +277,7 @@ static void SDL_ConvertStereoTo71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("stereo", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 2; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 2, dst -= 8) { @@ -325,7 +325,7 @@ static void SDL_Convert21ToQuad(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("2.1", "quad"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 3; dst += (num_frames-1) * 4; for (i = num_frames; i; i--, src -= 3, dst -= 4) { @@ -344,7 +344,7 @@ static void SDL_Convert21To41(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("2.1", "4.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 3; dst += (num_frames-1) * 5; for (i = num_frames; i; i--, src -= 3, dst -= 5) { @@ -363,7 +363,7 @@ static void SDL_Convert21To51(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("2.1", "5.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 3; dst += (num_frames-1) * 6; for (i = num_frames; i; i--, src -= 3, dst -= 6) { @@ -383,7 +383,7 @@ static void SDL_Convert21To61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("2.1", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 3; dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src -= 3, dst -= 7) { @@ -404,7 +404,7 @@ static void SDL_Convert21To71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("2.1", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 3; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 3, dst -= 8) { @@ -469,7 +469,7 @@ static void SDL_ConvertQuadTo41(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("quad", "4.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 4; dst += (num_frames-1) * 5; for (i = num_frames; i; i--, src -= 4, dst -= 5) { @@ -488,7 +488,7 @@ static void SDL_ConvertQuadTo51(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("quad", "5.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 4; dst += (num_frames-1) * 6; for (i = num_frames; i; i--, src -= 4, dst -= 6) { @@ -508,7 +508,7 @@ static void SDL_ConvertQuadTo61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("quad", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 4; dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src -= 4, dst -= 7) { @@ -531,7 +531,7 @@ static void SDL_ConvertQuadTo71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("quad", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 4; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 4, dst -= 8) { @@ -613,7 +613,7 @@ static void SDL_Convert41To51(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("4.1", "5.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 5; dst += (num_frames-1) * 6; for (i = num_frames; i; i--, src -= 5, dst -= 6) { @@ -633,7 +633,7 @@ static void SDL_Convert41To61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("4.1", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 5; dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src -= 5, dst -= 7) { @@ -656,7 +656,7 @@ static void SDL_Convert41To71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("4.1", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 5; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 5, dst -= 8) { @@ -758,7 +758,7 @@ static void SDL_Convert51To61(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("5.1", "6.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 6; dst += (num_frames-1) * 7; for (i = num_frames; i; i--, src -= 6, dst -= 7) { @@ -781,7 +781,7 @@ static void SDL_Convert51To71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("5.1", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 6; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 6, dst -= 8) { @@ -911,7 +911,7 @@ static void SDL_Convert61To71(float *dst, const float *src, int num_frames) LOG_DEBUG_AUDIO_CONVERT("6.1", "7.1"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-1) * 7; dst += (num_frames-1) * 8; for (i = num_frames; i; i--, src -= 7, dst -= 8) { diff --git a/src/audio/SDL_audio_resampler_filter.h b/src/audio/SDL_audio_resampler_filter.h index 1ea9c33d..ae12a97a 100644 --- a/src/audio/SDL_audio_resampler_filter.h +++ b/src/audio/SDL_audio_resampler_filter.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -19,7 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c */ +// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c #define RESAMPLER_ZERO_CROSSINGS 5 #define RESAMPLER_BITS_PER_SAMPLE 16 diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 12735bc3..54b8189d 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -#include "SDL_audio_c.h" +#include "SDL_sysaudio.h" #include "SDL_audioqueue.h" #include "SDL_audioresample.h" @@ -207,7 +207,7 @@ static SDL_bool SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt) static SDL_bool SDL_IsSupportedChannelCount(const int channels) { - return ((channels >= 1) && (channels <= 8)) ? SDL_TRUE : SDL_FALSE; + return ((channels >= 1) && (channels <= 8)); } @@ -278,7 +278,7 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i } } - if (scratch == NULL) { + if (!scratch) { scratch = dst; } @@ -389,7 +389,7 @@ static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSp if (stream->history_buffer_allocation < history_buffer_allocation) { history_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), history_buffer_allocation); if (!history_buffer) { - return SDL_OutOfMemory(); + return -1; } SDL_aligned_free(stream->history_buffer); stream->history_buffer = history_buffer; @@ -404,27 +404,24 @@ static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSp SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - SDL_SetError("Audio subsystem is not initialized"); - return NULL; - } + SDL_ChooseAudioConverters(); + SDL_SetupAudioResampler(); SDL_AudioStream *retval = (SDL_AudioStream *)SDL_calloc(1, sizeof(SDL_AudioStream)); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } retval->freq_ratio = 1.0f; retval->queue = SDL_CreateAudioQueue(4096); - if (retval->queue == NULL) { + if (!retval->queue) { SDL_free(retval); return NULL; } retval->lock = SDL_CreateMutex(); - if (retval->lock == NULL) { + if (!retval->lock) { SDL_free(retval->queue); SDL_free(retval); return NULL; @@ -440,6 +437,18 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ return retval; } +SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream) +{ + if (!stream) { + SDL_InvalidParamError("stream"); + return 0; + } + if (stream->props == 0) { + stream->props = SDL_CreateProperties(); + } + return stream->props; +} + int SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata) { if (!stream) { @@ -466,12 +475,20 @@ int SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallba int SDL_LockAudioStream(SDL_AudioStream *stream) { - return stream ? SDL_LockMutex(stream->lock) : SDL_InvalidParamError("stream"); + if (!stream) { + return SDL_InvalidParamError("stream"); + } + SDL_LockMutex(stream->lock); + return 0; } int SDL_UnlockAudioStream(SDL_AudioStream *stream) { - return stream ? SDL_UnlockMutex(stream->lock) : SDL_InvalidParamError("stream"); + if (!stream) { + return SDL_InvalidParamError("stream"); + } + SDL_UnlockMutex(stream->lock); + return 0; } int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec) @@ -568,7 +585,7 @@ float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream) } SDL_LockMutex(stream->lock); - float freq_ratio = stream->freq_ratio; + const float freq_ratio = stream->freq_ratio; SDL_UnlockMutex(stream->lock); return freq_ratio; @@ -581,8 +598,8 @@ int SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) } // Picked mostly arbitrarily. - static const float min_freq_ratio = 0.01f; - static const float max_freq_ratio = 100.0f; + const float min_freq_ratio = 0.01f; + const float max_freq_ratio = 100.0f; if (freq_ratio < min_freq_ratio) { return SDL_SetError("Frequency ratio is too low"); @@ -614,9 +631,9 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); #endif - if (stream == NULL) { + if (!stream) { return SDL_InvalidParamError("stream"); - } else if (buf == NULL) { + } else if (!buf) { return SDL_InvalidParamError("buf"); } else if (len < 0) { return SDL_InvalidParamError("len"); @@ -651,7 +668,7 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) size_t chunk_size = SDL_GetAudioQueueChunkSize(stream->queue); track = SDL_CreateChunkedAudioTrack(&src_spec, buf, len, chunk_size); - if (track == NULL) { + if (!track) { return -1; } @@ -662,7 +679,7 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) int retval = 0; - if (track != NULL) { + if (track) { SDL_AddTrackToAudioQueue(stream->queue, track); } else { retval = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, buf, len); @@ -683,7 +700,7 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) int SDL_FlushAudioStream(SDL_AudioStream *stream) { - if (stream == NULL) { + if (!stream) { return SDL_InvalidParamError("stream"); } @@ -703,8 +720,7 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne } Uint8 *ptr = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), newlen); - if (ptr == NULL) { - SDL_OutOfMemory(); + if (!ptr) { return NULL; // previous work buffer is still valid! } @@ -723,7 +739,7 @@ static void UpdateAudioStreamHistoryBuffer(SDL_AudioStream* stream, Uint8 *history_buffer = stream->history_buffer; int history_bytes = history_buffer_frames * SDL_AUDIO_FRAMESIZE(stream->input_spec); - if (left_padding != NULL) { + if (left_padding) { // Fill in the left padding using the history buffer SDL_assert(padding_bytes <= history_bytes); SDL_memcpy(left_padding, history_buffer + history_bytes - padding_bytes, padding_bytes); @@ -817,7 +833,7 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe { void* iter = SDL_BeginAudioQueueIter(stream->queue); - if (iter == NULL) { + if (!iter) { SDL_zerop(out_spec); *out_flushed = SDL_FALSE; return 0; @@ -1007,9 +1023,9 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len); #endif - if (stream == NULL) { + if (!stream) { return SDL_InvalidParamError("stream"); - } else if (buf == NULL) { + } else if (!buf) { return SDL_InvalidParamError("buf"); } else if (len < 0) { return SDL_InvalidParamError("len"); @@ -1142,7 +1158,7 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) int SDL_ClearAudioStream(SDL_AudioStream *stream) { - if (stream == NULL) { + if (!stream) { return SDL_InvalidParamError("stream"); } @@ -1159,16 +1175,20 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) void SDL_DestroyAudioStream(SDL_AudioStream *stream) { - if (stream == NULL) { + if (!stream) { return; } + SDL_DestroyProperties(stream->props); + OnAudioStreamDestroy(stream); const SDL_bool simplified = stream->simplified; if (simplified) { - SDL_assert(stream->bound_device->simplified); - SDL_CloseAudioDevice(stream->bound_device->instance_id); // this will unbind the stream. + if (stream->bound_device) { + SDL_assert(stream->bound_device->simplified); + SDL_CloseAudioDevice(stream->bound_device->instance_id); // this will unbind the stream. + } } else { SDL_UnbindAudioStream(stream); } @@ -1192,13 +1212,13 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data *dst_len = 0; } - if (src_data == NULL) { + if (!src_data) { return SDL_InvalidParamError("src_data"); } else if (src_len < 0) { return SDL_InvalidParamError("src_len"); - } else if (dst_data == NULL) { + } else if (!dst_data) { return SDL_InvalidParamError("dst_data"); - } else if (dst_len == NULL) { + } else if (!dst_len) { return SDL_InvalidParamError("dst_len"); } @@ -1207,14 +1227,12 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data int dstlen = 0; SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); - if (stream != NULL) { + if (stream) { if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { dstlen = SDL_GetAudioStreamAvailable(stream); if (dstlen >= 0) { dst = (Uint8 *)SDL_malloc(dstlen); - if (!dst) { - SDL_OutOfMemory(); - } else { + if (dst) { retval = (SDL_GetAudioStreamData(stream, dst, dstlen) >= 0) ? 0 : -1; } } diff --git a/src/audio/SDL_audiodev.c b/src/audio/SDL_audiodev.c index 9d73fbb5..ad8a2ad0 100644 --- a/src/audio/SDL_audiodev.c +++ b/src/audio/SDL_audiodev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,14 +20,14 @@ */ #include "SDL_internal.h" -/* Get the name of the audio device we use for output */ +// Get the name of the audio device we use for output #if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS) #include #include #include -#include /* For close() */ +#include // For close() #include "SDL_audiodev_c.h" @@ -80,22 +80,22 @@ static void SDL_EnumUnixAudioDevices_Internal(const SDL_bool iscapture, const SD const char *audiodev; char audiopath[1024]; - if (test == NULL) { + if (!test) { test = test_stub; } - /* Figure out what our audio device is */ + // Figure out what our audio device is audiodev = SDL_getenv("SDL_PATH_DSP"); - if (audiodev == NULL) { + if (!audiodev) { audiodev = SDL_getenv("AUDIODEV"); } - if (audiodev == NULL) { + if (!audiodev) { if (classic) { audiodev = SDL_PATH_DEV_AUDIO; } else { struct stat sb; - /* Added support for /dev/sound/\* in Linux 2.4 */ + // Added support for /dev/sound/\* in Linux 2.4 if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) { audiodev = SDL_PATH_DEV_DSP24; } else { @@ -122,4 +122,4 @@ void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int)) SDL_EnumUnixAudioDevices_Internal(SDL_FALSE, classic, test); } -#endif /* Audio driver selection */ +#endif // Audio device selection diff --git a/src/audio/SDL_audiodev_c.h b/src/audio/SDL_audiodev_c.h index 6301d07c..8ea867c2 100644 --- a/src/audio/SDL_audiodev_c.h +++ b/src/audio/SDL_audiodev_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,8 +25,8 @@ #include "SDL_internal.h" #include "SDL_sysaudio.h" -/* Open the audio device for playback, and don't block if busy */ -/* #define USE_BLOCKING_WRITES */ +// Open the audio device for playback, and don't block if busy +//#define USE_BLOCKING_WRITES #ifdef USE_BLOCKING_WRITES #define OPEN_FLAGS_OUTPUT O_WRONLY @@ -38,4 +38,4 @@ extern void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int)); -#endif /* SDL_audiodev_c_h_ */ +#endif // SDL_audiodev_c_h_ diff --git a/src/audio/SDL_audioqueue.c b/src/audio/SDL_audioqueue.c index c27bb409..d5a9e3ac 100644 --- a/src/audio/SDL_audioqueue.c +++ b/src/audio/SDL_audioqueue.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -92,7 +92,7 @@ static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size) { SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size); - if (chunk == NULL) { + if (!chunk) { return NULL; } @@ -146,11 +146,11 @@ static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len) SDL_AudioChunk *chunk = track->tail; // Handle the first chunk - if (chunk == NULL) { + if (!chunk) { chunk = CreateAudioTrackChunk(track); - if (chunk == NULL) { - return SDL_OutOfMemory(); + if (!chunk) { + return -1; } SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0)); @@ -180,7 +180,7 @@ static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len) } // Roll back the changes if we couldn't write all the data - if (chunk == NULL) { + if (!chunk) { chunk = track->tail; SDL_AudioChunk *next = chunk->next; @@ -189,7 +189,7 @@ static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len) DestroyAudioChunks(next); - return SDL_OutOfMemory(); + return -1; } track->tail = chunk; @@ -255,8 +255,7 @@ static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t { SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track)); - if (track == NULL) { - SDL_OutOfMemory(); + if (!track) { return NULL; } @@ -275,8 +274,7 @@ SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size) { SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue)); - if (queue == NULL) { - SDL_OutOfMemory(); + if (!queue) { return NULL; } @@ -338,7 +336,7 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue) queue->head = track; - if (track == NULL) { + if (!track) { queue->tail = NULL; } } @@ -352,7 +350,7 @@ SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uin { SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size); - if (track == NULL) { + if (!track) { return NULL; } @@ -390,15 +388,15 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons SDL_AudioTrack *track = queue->tail; - if ((track != NULL) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) { + if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) { SDL_FlushAudioTrack(track); } - if ((track == NULL) || (track->write == NULL)) { + if ((!track) || (!track->write)) { SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size); - if (new_track == NULL) { - return SDL_OutOfMemory(); + if (!new_track) { + return -1; } if (track) { @@ -462,7 +460,7 @@ int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) SDL_AudioTrack *track = queue->head; for (;;) { - if (track == NULL) { + if (!track) { return SDL_SetError("Reading past end of queue"); } @@ -478,7 +476,7 @@ int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) SDL_AudioTrack *next = track->next; - if (next == NULL) { + if (!next) { return SDL_SetError("Reading past end of incomplete track"); } @@ -495,7 +493,7 @@ int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) SDL_AudioTrack *track = queue->head; for (;;) { - if (track == NULL) { + if (!track) { return SDL_SetError("Peeking past end of queue"); } diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 76012e91..9b18a5c5 100644 --- a/src/audio/SDL_audioqueue.h +++ b/src/audio/SDL_audioqueue.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/audio/SDL_audioresample.c b/src/audio/SDL_audioresample.c index 6e3f4032..9ca6d0a2 100644 --- a/src/audio/SDL_audioresample.c +++ b/src/audio/SDL_audioresample.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,13 +23,13 @@ #include "SDL_sysaudio.h" #include "SDL_audioresample.h" -/* SDL's resampler uses a "bandlimited interpolation" algorithm: - https://ccrma.stanford.edu/~jos/resample/ */ +// SDL's resampler uses a "bandlimited interpolation" algorithm: +// https://ccrma.stanford.edu/~jos/resample/ #include "SDL_audio_resampler_filter.h" -/* For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`. - * Note, when upsampling, it is also possible to start sampling from `srcpos = -1`. */ +// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`. +// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`. #define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1) #define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING) diff --git a/src/audio/SDL_audioresample.h b/src/audio/SDL_audioresample.h index 84aaa2db..9a6920e0 100644 --- a/src/audio/SDL_audioresample.h +++ b/src/audio/SDL_audioresample.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,7 @@ // Internal functions used by SDL_AudioStream for resampling audio. // The resampler uses 32:32 fixed-point arithmetic to track its position. -Sint64 SDL_GetResampleRate(const int src_rate, const int dst_rate); +Sint64 SDL_GetResampleRate(int src_rate, int dst_rate); int SDL_GetResamplerHistoryFrames(void); int SDL_GetResamplerPaddingFrames(Sint64 resample_rate); diff --git a/src/audio/SDL_audiotypecvt.c b/src/audio/SDL_audiotypecvt.c index 1b45a0d9..770922bb 100644 --- a/src/audio/SDL_audiotypecvt.c +++ b/src/audio/SDL_audiotypecvt.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,30 +20,33 @@ */ #include "SDL_internal.h" -#include "SDL_audio_c.h" +#include "SDL_sysaudio.h" + +// TODO: NEON is disabled until https://github.com/libsdl-org/SDL/issues/8352 can be fixed +#undef SDL_NEON_INTRINSICS #ifndef SDL_CPUINFO_DISABLED #if defined(__x86_64__) && defined(SDL_SSE2_INTRINSICS) -#define NEED_SCALAR_CONVERTER_FALLBACKS 0 /* x86_64 guarantees SSE2. */ +#define NEED_SCALAR_CONVERTER_FALLBACKS 0 // x86_64 guarantees SSE2. #elif defined(__MACOS__) && defined(SDL_SSE2_INTRINSICS) -#define NEED_SCALAR_CONVERTER_FALLBACKS 0 /* macOS/Intel guarantees SSE2. */ +#define NEED_SCALAR_CONVERTER_FALLBACKS 0 // macOS/Intel guarantees SSE2. #elif defined(__ARM_ARCH) && (__ARM_ARCH >= 8) && defined(SDL_NEON_INTRINSICS) -#define NEED_SCALAR_CONVERTER_FALLBACKS 0 /* ARMv8+ promise NEON. */ +#define NEED_SCALAR_CONVERTER_FALLBACKS 0 // ARMv8+ promise NEON. #elif defined(__APPLE__) && defined(__ARM_ARCH) && (__ARM_ARCH >= 7) && defined(SDL_NEON_INTRINSICS) -#define NEED_SCALAR_CONVERTER_FALLBACKS 0 /* All Apple ARMv7 chips promise NEON support. */ +#define NEED_SCALAR_CONVERTER_FALLBACKS 0 // All Apple ARMv7 chips promise NEON support. #endif #endif -/* Set to zero if platform is guaranteed to use a SIMD codepath here. */ +// Set to zero if platform is guaranteed to use a SIMD codepath here. #if !defined(NEED_SCALAR_CONVERTER_FALLBACKS) || defined(SDL_CPUINFO_DISABLED) #define NEED_SCALAR_CONVERTER_FALLBACKS 1 #endif -#define DIVBY2147483648 0.0000000004656612873077392578125f /* 0x1p-31f */ +#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f #if NEED_SCALAR_CONVERTER_FALLBACKS -/* This code requires that floats are in the IEEE-754 binary32 format */ +// This code requires that floats are in the IEEE-754 binary32 format SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32)); union float_bits { @@ -107,7 +110,7 @@ static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num } } -/* Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right */ +// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right #define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31)) static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples) @@ -198,7 +201,7 @@ static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num #undef SIGNMASK -#endif /* NEED_SCALAR_CONVERTER_FALLBACKS */ +#endif // NEED_SCALAR_CONVERTER_FALLBACKS #ifdef SDL_SSE2_INTRINSICS static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples) @@ -320,7 +323,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const { int i = num_samples; - /* dst[i] = f32(src[i]) / f32(0x80000000) */ + // dst[i] = f32(src[i]) / f32(0x80000000) const __m128 scaler = _mm_set1_ps(DIVBY2147483648); LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)"); @@ -539,9 +542,9 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const #endif #ifdef SDL_NEON_INTRINSICS -#define DIVBY128 0.0078125f /* 0x1p-7f */ -#define DIVBY32768 0.000030517578125f /* 0x1p-15f */ -#define DIVBY8388607 0.00000011920930376163766f /* 0x1.000002p-23f */ +#define DIVBY128 0.0078125f // 0x1p-7f +#define DIVBY32768 0.000030517578125f // 0x1p-15f +#define DIVBY8388607 0.00000011920930376163766f // 0x1.000002p-23f static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples) { @@ -552,25 +555,25 @@ static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_sam src += num_samples - 1; dst += num_samples - 1; - /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + // Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) { *dst = ((float)*src) * DIVBY128; } src -= 15; - dst -= 15; /* adjust to read NEON blocks from the start. */ + dst -= 15; // adjust to read NEON blocks from the start. SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const int8_t *mmsrc = (const int8_t *)src; const float32x4_t divby128 = vdupq_n_f32(DIVBY128); - while (i >= 16) { /* 16 * 8-bit */ - const int8x16_t bytes = vld1q_s8(mmsrc); /* get 16 sint8 into a NEON register. */ - const int16x8_t int16hi = vmovl_s8(vget_high_s8(bytes)); /* convert top 8 bytes to 8 int16 */ - const int16x8_t int16lo = vmovl_s8(vget_low_s8(bytes)); /* convert bottom 8 bytes to 8 int16 */ - /* split int16 to two int32, then convert to float, then multiply to normalize, store. */ + while (i >= 16) { // 16 * 8-bit + const int8x16_t bytes = vld1q_s8(mmsrc); // get 16 sint8 into a NEON register. + const int16x8_t int16hi = vmovl_s8(vget_high_s8(bytes)); // convert top 8 bytes to 8 int16 + const int16x8_t int16lo = vmovl_s8(vget_low_s8(bytes)); // convert bottom 8 bytes to 8 int16 + // split int16 to two int32, then convert to float, then multiply to normalize, store. vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16lo))), divby128)); vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(int16lo))), divby128)); vst1q_f32(dst + 8, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16hi))), divby128)); @@ -584,9 +587,9 @@ static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_sam } src += 15; - dst += 15; /* adjust for any scalar finishing. */ + dst += 15; // adjust for any scalar finishing. - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { *dst = ((float)*src) * DIVBY128; i--; @@ -604,26 +607,26 @@ static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_sam src += num_samples - 1; dst += num_samples - 1; - /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + // Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) { *dst = (((float)*src) * DIVBY128) - 1.0f; } src -= 15; - dst -= 15; /* adjust to read NEON blocks from the start. */ + dst -= 15; // adjust to read NEON blocks from the start. SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const uint8_t *mmsrc = (const uint8_t *)src; const float32x4_t divby128 = vdupq_n_f32(DIVBY128); const float32x4_t negone = vdupq_n_f32(-1.0f); - while (i >= 16) { /* 16 * 8-bit */ - const uint8x16_t bytes = vld1q_u8(mmsrc); /* get 16 uint8 into a NEON register. */ - const uint16x8_t uint16hi = vmovl_u8(vget_high_u8(bytes)); /* convert top 8 bytes to 8 uint16 */ - const uint16x8_t uint16lo = vmovl_u8(vget_low_u8(bytes)); /* convert bottom 8 bytes to 8 uint16 */ - /* split uint16 to two uint32, then convert to float, then multiply to normalize, subtract to adjust for sign, store. */ + while (i >= 16) { // 16 * 8-bit + const uint8x16_t bytes = vld1q_u8(mmsrc); // get 16 uint8 into a NEON register. + const uint16x8_t uint16hi = vmovl_u8(vget_high_u8(bytes)); // convert top 8 bytes to 8 uint16 + const uint16x8_t uint16lo = vmovl_u8(vget_low_u8(bytes)); // convert bottom 8 bytes to 8 uint16 + // split uint16 to two uint32, then convert to float, then multiply to normalize, subtract to adjust for sign, store. vst1q_f32(dst, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16lo))), divby128)); vst1q_f32(dst + 4, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16lo))), divby128)); vst1q_f32(dst + 8, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16hi))), divby128)); @@ -637,9 +640,9 @@ static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_sam } src += 15; - dst += 15; /* adjust for any scalar finishing. */ + dst += 15; // adjust for any scalar finishing. - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { *dst = (((float)*src) * DIVBY128) - 1.0f; i--; @@ -657,22 +660,22 @@ static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_s src += num_samples - 1; dst += num_samples - 1; - /* Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) */ + // Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src) for (i = num_samples; i && (((size_t)(dst - 7)) & 15); --i, --src, --dst) { *dst = ((float)*src) * DIVBY32768; } src -= 7; - dst -= 7; /* adjust to read NEON blocks from the start. */ + dst -= 7; // adjust to read NEON blocks from the start. SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t divby32768 = vdupq_n_f32(DIVBY32768); - while (i >= 8) { /* 8 * 16-bit */ - const int16x8_t ints = vld1q_s16((int16_t const *)src); /* get 8 sint16 into a NEON register. */ - /* split int16 to two int32, then convert to float, then multiply to normalize, store. */ + while (i >= 8) { // 8 * 16-bit + const int16x8_t ints = vld1q_s16((int16_t const *)src); // get 8 sint16 into a NEON register. + // split int16 to two int32, then convert to float, then multiply to normalize, store. vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(ints))), divby32768)); vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(ints))), divby32768)); i -= 8; @@ -682,9 +685,9 @@ static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_s } src += 7; - dst += 7; /* adjust for any scalar finishing. */ + dst += 7; // adjust for any scalar finishing. - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { *dst = ((float)*src) * DIVBY32768; i--; @@ -699,20 +702,20 @@ static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_s LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)"); - /* Get dst aligned to 16 bytes */ + // Get dst aligned to 16 bytes for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) { *dst = ((float)(*src >> 8)) * DIVBY8388607; } SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t divby8388607 = vdupq_n_f32(DIVBY8388607); const int32_t *mmsrc = (const int32_t *)src; - while (i >= 4) { /* 4 * sint32 */ - /* shift out lowest bits so int fits in a float32. Small precision loss, but much faster. */ + while (i >= 4) { // 4 * sint32 + // shift out lowest bits so int fits in a float32. Small precision loss, but much faster. vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vshrq_n_s32(vld1q_s32(mmsrc), 8)), divby8388607)); i -= 4; mmsrc += 4; @@ -721,7 +724,7 @@ static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_s src = (const Sint32 *)mmsrc; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { *dst = ((float)(*src >> 8)) * DIVBY8388607; i--; @@ -736,7 +739,7 @@ static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_sam LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)"); - /* Get dst aligned to 16 bytes */ + // Get dst aligned to 16 bytes for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) { const float sample = *src; if (sample >= 1.0f) { @@ -750,21 +753,21 @@ static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_sam SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t one = vdupq_n_f32(1.0f); const float32x4_t negone = vdupq_n_f32(-1.0f); const float32x4_t mulby127 = vdupq_n_f32(127.0f); int8_t *mmdst = (int8_t *)dst; - while (i >= 16) { /* 16 * float32 */ - const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby127)); /* load 4 floats, clamp, convert to sint32 */ - const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby127)); /* load 4 floats, clamp, convert to sint32 */ - const int32x4_t ints3 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), mulby127)); /* load 4 floats, clamp, convert to sint32 */ - const int32x4_t ints4 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), mulby127)); /* load 4 floats, clamp, convert to sint32 */ - const int8x8_t i8lo = vmovn_s16(vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); /* narrow to sint16, combine, narrow to sint8 */ - const int8x8_t i8hi = vmovn_s16(vcombine_s16(vmovn_s32(ints3), vmovn_s32(ints4))); /* narrow to sint16, combine, narrow to sint8 */ - vst1q_s8(mmdst, vcombine_s8(i8lo, i8hi)); /* combine to int8x16_t, store out */ + while (i >= 16) { // 16 * float32 + const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby127)); // load 4 floats, clamp, convert to sint32 + const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby127)); // load 4 floats, clamp, convert to sint32 + const int32x4_t ints3 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), mulby127)); // load 4 floats, clamp, convert to sint32 + const int32x4_t ints4 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), mulby127)); // load 4 floats, clamp, convert to sint32 + const int8x8_t i8lo = vmovn_s16(vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, narrow to sint8 + const int8x8_t i8hi = vmovn_s16(vcombine_s16(vmovn_s32(ints3), vmovn_s32(ints4))); // narrow to sint16, combine, narrow to sint8 + vst1q_s8(mmdst, vcombine_s8(i8lo, i8hi)); // combine to int8x16_t, store out i -= 16; src += 16; mmdst += 16; @@ -772,7 +775,7 @@ static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_sam dst = (Sint8 *)mmdst; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { const float sample = *src; if (sample >= 1.0f) { @@ -794,7 +797,7 @@ static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_sam LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)"); - /* Get dst aligned to 16 bytes */ + // Get dst aligned to 16 bytes for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) { const float sample = *src; if (sample >= 1.0f) { @@ -808,21 +811,21 @@ static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_sam SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t one = vdupq_n_f32(1.0f); const float32x4_t negone = vdupq_n_f32(-1.0f); const float32x4_t mulby127 = vdupq_n_f32(127.0f); uint8_t *mmdst = (uint8_t *)dst; - while (i >= 16) { /* 16 * float32 */ - const uint32x4_t uints1 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), one), mulby127)); /* load 4 floats, clamp, convert to uint32 */ - const uint32x4_t uints2 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), one), mulby127)); /* load 4 floats, clamp, convert to uint32 */ - const uint32x4_t uints3 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), one), mulby127)); /* load 4 floats, clamp, convert to uint32 */ - const uint32x4_t uints4 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), one), mulby127)); /* load 4 floats, clamp, convert to uint32 */ - const uint8x8_t ui8lo = vmovn_u16(vcombine_u16(vmovn_u32(uints1), vmovn_u32(uints2))); /* narrow to uint16, combine, narrow to uint8 */ - const uint8x8_t ui8hi = vmovn_u16(vcombine_u16(vmovn_u32(uints3), vmovn_u32(uints4))); /* narrow to uint16, combine, narrow to uint8 */ - vst1q_u8(mmdst, vcombine_u8(ui8lo, ui8hi)); /* combine to uint8x16_t, store out */ + while (i >= 16) { // 16 * float32 + const uint32x4_t uints1 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32 + const uint32x4_t uints2 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32 + const uint32x4_t uints3 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32 + const uint32x4_t uints4 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32 + const uint8x8_t ui8lo = vmovn_u16(vcombine_u16(vmovn_u32(uints1), vmovn_u32(uints2))); // narrow to uint16, combine, narrow to uint8 + const uint8x8_t ui8hi = vmovn_u16(vcombine_u16(vmovn_u32(uints3), vmovn_u32(uints4))); // narrow to uint16, combine, narrow to uint8 + vst1q_u8(mmdst, vcombine_u8(ui8lo, ui8hi)); // combine to uint8x16_t, store out i -= 16; src += 16; mmdst += 16; @@ -831,7 +834,7 @@ static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_sam dst = (Uint8 *)mmdst; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { const float sample = *src; if (sample >= 1.0f) { @@ -853,7 +856,7 @@ static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_s LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)"); - /* Get dst aligned to 16 bytes */ + // Get dst aligned to 16 bytes for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) { const float sample = *src; if (sample >= 1.0f) { @@ -867,17 +870,17 @@ static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_s SDL_assert(!i || !(((size_t)dst) & 15)); - /* Make sure src is aligned too. */ + // Make sure src is aligned too. if (!(((size_t)src) & 15)) { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t one = vdupq_n_f32(1.0f); const float32x4_t negone = vdupq_n_f32(-1.0f); const float32x4_t mulby32767 = vdupq_n_f32(32767.0f); int16_t *mmdst = (int16_t *)dst; - while (i >= 8) { /* 8 * float32 */ - const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby32767)); /* load 4 floats, clamp, convert to sint32 */ - const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby32767)); /* load 4 floats, clamp, convert to sint32 */ - vst1q_s16(mmdst, vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); /* narrow to sint16, combine, store out. */ + while (i >= 8) { // 8 * float32 + const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby32767)); // load 4 floats, clamp, convert to sint32 + const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby32767)); // load 4 floats, clamp, convert to sint32 + vst1q_s16(mmdst, vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, store out. i -= 8; src += 8; mmdst += 8; @@ -885,7 +888,7 @@ static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_s dst = (Sint16 *)mmdst; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { const float sample = *src; if (sample >= 1.0f) { @@ -907,7 +910,7 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)"); - /* Get dst aligned to 16 bytes */ + // Get dst aligned to 16 bytes for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) { const float sample = *src; if (sample >= 1.0f) { @@ -923,12 +926,12 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s SDL_assert(!i || !(((size_t)src) & 15)); { - /* Aligned! Do NEON blocks as long as we have 16 bytes available. */ + // Aligned! Do NEON blocks as long as we have 16 bytes available. const float32x4_t one = vdupq_n_f32(1.0f); const float32x4_t negone = vdupq_n_f32(-1.0f); const float32x4_t mulby8388607 = vdupq_n_f32(8388607.0f); int32_t *mmdst = (int32_t *)dst; - while (i >= 4) { /* 4 * float32 */ + while (i >= 4) { // 4 * float32 vst1q_s32(mmdst, vshlq_n_s32(vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby8388607)), 8)); i -= 4; src += 4; @@ -937,7 +940,7 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s dst = (Sint32 *)mmdst; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { const float sample = *src; if (sample >= 1.0f) { @@ -954,7 +957,7 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s } #endif -/* Function pointers set to a CPU-specific implementation. */ +// Function pointers set to a CPU-specific implementation. void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL; void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL; void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL; diff --git a/src/audio/SDL_mixer.c b/src/audio/SDL_mixer.c index 0a4992d8..4e71620b 100644 --- a/src/audio/SDL_mixer.c +++ b/src/audio/SDL_mixer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -/* This provides the default mixing callback for the SDL audio routines */ +// This provides the default mixing callback for the SDL audio routines #include "SDL_sysaudio.h" @@ -77,12 +77,12 @@ static const Uint8 mix8[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; -/* The volume ranges from 0 - 128 */ +// The volume ranges from 0 - 128 #define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / SDL_MIX_MAXVOLUME)) #define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / SDL_MIX_MAXVOLUME) + 128)) -/* !!! FIXME: this needs some SIMD magic. */ +// !!! FIXME: this needs some SIMD magic. int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, int volume) @@ -237,7 +237,7 @@ int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, float *dst32 = (float *)dst; float src1, src2; double dst_sample; - /* !!! FIXME: are these right? */ + // !!! FIXME: are these right? const double max_audioval = 3.402823466e+38F; const double min_audioval = -3.402823466e+38F; @@ -265,7 +265,7 @@ int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, float *dst32 = (float *)dst; float src1, src2; double dst_sample; - /* !!! FIXME: are these right? */ + // !!! FIXME: are these right? const double max_audioval = 3.402823466e+38F; const double min_audioval = -3.402823466e+38F; @@ -285,7 +285,7 @@ int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, } } break; - default: /* If this happens... FIXME! */ + default: // If this happens... FIXME! return SDL_SetError("SDL_MixAudioFormat(): unknown audio format"); } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index e0d93160..eb6b38ba 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,6 +24,8 @@ #ifndef SDL_sysaudio_h_ #define SDL_sysaudio_h_ +#include "../SDL_hashtable.h" + #define DEBUG_AUDIOSTREAM 0 #define DEBUG_AUDIO_CONVERT 0 @@ -104,6 +106,9 @@ extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); // Backends can call this to get a standardized name for a thread to power a specific audio device. extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen); +// Backends can call these to change a device's refcount. +extern void RefPhysicalAudioDevice(SDL_AudioDevice *device); +extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device); // These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread. extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device); @@ -128,38 +133,48 @@ typedef struct SDL_AudioDriverImpl int (*OpenDevice)(SDL_AudioDevice *device); void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end - void (*WaitDevice)(SDL_AudioDevice *device); + int (*WaitDevice)(SDL_AudioDevice *device); int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience. Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); - void (*WaitCaptureDevice)(SDL_AudioDevice *device); + int (*WaitCaptureDevice)(SDL_AudioDevice *device); int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); void (*FlushCapture)(SDL_AudioDevice *device); void (*CloseDevice)(SDL_AudioDevice *device); void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice() + void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection. void (*Deinitialize)(void); // Some flags to push duplicate code into the core and reduce #ifdefs. SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore. SDL_bool HasCaptureSupport; SDL_bool OnlyHasDefaultOutputDevice; - SDL_bool OnlyHasDefaultCaptureDevice; - SDL_bool AllowsArbitraryDeviceNames; + SDL_bool OnlyHasDefaultCaptureDevice; // !!! FIXME: is there ever a time where you'd have a default output and not a default capture (or vice versa)? } SDL_AudioDriverImpl; + +typedef struct SDL_PendingAudioDeviceEvent +{ + Uint32 type; + SDL_AudioDeviceID devid; + struct SDL_PendingAudioDeviceEvent *next; +} SDL_PendingAudioDeviceEvent; + 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_list_lock; // A mutex for device detection - SDL_AudioDevice *output_devices; // the list of currently-available audio output devices. - SDL_AudioDevice *capture_devices; // the list of currently-available audio capture devices. + SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` + SDL_HashTable *device_hash; // the collection of currently-available audio devices (capture, playback, logical and physical!) SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams. SDL_AudioDeviceID default_output_device_id; SDL_AudioDeviceID default_capture_device_id; + SDL_PendingAudioDeviceEvent pending_events; + SDL_PendingAudioDeviceEvent *pending_events_tail; + + // !!! FIXME: most (all?) of these don't have to be atomic. SDL_AtomicInt output_device_count; SDL_AtomicInt capture_device_count; - SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs. } SDL_AudioDriver; @@ -169,6 +184,8 @@ struct SDL_AudioStream { SDL_Mutex* lock; + SDL_PropertiesID props; + SDL_AudioStreamCallback get_callback; void *get_callback_userdata; SDL_AudioStreamCallback put_callback; @@ -240,6 +257,20 @@ struct SDL_AudioDevice // A mutex for locking access to this struct SDL_Mutex *lock; + // A condition variable to protect device close, where we can't hold the device lock forever. + SDL_Condition *close_cond; + + // Reference count of the device; logical devices, device threads, etc, add to this. + SDL_AtomicInt refcount; + + // These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure. + int (*WaitDevice)(SDL_AudioDevice *device); + int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); + Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); + int (*WaitCaptureDevice)(SDL_AudioDevice *device); + int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); + void (*FlushCapture)(SDL_AudioDevice *device); + // human-readable name of the device. ("SoundBlaster Pro 16") char *name; @@ -265,15 +296,9 @@ struct SDL_AudioDevice // non-zero if we are signaling the audio thread to end. SDL_AtomicInt shutdown; - // non-zero if we want the device to be destroyed (so audio thread knows to do it on termination). - SDL_AtomicInt condemned; - - // non-zero if this was a disconnected default device and we're waiting for its replacement. + // non-zero if this was a disconnected device and we're waiting for it to be decommissioned. SDL_AtomicInt zombie; - // non-zero if this has a thread running (which might be `thread` or something provided by the backend!) - SDL_AtomicInt thread_alive; - // SDL_TRUE if this is a capture device instead of an output device SDL_bool iscapture; @@ -299,10 +324,6 @@ struct SDL_AudioDevice // All logical devices associated with this physical device. SDL_LogicalAudioDevice *logical_devices; - - // double-linked list of all physical devices. - struct SDL_AudioDevice *prev; - struct SDL_AudioDevice *next; }; typedef struct AudioBootStrap @@ -329,7 +350,7 @@ extern AudioBootStrap COREAUDIO_bootstrap; extern AudioBootStrap DISKAUDIO_bootstrap; extern AudioBootStrap DUMMYAUDIO_bootstrap; extern AudioBootStrap AAUDIO_bootstrap; -extern AudioBootStrap openslES_bootstrap; // !!! FIXME: capitalize this to match the others +extern AudioBootStrap OPENSLES_bootstrap; extern AudioBootStrap ANDROIDAUDIO_bootstrap; extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index 2afb70bd..633783af 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,7 +34,7 @@ /* Microsoft WAVE file loading routines */ #include "SDL_wave.h" -#include "SDL_audio_c.h" +#include "SDL_sysaudio.h" /* Reads the value stored at the location of the f1 pointer, multiplies it * with the second argument and then stores the result to f1. @@ -262,7 +262,7 @@ static void WaveDebugDumpFormat(WaveFile *file, Uint32 rifflen, Uint32 fmtlen, U int res; dumpstr = SDL_malloc(bufsize); - if (dumpstr == NULL) { + if (!dumpstr) { return; } dumpstr[0] = 0; @@ -439,8 +439,8 @@ static int MS_ADPCM_Init(WaveFile *file, size_t datalength) coeffdata = (MS_ADPCM_CoeffData *)SDL_malloc(sizeof(MS_ADPCM_CoeffData) + coeffcount * 4); file->decoderdata = coeffdata; /* Freed in cleanup. */ - if (coeffdata == NULL) { - return SDL_OutOfMemory(); + if (!coeffdata) { + return -1; } coeffdata->coeff = &coeffdata->aligndummy; coeffdata->coeffcount = (Uint16)coeffcount; @@ -674,7 +674,7 @@ static int MS_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len) /* The output size in bytes. May get modified if data is truncated. */ outputsize = (size_t)state.framestotal; if (SafeMult(&outputsize, state.framesize)) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } else if (outputsize > SDL_MAX_UINT32 || state.framestotal > SIZE_MAX) { return SDL_SetError("WAVE file too big"); } @@ -682,8 +682,8 @@ static int MS_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len) state.output.pos = 0; state.output.size = outputsize / sizeof(Sint16); state.output.data = (Sint16 *)SDL_calloc(1, outputsize); - if (state.output.data == NULL) { - return SDL_OutOfMemory(); + if (!state.output.data) { + return -1; } state.cstate = cstate; @@ -985,7 +985,7 @@ static int IMA_ADPCM_DecodeBlockData(ADPCM_DecoderState *state) const size_t remainingbytes = blockleft % subblockframesize; blockframesleft = guaranteedframes; if (remainingbytes > subblockframesize - 4) { - blockframesleft += (remainingbytes % 4) * 2; + blockframesleft += (Sint64)(remainingbytes % 4) * 2; } /* Signal the truncation. */ retval = -1; @@ -1065,7 +1065,7 @@ static int IMA_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len /* The output size in bytes. May get modified if data is truncated. */ outputsize = (size_t)state.framestotal; if (SafeMult(&outputsize, state.framesize)) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } else if (outputsize > SDL_MAX_UINT32 || state.framestotal > SIZE_MAX) { return SDL_SetError("WAVE file too big"); } @@ -1073,14 +1073,14 @@ static int IMA_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len state.output.pos = 0; state.output.size = outputsize / sizeof(Sint16); state.output.data = (Sint16 *)SDL_malloc(outputsize); - if (state.output.data == NULL) { - return SDL_OutOfMemory(); + if (!state.output.data) { + return -1; } cstate = (Sint8 *)SDL_calloc(state.channels, sizeof(Sint8)); - if (cstate == NULL) { + if (!cstate) { SDL_free(state.output.data); - return SDL_OutOfMemory(); + return -1; } state.cstate = cstate; @@ -1221,20 +1221,20 @@ static int LAW_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len) sample_count = (size_t)file->sampleframes; if (SafeMult(&sample_count, format->channels)) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } expanded_len = sample_count; if (SafeMult(&expanded_len, sizeof(Sint16))) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } else if (expanded_len > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) { return SDL_SetError("WAVE file too big"); } /* 1 to avoid allocating zero bytes, to keep static analysis happy. */ src = (Uint8 *)SDL_realloc(chunk->data, expanded_len ? expanded_len : 1); - if (src == NULL) { - return SDL_OutOfMemory(); + if (!src) { + return -1; } chunk->data = NULL; chunk->size = 0; @@ -1352,20 +1352,20 @@ static int PCM_ConvertSint24ToSint32(WaveFile *file, Uint8 **audio_buf, Uint32 * sample_count = (size_t)file->sampleframes; if (SafeMult(&sample_count, format->channels)) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } expanded_len = sample_count; if (SafeMult(&expanded_len, sizeof(Sint32))) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } else if (expanded_len > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) { return SDL_SetError("WAVE file too big"); } /* 1 to avoid allocating zero bytes, to keep static analysis happy. */ ptr = (Uint8 *)SDL_realloc(chunk->data, expanded_len ? expanded_len : 1); - if (ptr == NULL) { - return SDL_OutOfMemory(); + if (!ptr) { + return -1; } /* This pointer is now invalid. */ @@ -1421,7 +1421,7 @@ static int PCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len) outputsize = (size_t)file->sampleframes; if (SafeMult(&outputsize, format->blockalign)) { - return SDL_OutOfMemory(); + return SDL_SetError("WAVE file too big"); } else if (outputsize > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) { return SDL_SetError("WAVE file too big"); } @@ -1440,7 +1440,7 @@ static WaveRiffSizeHint WaveGetRiffSizeHint(void) { const char *hint = SDL_GetHint(SDL_HINT_WAVE_RIFF_CHUNK_SIZE); - if (hint != NULL) { + if (hint) { if (SDL_strcmp(hint, "force") == 0) { return RiffSizeForce; } else if (SDL_strcmp(hint, "ignore") == 0) { @@ -1459,7 +1459,7 @@ static WaveTruncationHint WaveGetTruncationHint(void) { const char *hint = SDL_GetHint(SDL_HINT_WAVE_TRUNCATION); - if (hint != NULL) { + if (hint) { if (SDL_strcmp(hint, "verystrict") == 0) { return TruncVeryStrict; } else if (SDL_strcmp(hint, "strict") == 0) { @@ -1478,7 +1478,7 @@ static WaveFactChunkHint WaveGetFactChunkHint(void) { const char *hint = SDL_GetHint(SDL_HINT_WAVE_FACT_CHUNK); - if (hint != NULL) { + if (hint) { if (SDL_strcmp(hint, "truncate") == 0) { return FactTruncate; } else if (SDL_strcmp(hint, "strict") == 0) { @@ -1495,7 +1495,7 @@ static WaveFactChunkHint WaveGetFactChunkHint(void) static void WaveFreeChunkData(WaveChunk *chunk) { - if (chunk->data != NULL) { + if (chunk->data) { SDL_free(chunk->data); chunk->data = NULL; } @@ -1544,8 +1544,8 @@ static int WaveReadPartialChunkData(SDL_RWops *src, WaveChunk *chunk, size_t len if (length > 0) { chunk->data = (Uint8 *)SDL_malloc(length); - if (chunk->data == NULL) { - return SDL_OutOfMemory(); + if (!chunk->data) { + return -1; } if (SDL_RWseek(src, chunk->position, SDL_RW_SEEK_SET) != chunk->position) { @@ -1610,8 +1610,8 @@ static int WaveReadFormat(WaveFile *file) return SDL_SetError("Data of WAVE fmt chunk too big"); } fmtsrc = SDL_RWFromConstMem(chunk->data, (int)chunk->size); - if (fmtsrc == NULL) { - return SDL_OutOfMemory(); + if (!fmtsrc) { + return -1; } if (!SDL_ReadU16LE(fmtsrc, &format->formattag) || @@ -1788,7 +1788,7 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * SDL_zero(datachunk); envchunkcountlimit = SDL_getenv("SDL_WAVE_CHUNK_LIMIT"); - if (envchunkcountlimit != NULL) { + if (envchunkcountlimit) { unsigned int count; if (SDL_sscanf(envchunkcountlimit, "%u", &count) == 1) { chunkcountlimit = count <= SDL_MAX_UINT32 ? count : SDL_MAX_UINT32; @@ -2081,15 +2081,15 @@ int SDL_LoadWAV_RW(SDL_RWops *src, SDL_bool freesrc, SDL_AudioSpec *spec, Uint8 WaveFile file; /* Make sure we are passed a valid data source */ - if (src == NULL) { + if (!src) { goto done; /* Error may come from RWops. */ - } else if (spec == NULL) { + } else if (!spec) { SDL_InvalidParamError("spec"); goto done; - } else if (audio_buf == NULL) { + } else if (!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; - } else if (audio_len == NULL) { + } else if (!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } diff --git a/src/audio/SDL_wave.h b/src/audio/SDL_wave.h index f91a2803..66128f75 100644 --- a/src/audio/SDL_wave.h +++ b/src/audio/SDL_wave.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 7692f4e2..4445ba8c 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,7 +23,6 @@ #ifdef SDL_AUDIO_DRIVER_AAUDIO #include "../SDL_sysaudio.h" -#include "../SDL_audio_c.h" #include "SDL_aaudio.h" #include "../../core/android/SDL_android.h" @@ -37,10 +36,14 @@ struct SDL_PrivateAudioData { AAudioStream *stream; - Uint8 *mixbuf; // Raw mixing buffer + int num_buffers; + Uint8 *mixbuf; // Raw mixing buffer + size_t mixbuf_bytes; // num_buffers * device->buffer_size + size_t callback_bytes; + size_t processed_bytes; SDL_Semaphore *semaphore; SDL_AtomicInt error_callback_triggered; - int resume; // Resume device if it was paused automatically + SDL_bool resume; // Resume device if it was paused automatically }; // Debug @@ -79,54 +82,168 @@ static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_re LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); // You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. - // Just flag the device so we can kill it in WaitDevice/PlayDevice instead. + // Just flag the device so we can kill it in PlayDevice instead. SDL_AudioDevice *device = (SDL_AudioDevice *) userData; - SDL_AtomicSet(&device->hidden->error_callback_triggered, 1); + SDL_AtomicSet(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error. SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice. } -// due to the way the aaudio data callback works, PlayDevice is a no-op. The callback collects audio while SDL camps in WaitDevice and -// fires a semaphore that will unblock WaitDevice and start a new iteration, so when the callback runs again, WaitDevice is ready -// to hand it more data. static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) { SDL_AudioDevice *device = (SDL_AudioDevice *) userData; - SDL_assert(numFrames == device->sample_frames); + struct SDL_PrivateAudioData *hidden = device->hidden; + size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec); + size_t callback_bytes = numFrames * framesize; + size_t old_buffer_index = hidden->callback_bytes / device->buffer_size; + if (device->iscapture) { - SDL_memcpy(device->hidden->mixbuf, audioData, device->buffer_size); + const Uint8 *input = (const Uint8 *)audioData; + size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes); + size_t size = SDL_min(available_bytes, callback_bytes); + size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; + size_t end = (offset + size) % hidden->mixbuf_bytes; + SDL_assert(size <= hidden->mixbuf_bytes); + +//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize); + + if (offset <= end) { + SDL_memcpy(&hidden->mixbuf[offset], input, size); + } else { + size_t partial = (hidden->mixbuf_bytes - offset); + SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial); + SDL_memcpy(&hidden->mixbuf[0], &input[partial], end); + } + + SDL_MemoryBarrierRelease(); + hidden->callback_bytes += size; + + if (size < callback_bytes) { + LOGI("Audio recording overflow, dropped %zu frames\n", (callback_bytes - size) / framesize); + } } else { - SDL_memcpy(audioData, device->hidden->mixbuf, device->buffer_size); + Uint8 *output = (Uint8 *)audioData; + size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes); + size_t size = SDL_min(available_bytes, callback_bytes); + size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; + size_t end = (offset + size) % hidden->mixbuf_bytes; + SDL_assert(size <= hidden->mixbuf_bytes); + +//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize); + + SDL_MemoryBarrierAcquire(); + if (offset <= end) { + SDL_memcpy(output, &hidden->mixbuf[offset], size); + } else { + size_t partial = (hidden->mixbuf_bytes - offset); + SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial); + SDL_memcpy(&output[partial], &hidden->mixbuf[0], end); + } + hidden->callback_bytes += size; + + if (size < callback_bytes) { + LOGI("Audio playback underflow, missed %zu frames\n", (callback_bytes - size) / framesize); + SDL_memset(&output[size], device->silence_value, (callback_bytes - size)); + } } - SDL_PostSemaphore(device->hidden->semaphore); + + size_t new_buffer_index = hidden->callback_bytes / device->buffer_size; + while (old_buffer_index < new_buffer_index) { + // Trigger audio processing + SDL_PostSemaphore(hidden->semaphore); + ++old_buffer_index; + } + return AAUDIO_CALLBACK_RESULT_CONTINUE; } static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) { - return device->hidden->mixbuf; + struct SDL_PrivateAudioData *hidden = device->hidden; + size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); + return &hidden->mixbuf[offset]; } -static void AAUDIO_WaitDevice(SDL_AudioDevice *device) +static int AAUDIO_WaitDevice(SDL_AudioDevice *device) { SDL_WaitSemaphore(device->hidden->semaphore); + return 0; } -static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +static int BuildAAudioStream(SDL_AudioDevice *device); + +static int RecoverAAudioDevice(SDL_AudioDevice *device) { - // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. - if (SDL_AtomicGet(&device->hidden->error_callback_triggered)) { - SDL_AtomicSet(&device->hidden->error_callback_triggered, 0); - return -1; + struct SDL_PrivateAudioData *hidden = device->hidden; + + // attempt to build a new stream, in case there's a new default device. + ctx.AAudioStream_requestStop(hidden->stream); + ctx.AAudioStream_close(hidden->stream); + hidden->stream = NULL; + + SDL_aligned_free(hidden->mixbuf); + hidden->mixbuf = NULL; + + SDL_DestroySemaphore(hidden->semaphore); + hidden->semaphore = NULL; + + const int prev_sample_frames = device->sample_frames; + SDL_AudioSpec prevspec; + SDL_copyp(&prevspec, &device->spec); + + if (BuildAAudioStream(device) < 0) { + return -1; // oh well, we tried. + } + + // we don't know the new device spec until we open the new device, so we saved off the old one and force it back + // so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec. + const int new_sample_frames = device->sample_frames; + SDL_AudioSpec newspec; + SDL_copyp(&newspec, &device->spec); + + device->sample_frames = prev_sample_frames; + SDL_copyp(&device->spec, &prevspec); + if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) { + return -1; // ugh + } + return 0; +} + + +static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + struct SDL_PrivateAudioData *hidden = device->hidden; + + // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. + const aaudio_result_t err = (aaudio_result_t) SDL_AtomicGet(&hidden->error_callback_triggered); + if (err) { + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err)); + + if (RecoverAAudioDevice(device) < 0) { + return -1; // oh well, we went down hard. + } + } else { + SDL_MemoryBarrierRelease(); + hidden->processed_bytes += buflen; } return 0; } -// no need for a FlushCapture implementation, just don't read mixbuf until the next iteration. static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - const int cpy = SDL_min(buflen, device->buffer_size); - SDL_memcpy(buffer, device->hidden->mixbuf, cpy); - return cpy; + struct SDL_PrivateAudioData *hidden = device->hidden; + + // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. + if (SDL_AtomicGet(&hidden->error_callback_triggered)) { + SDL_AtomicSet(&hidden->error_callback_triggered, 0); + return -1; + } + + SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here + size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); + SDL_MemoryBarrierAcquire(); + SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen); + hidden->processed_bytes += buflen; + return buflen; } static void AAUDIO_CloseDevice(SDL_AudioDevice *device) @@ -146,34 +263,18 @@ static void AAUDIO_CloseDevice(SDL_AudioDevice *device) SDL_DestroySemaphore(hidden->semaphore); } - SDL_free(hidden->mixbuf); + SDL_aligned_free(hidden->mixbuf); SDL_free(hidden); device->hidden = NULL; } } -static int AAUDIO_OpenDevice(SDL_AudioDevice *device) +static int BuildAAudioStream(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *hidden; + struct SDL_PrivateAudioData *hidden = device->hidden; const SDL_bool iscapture = device->iscapture; aaudio_result_t res; - SDL_assert(device->handle != NULL); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. - - LOGI(__func__); - - if (iscapture) { - if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) { - LOGI("This app doesn't have RECORD_AUDIO permission"); - return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); - } - } - - hidden = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (hidden == NULL) { - return SDL_OutOfMemory(); - } - SDL_AtomicSet(&hidden->error_callback_triggered, 0); AAudioStreamBuilder *builder = NULL; @@ -181,23 +282,19 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) if (res != AAUDIO_OK) { LOGI("SDL Failed AAudio_createStreamBuilder %d", res); return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res); - } else if (builder == NULL) { + } else if (!builder) { LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL"); } - // !!! FIXME: call AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); ? - - ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); - ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); - +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES const int aaudio_device_id = (int) ((size_t) device->handle); LOGI("Opening device id %d", aaudio_device_id); ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id); +#endif - const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); - ctx.AAudioStreamBuilder_setDirection(builder, direction); aaudio_format_t format; +#ifdef SET_AUDIO_FORMAT if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) { format = AAUDIO_FORMAT_PCM_I32; } else if (device->spec.format == SDL_AUDIO_F32) { @@ -205,41 +302,38 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) } else { format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else. } - ctx.AAudioStreamBuilder_setFormat(builder, format); + ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); + ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); +#endif + + const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); + ctx.AAudioStreamBuilder_setDirection(builder, direction); ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device); ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device); - ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + // Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people + if (SDL_GetHintBoolean("SDL_ANDROID_LOW_LATENCY_AUDIO", SDL_TRUE)) { + ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + } LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames); res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); - if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); ctx.AAudioStreamBuilder_delete(builder); return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); } - - device->sample_frames = (int) ctx.AAudioStream_getFramesPerDataCallback(hidden->stream); - if (device->sample_frames == AAUDIO_UNSPECIFIED) { - // if this happens, figure out a reasonable sample frame count, tear down this stream and force it in a new stream. - device->sample_frames = (int) (ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 4); - LOGI("AAUDIO: Got a stream with unspecified sample frames per data callback! Retrying with %d frames...", device->sample_frames); - ctx.AAudioStream_close(hidden->stream); - ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); - res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); - if (res != AAUDIO_OK) { // oh well, we tried. - LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); - ctx.AAudioStreamBuilder_delete(builder); - return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } - } - ctx.AAudioStreamBuilder_delete(builder); + device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream); + if (device->sample_frames == AAUDIO_UNSPECIFIED) { + // We'll get variable frames in the callback, make sure we have at least half a buffer available + device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2; + } + device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream); device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream); @@ -254,25 +348,28 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format); } - LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u", - device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), - device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames); - SDL_UpdatedAudioDeviceFormat(device); - // Allocate mixing buffer - hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + // Allocate a double buffered mixing buffer + hidden->num_buffers = 2; + hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size); + hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), hidden->mixbuf_bytes); + if (!hidden->mixbuf) { + return -1; } - SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size); + hidden->processed_bytes = 0; + hidden->callback_bytes = 0; - hidden->semaphore = SDL_CreateSemaphore(0); + hidden->semaphore = SDL_CreateSemaphore(iscapture ? 0 : hidden->num_buffers); if (!hidden->semaphore) { LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture); return -1; } + LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u, buffers %d", + device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), + device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames, hidden->num_buffers); + res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture); @@ -280,13 +377,37 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) } LOGI("SDL AAudioStream_requestStart OK"); + return 0; } +static int AAUDIO_OpenDevice(SDL_AudioDevice *device) +{ +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. +#endif + + LOGI(__func__); + + if (device->iscapture) { + if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) { + LOGI("This app doesn't have RECORD_AUDIO permission"); + return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); + } + } + + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (!device->hidden) { + return -1; + } + + return BuildAAudioStream(device); +} + static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) { struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden; - if (hidden != NULL) { + if (hidden) { if (hidden->stream) { aaudio_result_t res; @@ -312,7 +433,7 @@ static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) // Pause (block) all non already paused audio devices by taking their mixer lock void AAUDIO_PauseDevices(void) { - if (ctx.handle != NULL) { // AAUDIO driver is used? + if (ctx.handle) { // AAUDIO driver is used? (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL); } } @@ -321,7 +442,7 @@ void AAUDIO_PauseDevices(void) static SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) { struct SDL_PrivateAudioData *hidden = device->hidden; - if (hidden != NULL) { + if (hidden) { if (hidden->resume) { hidden->resume = SDL_FALSE; SDL_UnlockMutex(device->lock); @@ -340,42 +461,11 @@ static SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) void AAUDIO_ResumeDevices(void) { - if (ctx.handle != NULL) { // AAUDIO driver is used? + if (ctx.handle) { // AAUDIO driver is used? (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL); } } -// !!! FIXME: do we need this now that we use the callback? -/* - We can sometimes get into a state where AAudioStream_write() will just block forever until we pause and unpause. - None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called. - But, AAudioStream_getTimestamp() does return AAUDIO_ERROR_INVALID_STATE -*/ -static SDL_bool DetectBrokenPlayStatePerDevice(SDL_AudioDevice *device, void *userdata) -{ - SDL_assert(device != NULL); - if (!device->iscapture && device->hidden != NULL) { - struct SDL_PrivateAudioData *hidden = device->hidden; - int64_t framePosition, timeNanoseconds; - aaudio_result_t res = ctx.AAudioStream_getTimestamp(hidden->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds); - if (res == AAUDIO_ERROR_INVALID_STATE) { - aaudio_stream_state_t currentState = ctx.AAudioStream_getState(hidden->stream); - // AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. - if (currentState == AAUDIO_STREAM_STATE_STARTED) { - LOGI("SDL AAUDIO_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); - return SDL_TRUE; // this guy. - } - } - } - - return SDL_FALSE; // enumerate more devices. -} - -SDL_bool AAUDIO_DetectBrokenPlayState(void) -{ - return (ctx.handle && SDL_FindPhysicalAudioDeviceByCallback(DetectBrokenPlayStatePerDevice, NULL) != NULL) ? SDL_TRUE : SDL_FALSE; -} - static void AAUDIO_Deinitialize(void) { Android_StopAudioHotplug(); @@ -405,7 +495,7 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) SDL_zero(ctx); ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); - if (ctx.handle == NULL) { + if (!ctx.handle) { LOGI("SDL couldn't find " LIB_AAUDIO_SO); return SDL_FALSE; } @@ -417,7 +507,6 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) } impl->ThreadInit = Android_AudioThreadInit; - impl->DetectDevices = Android_StartAudioHotplug; impl->Deinitialize = AAUDIO_Deinitialize; impl->OpenDevice = AAUDIO_OpenDevice; impl->CloseDevice = AAUDIO_CloseDevice; @@ -429,6 +518,13 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) impl->HasCaptureSupport = SDL_TRUE; +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + impl->DetectDevices = Android_StartAudioHotplug; +#else + impl->OnlyHasDefaultOutputDevice = SDL_TRUE; + impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; +#endif + LOGI("SDL AAUDIO_Init OK"); return SDL_TRUE; } diff --git a/src/audio/aaudio/SDL_aaudio.h b/src/audio/aaudio/SDL_aaudio.h index 137dd815..3354304a 100644 --- a/src/audio/aaudio/SDL_aaudio.h +++ b/src/audio/aaudio/SDL_aaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,14 +27,12 @@ void AAUDIO_ResumeDevices(void); void AAUDIO_PauseDevices(void); -SDL_bool AAUDIO_DetectBrokenPlayState(void); #else #define AAUDIO_ResumeDevices() #define AAUDIO_PauseDevices() -#define AAUDIO_DetectBrokenPlayState() (SDL_FALSE) #endif -#endif /* SDL_aaudio_h_ */ +#endif // SDL_aaudio_h_ diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index febbb89d..59771b38 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright , (C) 1997-2023 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 @@ -33,18 +33,18 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) /* API 28 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) /* API 28 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) /* API 28 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) /* API 29 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) /* API 28 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) /* API 30 */ +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29 +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28 +SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30 SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData)) SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData)) SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream)) SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder)) -SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) /* API 30 */ +SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30 SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream)) SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream)) SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream)) @@ -70,13 +70,13 @@ SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAu SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream)) SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream)) SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream)) -SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) /* API 28 */ +SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28 SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds)) -SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) /* API 28 */ -SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) /* API 28 */ -SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) /* API 28 */ -SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) /* API 29 */ -SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) /* API 30 */ +SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28 +SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28 +SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28 +SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29 +SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30 #undef SDL_PROC #undef SDL_PROC_UNUSED diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index a8b9abfa..c0f696b9 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,26 +20,24 @@ */ #include "SDL_internal.h" -// !!! FIXME: Clean out the fprintf and printf calls, replace with SDL_Log - #ifdef SDL_AUDIO_DRIVER_ALSA #ifndef SDL_ALSA_NON_BLOCKING #define SDL_ALSA_NON_BLOCKING 0 #endif -/* without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay. */ +// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay. #ifndef SDL_ALSA_HOTPLUG_THREAD #define SDL_ALSA_HOTPLUG_THREAD 1 #endif -/* Allow access to a raw mixing buffer */ +// Allow access to a raw mixing buffer #include -#include /* For kill() */ +#include // For kill() #include -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_alsa_audio.h" #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC @@ -98,15 +96,15 @@ static void *alsa_handle = NULL; static int load_alsa_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(alsa_handle, fn); - if (*addr == NULL) { - /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ + if (!*addr) { + // Don't call SDL_SetError(): SDL_LoadFunction already did. return 0; } return 1; } -/* cast funcs to char* first, to please GCC's strict aliasing rules. */ +// cast funcs to char* first, to please GCC's strict aliasing rules. #define SDL_ALSA_SYM(x) \ if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \ return -1 @@ -167,7 +165,7 @@ static int load_alsa_syms(void) static void UnloadALSALibrary(void) { - if (alsa_handle != NULL) { + if (alsa_handle) { SDL_UnloadObject(alsa_handle); alsa_handle = NULL; } @@ -176,11 +174,11 @@ static void UnloadALSALibrary(void) static int LoadALSALibrary(void) { int retval = 0; - if (alsa_handle == NULL) { + if (!alsa_handle) { alsa_handle = SDL_LoadObject(alsa_library); - if (alsa_handle == NULL) { + if (!alsa_handle) { retval = -1; - /* Don't call SDL_SetError(): SDL_LoadObject already did. */ + // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_alsa_syms(); if (retval < 0) { @@ -203,15 +201,35 @@ static int LoadALSALibrary(void) return 0; } -#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */ +#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC + +typedef struct ALSA_Device +{ + char *name; + SDL_bool iscapture; + struct ALSA_Device *next; +} ALSA_Device; + +static const ALSA_Device default_output_handle = { + "default", + SDL_FALSE, + NULL +}; + +static const ALSA_Device default_capture_handle = { + "default", + SDL_TRUE, + NULL +}; static const char *get_audio_device(void *handle, const int channels) { SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3. - if (SDL_strcmp((const char *) handle, "default") == 0) { - const char *device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */ - if (device != NULL) { + ALSA_Device *dev = (ALSA_Device *)handle; + if (SDL_strcmp(dev->name, "default") == 0) { + const char *device = SDL_getenv("AUDIODEV"); // Is there a standard variable name? + if (device) { return device; } else if (channels == 6) { return "plug:surround51"; @@ -221,15 +239,14 @@ static const char *get_audio_device(void *handle, const int channels) return "default"; } - return (const char *)handle; + return dev->name; } -/* !!! FIXME: is there a channel swizzler in alsalib instead? */ -/* - * https://bugzilla.libsdl.org/show_bug.cgi?id=110 - * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE - * and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR" - */ +// !!! FIXME: is there a channel swizzler in alsalib instead? + +// https://bugzilla.libsdl.org/show_bug.cgi?id=110 +// "For Linux ALSA, this is FL-FR-RL-RR-C-LFE +// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR" #define SWIZ6(T) \ static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) \ { \ @@ -246,13 +263,13 @@ static const char *get_audio_device(void *handle, const int channels) } \ } -/* !!! FIXME: is there a channel swizzler in alsalib instead? */ -/* !!! FIXME: this screams for a SIMD shuffle operation. */ -/* - * https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations - * For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR - * and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR" - */ + +// !!! FIXME: is there a channel swizzler in alsalib instead? +// !!! FIXME: this screams for a SIMD shuffle operation. + +// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations +// For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR +// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR" #define SWIZ8(T) \ static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) \ { \ @@ -287,10 +304,8 @@ CHANNEL_SWIZZLE(SWIZ8) #undef SWIZ6 #undef SWIZ8 -/* - * Called right before feeding device->hidden->mixbuf to the hardware. Swizzle - * channels from Windows/Mac order to the format alsalib will want. - */ +// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle +// channels from Windows/Mac order to the format alsalib will want. static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) { switch (device->spec.channels) { @@ -324,70 +339,66 @@ static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 } #ifdef SND_CHMAP_API_VERSION -/* Some devices have the right channel map, no swizzling necessary */ +// Some devices have the right channel map, no swizzling necessary static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) { } -#endif /* SND_CHMAP_API_VERSION */ +#endif // SND_CHMAP_API_VERSION -/* This function waits until it is possible to write a full sound buffer */ -static void ALSA_WaitDevice(SDL_AudioDevice *device) +// This function waits until it is possible to write a full sound buffer +static int ALSA_WaitDevice(SDL_AudioDevice *device) { - const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->sample_frames; + const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq); + const int delay = SDL_max(fulldelay, 10); + while (!SDL_AtomicGet(&device->shutdown)) { - const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); - if ((rc < 0) && (rc != -EAGAIN)) { - /* Hmm, not much we can do - abort */ - fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n", - ALSA_snd_strerror(rc)); - SDL_AudioDeviceDisconnected(device); - return; - } else if (rc < needed) { - const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq; - SDL_Delay(SDL_max(delay, 10)); - } else { - break; /* ready to go! */ + const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay); + if (rc < 0 && (rc != -EAGAIN)) { + const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0); + if (status < 0) { + // Hmm, not much we can do - abort + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); + return -1; + } + continue; } + + if (rc > 0) { + break; // ready to go! + } + + // Timed out! Make sure we aren't shutting down and then wait again. } + + return 0; } static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { SDL_assert(buffer == device->hidden->mixbuf); - Uint8 *sample_buf = device->hidden->mixbuf; + Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec); snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size); device->hidden->swizzle_func(device, sample_buf, frames_left); while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { - int status = ALSA_snd_pcm_writei(device->hidden->pcm_handle, - sample_buf, frames_left); - - if (status < 0) { - if (status == -EAGAIN) { - /* Apparently snd_pcm_recover() doesn't handle this case - - does it assume snd_pcm_wait() above? */ - SDL_Delay(1); - continue; - } - status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, status, 0); + const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left); + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size)); + 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_handle, rc, 0); if (status < 0) { - /* Hmm, not much we can do - abort */ - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, - "ALSA write failed (unrecoverable): %s", - ALSA_snd_strerror(status)); + // Hmm, not much we can do - abort + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc)); return -1; } continue; - } else if (status == 0) { - /* No frames were written (no available space in pcm device). - Allow other threads to catch up. */ - SDL_Delay((frames_left / 2 * 1000) / device->spec.freq); } - sample_buf += status * frame_size; - frames_left -= status; + sample_buf += rc * frame_size; + frames_left -= rc; } return 0; @@ -395,45 +406,54 @@ static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { + snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); + if (rc <= 0) { + // Wait a bit and try again, maybe the hardware isn't quite ready yet? + SDL_Delay(1); + + rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); + if (rc <= 0) { + // We'll catch it next time + *buffer_size = 0; + return NULL; + } + } + + const int requested_frames = SDL_min(device->sample_frames, rc); + const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec); + SDL_assert(requested_bytes <= *buffer_size); + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes); + *buffer_size = requested_bytes; return device->hidden->mixbuf; } static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - Uint8 *sample_buf = (Uint8 *)buffer; const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec); - const int total_frames = buflen / frame_size; - snd_pcm_uframes_t frames_left = total_frames; - SDL_assert((buflen % frame_size) == 0); - while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { - int status = ALSA_snd_pcm_readi(device->hidden->pcm_handle, - sample_buf, frames_left); + const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm_handle); + const int total_frames = SDL_min(buflen / frame_size, total_available); - if (status == -EAGAIN) { - break; // Can this even happen? Go back to WaitCaptureDevice, where the device lock isn't held. - } else if (status < 0) { - /*printf("ALSA: capture error %d\n", status);*/ - status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, status, 0); - if (status < 0) { - /* Hmm, not much we can do - abort */ - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, - "ALSA read failed (unrecoverable): %s\n", - ALSA_snd_strerror(status)); - return -1; - } - break; // Go back to WaitCaptureDevice, where the device lock isn't held. + const int rc = ALSA_snd_pcm_readi(device->hidden->pcm_handle, buffer, total_frames); + + 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_handle, rc, 0); + 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)); + return -1; } - - /*printf("ALSA: captured %d bytes\n", status * frame_size);*/ - sample_buf += status * frame_size; - frames_left -= status; + return 0; // go back to WaitDevice and try again. + } else if (rc > 0) { + device->hidden->swizzle_func(device, buffer, total_frames - rc); } - device->hidden->swizzle_func(device, buffer, total_frames - frames_left); + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: captured %d bytes", rc * frame_size); - return (total_frames - frames_left) * frame_size; + return rc * frame_size; } static void ALSA_FlushCapture(SDL_AudioDevice *device) @@ -445,9 +465,7 @@ static void ALSA_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { if (device->hidden->pcm_handle) { - /* Wait for the submitted audio to drain - ALSA_snd_pcm_drop() can hang, so don't use that. - */ + // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that. SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2); ALSA_snd_pcm_close(device->hidden->pcm_handle); } @@ -463,11 +481,11 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa snd_pcm_uframes_t persize; unsigned int periods; - /* Copy the hardware parameters for this setup */ + // Copy the hardware parameters for this setup snd_pcm_hw_params_alloca(&hwparams); ALSA_snd_pcm_hw_params_copy(hwparams, params); - /* Attempt to match the period size to the requested buffer size */ + // Attempt to match the period size to the requested buffer size persize = device->sample_frames; status = ALSA_snd_pcm_hw_params_set_period_size_near( device->hidden->pcm_handle, hwparams, &persize, NULL); @@ -475,7 +493,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa return -1; } - /* Need to at least double buffer */ + // Need to at least double buffer periods = 2; status = ALSA_snd_pcm_hw_params_set_periods_min( device->hidden->pcm_handle, hwparams, &periods, NULL); @@ -489,7 +507,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa return -1; } - /* "set" the hardware with the desired parameters */ + // "set" the hardware with the desired parameters status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams); if (status < 0) { return -1; @@ -497,14 +515,14 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa device->sample_frames = persize; - /* This is useful for debugging */ + // This is useful for debugging if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) { snd_pcm_uframes_t bufsize; ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize); SDL_LogError(SDL_LOG_CATEGORY_AUDIO, - "ALSA: period size = %ld, periods = %u, buffer size = %lu\n", + "ALSA: period size = %ld, periods = %u, buffer size = %lu", persize, periods, bufsize); } @@ -516,14 +534,14 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) const SDL_bool iscapture = device->iscapture; int status = 0; - /* Initialize all variables that we clean on shutdown */ + // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } - /* Open the audio device */ - /* Name of device should depend on # channels in spec */ + // Open the audio device + // Name of device should depend on # channels in spec snd_pcm_t *pcm_handle = NULL; status = ALSA_snd_pcm_open(&pcm_handle, get_audio_device(device->handle, device->spec.channels), @@ -536,7 +554,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) device->hidden->pcm_handle = pcm_handle; - /* Figure out what the hardware is capable of */ + // Figure out what the hardware is capable of snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_hw_params_alloca(&hwparams); status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams); @@ -544,14 +562,14 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status)); } - /* SDL only uses interleaved sample output */ + // SDL only uses interleaved sample output status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); if (status < 0) { return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status)); } - /* Try for a closest match on audio format */ + // Try for a closest match on audio format snd_pcm_format_t format = 0; const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; @@ -593,9 +611,8 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) } device->spec.format = test_format; - /* Validate number of channels and determine if swizzling is necessary - * Assume original swizzling, until proven otherwise. - */ + // Validate number of channels and determine if swizzling is necessary. + // Assume original swizzling, until proven otherwise. device->hidden->swizzle_func = swizzle_alsa_channels; #ifdef SND_CHMAP_API_VERSION snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle); @@ -607,11 +624,11 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) device->hidden->swizzle_func = no_swizzle; } } - free(chmap); /* This should NOT be SDL_free() */ + free(chmap); // This should NOT be SDL_free() } -#endif /* SND_CHMAP_API_VERSION */ +#endif // SND_CHMAP_API_VERSION - /* Set the number of channels */ + // Set the number of channels status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, device->spec.channels); unsigned int channels = device->spec.channels; @@ -623,7 +640,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) device->spec.channels = channels; } - /* Set the audio rate */ + // Set the audio rate unsigned int rate = device->spec.freq; status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate, NULL); @@ -632,13 +649,13 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) } device->spec.freq = rate; - /* Set the buffer size, in samples */ + // Set the buffer size, in samples status = ALSA_set_buffer_size(device, hwparams); if (status < 0) { return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); } - /* Set the software parameters */ + // Set the software parameters snd_pcm_sw_params_t *swparams = NULL; snd_pcm_sw_params_alloca(&swparams); status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams); @@ -665,8 +682,8 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) // Allocate mixing buffer if (!iscapture) { device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } @@ -679,35 +696,26 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) ALSA_snd_pcm_start(pcm_handle); - /* We're ready to rock and roll. :-) */ - return 0; + return 0; // We're ready to rock and roll. :-) } -typedef struct ALSA_Device -{ - char *name; - SDL_bool iscapture; - struct ALSA_Device *next; -} ALSA_Device; - static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen) { ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device)); char *desc; - char *handle = NULL; char *ptr; - if (dev == NULL) { + if (!dev) { return; } - /* Not all alsa devices are enumerable via snd_device_name_get_hint - (i.e. bluetooth devices). Therefore if hint is passed in to this - function as NULL, assume name contains desc. - Make sure not to free the storage associated with desc in this case */ + // Not all alsa devices are enumerable via snd_device_name_get_hint + // (i.e. bluetooth devices). Therefore if hint is passed in to this + // function as NULL, assume name contains desc. + // Make sure not to free the storage associated with desc in this case if (hint) { desc = ALSA_snd_device_name_get_hint(hint, "DESC"); - if (desc == NULL) { + if (!desc) { SDL_free(dev); return; } @@ -717,34 +725,34 @@ static void add_device(const SDL_bool iscapture, const char *name, void *hint, A SDL_assert(name != NULL); - /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output". - just chop the extra lines off, this seems to get a reasonable device - name without extra details. */ + // some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output". + // just chop the extra lines off, this seems to get a reasonable device + // name without extra details. ptr = SDL_strchr(desc, '\n'); - if (ptr != NULL) { + if (ptr) { *ptr = '\0'; } - /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/ + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", iscapture ? "capture" : "output", name, desc); - handle = SDL_strdup(name); - if (handle == NULL) { + dev->name = SDL_strdup(name); + if (!dev->name) { if (hint) { - free(desc); /* This should NOT be SDL_free() */ + free(desc); // This should NOT be SDL_free() } + SDL_free(dev->name); SDL_free(dev); return; } - /* Note that spec is NULL, because we are required to open the device before - * acquiring the mix format, making this information inaccessible at - * enumeration time - */ - SDL_AddAudioDevice(iscapture, desc, NULL, handle); + // Note that spec is NULL, because we are required to open the device before + // acquiring the mix format, making this information inaccessible at + // enumeration time + SDL_AddAudioDevice(iscapture, desc, NULL, dev); if (hint) { - free(desc); /* This should NOT be SDL_free() */ + free(desc); // This should NOT be SDL_free() } - dev->name = handle; + dev->iscapture = iscapture; dev->next = *pSeen; *pSeen = dev; @@ -770,13 +778,13 @@ static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_de unseen = hotplug_devices; seen = NULL; - /* Apparently there are several different ways that ALSA lists - actual hardware. It could be prefixed with "hw:" or "default:" - or "sysdefault:" and maybe others. Go through the list and see - if we can find a preferred prefix for the system. */ + // Apparently there are several different ways that ALSA lists + // actual hardware. It could be prefixed with "hw:" or "default:" + // or "sysdefault:" and maybe others. Go through the list and see + // if we can find a preferred prefix for the system. for (int i = 0; hints[i]; i++) { char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); - if (name == NULL) { + if (!name) { continue; } @@ -797,31 +805,31 @@ static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_de } } - free(name); /* This should NOT be SDL_free() */ + free(name); // This should NOT be SDL_free() } } - /* look through the list of device names to find matches */ + // look through the list of device names to find matches if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...? for (int i = 0; hints[i]; i++) { char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); - if (name == NULL) { + if (!name) { continue; } // only want physical hardware interfaces - const SDL_bool is_default = (has_default == i) ? SDL_TRUE : SDL_FALSE; - if (is_default || (match != NULL && SDL_strncmp(name, match, match_len) == 0)) { + const SDL_bool is_default = (has_default == i); + if (is_default || (match && SDL_strncmp(name, match, match_len) == 0)) { char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID"); - const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0); - const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0); + const SDL_bool isoutput = (!ioid) || (SDL_strcmp(ioid, "Output") == 0); + const SDL_bool isinput = (!ioid) || (SDL_strcmp(ioid, "Input") == 0); SDL_bool have_output = SDL_FALSE; SDL_bool have_input = SDL_FALSE; - free(ioid); + free(ioid); // This should NOT be SDL_free() if (!isoutput && !isinput) { - free(name); + free(name); // This should NOT be SDL_free() continue; } @@ -831,7 +839,7 @@ static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_de } else if (has_default_capture && isinput) { *has_default_capture = SDL_TRUE; } - free(name); + free(name); // This should NOT be SDL_free() continue; } @@ -866,20 +874,20 @@ static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_de } } - free(name); /* This should NOT be SDL_free() */ + free(name); // This should NOT be SDL_free() } } ALSA_snd_device_name_free_hint(hints); - hotplug_devices = seen; /* now we have a known-good list of attached devices. */ + hotplug_devices = seen; // now we have a known-good list of attached devices. - /* report anything still in unseen as removed. */ + // report anything still in unseen as removed. ALSA_Device *next = NULL; for (ALSA_Device *dev = unseen; dev; dev = next) { - /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/ + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name); next = dev->next; - SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev->name)); + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev)); SDL_free(dev->name); SDL_free(dev); } @@ -895,13 +903,13 @@ static int SDLCALL ALSA_HotplugThread(void *arg) SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) { - /* Block awhile before checking again, unless we're told to stop. */ + // Block awhile before checking again, unless we're told to stop. const Uint64 ticks = SDL_GetTicks() + 5000; while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && SDL_GetTicks() < ticks) { SDL_Delay(100); } - ALSA_HotplugIteration(NULL, NULL); /* run the check. */ + ALSA_HotplugIteration(NULL, NULL); // run the check. } return 0; @@ -913,43 +921,46 @@ static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default // device here. It's the best we can do at this level. SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE; - ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check. */ + ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check. if (has_default_output) { - *default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, SDL_strdup("default")); + *default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, (void*)&default_output_handle); } if (has_default_capture) { - *default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, SDL_strdup("default")); + *default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, (void*)&default_capture_handle); } #if SDL_ALSA_HOTPLUG_THREAD SDL_AtomicSet(&ALSA_hotplug_shutdown, 0); ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL); - /* if the thread doesn't spin, oh well, you just don't get further hotplug events. */ + // if the thread doesn't spin, oh well, you just don't get further hotplug events. #endif } -static void ALSA_Deinitialize(void) +static void ALSA_DeinitializeStart(void) { ALSA_Device *dev; ALSA_Device *next; #if SDL_ALSA_HOTPLUG_THREAD - if (ALSA_hotplug_thread != NULL) { + if (ALSA_hotplug_thread) { SDL_AtomicSet(&ALSA_hotplug_shutdown, 1); SDL_WaitThread(ALSA_hotplug_thread, NULL); ALSA_hotplug_thread = NULL; } #endif - /* Shutting down! Clean up any data we've gathered. */ + // Shutting down! Clean up any data we've gathered. for (dev = hotplug_devices; dev; dev = next) { - /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/ + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name); next = dev->next; SDL_free(dev->name); SDL_free(dev); } hotplug_devices = NULL; +} +static void ALSA_Deinitialize(void) +{ UnloadALSALibrary(); } @@ -959,13 +970,13 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl) return SDL_FALSE; } - /* Set the function pointers */ impl->DetectDevices = ALSA_DetectDevices; impl->OpenDevice = ALSA_OpenDevice; impl->WaitDevice = ALSA_WaitDevice; impl->GetDeviceBuf = ALSA_GetDeviceBuf; impl->PlayDevice = ALSA_PlayDevice; impl->CloseDevice = ALSA_CloseDevice; + impl->DeinitializeStart = ALSA_DeinitializeStart; impl->Deinitialize = ALSA_Deinitialize; impl->WaitCaptureDevice = ALSA_WaitDevice; impl->CaptureFromDevice = ALSA_CaptureFromDevice; @@ -973,11 +984,11 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl) impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap ALSA_bootstrap = { "alsa", "ALSA PCM audio", ALSA_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_ALSA */ +#endif // SDL_AUDIO_DRIVER_ALSA diff --git a/src/audio/alsa/SDL_alsa_audio.h b/src/audio/alsa/SDL_alsa_audio.h index ef7ce1bb..1209fe2d 100644 --- a/src/audio/alsa/SDL_alsa_audio.h +++ b/src/audio/alsa/SDL_alsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,14 +29,14 @@ struct SDL_PrivateAudioData { - /* The audio device handle */ + // The audio device handle snd_pcm_t *pcm_handle; - /* Raw mixing buffer */ + // Raw mixing buffer Uint8 *mixbuf; - /* swizzle function */ + // swizzle function void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen); }; -#endif /* SDL_ALSA_audio_h_ */ +#endif // SDL_ALSA_audio_h_ diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c index c0dade8a..0c8713ee 100644 --- a/src/audio/android/SDL_androidaudio.c +++ b/src/audio/android/SDL_androidaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,6 @@ // Output audio to Android (legacy interface) #include "../SDL_sysaudio.h" -#include "../SDL_audio_c.h" #include "SDL_androidaudio.h" #include "../../core/android/SDL_android.h" @@ -43,8 +42,8 @@ static SDL_AudioDevice *captureDevice = NULL; static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device) { device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } const SDL_bool iscapture = device->iscapture; @@ -132,13 +131,13 @@ void ANDROIDAUDIO_PauseDevices(void) { // TODO: Handle multiple devices? struct SDL_PrivateAudioData *hidden; - if (audioDevice != NULL && audioDevice->hidden != NULL) { + if (audioDevice && audioDevice->hidden) { hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden; SDL_LockMutex(audioDevice->lock); hidden->resume = SDL_TRUE; } - if (captureDevice != NULL && captureDevice->hidden != NULL) { + if (captureDevice && captureDevice->hidden) { hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden; SDL_LockMutex(captureDevice->lock); hidden->resume = SDL_TRUE; @@ -150,7 +149,7 @@ void ANDROIDAUDIO_ResumeDevices(void) { // TODO: Handle multiple devices? struct SDL_PrivateAudioData *hidden; - if (audioDevice != NULL && audioDevice->hidden != NULL) { + if (audioDevice && audioDevice->hidden) { hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden; if (hidden->resume) { hidden->resume = SDL_FALSE; @@ -158,7 +157,7 @@ void ANDROIDAUDIO_ResumeDevices(void) } } - if (captureDevice != NULL && captureDevice->hidden != NULL) { + if (captureDevice && captureDevice->hidden) { hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden; if (hidden->resume) { hidden->resume = SDL_FALSE; @@ -172,7 +171,7 @@ static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl) // !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+. impl->ThreadInit = Android_AudioThreadInit; impl->DetectDevices = Android_StartAudioHotplug; - impl->Deinitialize = Android_StopAudioHotplug; + impl->DeinitializeStart = Android_StopAudioHotplug; impl->OpenDevice = ANDROIDAUDIO_OpenDevice; impl->PlayDevice = ANDROIDAUDIO_PlayDevice; impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf; diff --git a/src/audio/android/SDL_androidaudio.h b/src/audio/android/SDL_androidaudio.h index 75995112..ba00e615 100644 --- a/src/audio/android/SDL_androidaudio.h +++ b/src/audio/android/SDL_androidaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,4 +35,4 @@ static void ANDROIDAUDIO_PauseDevices(void) {} #endif -#endif /* SDL_androidaudio_h_ */ +#endif // SDL_androidaudio_h_ diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h index 4b7f81bf..ffa221fe 100644 --- a/src/audio/coreaudio/SDL_coreaudio.h +++ b/src/audio/coreaudio/SDL_coreaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -39,7 +39,7 @@ #include #include -/* Things named "Master" were renamed to "Main" in macOS 12.0's SDK. */ +// Things named "Master" were renamed to "Main" in macOS 12.0's SDK. #ifdef MACOSX_COREAUDIO #include #ifndef MAC_OS_VERSION_12_0 @@ -65,4 +65,4 @@ struct SDL_PrivateAudioData #endif }; -#endif /* SDL_coreaudio_h_ */ +#endif // SDL_coreaudio_h_ diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index bbe37161..79fc65aa 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,6 @@ #ifdef SDL_AUDIO_DRIVER_COREAUDIO -#include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include "SDL_coreaudio.h" #include "../../thread/SDL_systhread.h" @@ -79,9 +78,9 @@ static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, co SDL_bool dead = SDL_FALSE; if (error == kAudioHardwareBadDeviceError) { - dead = SDL_TRUE; /* device was unplugged. */ + dead = SDL_TRUE; // device was unplugged. } else if ((error == kAudioHardwareNoError) && (!alive)) { - dead = SDL_TRUE; /* device died in some other way. */ + dead = SDL_TRUE; // device died in some other way. } if (dead) { @@ -186,7 +185,7 @@ static void RefreshPhysicalDevices(void) CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8); char *name = (char *)SDL_malloc(len + 1); - SDL_bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))) ? SDL_TRUE : SDL_FALSE; + SDL_bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))); CFRelease(cfstr); @@ -232,7 +231,7 @@ static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 nu static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) { AudioDeviceID devid; - Uint32 size = sizeof(devid); + UInt32 size = sizeof(devid); if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid))); } @@ -263,7 +262,7 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); - /* Get the Device ID */ + // Get the Device ID UInt32 size; AudioDeviceID devid; @@ -428,7 +427,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for Apple TV but is still needed in order to output to Bluetooth devices. */ - options |= 0x4; /* AVAudioSessionCategoryOptionAllowBluetooth; */ + options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth; } if (category == AVAudioSessionCategoryPlayAndRecord) { options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP | @@ -441,7 +440,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) { if (![session.category isEqualToString:category] || session.categoryOptions != options) { - /* Stop the current session so we don't interrupt other application audio */ + // Stop the current session so we don't interrupt other application audio PauseAudioDevices(); [session setActive:NO error:nil]; session_active = SDL_FALSE; @@ -454,7 +453,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b } } else { if (![session.category isEqualToString:category]) { - /* Stop the current session so we don't interrupt other application audio */ + // Stop the current session so we don't interrupt other application audio PauseAudioDevices(); [session setActive:NO error:nil]; session_active = SDL_FALSE; @@ -498,7 +497,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b /* An interruption end notification is not guaranteed to be sent if we were previously interrupted... resuming if needed when the app becomes active seems to be the way to go. */ - // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. johna + // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. [center addObserver:listener selector:@selector(applicationBecameActive:) name:UIApplicationDidBecomeActiveNotification @@ -717,7 +716,7 @@ static int PrepareAudioQueue(SDL_AudioDevice *device) SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct. - /* Set the channel layout for the audio queue */ + // Set the channel layout for the audio queue AudioChannelLayout layout; SDL_zero(layout); switch (device->spec.channels) { @@ -740,7 +739,7 @@ static int PrepareAudioQueue(SDL_AudioDevice *device) layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A; break; case 7: - /* FIXME: Need to move channel[4] (BC) to channel[6] */ + // FIXME: Need to move channel[4] (BC) to channel[6] layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; break; case 8: @@ -763,14 +762,14 @@ static int PrepareAudioQueue(SDL_AudioDevice *device) int numAudioBuffers = 2; const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0; - if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ + if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { // use more buffers if we have a VERY small sample set. numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); } device->hidden->numAudioBuffers = numAudioBuffers; device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef)); if (device->hidden->audioBuffer == NULL) { - return SDL_OutOfMemory(); + return -1; } #if DEBUG_COREAUDIO @@ -782,7 +781,7 @@ static int PrepareAudioQueue(SDL_AudioDevice *device) CHECK_RESULT("AudioQueueAllocateBuffer"); SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity; - /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ + // !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); CHECK_RESULT("AudioQueueEnqueueBuffer"); } @@ -831,10 +830,10 @@ static int AudioQueueThreadEntry(void *arg) static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) { - /* Initialize all variables that we clean on shutdown */ + // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { - return SDL_OutOfMemory(); + return -1; } #ifndef MACOSX_COREAUDIO @@ -842,7 +841,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) return -1; } - /* Stop CoreAudio from doing expensive audio rate conversion */ + // Stop CoreAudio from doing expensive audio rate conversion @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; [session setPreferredSampleRate:device->spec.freq error:nil]; @@ -856,12 +855,12 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) device->spec.channels = session.preferredOutputNumberOfChannels; } #else - /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ - #endif /* TARGET_OS_TV */ + // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS + #endif // TARGET_OS_TV } #endif - /* Setup a AudioStreamBasicDescription with the requested format */ + // Setup a AudioStreamBasicDescription with the requested format AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; strdesc->mFormatID = kAudioFormatLinearPCM; strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; @@ -872,7 +871,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { - /* CoreAudio handles most of SDL's formats natively. */ + // CoreAudio handles most of SDL's formats natively. switch (test_format) { case SDL_AUDIO_U8: case SDL_AUDIO_S8: @@ -890,7 +889,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) break; } - if (!test_format) { /* shouldn't happen, but just in case... */ + if (!test_format) { // shouldn't happen, but just in case... return SDL_SetError("%s: Unsupported audio format", "coreaudio"); } device->spec.format = test_format; @@ -914,10 +913,10 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) } #endif - /* This has to init in a new thread so it can get its own CFRunLoop. :/ */ + // This has to init in a new thread so it can get its own CFRunLoop. :/ device->hidden->ready_semaphore = SDL_CreateSemaphore(0); if (!device->hidden->ready_semaphore) { - return -1; /* oh well. */ + return -1; // oh well. } char threadname[64]; @@ -940,7 +939,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) return (device->hidden->thread != NULL) ? 0 : -1; } -static void COREAUDIO_Deinitialize(void) +static void COREAUDIO_DeinitializeStart(void) { #ifdef MACOSX_COREAUDIO AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); @@ -951,14 +950,13 @@ static void COREAUDIO_Deinitialize(void) static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = COREAUDIO_OpenDevice; impl->PlayDevice = COREAUDIO_PlayDevice; impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; impl->FlushCapture = COREAUDIO_FlushCapture; impl->CloseDevice = COREAUDIO_CloseDevice; - impl->Deinitialize = COREAUDIO_Deinitialize; + impl->DeinitializeStart = COREAUDIO_DeinitializeStart; #ifdef MACOSX_COREAUDIO impl->DetectDevices = COREAUDIO_DetectDevices; @@ -971,11 +969,11 @@ static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) impl->ProvidesOwnCallbackThread = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap COREAUDIO_bootstrap = { "coreaudio", "CoreAudio", COREAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_COREAUDIO */ +#endif // SDL_AUDIO_DRIVER_COREAUDIO diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index ab12c8cd..cd659c06 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_DSOUND -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_directsound.h" #include #ifdef HAVE_MMDEVICEAPI_H @@ -62,7 +62,7 @@ static void DSOUND_Unload(void) pDirectSoundCaptureEnumerateW = NULL; pGetDeviceID = NULL; - if (DSoundDLL != NULL) { + if (DSoundDLL) { SDL_UnloadObject(DSoundDLL); DSoundDLL = NULL; } @@ -75,7 +75,7 @@ static int DSOUND_Load(void) DSOUND_Unload(); DSoundDLL = SDL_LoadObject("DSOUND.DLL"); - if (DSoundDLL == NULL) { + if (!DSoundDLL) { SDL_SetError("DirectSound: failed to load DSOUND.DLL"); } else { // Now make sure we have DirectX 8 or better... @@ -176,7 +176,7 @@ static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVO FindAllDevsData *data = (FindAllDevsData *) userdata; if (guid != NULL) { // skip default device char *str = WIN_LookupAudioDeviceName(desc, guid); - if (str != NULL) { + if (str) { LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID)); if (cpyguid) { SDL_copyp(cpyguid, guid); @@ -225,64 +225,39 @@ static void DSOUND_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevi } -static void DSOUND_WaitDevice(SDL_AudioDevice *device) +static int DSOUND_WaitDevice(SDL_AudioDevice *device) { - DWORD status = 0; - DWORD cursor = 0; - DWORD junk = 0; - HRESULT result = DS_OK; - /* Semi-busy wait, since we have no way of getting play notification on a primary mixing buffer located in hardware (DirectX 5.0) */ - result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, - &junk, &cursor); - if (result != DS_OK) { - if (result == DSERR_BUFFERLOST) { - IDirectSoundBuffer_Restore(device->hidden->mixbuf); - } -#ifdef DEBUG_SOUND - SetDSerror("DirectSound GetCurrentPosition", result); -#endif - return; - } - - while ((cursor / device->buffer_size) == device->hidden->lastchunk) { - if (SDL_AtomicGet(&device->shutdown)) { - return; - } - - SDL_Delay(1); + while (!SDL_AtomicGet(&device->shutdown)) { + DWORD status = 0; + DWORD cursor = 0; + DWORD junk = 0; + HRESULT result = DS_OK; // Try to restore a lost sound buffer IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status); if (status & DSBSTATUS_BUFFERLOST) { IDirectSoundBuffer_Restore(device->hidden->mixbuf); - IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status); - if (status & DSBSTATUS_BUFFERLOST) { - break; + } else if (!(status & DSBSTATUS_PLAYING)) { + result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING); + } else { + // Find out where we are playing + result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor); + if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) { + break; // ready for next chunk! } } - if (!(status & DSBSTATUS_PLAYING)) { - result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, - DSBPLAY_LOOPING); - if (result == DS_OK) { - continue; - } -#ifdef DEBUG_SOUND - SetDSerror("DirectSound Play", result); -#endif - return; - } - // Find out where we are playing - result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, - &junk, &cursor); - if (result != DS_OK) { - SetDSerror("DirectSound GetCurrentPosition", result); - return; + if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) { + return -1; } + + SDL_Delay(1); // not ready yet; sleep a bit. } + + return 0; } static int DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) @@ -354,19 +329,20 @@ static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) return device->hidden->locked_buf; } -static void DSOUND_WaitCaptureDevice(SDL_AudioDevice *device) +static int DSOUND_WaitCaptureDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *h = device->hidden; while (!SDL_AtomicGet(&device->shutdown)) { DWORD junk, cursor; if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) { - SDL_AudioDeviceDisconnected(device); - return; + return -1; } else if ((cursor / device->buffer_size) != h->lastchunk) { - return; + break; } SDL_Delay(1); } + + return 0; } static int DSOUND_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) @@ -408,18 +384,18 @@ static void DSOUND_FlushCapture(SDL_AudioDevice *device) static void DSOUND_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - if (device->hidden->mixbuf != NULL) { + if (device->hidden->mixbuf) { IDirectSoundBuffer_Stop(device->hidden->mixbuf); IDirectSoundBuffer_Release(device->hidden->mixbuf); } - if (device->hidden->sound != NULL) { + if (device->hidden->sound) { IDirectSound_Release(device->hidden->sound); } - if (device->hidden->capturebuf != NULL) { + if (device->hidden->capturebuf) { IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf); IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf); } - if (device->hidden->capture != NULL) { + if (device->hidden->capture) { IDirectSoundCapture_Release(device->hidden->capture); } SDL_free(device->hidden); @@ -515,8 +491,8 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *device) { // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // Open the audio device @@ -647,15 +623,21 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *device) return 0; // good to go. } -static void DSOUND_Deinitialize(void) +static void DSOUND_DeinitializeStart(void) { #ifdef HAVE_MMDEVICEAPI_H if (SupportsIMMDevice) { SDL_IMMDevice_Quit(); - SupportsIMMDevice = SDL_FALSE; } #endif +} + +static void DSOUND_Deinitialize(void) +{ DSOUND_Unload(); +#ifdef HAVE_MMDEVICEAPI_H + SupportsIMMDevice = SDL_FALSE; +#endif } static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) @@ -665,7 +647,7 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) } #ifdef HAVE_MMDEVICEAPI_H - SupportsIMMDevice = !(SDL_IMMDevice_Init() < 0); + SupportsIMMDevice = !(SDL_IMMDevice_Init(NULL) < 0); #endif impl->DetectDevices = DSOUND_DetectDevices; @@ -678,6 +660,7 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) impl->FlushCapture = DSOUND_FlushCapture; impl->CloseDevice = DSOUND_CloseDevice; impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle; + impl->DeinitializeStart = DSOUND_DeinitializeStart; impl->Deinitialize = DSOUND_Deinitialize; impl->HasCaptureSupport = SDL_TRUE; diff --git a/src/audio/directsound/SDL_directsound.h b/src/audio/directsound/SDL_directsound.h index 18b67dac..e3e0c269 100644 --- a/src/audio/directsound/SDL_directsound.h +++ b/src/audio/directsound/SDL_directsound.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index b4cf2bee..e0fd415f 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,7 +24,7 @@ // Output raw audio data to a file. -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_diskaudio.h" // !!! FIXME: these should be SDL hints, not environment variables. @@ -35,9 +35,10 @@ #define DISKDEFAULT_INFILE "sdlaudio-in.raw" #define DISKENVR_IODELAY "SDL_DISKAUDIODELAY" -static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device) +static int DISKAUDIO_WaitDevice(SDL_AudioDevice *device) { SDL_Delay(device->hidden->io_delay); + return 0; } static int DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) @@ -86,7 +87,7 @@ static void DISKAUDIO_FlushCapture(SDL_AudioDevice *device) static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - if (device->hidden->io != NULL) { + if (device->hidden->io) { SDL_RWclose(device->hidden->io); } SDL_free(device->hidden->mixbuf); @@ -98,7 +99,7 @@ static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device) static const char *get_filename(const SDL_bool iscapture) { const char *devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE); - if (devname == NULL) { + if (!devname) { devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE; } return devname; @@ -111,11 +112,11 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) const char *envr = SDL_getenv(DISKENVR_IODELAY); device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } - if (envr != NULL) { + if (envr) { device->hidden->io_delay = SDL_atoi(envr); } else { device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq); @@ -123,15 +124,15 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) // Open the "audio device" device->hidden->io = SDL_RWFromFile(fname, iscapture ? "rb" : "wb"); - if (device->hidden->io == NULL) { + if (!device->hidden->io) { return -1; } // Allocate mixing buffer if (!iscapture) { device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } @@ -160,7 +161,6 @@ static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) impl->CloseDevice = DISKAUDIO_CloseDevice; impl->DetectDevices = DISKAUDIO_DetectDevices; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; return SDL_TRUE; diff --git a/src/audio/disk/SDL_diskaudio.h b/src/audio/disk/SDL_diskaudio.h index bdc734bd..81d22b65 100644 --- a/src/audio/disk/SDL_diskaudio.h +++ b/src/audio/disk/SDL_diskaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,10 +27,10 @@ struct SDL_PrivateAudioData { - /* The file descriptor for the audio device */ + // The file descriptor for the audio device SDL_RWops *io; Uint32 io_delay; Uint8 *mixbuf; }; -#endif /* SDL_diskaudio_h_ */ +#endif // SDL_diskaudio_h_ diff --git a/src/audio/dsp/SDL_dspaudio.c b/src/audio/dsp/SDL_dspaudio.c index 05572d22..e9412e08 100644 --- a/src/audio/dsp/SDL_dspaudio.c +++ b/src/audio/dsp/SDL_dspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,8 +24,8 @@ #ifdef SDL_AUDIO_DRIVER_OSS -#include /* For perror() */ -#include /* For strerror() */ +#include // For perror() +#include // For strerror() #include #include #include @@ -36,7 +36,6 @@ #include -#include "../SDL_audio_c.h" #include "../SDL_audiodev_c.h" #include "../../SDL_utils_c.h" #include "SDL_dspaudio.h" @@ -71,8 +70,8 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that. @@ -97,7 +96,7 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Couldn't get audio format list"); } - /* Try for a closest match on audio format */ + // Try for a closest match on audio format int format = 0; SDL_AudioFormat test_format; const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); @@ -156,7 +155,7 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) } device->spec.freq = value; - /* Calculate the final parameters for this audio specification */ + // Calculate the final parameters for this audio specification SDL_UpdatedAudioDeviceFormat(device); /* Determine the power of two of the fragment size @@ -168,9 +167,9 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) while ((0x01U << frag_spec) < device->buffer_size) { frag_spec++; } - frag_spec |= 0x00020000; /* two fragments, for low latency */ + frag_spec |= 0x00020000; // two fragments, for low latency - /* Set the audio buffering parameters */ + // Set the audio buffering parameters #ifdef DEBUG_AUDIO fprintf(stderr, "Requesting %d fragments of size %d\n", (frag_spec >> 16), 1 << (frag_spec & 0xFFFF)); @@ -189,11 +188,11 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) } #endif - /* Allocate mixing buffer */ + // Allocate mixing buffer if (!device->iscapture) { device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } @@ -201,7 +200,7 @@ static int DSP_OpenDevice(SDL_AudioDevice *device) return 0; // We're ready to rock and roll. :-) } -static void DSP_WaitDevice(SDL_AudioDevice *device) +static int DSP_WaitDevice(SDL_AudioDevice *device) { const unsigned long ioctlreq = device->iscapture ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE; struct SDL_PrivateAudioData *h = device->hidden; @@ -215,14 +214,15 @@ static void DSP_WaitDevice(SDL_AudioDevice *device) } // Hmm, not much we can do - abort fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno)); - SDL_AudioDeviceDisconnected(device); - return; + return -1; } else if (info.bytes < device->buffer_size) { SDL_Delay(10); } else { break; // ready to go! } } + + return 0; } static int DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) @@ -268,8 +268,8 @@ static void DSP_FlushCapture(SDL_AudioDevice *device) static SDL_bool InitTimeDevicesExist = SDL_FALSE; static SDL_bool look_for_devices_test(int fd) { - InitTimeDevicesExist = SDL_TRUE; /* note that _something_ exists. */ - /* Don't add to the device list, we're just seeing if any devices exist. */ + InitTimeDevicesExist = SDL_TRUE; // note that _something_ exists. + // Don't add to the device list, we're just seeing if any devices exist. return SDL_FALSE; } @@ -279,10 +279,9 @@ static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl) SDL_EnumUnixAudioDevices(SDL_FALSE, look_for_devices_test); if (!InitTimeDevicesExist) { SDL_SetError("dsp: No such audio device"); - return SDL_FALSE; /* maybe try a different backend. */ + return SDL_FALSE; // maybe try a different backend. } - /* Set the function pointers */ impl->DetectDevices = DSP_DetectDevices; impl->OpenDevice = DSP_OpenDevice; impl->WaitDevice = DSP_WaitDevice; @@ -293,14 +292,13 @@ static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl) impl->CaptureFromDevice = DSP_CaptureFromDevice; impl->FlushCapture = DSP_FlushCapture; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap DSP_bootstrap = { "dsp", "Open Sound System (/dev/dsp)", DSP_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_OSS */ +#endif // SDL_AUDIO_DRIVER_OSS diff --git a/src/audio/dsp/SDL_dspaudio.h b/src/audio/dsp/SDL_dspaudio.h index abe0c347..b7634e16 100644 --- a/src/audio/dsp/SDL_dspaudio.h +++ b/src/audio/dsp/SDL_dspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,11 +27,11 @@ struct SDL_PrivateAudioData { - /* The file descriptor for the audio device */ + // The file descriptor for the audio device int audio_fd; - /* Raw mixing buffer */ + // Raw mixing buffer Uint8 *mixbuf; }; -#endif /* SDL_dspaudio_h_ */ +#endif // SDL_dspaudio_h_ diff --git a/src/audio/dummy/SDL_dummyaudio.c b/src/audio/dummy/SDL_dummyaudio.c index bd3002da..19c4b4f0 100644 --- a/src/audio/dummy/SDL_dummyaudio.c +++ b/src/audio/dummy/SDL_dummyaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,15 +22,16 @@ // Output audio to nowhere... -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_dummyaudio.h" // !!! FIXME: this should be an SDL hint, not an environment variable. #define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY" -static void DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device) +static int DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device) { SDL_Delay(device->hidden->io_delay); + return 0; } static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) @@ -39,13 +40,13 @@ static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); if (!device->hidden) { - return SDL_OutOfMemory(); + return -1; } if (!device->iscapture) { device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size); if (!device->hidden->mixbuf) { - return SDL_OutOfMemory(); + return -1; } } diff --git a/src/audio/dummy/SDL_dummyaudio.h b/src/audio/dummy/SDL_dummyaudio.h index d629e0d8..6f4c31c9 100644 --- a/src/audio/dummy/SDL_dummyaudio.h +++ b/src/audio/dummy/SDL_dummyaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c index b8740a88..764f0b55 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/src/audio/emscripten/SDL_emscriptenaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_emscriptenaudio.h" #include @@ -177,8 +177,8 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // limit to native freq @@ -188,8 +188,8 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) if (!device->iscapture) { device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } @@ -321,7 +321,7 @@ static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) return true; } return false; - }) ? SDL_TRUE : SDL_FALSE; + }); if (!available) { SDL_SetError("No audio context available"); @@ -334,10 +334,10 @@ static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) return true; } return false; - }) ? SDL_TRUE : SDL_FALSE; + }); - impl->HasCaptureSupport = capture_available ? SDL_TRUE : SDL_FALSE; - impl->OnlyHasDefaultCaptureDevice = capture_available ? SDL_TRUE : SDL_FALSE; + impl->HasCaptureSupport = capture_available; + impl->OnlyHasDefaultCaptureDevice = capture_available; return available; } diff --git a/src/audio/emscripten/SDL_emscriptenaudio.h b/src/audio/emscripten/SDL_emscriptenaudio.h index cc2f49b2..d10d4a2a 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.h +++ b/src/audio/emscripten/SDL_emscriptenaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,4 +30,4 @@ struct SDL_PrivateAudioData Uint8 *mixbuf; }; -#endif /* SDL_emscriptenaudio_h_ */ +#endif // SDL_emscriptenaudio_h_ diff --git a/src/audio/haiku/SDL_haikuaudio.cc b/src/audio/haiku/SDL_haikuaudio.cc index 15204362..ed234d01 100644 --- a/src/audio/haiku/SDL_haikuaudio.cc +++ b/src/audio/haiku/SDL_haikuaudio.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,7 +32,6 @@ extern "C" { -#include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include "SDL_haikuaudio.h" diff --git a/src/audio/haiku/SDL_haikuaudio.h b/src/audio/haiku/SDL_haikuaudio.h index c78c6061..6ba2bb22 100644 --- a/src/audio/haiku/SDL_haikuaudio.h +++ b/src/audio/haiku/SDL_haikuaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,4 +32,4 @@ struct SDL_PrivateAudioData int current_buffer_len; }; -#endif /* SDL_haikuaudio_h_ */ +#endif // SDL_haikuaudio_h_ diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c index ce2fde14..3fb16fde 100644 --- a/src/audio/jack/SDL_jackaudio.c +++ b/src/audio/jack/SDL_jackaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_JACK -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_jackaudio.h" #include "../../thread/SDL_systhread.h" @@ -43,6 +43,8 @@ static const char *(*JACK_jack_port_name)(const jack_port_t *); static const char *(*JACK_jack_port_type)(const jack_port_t *); static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *); static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *); +static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *); +static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *); static int load_jack_syms(void); @@ -51,26 +53,26 @@ static int load_jack_syms(void); static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; static void *jack_handle = NULL; -/* !!! FIXME: this is copy/pasted in several places now */ +// !!! FIXME: this is copy/pasted in several places now static int load_jack_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(jack_handle, fn); - if (*addr == NULL) { - /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ + if (!*addr) { + // Don't call SDL_SetError(): SDL_LoadFunction already did. return 0; } return 1; } -/* cast funcs to char* first, to please GCC's strict aliasing rules. */ +// cast funcs to char* first, to please GCC's strict aliasing rules. #define SDL_JACK_SYM(x) \ if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \ return -1 static void UnloadJackLibrary(void) { - if (jack_handle != NULL) { + if (jack_handle) { SDL_UnloadObject(jack_handle); jack_handle = NULL; } @@ -79,11 +81,11 @@ static void UnloadJackLibrary(void) static int LoadJackLibrary(void) { int retval = 0; - if (jack_handle == NULL) { + if (!jack_handle) { jack_handle = SDL_LoadObject(jack_library); - if (jack_handle == NULL) { + if (!jack_handle) { retval = -1; - /* Don't call SDL_SetError(): SDL_LoadObject already did. */ + // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_jack_syms(); if (retval < 0) { @@ -108,7 +110,7 @@ static int LoadJackLibrary(void) return 0; } -#endif /* SDL_AUDIO_DRIVER_JACK_DYNAMIC */ +#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC static int load_jack_syms(void) { @@ -129,18 +131,41 @@ static int load_jack_syms(void) SDL_JACK_SYM(jack_port_type); SDL_JACK_SYM(jack_connect); SDL_JACK_SYM(jack_set_process_callback); + SDL_JACK_SYM(jack_set_sample_rate_callback); + SDL_JACK_SYM(jack_set_buffer_size_callback); return 0; } -static void jackShutdownCallback(void *arg) /* JACK went away; device is lost. */ +static void jackShutdownCallback(void *arg) // JACK went away; device is lost. { SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg); } -// !!! FIXME: implement and register these! -// typedef int(* JackSampleRateCallback)(jack_nframes_t nframes, void *arg) -// typedef int(* JackBufferSizeCallback)(jack_nframes_t nframes, void *arg) +static int jackSampleRateCallback(jack_nframes_t nframes, void *arg) +{ + //SDL_Log("JACK Sample Rate Callback! %d", (int) nframes); + SDL_AudioDevice *device = (SDL_AudioDevice *) arg; + SDL_AudioSpec newspec; + SDL_copyp(&newspec, &device->spec); + newspec.freq = (int) nframes; + if (SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames) < 0) { + SDL_AudioDeviceDisconnected(device); + } + return 0; +} + +static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg) +{ + //SDL_Log("JACK Buffer Size Callback! %d", (int) nframes); + SDL_AudioDevice *device = (SDL_AudioDevice *) arg; + SDL_AudioSpec newspec; + SDL_copyp(&newspec, &device->spec); + if (SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes) < 0) { + SDL_AudioDeviceDisconnected(device); + } + return 0; +} static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg) { @@ -269,34 +294,34 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) int ports = 0; int i; - /* Initialize all variables that we clean on shutdown */ + // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL); device->hidden->client = client; - if (client == NULL) { + if (!client) { return SDL_SetError("Can't open JACK client"); } devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags); - if (devports == NULL || !devports[0]) { + if (!devports || !devports[0]) { return SDL_SetError("No physical JACK ports available"); } while (devports[++ports]) { - /* spin to count devports */ + // spin to count devports } - /* Filter out non-audio ports */ + // Filter out non-audio ports audio_ports = SDL_calloc(ports, sizeof(*audio_ports)); for (i = 0; i < ports; i++) { const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]); const char *type = JACK_jack_port_type(dport); const int len = SDL_strlen(type); - /* See if type ends with "audio" */ + // See if type ends with "audio" if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) { audio_ports[channels++] = i; } @@ -306,9 +331,7 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("No physical JACK ports available"); } - /* !!! FIXME: docs say about buffer size: "This size may change, clients that depend on it must register a bufsize_callback so they will be notified if it does." */ - - /* Jack pretty much demands what it wants. */ + // Jack pretty much demands what it wants. device->spec.format = SDL_AUDIO_F32; device->spec.freq = JACK_jack_get_sample_rate(client); device->spec.channels = channels; @@ -320,15 +343,15 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size); if (!device->hidden->iobuffer) { SDL_free(audio_ports); - return SDL_OutOfMemory(); + return -1; } } - /* Build SDL's ports, which we will connect to the device ports. */ + // Build SDL's ports, which we will connect to the device ports. device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *)); - if (device->hidden->sdlports == NULL) { + if (!device->hidden->sdlports) { SDL_free(audio_ports); - return SDL_OutOfMemory(); + return -1; } for (i = 0; i < channels; i++) { @@ -341,7 +364,13 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) } } - if (JACK_jack_set_process_callback(client, callback, device) != 0) { + if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) { + SDL_free(audio_ports); + return SDL_SetError("JACK: Couldn't set buffer size callback"); + } else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) { + SDL_free(audio_ports); + return SDL_SetError("JACK: Couldn't set sample rate callback"); + } else if (JACK_jack_set_process_callback(client, callback, device) != 0) { SDL_free(audio_ports); return SDL_SetError("JACK: Couldn't set process callback"); } @@ -353,7 +382,7 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Failed to activate JACK client"); } - /* once activated, we can connect all the ports. */ + // once activated, we can connect all the ports. for (i = 0; i < channels; i++) { const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]); const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport; @@ -364,11 +393,11 @@ static int JACK_OpenDevice(SDL_AudioDevice *device) } } - /* don't need these anymore. */ + // don't need these anymore. JACK_jack_free(devports); SDL_free(audio_ports); - /* We're ready to rock and roll. :-) */ + // We're ready to rock and roll. :-) return 0; } @@ -382,17 +411,16 @@ static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl) if (LoadJackLibrary() < 0) { return SDL_FALSE; } else { - /* Make sure a JACK server is running and available. */ + // Make sure a JACK server is running and available. jack_status_t status; jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL); - if (client == NULL) { + if (!client) { UnloadJackLibrary(); return SDL_FALSE; } JACK_jack_client_close(client); } - /* Set the function pointers */ impl->OpenDevice = JACK_OpenDevice; impl->GetDeviceBuf = JACK_GetDeviceBuf; impl->PlayDevice = JACK_PlayDevice; @@ -405,11 +433,11 @@ static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl) impl->HasCaptureSupport = SDL_TRUE; impl->ProvidesOwnCallbackThread = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap JACK_bootstrap = { "jack", "JACK Audio Connection Kit", JACK_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_JACK */ +#endif // SDL_AUDIO_DRIVER_JACK diff --git a/src/audio/jack/SDL_jackaudio.h b/src/audio/jack/SDL_jackaudio.h index a8cf81d4..4fe0053f 100644 --- a/src/audio/jack/SDL_jackaudio.h +++ b/src/audio/jack/SDL_jackaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,4 +32,4 @@ struct SDL_PrivateAudioData float *iobuffer; }; -#endif /* SDL_jackaudio_h_ */ +#endif // SDL_jackaudio_h_ diff --git a/src/audio/n3ds/SDL_n3dsaudio.c b/src/audio/n3ds/SDL_n3dsaudio.c index a7dc9fb0..35f9a319 100644 --- a/src/audio/n3ds/SDL_n3dsaudio.c +++ b/src/audio/n3ds/SDL_n3dsaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,7 @@ static dspHookCookie dsp_hook; static SDL_AudioDevice *audio_device; +// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex! static SDL_INLINE void contextLock(SDL_AudioDevice *device) { LightLock_Lock(&device->hidden->lock); @@ -82,8 +83,8 @@ static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device) float mix[12]; device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // Initialise the DSP service @@ -133,14 +134,14 @@ static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device) } device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS); - if (data_vaddr == NULL) { + if (!data_vaddr) { return SDL_OutOfMemory(); } @@ -200,7 +201,7 @@ static int N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in return 0; } -static void N3DSAUDIO_WaitDevice(SDL_AudioDevice *device) +static int N3DSAUDIO_WaitDevice(SDL_AudioDevice *device) { contextLock(device); while (!device->hidden->isCancelled && !SDL_AtomicGet(&device->shutdown) && @@ -208,6 +209,7 @@ static void N3DSAUDIO_WaitDevice(SDL_AudioDevice *device) CondVar_Wait(&device->hidden->cv, &device->hidden->lock); } contextUnlock(device); + return 0; } static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) @@ -251,7 +253,7 @@ static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device) static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device) { - s32 current_priority; + s32 current_priority = 0x30; svcGetThreadPriority(¤t_priority, CUR_THREAD_HANDLE); current_priority--; // 0x18 is reserved for video, 0x30 is the default for main thread diff --git a/src/audio/n3ds/SDL_n3dsaudio.h b/src/audio/n3ds/SDL_n3dsaudio.h index 83f9ca83..932944d3 100644 --- a/src/audio/n3ds/SDL_n3dsaudio.h +++ b/src/audio/n3ds/SDL_n3dsaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,11 +24,11 @@ #include <3ds.h> -#define NUM_BUFFERS 2 /* -- Don't lower this! */ +#define NUM_BUFFERS 3 // -- Minimum 2! struct SDL_PrivateAudioData { - /* Speaker data */ + // Speaker data Uint8 *mixbuf; Uint32 nextbuf; ndspWaveBuf waveBuf[NUM_BUFFERS]; @@ -37,4 +37,4 @@ struct SDL_PrivateAudioData SDL_bool isCancelled; }; -#endif /* SDL_n3dsaudio_h */ +#endif // SDL_n3dsaudio_h diff --git a/src/audio/netbsd/SDL_netbsdaudio.c b/src/audio/netbsd/SDL_netbsdaudio.c index d8aafc08..f0de6948 100644 --- a/src/audio/netbsd/SDL_netbsdaudio.c +++ b/src/audio/netbsd/SDL_netbsdaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,7 +34,6 @@ #include #include "../../core/unix/SDL_poll.h" -#include "../SDL_audio_c.h" #include "../SDL_audiodev_c.h" #include "SDL_netbsdaudio.h" @@ -115,7 +114,7 @@ static void NETBSDAUDIO_Status(SDL_AudioDevice *device) #endif // DEBUG_AUDIO } -static void NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device) +static int NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device) { const SDL_bool iscapture = device->iscapture; while (!SDL_AtomicGet(&device->shutdown)) { @@ -127,8 +126,7 @@ static void NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device) } // Hmm, not much we can do - abort fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno)); - SDL_AudioDeviceDisconnected(device); - return; + return -1; } const size_t remain = (size_t)((iscapture ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format)); if (!iscapture && (remain >= device->buffer_size)) { @@ -136,9 +134,11 @@ static void NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device) } else if (iscapture && (remain < device->buffer_size)) { SDL_Delay(10); } else { - break; /* ready to go! */ + break; // ready to go! } } + + return 0; } static int NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) @@ -215,8 +215,8 @@ static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device) // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that. @@ -293,8 +293,8 @@ static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device) // Allocate mixing buffer device->hidden->mixlen = device->buffer_size; device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } @@ -317,7 +317,6 @@ static SDL_bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl) impl->FlushCapture = NETBSDAUDIO_FlushCapture; impl->HasCaptureSupport = SDL_TRUE; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; return SDL_TRUE; } diff --git a/src/audio/netbsd/SDL_netbsdaudio.h b/src/audio/netbsd/SDL_netbsdaudio.h index 9c1d93c5..dc3247cb 100644 --- a/src/audio/netbsd/SDL_netbsdaudio.h +++ b/src/audio/netbsd/SDL_netbsdaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,18 +27,18 @@ struct SDL_PrivateAudioData { - /* The file descriptor for the audio device */ + // The file descriptor for the audio device int audio_fd; - /* Raw mixing buffer */ + // Raw mixing buffer Uint8 *mixbuf; int mixlen; - /* Support for audio timing using a timer, in addition to SDL_IOReady() */ + // Support for audio timing using a timer, in addition to SDL_IOReady() float frame_ticks; float next_frame; }; -#define FUDGE_TICKS 10 /* The scheduler overhead ticks per frame */ +#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame -#endif /* SDL_netbsdaudio_h_ */ +#endif // SDL_netbsdaudio_h_ diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c index c4855eae..84f92a41 100644 --- a/src/audio/openslES/SDL_openslES.c +++ b/src/audio/openslES/SDL_openslES.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,6 @@ // https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html #include "../SDL_sysaudio.h" -#include "../SDL_audio_c.h" #include "SDL_openslES.h" #include "../../core/android/SDL_android.h" @@ -108,7 +107,7 @@ static const char *sldevaudioplayerstr = "SLES Audio Player"; #define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr #define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr -static void openslES_DetectDevices( int iscapture ) +static void OPENSLES_DetectDevices( int iscapture ) { LOGI( "openSLES_DetectDevices()" ); if ( iscapture ) @@ -118,9 +117,9 @@ static void openslES_DetectDevices( int iscapture ) } #endif -static void openslES_DestroyEngine(void) +static void OPENSLES_DestroyEngine(void) { - LOGI("openslES_DestroyEngine()"); + LOGI("OPENSLES_DestroyEngine()"); // destroy output mix object, and invalidate all associated interfaces if (outputMixObject != NULL) { @@ -136,7 +135,7 @@ static void openslES_DestroyEngine(void) } } -static int openslES_CreateEngine(void) +static int OPENSLES_CreateEngine(void) { const SLInterfaceID ids[1] = { SL_IID_VOLUME }; const SLboolean req[1] = { SL_BOOLEAN_FALSE }; @@ -185,7 +184,7 @@ static int openslES_CreateEngine(void) return 1; error: - openslES_DestroyEngine(); + OPENSLES_DestroyEngine(); return 0; } @@ -198,7 +197,7 @@ static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) SDL_PostSemaphore(audiodata->playsem); } -static void openslES_DestroyPCMRecorder(SDL_AudioDevice *device) +static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *audiodata = device->hidden; SLresult result; @@ -229,7 +228,7 @@ static void openslES_DestroyPCMRecorder(SDL_AudioDevice *device) } } -static int openslES_CreatePCMRecorder(SDL_AudioDevice *device) +static int OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *audiodata = device->hidden; SLDataFormat_PCM format_pcm; @@ -328,7 +327,7 @@ static int openslES_CreatePCMRecorder(SDL_AudioDevice *device) // Create the sound buffers audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size); - if (audiodata->mixbuff == NULL) { + if (!audiodata->mixbuff) { LOGE("mixbuffer allocate - out of memory"); goto failed; } @@ -375,7 +374,7 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) SDL_PostSemaphore(audiodata->playsem); } -static void openslES_DestroyPCMPlayer(SDL_AudioDevice *device) +static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *audiodata = device->hidden; @@ -406,7 +405,7 @@ static void openslES_DestroyPCMPlayer(SDL_AudioDevice *device) } } -static int openslES_CreatePCMPlayer(SDL_AudioDevice *device) +static int 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: @@ -575,7 +574,7 @@ static int openslES_CreatePCMPlayer(SDL_AudioDevice *device) // Create the sound buffers audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size); - if (audiodata->mixbuff == NULL) { + if (!audiodata->mixbuff) { LOGE("mixbuffer allocate - out of memory"); goto failed; } @@ -597,26 +596,26 @@ failed: return -1; } -static int openslES_OpenDevice(SDL_AudioDevice *device) +static int OPENSLES_OpenDevice(SDL_AudioDevice *device) { device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } if (device->iscapture) { - LOGI("openslES_OpenDevice() for capture"); - return openslES_CreatePCMRecorder(device); + LOGI("OPENSLES_OpenDevice() for capture"); + return OPENSLES_CreatePCMRecorder(device); } else { int ret; - LOGI("openslES_OpenDevice() for playing"); - ret = openslES_CreatePCMPlayer(device); + LOGI("OPENSLES_OpenDevice() for playing"); + ret = OPENSLES_CreatePCMPlayer(device); if (ret < 0) { // Another attempt to open the device with a lower frequency if (device->spec.freq > 48000) { - openslES_DestroyPCMPlayer(device); + OPENSLES_DestroyPCMPlayer(device); device->spec.freq = 48000; - ret = openslES_CreatePCMPlayer(device); + ret = OPENSLES_CreatePCMPlayer(device); } } @@ -628,21 +627,21 @@ static int openslES_OpenDevice(SDL_AudioDevice *device) return 0; } -static void openslES_WaitDevice(SDL_AudioDevice *device) +static int OPENSLES_WaitDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *audiodata = device->hidden; - LOGV("openslES_WaitDevice()"); + LOGV("OPENSLES_WaitDevice()"); // Wait for an audio chunk to finish - SDL_WaitSemaphore(audiodata->playsem); + return SDL_WaitSemaphore(audiodata->playsem); } -static int openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +static int OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { struct SDL_PrivateAudioData *audiodata = device->hidden; - LOGV("======openslES_PlayDevice()======"); + LOGV("======OPENSLES_PlayDevice()======"); // Queue it up const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen); @@ -673,15 +672,15 @@ static int openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int // // okay.. -static Uint8 *openslES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) +static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) { struct SDL_PrivateAudioData *audiodata = device->hidden; - LOGV("openslES_GetDeviceBuf()"); + LOGV("OPENSLES_GetDeviceBuf()"); return audiodata->pmixbuff[audiodata->next_buffer]; } -static int openslES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +static int OPENSLES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { struct SDL_PrivateAudioData *audiodata = device->hidden; @@ -704,16 +703,16 @@ static int openslES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int return device->buffer_size; } -static void openslES_CloseDevice(SDL_AudioDevice *device) +static void OPENSLES_CloseDevice(SDL_AudioDevice *device) { // struct SDL_PrivateAudioData *audiodata = device->hidden; if (device->hidden) { if (device->iscapture) { - LOGI("openslES_CloseDevice() for capture"); - openslES_DestroyPCMRecorder(device); + LOGI("OPENSLES_CloseDevice() for capture"); + OPENSLES_DestroyPCMRecorder(device); } else { - LOGI("openslES_CloseDevice() for playing"); - openslES_DestroyPCMPlayer(device); + LOGI("OPENSLES_CloseDevice() for playing"); + OPENSLES_DestroyPCMPlayer(device); } SDL_free(device->hidden); @@ -721,61 +720,61 @@ static void openslES_CloseDevice(SDL_AudioDevice *device) } } -static SDL_bool openslES_Init(SDL_AudioDriverImpl *impl) +static SDL_bool OPENSLES_Init(SDL_AudioDriverImpl *impl) { - LOGI("openslES_Init() called"); + LOGI("OPENSLES_Init() called"); - if (!openslES_CreateEngine()) { + if (!OPENSLES_CreateEngine()) { return SDL_FALSE; } - LOGI("openslES_Init() - set pointers"); + LOGI("OPENSLES_Init() - set pointers"); // Set the function pointers - // impl->DetectDevices = openslES_DetectDevices; + // impl->DetectDevices = OPENSLES_DetectDevices; impl->ThreadInit = Android_AudioThreadInit; - impl->OpenDevice = openslES_OpenDevice; - impl->WaitDevice = openslES_WaitDevice; - impl->PlayDevice = openslES_PlayDevice; - impl->GetDeviceBuf = openslES_GetDeviceBuf; - impl->WaitCaptureDevice = openslES_WaitDevice; - impl->CaptureFromDevice = openslES_CaptureFromDevice; - impl->CloseDevice = openslES_CloseDevice; - impl->Deinitialize = openslES_DestroyEngine; + impl->OpenDevice = OPENSLES_OpenDevice; + impl->WaitDevice = OPENSLES_WaitDevice; + impl->PlayDevice = OPENSLES_PlayDevice; + impl->GetDeviceBuf = OPENSLES_GetDeviceBuf; + impl->WaitCaptureDevice = OPENSLES_WaitDevice; + impl->CaptureFromDevice = OPENSLES_CaptureFromDevice; + impl->CloseDevice = OPENSLES_CloseDevice; + impl->Deinitialize = OPENSLES_DestroyEngine; // and the capabilities impl->HasCaptureSupport = SDL_TRUE; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; - LOGI("openslES_Init() - success"); + LOGI("OPENSLES_Init() - success"); // this audio target is available. return SDL_TRUE; } -AudioBootStrap openslES_bootstrap = { - "openslES", "opensl ES audio driver", openslES_Init, SDL_FALSE +AudioBootStrap OPENSLES_bootstrap = { + "openslES", "OpenSL ES audio driver", OPENSLES_Init, SDL_FALSE }; -void openslES_ResumeDevices(void) +void OPENSLES_ResumeDevices(void) { if (bqPlayerPlay != NULL) { // set the player's state to 'playing' SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); if (SL_RESULT_SUCCESS != result) { - LOGE("openslES_ResumeDevices failed: %d", result); + LOGE("OPENSLES_ResumeDevices failed: %d", result); } } } -void openslES_PauseDevices(void) +void OPENSLES_PauseDevices(void) { if (bqPlayerPlay != NULL) { // set the player's state to 'paused' SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); if (SL_RESULT_SUCCESS != result) { - LOGE("openslES_PauseDevices failed: %d", result); + LOGE("OPENSLES_PauseDevices failed: %d", result); } } } diff --git a/src/audio/openslES/SDL_openslES.h b/src/audio/openslES/SDL_openslES.h index d7abc19e..9d65a7da 100644 --- a/src/audio/openslES/SDL_openslES.h +++ b/src/audio/openslES/SDL_openslES.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,14 +25,14 @@ #ifdef SDL_AUDIO_DRIVER_OPENSLES -void openslES_ResumeDevices(void); -void openslES_PauseDevices(void); +void OPENSLES_ResumeDevices(void); +void OPENSLES_PauseDevices(void); #else -static void openslES_ResumeDevices(void) {} -static void openslES_PauseDevices(void) {} +static void OPENSLES_ResumeDevices(void) {} +static void OPENSLES_PauseDevices(void) {} #endif -#endif /* SDL_openslesaudio_h_ */ +#endif // SDL_openslesaudio_h_ diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 0dda0d8c..492b0e49 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,7 +63,7 @@ * This seems to be a sane lower limit as Pipewire * uses it in several of it's own modules. */ -#define PW_MIN_SAMPLES 32 /* About 0.67ms at 48kHz */ +#define PW_MIN_SAMPLES 32 // About 0.67ms at 48kHz #define PW_BASE_CLOCK_RATE 48000 #define PW_POD_BUFFER_LENGTH 1024 @@ -82,7 +82,7 @@ enum PW_READY_FLAGS static SDL_bool pipewire_initialized = SDL_FALSE; -/* Pipewire entry points */ +// Pipewire entry points static const char *(*PIPEWIRE_pw_get_library_version)(void); static void (*PIPEWIRE_pw_init)(int *, char ***); static void (*PIPEWIRE_pw_deinit)(void); @@ -126,8 +126,8 @@ static void *pipewire_handle = NULL; static int pipewire_dlsym(const char *fn, void **addr) { *addr = SDL_LoadFunction(pipewire_handle, fn); - if (*addr == NULL) { - /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ + if (!*addr) { + // Don't call SDL_SetError(): SDL_LoadFunction already did. return 0; } @@ -142,7 +142,7 @@ static int pipewire_dlsym(const char *fn, void **addr) static int load_pipewire_library(void) { pipewire_handle = SDL_LoadObject(pipewire_library); - return pipewire_handle != NULL ? 0 : -1; + return pipewire_handle ? 0 : -1; } static void unload_pipewire_library(void) @@ -163,10 +163,11 @@ static int load_pipewire_library(void) } static void unload_pipewire_library(void) -{ /* Nothing to do */ +{ + // Nothing to do } -#endif /* SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC */ +#endif // SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC static int load_pipewire_syms(void) { @@ -220,7 +221,7 @@ static int init_pipewire_library(void) return -1; } - /* SDL can build against 0.3.20, but requires 0.3.24 */ + // SDL can build against 0.3.20, but requires 0.3.24 if (pipewire_version_at_least(0, 3, 24)) { PIPEWIRE_pw_init(NULL, NULL); return 0; @@ -237,7 +238,7 @@ static void deinit_pipewire_library(void) unload_pipewire_library(); } -/* A generic Pipewire node object used for enumeration. */ +// A generic Pipewire node object used for enumeration. struct node_object { struct spa_list link; @@ -260,7 +261,7 @@ struct node_object struct spa_hook core_listener; }; -/* A sink/source node used for stream I/O. */ +// A sink/source node used for stream I/O. struct io_node { struct spa_list link; @@ -269,13 +270,13 @@ struct io_node SDL_bool is_capture; SDL_AudioSpec spec; - const char *name; /* Friendly name */ - const char *path; /* OS identifier (i.e. ALSA endpoint) */ + const char *name; // Friendly name + const char *path; // OS identifier (i.e. ALSA endpoint) - char buf[]; /* Buffer to hold the name and path strings. */ + char buf[]; // Buffer to hold the name and path strings. }; -/* The global hotplug thread and associated objects. */ +// The global hotplug thread and associated objects. static struct pw_thread_loop *hotplug_loop; static struct pw_core *hotplug_core; static struct pw_context *hotplug_context; @@ -291,13 +292,13 @@ static SDL_bool hotplug_events_enabled; static char *pipewire_default_sink_id = NULL; static char *pipewire_default_source_id = NULL; -/* The active node list */ +// The active node list static SDL_bool io_list_check_add(struct io_node *node) { struct io_node *n; SDL_bool ret = SDL_TRUE; - /* See if the node is already in the list */ + // See if the node is already in the list spa_list_for_each (n, &hotplug_io_list, link) { if (n->id == node->id) { ret = SDL_FALSE; @@ -305,7 +306,7 @@ static SDL_bool io_list_check_add(struct io_node *node) } } - /* Add to the list if the node doesn't already exist */ + // Add to the list if the node doesn't already exist spa_list_append(&hotplug_io_list, &node->link); if (hotplug_events_enabled) { @@ -321,7 +322,7 @@ static void io_list_remove(Uint32 id) { struct io_node *n, *temp; - /* Find and remove the node from the list */ + // Find and remove the node from the list spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { if (n->id == id) { spa_list_remove(&n->link); @@ -360,7 +361,7 @@ static struct io_node *io_list_get_by_id(Uint32 id) static void node_object_destroy(struct node_object *node) { - SDL_assert(node); + SDL_assert(node != NULL); spa_list_remove(&node->link); spa_hook_remove(&node->node_listener); @@ -369,10 +370,10 @@ static void node_object_destroy(struct node_object *node) PIPEWIRE_pw_proxy_destroy(node->proxy); } -/* The pending node list */ +// The pending node list static void pending_list_add(struct node_object *node) { - SDL_assert(node); + SDL_assert(node != NULL); spa_list_append(&hotplug_pending_list, &node->link); } @@ -401,9 +402,9 @@ static void *node_object_new(Uint32 id, const char *type, Uint32 version, const struct pw_proxy *proxy; struct node_object *node; - /* Create the proxy object */ + // Create the proxy object proxy = pw_registry_bind(hotplug_registry, id, type, version, sizeof(struct node_object)); - if (proxy == NULL) { + if (!proxy) { SDL_SetError("Pipewire: Failed to create proxy object (%i)", errno); return NULL; } @@ -414,24 +415,24 @@ static void *node_object_new(Uint32 id, const char *type, Uint32 version, const node->id = id; node->proxy = proxy; - /* Add the callbacks */ + // Add the callbacks pw_core_add_listener(hotplug_core, &node->core_listener, core_events, node); PIPEWIRE_pw_proxy_add_object_listener(node->proxy, &node->node_listener, funcs, node); - /* Add the node to the active list */ + // Add the node to the active list pending_list_add(node); return node; } -/* Core sync points */ +// Core sync points static void core_events_hotplug_init_callback(void *object, uint32_t id, int seq) { if (id == PW_ID_CORE && seq == hotplug_init_seq_val) { - /* This core listener is no longer needed. */ + // This core listener is no longer needed. spa_hook_remove(&hotplug_core_listener); - /* Signal that the initial I/O list is populated */ + // Signal that the initial I/O list is populated hotplug_init_complete = SDL_TRUE; PIPEWIRE_pw_thread_loop_signal(hotplug_loop, false); } @@ -483,7 +484,7 @@ static void hotplug_core_sync(struct node_object *node) } } -/* Helpers for retrieving values from params */ +// Helpers for retrieving values from params static SDL_bool get_range_param(const struct spa_pod *param, Uint32 key, int *def, int *min, int *max) { const struct spa_pod_prop *prop; @@ -535,7 +536,7 @@ static SDL_bool get_int_param(const struct spa_pod *param, Uint32 key, int *val) return SDL_FALSE; } -/* Interface node callbacks */ +// Interface node callbacks static void node_event_info(void *object, const struct pw_node_info *info) { struct node_object *node = object; @@ -549,7 +550,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) io->spec.channels = (Uint8)SDL_atoi(prop_val); } - /* Need to parse the parameters to get the sample rate */ + // Need to parse the parameters to get the sample rate for (i = 0; i < info->n_params; ++i) { pw_node_enum_params(node->proxy, 0, info->params[i].id, 0, 0, NULL); } @@ -563,7 +564,7 @@ static void node_event_param(void *object, int seq, uint32_t id, uint32_t index, struct node_object *node = object; struct io_node *io = node->userdata; - /* Get the default frequency */ + // Get the default frequency if (io->spec.freq == 0) { get_range_param(param, SPA_FORMAT_AUDIO_rate, &io->spec.freq, NULL, NULL); } @@ -586,19 +587,19 @@ static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVE static char *get_name_from_json(const char *json) { struct spa_json parser[2]; - char key[7]; /* "name" */ + char key[7]; // "name" char value[PW_MAX_IDENTIFIER_LENGTH]; spa_json_init(&parser[0], json, SDL_strlen(json)); if (spa_json_enter_object(&parser[0], &parser[1]) <= 0) { - /* Not actually JSON */ + // Not actually JSON return NULL; } if (spa_json_get_string(&parser[1], key, sizeof(key)) <= 0) { - /* Not actually a key/value pair */ + // Not actually a key/value pair return NULL; } if (spa_json_get_string(&parser[1], value, sizeof(value)) <= 0) { - /* Somehow had a key with no value? */ + // Somehow had a key with no value? return NULL; } return SDL_strdup(value); @@ -617,21 +618,21 @@ static void change_default_device(const char *path) } } -/* Metadata node callback */ +// Metadata node callback static int metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value) { struct node_object *node = object; - if (subject == PW_ID_CORE && key != NULL && value != NULL) { + if (subject == PW_ID_CORE && key && value) { if (!SDL_strcmp(key, "default.audio.sink")) { - if (pipewire_default_sink_id != NULL) { + if (pipewire_default_sink_id) { SDL_free(pipewire_default_sink_id); } pipewire_default_sink_id = get_name_from_json(value); node->persist = SDL_TRUE; change_default_device(pipewire_default_sink_id); } else if (!SDL_strcmp(key, "default.audio.source")) { - if (pipewire_default_source_id != NULL) { + if (pipewire_default_source_id) { SDL_free(pipewire_default_source_id); } pipewire_default_source_id = get_name_from_json(value); @@ -645,13 +646,13 @@ static int metadata_property(void *object, Uint32 subject, const char *key, cons static const struct pw_metadata_events metadata_node_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property }; -/* Global registry callbacks */ +// Global registry callbacks static void registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct node_object *node; - /* We're only interested in interface and metadata nodes. */ + // We're only interested in interface and metadata nodes. if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Node)) { const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); @@ -663,7 +664,7 @@ static void registry_event_global_callback(void *object, uint32_t id, uint32_t p int desc_buffer_len; int path_buffer_len; - /* Just want sink and capture */ + // Just want sink and capture if (!SDL_strcasecmp(media_class, "Audio/Sink")) { is_capture = SDL_FALSE; } else if (!SDL_strcasecmp(media_class, "Audio/Source")) { @@ -677,42 +678,41 @@ static void registry_event_global_callback(void *object, uint32_t id, uint32_t p if (node_desc && node_path) { node = node_object_new(id, type, version, &interface_node_events, &interface_core_events); - if (node == NULL) { + if (!node) { SDL_SetError("Pipewire: Failed to allocate interface node"); return; } - /* Allocate and initialize the I/O node information struct */ + // Allocate and initialize the I/O node information struct desc_buffer_len = SDL_strlen(node_desc) + 1; path_buffer_len = SDL_strlen(node_path) + 1; node->userdata = io = SDL_calloc(1, sizeof(struct io_node) + desc_buffer_len + path_buffer_len); - if (io == NULL) { + if (!io) { node_object_destroy(node); - SDL_OutOfMemory(); return; } - /* Begin setting the node properties */ + // Begin setting the node properties io->id = id; io->is_capture = is_capture; - io->spec.format = SDL_AUDIO_F32; /* Pipewire uses floats internally, other formats require conversion. */ + io->spec.format = SDL_AUDIO_F32; // Pipewire uses floats internally, other formats require conversion. io->name = io->buf; io->path = io->buf + desc_buffer_len; SDL_strlcpy(io->buf, node_desc, desc_buffer_len); SDL_strlcpy(io->buf + desc_buffer_len, node_path, path_buffer_len); - /* Update sync points */ + // Update sync points hotplug_core_sync(node); } } } else if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Metadata)) { node = node_object_new(id, type, version, &metadata_node_events, &metadata_core_events); - if (node == NULL) { + if (!node) { SDL_SetError("Pipewire: Failed to allocate metadata node"); return; } - /* Update sync points */ + // Update sync points hotplug_core_sync(node); } } @@ -726,7 +726,7 @@ static void registry_event_remove_callback(void *object, uint32_t id) static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global_callback, .global_remove = registry_event_remove_callback }; -/* The hotplug thread */ +// The hotplug thread static int hotplug_loop_init(void) { int res; @@ -735,22 +735,22 @@ static int hotplug_loop_init(void) spa_list_init(&hotplug_io_list); hotplug_loop = PIPEWIRE_pw_thread_loop_new("SDLAudioHotplug", NULL); - if (hotplug_loop == NULL) { + if (!hotplug_loop) { return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno); } hotplug_context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug_loop), NULL, 0); - if (hotplug_context == NULL) { + if (!hotplug_context) { return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno); } hotplug_core = PIPEWIRE_pw_context_connect(hotplug_context, NULL, 0); - if (hotplug_core == NULL) { + if (!hotplug_core) { return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno); } hotplug_registry = pw_core_get_registry(hotplug_core, PW_VERSION_REGISTRY, 0); - if (hotplug_registry == NULL) { + if (!hotplug_registry) { return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno); } @@ -782,11 +782,11 @@ static void hotplug_loop_destroy(void) hotplug_init_complete = SDL_FALSE; hotplug_events_enabled = SDL_FALSE; - if (pipewire_default_sink_id != NULL) { + if (pipewire_default_sink_id) { SDL_free(pipewire_default_sink_id); pipewire_default_sink_id = NULL; } - if (pipewire_default_source_id != NULL) { + if (pipewire_default_source_id) { SDL_free(pipewire_default_source_id); pipewire_default_source_id = NULL; } @@ -818,17 +818,17 @@ static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDe PIPEWIRE_pw_thread_loop_lock(hotplug_loop); - /* Wait until the initial registry enumeration is complete */ + // Wait until the initial registry enumeration is complete if (!hotplug_init_complete) { PIPEWIRE_pw_thread_loop_wait(hotplug_loop); } spa_list_for_each (io, &hotplug_io_list, link) { SDL_AudioDevice *device = SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); - if (pipewire_default_sink_id != NULL && SDL_strcmp(io->path, pipewire_default_sink_id) == 0) { + if (pipewire_default_sink_id && SDL_strcmp(io->path, pipewire_default_sink_id) == 0) { SDL_assert(!io->is_capture); *default_output = device; - } else if (pipewire_default_source_id != NULL && SDL_strcmp(io->path, pipewire_default_source_id) == 0) { + } else if (pipewire_default_source_id && SDL_strcmp(io->path, pipewire_default_source_id) == 0) { SDL_assert(io->is_capture); *default_capture = device; } @@ -839,7 +839,7 @@ static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDe PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); } -/* Channel maps that match the order in SDL_Audio.h */ +// Channel maps that match the order in SDL_Audio.h static const enum spa_audio_channel PIPEWIRE_channel_map_1[] = { SPA_AUDIO_CHANNEL_MONO }; static const enum spa_audio_channel PIPEWIRE_channel_map_2[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }; static const enum spa_audio_channel PIPEWIRE_channel_map_3[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE }; @@ -890,7 +890,7 @@ static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info break; } - /* Pipewire natively supports all of SDL's sample formats */ + // Pipewire natively supports all of SDL's sample formats switch (spec->format) { case SDL_AUDIO_U8: info->format = SPA_AUDIO_FORMAT_U8; @@ -964,7 +964,7 @@ static void PIPEWIRE_FlushCapture(SDL_AudioDevice *device) { struct pw_stream *stream = device->hidden->stream; struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); - if (pw_buf != NULL) { // just requeue it without any further thought. + if (pw_buf) { // just requeue it without any further thought. PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); } } @@ -1061,27 +1061,27 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) struct SDL_PrivateAudioData *priv; struct pw_properties *props; const char *app_name, *app_id, *stream_name, *stream_role, *error; - Uint32 node_id = device->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle); + Uint32 node_id = !device->handle ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle); const SDL_bool iscapture = device->iscapture; int res; - /* Clamp the period size to sane values */ + // Clamp the period size to sane values const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1); - /* Get the hints for the application name, stream name and role */ + // Get the hints for the application name, stream name and role app_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME); - if (app_name == NULL || *app_name == '\0') { + if (!app_name || *app_name == '\0') { app_name = SDL_GetHint(SDL_HINT_APP_NAME); - if (app_name == NULL || *app_name == '\0') { + if (!app_name || *app_name == '\0') { app_name = "SDL Application"; } } - /* App ID. Default to NULL if not available. */ + // App ID. Default to NULL if not available. app_id = SDL_GetHint(SDL_HINT_APP_ID); stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); - if (stream_name == NULL || *stream_name == '\0') { + if (!stream_name || *stream_name == '\0') { stream_name = "Audio Stream"; } @@ -1090,24 +1090,24 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) * but 'Game' seems more appropriate for the majority of SDL applications. */ stream_role = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); - if (stream_role == NULL || *stream_role == '\0') { + if (!stream_role || *stream_role == '\0') { stream_role = "Game"; } - /* Initialize the Pipewire stream info from the SDL audio spec */ + // Initialize the Pipewire stream info from the SDL audio spec initialize_spa_info(&device->spec, &spa_info); params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info); - if (params == NULL) { + if (!params) { return SDL_SetError("Pipewire: Failed to set audio format parameters"); } priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData)); device->hidden = priv; - if (priv == NULL) { - return SDL_OutOfMemory(); + if (!priv) { + return -1; } - /* Size of a single audio frame in bytes */ + // Size of a single audio frame in bytes priv->stride = SDL_AUDIO_FRAMESIZE(device->spec); if (device->sample_frames < min_period) { @@ -1118,23 +1118,23 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) SDL_GetAudioThreadName(device, thread_name, sizeof(thread_name)); priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL); - if (priv->loop == NULL) { + if (!priv->loop) { return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno); } - /* Load the realtime module so Pipewire can set the loop thread to the appropriate priority. */ + // Load the realtime module so Pipewire can set the loop thread to the appropriate priority. props = PIPEWIRE_pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL); - if (props == NULL) { + if (!props) { return SDL_SetError("Pipewire: Failed to create stream context properties (%i)", errno); } priv->context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), props, 0); - if (priv->context == NULL) { + if (!priv->context) { return SDL_SetError("Pipewire: Failed to create stream context (%i)", errno); } props = PIPEWIRE_pw_properties_new(NULL, NULL); - if (props == NULL) { + if (!props) { return SDL_SetError("Pipewire: Failed to create stream properties (%i)", errno); } @@ -1142,7 +1142,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, iscapture ? "Capture" : "Playback"); PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_ROLE, stream_role); PIPEWIRE_pw_properties_set(props, PW_KEY_APP_NAME, app_name); - if (app_id != NULL) { + if (app_id) { PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ID, app_id); } PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name); @@ -1164,7 +1164,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) PIPEWIRE_pw_thread_loop_lock(hotplug_loop); node = io_list_get_by_id(node_id); - if (node != NULL) { + if (node) { PIPEWIRE_pw_properties_set(props, PW_KEY_TARGET_OBJECT, node->path); } PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); @@ -1173,10 +1173,10 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) } } - /* Create the new stream */ + // Create the new stream priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props, iscapture ? &stream_input_events : &stream_output_events, device); - if (priv->stream == NULL) { + if (!priv->stream) { return SDL_SetError("Pipewire: Failed to create stream (%i)", errno); } @@ -1191,7 +1191,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Pipewire: Failed to start stream loop"); } - /* Wait until all init flags are set or the stream has failed. */ + // Wait until all init flags are set or the stream has failed. PIPEWIRE_pw_thread_loop_lock(priv->loop); while (priv->stream_init_status != PW_READY_FLAG_ALL_BITS && PIPEWIRE_pw_stream_get_state(priv->stream, NULL) != PW_STREAM_STATE_ERROR) { @@ -1234,10 +1234,16 @@ static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device) SDL_AudioThreadFinalize(device); } -static void PIPEWIRE_Deinitialize(void) +static void PIPEWIRE_DeinitializeStart(void) { if (pipewire_initialized) { hotplug_loop_destroy(); + } +} + +static void PIPEWIRE_Deinitialize(void) +{ + if (pipewire_initialized) { deinit_pipewire_library(); pipewire_initialized = SDL_FALSE; } @@ -1258,9 +1264,9 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) } } - /* Set the function pointers */ impl->DetectDevices = PIPEWIRE_DetectDevices; impl->OpenDevice = PIPEWIRE_OpenDevice; + impl->DeinitializeStart = PIPEWIRE_DeinitializeStart; impl->Deinitialize = PIPEWIRE_Deinitialize; impl->PlayDevice = PIPEWIRE_PlayDevice; impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf; @@ -1276,4 +1282,4 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) AudioBootStrap PIPEWIRE_bootstrap = { "pipewire", "Pipewire", PIPEWIRE_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_PIPEWIRE */ +#endif // SDL_AUDIO_DRIVER_PIPEWIRE diff --git a/src/audio/pipewire/SDL_pipewire.h b/src/audio/pipewire/SDL_pipewire.h index 5a6772ab..1b07185c 100644 --- a/src/audio/pipewire/SDL_pipewire.h +++ b/src/audio/pipewire/SDL_pipewire.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,11 +33,11 @@ struct SDL_PrivateAudioData struct pw_stream *stream; struct pw_context *context; - Sint32 stride; /* Bytes-per-frame */ + Sint32 stride; // Bytes-per-frame int stream_init_status; // Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice struct pw_buffer *pw_buf; }; -#endif /* SDL_pipewire_h_ */ +#endif // SDL_pipewire_h_ diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index 2165194e..a8230715 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_ps2audio.h" #include @@ -30,8 +30,8 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) { device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // These are the native supported audio PS2 configs @@ -73,7 +73,7 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) 64, so spec->size should be a multiple of 64 as well. */ const int mixlen = device->buffer_size * NUM_BUFFERS; device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (device->hidden->rawbuf == NULL) { + if (!device->hidden->rawbuf) { return SDL_SetError("Couldn't allocate mixing buffer"); } @@ -91,9 +91,10 @@ static int PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int return (audsrv_play_audio((char *)buffer, buflen) != buflen) ? -1 : 0; } -static void PS2AUDIO_WaitDevice(SDL_AudioDevice *device) +static int PS2AUDIO_WaitDevice(SDL_AudioDevice *device) { audsrv_wait_audio(device->buffer_size); + return 0; } static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) @@ -111,7 +112,7 @@ static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device) device->hidden->channel = -1; } - if (device->hidden->rawbuf != NULL) { + if (device->hidden->rawbuf) { SDL_aligned_free(device->hidden->rawbuf); device->hidden->rawbuf = NULL; } diff --git a/src/audio/ps2/SDL_ps2audio.h b/src/audio/ps2/SDL_ps2audio.h index b4ed26f1..d75cb8bf 100644 --- a/src/audio/ps2/SDL_ps2audio.h +++ b/src/audio/ps2/SDL_ps2audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,14 +29,14 @@ struct SDL_PrivateAudioData { - /* The hardware output channel. */ + // The hardware output channel. int channel; - /* The raw allocated mixing buffer. */ + // The raw allocated mixing buffer. Uint8 *rawbuf; - /* Individual mixing buffers. */ + // Individual mixing buffers. Uint8 *mixbufs[NUM_BUFFERS]; - /* Index of the next available mixing buffer. */ + // Index of the next available mixing buffer. int next_buffer; }; -#endif /* SDL_ps2audio_h_ */ +#endif // SDL_ps2audio_h_ diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c index d701b5f2..5783e7df 100644 --- a/src/audio/psp/SDL_pspaudio.c +++ b/src/audio/psp/SDL_pspaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,6 @@ #include #include -#include "../SDL_audio_c.h" #include "../SDL_audiodev_c.h" #include "../SDL_sysaudio.h" #include "SDL_pspaudio.h" @@ -42,8 +41,8 @@ static inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec) static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) { device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // device only natively supports S16LSB @@ -94,7 +93,7 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) 64, so spec->size should be a multiple of 64 as well. */ const int mixlen = device->buffer_size * NUM_BUFFERS; device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (device->hidden->rawbuf == NULL) { + if (!device->hidden->rawbuf) { return SDL_SetError("Couldn't allocate mixing buffer"); } @@ -118,9 +117,9 @@ static int PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int return (rc == 0) ? 0 : -1; } -static void PSPAUDIO_WaitDevice(SDL_AudioDevice *device) +static int PSPAUDIO_WaitDevice(SDL_AudioDevice *device) { - // Because we block when sending audio, there's no need for this function to do anything. + return 0; // Because we block when sending audio, there's no need for this function to do anything. } static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) @@ -142,7 +141,7 @@ static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device) device->hidden->channel = -1; } - if (device->hidden->rawbuf != NULL) { + if (device->hidden->rawbuf) { SDL_aligned_free(device->hidden->rawbuf); device->hidden->rawbuf = NULL; } diff --git a/src/audio/psp/SDL_pspaudio.h b/src/audio/psp/SDL_pspaudio.h index c030db82..6b3c0006 100644 --- a/src/audio/psp/SDL_pspaudio.h +++ b/src/audio/psp/SDL_pspaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,14 +28,14 @@ struct SDL_PrivateAudioData { - /* The hardware output channel. */ + // The hardware output channel. int channel; - /* The raw allocated mixing buffer. */ + // The raw allocated mixing buffer. Uint8 *rawbuf; - /* Individual mixing buffers. */ + // Individual mixing buffers. Uint8 *mixbufs[NUM_BUFFERS]; - /* Index of the next available mixing buffer. */ + // Index of the next available mixing buffer. int next_buffer; }; -#endif /* SDL_pspaudio_h_ */ +#endif // SDL_pspaudio_h_ diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index a0069cf2..2d0ca100 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,7 +23,7 @@ #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO -/* Allow access to a raw mixing buffer */ +// Allow access to a raw mixing buffer #ifdef HAVE_SIGNAL_H #include @@ -31,11 +31,21 @@ #include #include -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_pulseaudio.h" #include "../../thread/SDL_systhread.h" -/* should we include monitors in the device list? Set at SDL_Init time */ +#if (PA_PROTOCOL_VERSION < 28) +typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata); +#endif + +typedef struct PulseDeviceHandle +{ + char *device_path; + uint32_t device_index; +} PulseDeviceHandle; + +// should we include monitors in the device list? Set at SDL_Init time static SDL_bool include_monitors = SDL_FALSE; static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL; @@ -43,12 +53,14 @@ static pa_context *pulseaudio_context = NULL; static SDL_Thread *pulseaudio_hotplug_thread = NULL; static SDL_AtomicInt pulseaudio_hotplug_thread_active; -// These are the OS identifiers (i.e. ALSA strings)... +// These are the OS identifiers (i.e. ALSA strings)...these are allocated in a callback +// when the default changes, and noticed by the hotplug thread when it alerts SDL +// to the change. static char *default_sink_path = NULL; static char *default_source_path = NULL; -// ... and these are the PulseAudio device indices of the default devices. -static uint32_t default_sink_index = 0; -static uint32_t default_source_index = 0; +static SDL_bool default_sink_changed = SDL_FALSE; +static SDL_bool default_source_changed = SDL_FALSE; + static const char *(*PULSEAUDIO_pa_get_library_version)(void); static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)( @@ -94,11 +106,13 @@ static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *, const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *); static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *, const pa_buffer_attr *, pa_stream_flags_t); +static const pa_buffer_attr *(*PULSEAUDIO_pa_stream_get_buffer_attr)(pa_stream *); static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state)(const pa_stream *); static size_t (*PULSEAUDIO_pa_stream_writable_size)(const pa_stream *); static size_t (*PULSEAUDIO_pa_stream_readable_size)(const pa_stream *); static int (*PULSEAUDIO_pa_stream_write)(pa_stream *, const void *, size_t, pa_free_cb_t, int64_t, pa_seek_mode_t); +static int (*PULSEAUDIO_pa_stream_begin_write)(pa_stream *, void **, size_t *); static pa_operation *(*PULSEAUDIO_pa_stream_drain)(pa_stream *, pa_stream_success_cb_t, void *); static int (*PULSEAUDIO_pa_stream_peek)(pa_stream *, const void **, size_t *); @@ -121,22 +135,22 @@ static void *pulseaudio_handle = NULL; static int load_pulseaudio_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(pulseaudio_handle, fn); - if (*addr == NULL) { - /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ + if (!*addr) { + // Don't call SDL_SetError(): SDL_LoadFunction already did. return 0; } return 1; } -/* cast funcs to char* first, to please GCC's strict aliasing rules. */ +// cast funcs to char* first, to please GCC's strict aliasing rules. #define SDL_PULSEAUDIO_SYM(x) \ if (!load_pulseaudio_sym(#x, (void **)(char *)&PULSEAUDIO_##x)) \ return -1 static void UnloadPulseAudioLibrary(void) { - if (pulseaudio_handle != NULL) { + if (pulseaudio_handle) { SDL_UnloadObject(pulseaudio_handle); pulseaudio_handle = NULL; } @@ -145,11 +159,11 @@ static void UnloadPulseAudioLibrary(void) static int LoadPulseAudioLibrary(void) { int retval = 0; - if (pulseaudio_handle == NULL) { + if (!pulseaudio_handle) { pulseaudio_handle = SDL_LoadObject(pulseaudio_library); - if (pulseaudio_handle == NULL) { + if (!pulseaudio_handle) { retval = -1; - /* Don't call SDL_SetError(): SDL_LoadObject already did. */ + // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_pulseaudio_syms(); if (retval < 0) { @@ -174,7 +188,7 @@ static int LoadPulseAudioLibrary(void) return 0; } -#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ +#endif // SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC static int load_pulseaudio_syms(void) { @@ -188,10 +202,8 @@ static int load_pulseaudio_syms(void) SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_wait); SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_signal); SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_free); - SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_set_name); SDL_PULSEAUDIO_SYM(pa_operation_get_state); SDL_PULSEAUDIO_SYM(pa_operation_cancel); - SDL_PULSEAUDIO_SYM(pa_operation_set_state_callback); SDL_PULSEAUDIO_SYM(pa_operation_unref); SDL_PULSEAUDIO_SYM(pa_context_new); SDL_PULSEAUDIO_SYM(pa_context_set_state_callback); @@ -209,9 +221,11 @@ static int load_pulseaudio_syms(void) SDL_PULSEAUDIO_SYM(pa_stream_set_state_callback); SDL_PULSEAUDIO_SYM(pa_stream_connect_playback); SDL_PULSEAUDIO_SYM(pa_stream_connect_record); + SDL_PULSEAUDIO_SYM(pa_stream_get_buffer_attr); SDL_PULSEAUDIO_SYM(pa_stream_get_state); SDL_PULSEAUDIO_SYM(pa_stream_writable_size); SDL_PULSEAUDIO_SYM(pa_stream_readable_size); + SDL_PULSEAUDIO_SYM(pa_stream_begin_write); SDL_PULSEAUDIO_SYM(pa_stream_write); SDL_PULSEAUDIO_SYM(pa_stream_drain); SDL_PULSEAUDIO_SYM(pa_stream_disconnect); @@ -225,6 +239,21 @@ static int load_pulseaudio_syms(void) SDL_PULSEAUDIO_SYM(pa_stream_set_read_callback); SDL_PULSEAUDIO_SYM(pa_context_get_server_info); + // optional +#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC + load_pulseaudio_sym("pa_operation_set_state_callback", (void **)(char *)&PULSEAUDIO_pa_operation_set_state_callback); // needs pulseaudio 4.0 + load_pulseaudio_sym("pa_threaded_mainloop_set_name", (void **)(char *)&PULSEAUDIO_pa_threaded_mainloop_set_name); // needs pulseaudio 5.0 +#elif (PA_PROTOCOL_VERSION >= 29) + PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback; + PULSEAUDIO_pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name; +#elif (PA_PROTOCOL_VERSION >= 28) + PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback; + PULSEAUDIO_pa_threaded_mainloop_set_name = NULL; +#else + PULSEAUDIO_pa_operation_set_state_callback = NULL; + PULSEAUDIO_pa_threaded_mainloop_set_name = NULL; +#endif + return 0; } @@ -233,7 +262,7 @@ static SDL_INLINE int squashVersion(const int major, const int minor, const int return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF); } -/* Workaround for older pulse: pa_context_new() must have non-NULL appname */ +// Workaround for older pulse: pa_context_new() must have non-NULL appname static const char *getAppName(void) { const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME); @@ -245,12 +274,12 @@ static const char *getAppName(void) return retval; } else { const char *verstr = PULSEAUDIO_pa_get_library_version(); - retval = "SDL Application"; /* the "oh well" default. */ - if (verstr != NULL) { + retval = "SDL Application"; // the "oh well" default. + if (verstr) { int maj, min, patch; if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) { if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) { - retval = NULL; /* 0.9.15+ handles NULL correctly. */ + retval = NULL; // 0.9.15+ handles NULL correctly. } } } @@ -267,12 +296,18 @@ static void OperationStateChangeCallback(pa_operation *o, void *userdata) you did the work in the callback and just want to know it's done, though. */ static void WaitForPulseOperation(pa_operation *o) { - /* This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. */ + // This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about. SDL_assert(pulseaudio_threaded_mainloop != NULL); if (o) { - PULSEAUDIO_pa_operation_set_state_callback(o, OperationStateChangeCallback, NULL); + // note that if PULSEAUDIO_pa_operation_set_state_callback == NULL, then `o` must have a callback that will signal pulseaudio_threaded_mainloop. + // If not, on really old (earlier PulseAudio 4.0, from the year 2013!) installs, this call will block forever. + // On more modern installs, we won't ever block forever, and maybe be more efficient, thanks to pa_operation_set_state_callback. + // WARNING: at the time of this writing: the Steam Runtime is still on PulseAudio 1.1! + if (PULSEAUDIO_pa_operation_set_state_callback) { + PULSEAUDIO_pa_operation_set_state_callback(o, OperationStateChangeCallback, NULL); + } while (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING) { - PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); /* this releases the lock and blocks on an internal condition variable. */ + PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); // this releases the lock and blocks on an internal condition variable. } PULSEAUDIO_pa_operation_unref(o); } @@ -280,13 +315,15 @@ static void WaitForPulseOperation(pa_operation *o) static void DisconnectFromPulseServer(void) { + if (pulseaudio_threaded_mainloop) { + PULSEAUDIO_pa_threaded_mainloop_stop(pulseaudio_threaded_mainloop); + } if (pulseaudio_context) { PULSEAUDIO_pa_context_disconnect(pulseaudio_context); PULSEAUDIO_pa_context_unref(pulseaudio_context); pulseaudio_context = NULL; } - if (pulseaudio_threaded_mainloop != NULL) { - PULSEAUDIO_pa_threaded_mainloop_stop(pulseaudio_threaded_mainloop); + if (pulseaudio_threaded_mainloop) { PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop); pulseaudio_threaded_mainloop = NULL; } @@ -294,7 +331,7 @@ static void DisconnectFromPulseServer(void) static void PulseContextStateChangeCallback(pa_context *context, void *userdata) { - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* just signal any waiting code, it can look up the details. */ + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static int ConnectToPulseServer(void) @@ -305,12 +342,14 @@ static int ConnectToPulseServer(void) SDL_assert(pulseaudio_threaded_mainloop == NULL); SDL_assert(pulseaudio_context == NULL); - /* Set up a new main loop */ + // Set up a new main loop if (!(pulseaudio_threaded_mainloop = PULSEAUDIO_pa_threaded_mainloop_new())) { return SDL_SetError("pa_threaded_mainloop_new() failed"); } - PULSEAUDIO_pa_threaded_mainloop_set_name(pulseaudio_threaded_mainloop, "PulseMainloop"); + if (PULSEAUDIO_pa_threaded_mainloop_set_name) { + PULSEAUDIO_pa_threaded_mainloop_set_name(pulseaudio_threaded_mainloop, "PulseMainloop"); + } if (PULSEAUDIO_pa_threaded_mainloop_start(pulseaudio_threaded_mainloop) < 0) { PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop); @@ -321,17 +360,17 @@ static int ConnectToPulseServer(void) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); mainloop_api = PULSEAUDIO_pa_threaded_mainloop_get_api(pulseaudio_threaded_mainloop); - SDL_assert(mainloop_api); /* this never fails, right? */ + SDL_assert(mainloop_api != NULL); // this never fails, right? pulseaudio_context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName()); - if (pulseaudio_context == NULL) { + if (!pulseaudio_context) { SDL_SetError("pa_context_new() failed"); goto failed; } PULSEAUDIO_pa_context_set_state_callback(pulseaudio_context, PulseContextStateChangeCallback, NULL); - /* Connect to the PulseAudio server */ + // Connect to the PulseAudio server if (PULSEAUDIO_pa_context_connect(pulseaudio_context, NULL, 0, NULL) < 0) { SDL_SetError("Could not setup connection to PulseAudio"); goto failed; @@ -350,7 +389,7 @@ static int ConnectToPulseServer(void) PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - return 0; /* connected and ready! */ + return 0; // connected and ready! failed: PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); @@ -361,38 +400,42 @@ failed: static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata) { struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata; - /*printf("PULSEAUDIO WRITE CALLBACK! nbytes=%u\n", (unsigned int) nbytes);*/ + //SDL_Log("PULSEAUDIO WRITE CALLBACK! nbytes=%u", (unsigned int) nbytes); h->bytes_requested += nbytes; PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -/* This function waits until it is possible to write a full sound buffer */ -static void PULSEAUDIO_WaitDevice(SDL_AudioDevice *device) +// This function waits until it is possible to write a full sound buffer +static int PULSEAUDIO_WaitDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *h = device->hidden; + int retval = 0; - /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/ + //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - while (!SDL_AtomicGet(&device->shutdown) && (h->bytes_requested < (device->buffer_size / 2))) { - /*printf("PULSEAUDIO WAIT IN WAITDEVICE!\n");*/ + while (!SDL_AtomicGet(&device->shutdown) && (h->bytes_requested == 0)) { + //SDL_Log("PULSEAUDIO WAIT IN WAITDEVICE!"); PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { - /*printf("PULSEAUDIO DEVICE FAILURE IN WAITDEVICE!\n");*/ - SDL_AudioDeviceDisconnected(device); + //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITDEVICE!"); + retval = -1; break; } } + PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + + return retval; } static int PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { struct SDL_PrivateAudioData *h = device->hidden; - /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/ + //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available); SDL_assert(h->bytes_requested >= buffer_size); @@ -404,41 +447,52 @@ static int PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, i return -1; } - /*printf("PULSEAUDIO FEED! nbytes=%d\n", buffer_size);*/ + //SDL_Log("PULSEAUDIO FEED! nbytes=%d", buffer_size); h->bytes_requested -= buffer_size; - /*printf("PULSEAUDIO PLAYDEVICE END! written=%d\n", written);*/ + //SDL_Log("PULSEAUDIO PLAYDEVICE END! written=%d", written); return 0; } static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { struct SDL_PrivateAudioData *h = device->hidden; - *buffer_size = SDL_min(*buffer_size, h->bytes_requested); + const size_t reqsize = (size_t) SDL_min(*buffer_size, h->bytes_requested); + size_t nbytes = reqsize; + void *data = NULL; + if (PULSEAUDIO_pa_stream_begin_write(h->stream, &data, &nbytes) == 0) { + *buffer_size = (int) nbytes; + return (Uint8 *) data; + } + + // don't know why this would fail, but we'll fall back just in case. + *buffer_size = (int) reqsize; return device->hidden->mixbuf; } static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) { - /*printf("PULSEAUDIO READ CALLBACK! nbytes=%u\n", (unsigned int) nbytes);*/ - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* the capture code queries what it needs, we just need to signal to end any wait */ + //SDL_Log("PULSEAUDIO READ CALLBACK! nbytes=%u", (unsigned int) nbytes); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // the capture code queries what it needs, we just need to signal to end any wait } -static void PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device) +static int PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *h = device->hidden; - if (h->capturebuf != NULL) { - return; // there's still data available to read. + if (h->capturebuf) { + return 0; // there's still data available to read. } + int retval = 0; + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); while (!SDL_AtomicGet(&device->shutdown)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { - //printf("PULSEAUDIO DEVICE FAILURE IN WAITCAPTUREDEVICE!\n"); - SDL_AudioDeviceDisconnected(device); + //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITCAPTUREDEVICE!"); + retval = -1; break; } else if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) { // a new fragment is available! @@ -446,11 +500,11 @@ static void PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device) size_t nbytes = 0; PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); SDL_assert(nbytes > 0); - if (data == NULL) { // If NULL, then the buffer had a hole, ignore that + if (!data) { // If NULL, then the buffer had a hole, ignore that PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment. } else { // store this fragment's data for use with CaptureFromDevice - //printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes); + //SDL_Log("PULSEAUDIO: captured %d new bytes", (int) nbytes); h->capturebuf = (const Uint8 *)data; h->capturelen = nbytes; break; @@ -459,16 +513,18 @@ static void PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device) } PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + + return retval; } static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { struct SDL_PrivateAudioData *h = device->hidden; - if (h->capturebuf != NULL) { + if (h->capturebuf) { const int cpy = SDL_min(buflen, h->capturelen); if (cpy > 0) { - //printf("PULSEAUDIO: fed %d captured bytes\n", cpy); + //SDL_Log("PULSEAUDIO: fed %d captured bytes", cpy); SDL_memcpy(buffer, h->capturebuf, cpy); h->capturebuf += cpy; h->capturelen -= cpy; @@ -479,7 +535,7 @@ static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, i PULSEAUDIO_pa_stream_drop(h->stream); // done with this fragment. PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); } - return cpy; /* new data, return it. */ + return cpy; // new data, return it. } return 0; @@ -493,7 +549,7 @@ static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - if (h->capturebuf != NULL) { + if (h->capturebuf) { PULSEAUDIO_pa_stream_drop(h->stream); h->capturebuf = NULL; h->capturelen = 0; @@ -502,15 +558,15 @@ static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) while (!SDL_AtomicGet(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { - /*printf("PULSEAUDIO DEVICE FAILURE IN FLUSHCAPTURE!\n");*/ + //SDL_Log("PULSEAUDIO DEVICE FAILURE IN FLUSHCAPTURE!"); SDL_AudioDeviceDisconnected(device); break; } if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) { - /* a new fragment is available! Just dump it. */ + // a new fragment is available! Just dump it. PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); - PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */ + PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment. } } @@ -522,7 +578,7 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); if (device->hidden->stream) { - if (device->hidden->capturebuf != NULL) { + if (device->hidden->capturebuf) { PULSEAUDIO_pa_stream_drop(device->hidden->stream); } PULSEAUDIO_pa_stream_disconnect(device->hidden->stream); @@ -532,46 +588,12 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); SDL_free(device->hidden->mixbuf); - SDL_free(device->hidden->device_name); SDL_free(device->hidden); } -static void SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) -{ - if (i) { - char **devname = (char **)data; - *devname = SDL_strdup(i->name); - } - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); -} - -static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) -{ - if (i) { - char **devname = (char **)data; - *devname = SDL_strdup(i->name); - } - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); -} - -static SDL_bool FindDeviceName(SDL_AudioDevice *device) -{ - struct SDL_PrivateAudioData *h = device->hidden; - SDL_assert(device->handle != NULL); // this was a thing in SDL2, but shouldn't be in SDL3. - const uint32_t idx = ((uint32_t)((intptr_t)device->handle)) - 1; - - if (device->iscapture) { - WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceDeviceNameCallback, &h->device_name)); - } else { - WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkDeviceNameCallback, &h->device_name)); - } - - return h->device_name != NULL; -} - static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) { - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* just signal any waiting code, it can look up the details. */ + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) @@ -590,17 +612,17 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_assert(pulseaudio_threaded_mainloop != NULL); SDL_assert(pulseaudio_context != NULL); - /* Initialize all variables that we clean on shutdown */ + // Initialize all variables that we clean on shutdown h = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } - /* Try for a closest match on audio format */ + // Try for a closest match on audio format closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { #ifdef DEBUG_AUDIO - fprintf(stderr, "Trying format 0x%4.4x\n", test_format); + SDL_Log("pulseaudio: Trying format 0x%4.4x", test_format); #endif switch (test_format) { case SDL_AUDIO_U8: @@ -635,14 +657,14 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) device->spec.format = test_format; paspec.format = format; - /* Calculate the final parameters for this audio specification */ + // Calculate the final parameters for this audio specification SDL_UpdatedAudioDeviceFormat(device); - /* Allocate mixing buffer */ + // Allocate mixing buffer if (!iscapture) { h->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (h->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!h->mixbuf) { + return -1; } SDL_memset(h->mixbuf, device->silence_value, device->buffer_size); } @@ -650,8 +672,8 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) paspec.channels = device->spec.channels; paspec.rate = device->spec.freq; - /* Reduced prebuffering compared to the defaults. */ - paattr.fragsize = device->buffer_size; + // Reduced prebuffering compared to the defaults. + paattr.fragsize = device->buffer_size; // despite the name, this is only used for capture devices, according to PulseAudio docs! paattr.tlength = device->buffer_size; paattr.prebuf = -1; paattr.maxlength = -1; @@ -660,50 +682,55 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - if (!FindDeviceName(device)) { - retval = SDL_SetError("Requested PulseAudio sink/source missing?"); + const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); + // The SDL ALSA output hints us that we use Windows' channel mapping + // https://bugzilla.libsdl.org/show_bug.cgi?id=110 + PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX); + + h->stream = PULSEAUDIO_pa_stream_new( + pulseaudio_context, + (name && *name) ? name : "Audio Stream", // stream description + &paspec, // sample format spec + &pacmap // channel map + ); + + if (!h->stream) { + retval = SDL_SetError("Could not set up PulseAudio stream"); } else { - const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); - /* The SDL ALSA output hints us that we use Windows' channel mapping */ - /* https://bugzilla.libsdl.org/show_bug.cgi?id=110 */ - PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX); + int rc; - h->stream = PULSEAUDIO_pa_stream_new( - pulseaudio_context, - (name && *name) ? name : "Audio Stream", /* stream description */ - &paspec, /* sample format spec */ - &pacmap /* channel map */ - ); + PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL); - if (h->stream == NULL) { - retval = SDL_SetError("Could not set up PulseAudio stream"); + // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream. + flags |= PA_STREAM_DONT_MOVE; + + const char *device_path = ((PulseDeviceHandle *) device->handle)->device_path; + if (iscapture) { + PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h); + rc = PULSEAUDIO_pa_stream_connect_record(h->stream, device_path, &paattr, flags); } else { - int rc; + PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h); + rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, device_path, &paattr, flags, NULL, NULL); + } - PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL); - - // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream. - flags |= PA_STREAM_DONT_MOVE; - - if (iscapture) { - PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h); - rc = PULSEAUDIO_pa_stream_connect_record(h->stream, h->device_name, &paattr, flags); - } else { - PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h); - rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, h->device_name, &paattr, flags, NULL, NULL); + if (rc < 0) { + retval = SDL_SetError("Could not connect PulseAudio stream"); + } else { + int state = PULSEAUDIO_pa_stream_get_state(h->stream); + while (PA_STREAM_IS_GOOD(state) && (state != PA_STREAM_READY)) { + PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); + state = PULSEAUDIO_pa_stream_get_state(h->stream); } - if (rc < 0) { + if (!PA_STREAM_IS_GOOD(state)) { retval = SDL_SetError("Could not connect PulseAudio stream"); } else { - int state = PULSEAUDIO_pa_stream_get_state(h->stream); - while (PA_STREAM_IS_GOOD(state) && (state != PA_STREAM_READY)) { - PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - state = PULSEAUDIO_pa_stream_get_state(h->stream); - } - - if (!PA_STREAM_IS_GOOD(state)) { - retval = SDL_SetError("Could not connect PulseAudio stream"); + const pa_buffer_attr *actual_bufattr = PULSEAUDIO_pa_stream_get_buffer_attr(h->stream); + if (!actual_bufattr) { + retval = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes"); + } else { + device->buffer_size = (int) iscapture ? actual_bufattr->tlength : actual_bufattr->fragsize; + device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); } } } @@ -711,11 +738,11 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - /* We're (hopefully) ready to rock and roll. :-) */ + // We're (hopefully) ready to rock and roll. :-) return retval; } -/* device handles are device index + 1, cast to void*, so we never pass a NULL. */ +// device handles are device index + 1, cast to void*, so we never pass a NULL. static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format) { @@ -739,76 +766,90 @@ static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format) } } +static void AddPulseAudioDevice(const SDL_bool iscapture, const char *description, const char *name, const uint32_t index, const pa_sample_spec *sample_spec) +{ + SDL_AudioSpec spec; + spec.format = PulseFormatToSDLFormat(sample_spec->format); + spec.channels = sample_spec->channels; + spec.freq = sample_spec->rate; + PulseDeviceHandle *handle = (PulseDeviceHandle *) SDL_malloc(sizeof (PulseDeviceHandle)); + if (handle) { + handle->device_path = SDL_strdup(name); + if (!handle->device_path) { + SDL_free(handle); + } else { + handle->device_index = index; + } + SDL_AddAudioDevice(iscapture, description, &spec, handle); + } +} + // This is called when PulseAudio adds an output ("sink") device. -// !!! FIXME: this is almost identical to SourceInfoCallback, merge the two. static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) { if (i) { - const SDL_bool add = (SDL_bool) ((intptr_t)data); - - if (add) { - SDL_AudioSpec spec; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.channels = i->sample_spec.channels; - spec.freq = i->sample_spec.rate; - SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *)((intptr_t)i->index + 1)); - } - - if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) { - default_sink_index = i->index; - } + AddPulseAudioDevice(SDL_FALSE, i->description, i->name, i->index, &i->sample_spec); } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -/* This is called when PulseAudio adds a capture ("source") device. */ -// !!! FIXME: this is almost identical to SinkInfoCallback, merge the two. +// This is called when PulseAudio adds a capture ("source") device. static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) { - /* Maybe skip "monitor" sources. These are just output from other sinks. */ + // Maybe skip "monitor" sources. These are just output from other sinks. if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { - const SDL_bool add = (SDL_bool) ((intptr_t)data); - - if (add) { - SDL_AudioSpec spec; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.channels = i->sample_spec.channels; - spec.freq = i->sample_spec.rate; - SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *)((intptr_t)i->index + 1)); - } - - if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { - default_source_index = i->index; - } + AddPulseAudioDevice(SDL_TRUE, i->description, i->name, i->index, &i->sample_spec); } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) { - if (!default_sink_path || (SDL_strcmp(i->default_sink_name, default_sink_path) != 0)) { - /*printf("DEFAULT SINK PATH CHANGED TO '%s'\n", i->default_sink_name);*/ - SDL_free(default_sink_path); - default_sink_path = SDL_strdup(i->default_sink_name); + //SDL_Log("PULSEAUDIO ServerInfoCallback!"); + + if (!default_sink_path || (SDL_strcmp(default_sink_path, i->default_sink_name) != 0)) { + char *str = SDL_strdup(i->default_sink_name); + if (str) { + SDL_free(default_sink_path); + default_sink_path = str; + default_sink_changed = SDL_TRUE; + } } - if (!default_source_path || (SDL_strcmp(i->default_source_name, default_source_path) != 0)) { - /*printf("DEFAULT SOURCE PATH CHANGED TO '%s'\n", i->default_source_name);*/ - SDL_free(default_source_path); - default_source_path = SDL_strdup(i->default_source_name); + if (!default_source_path || (SDL_strcmp(default_source_path, i->default_source_name) != 0)) { + char *str = SDL_strdup(i->default_source_name); + if (str) { + SDL_free(default_source_path); + default_source_path = str; + default_source_changed = SDL_TRUE; + } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -// This is called when PulseAudio has a device connected/removed/changed. */ +static SDL_bool FindAudioDeviceByIndex(SDL_AudioDevice *device, void *userdata) +{ + const uint32_t idx = (uint32_t) (uintptr_t) userdata; + const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle; + return (handle->device_index == idx); +} + +static SDL_bool FindAudioDeviceByPath(SDL_AudioDevice *device, void *userdata) +{ + const char *path = (const char *) userdata; + const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle; + return (SDL_strcmp(handle->device_path, path) == 0); +} + +// This is called when PulseAudio has a device connected/removed/changed. static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data) { const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); - if (added || removed || changed) { /* we only care about add/remove events. */ + if (added || removed || changed) { // we only care about add/remove events. const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); @@ -819,41 +860,44 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 /* adds need sink details from the PulseAudio server. Another callback... (just unref all these operations right away, because we aren't going to wait on them and their callbacks will handle any work, so they can free as soon as that happens.) */ - if ((added || changed) && sink) { - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added))); - } else if ((added || changed) && source) { - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); + if (added && sink) { + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, NULL)); + } else if (added && source) { + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, NULL)); } else if (removed && (sink || source)) { // removes we can handle just with the device index. - SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1))); + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByIndex, (void *)(uintptr_t)idx)); } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -static void CheckDefaultDevice(uint32_t *prev_default, uint32_t new_default) +static SDL_bool CheckDefaultDevice(const SDL_bool changed, char *device_path) { - if (*prev_default != new_default) { - SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1)); - if (device) { - *prev_default = new_default; - SDL_DefaultAudioDeviceChanged(device); - } + if (!changed) { + return SDL_FALSE; // nothing's happening, leave the flag marked as unchanged. + } else if (!device_path) { + return SDL_TRUE; // check again later, we don't have a device name... } + + SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, device_path); + if (device) { // if NULL, we might still be waiting for a SinkInfoCallback or something, we'll try later. + SDL_DefaultAudioDeviceChanged(device); + return SDL_FALSE; // changing complete, set flag to unchanged for future tests. + } + return SDL_TRUE; // couldn't find the changed device, leave it marked as changed to try again later. } // this runs as a thread while the Pulse target is initialized to catch hotplug events. static int SDLCALL HotplugThread(void *data) { - uint32_t prev_default_sink_index = default_sink_index; - uint32_t prev_default_source_index = default_source_index; pa_operation *op; SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); - /* don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything. */ + // don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything. op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL); SDL_PostSemaphore((SDL_Semaphore *) data); @@ -866,11 +910,23 @@ static int SDLCALL HotplugThread(void *data) } // Update default devices; don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here. + SDL_bool check_default_sink = default_sink_changed; + SDL_bool check_default_source = default_source_changed; + char *current_default_sink = check_default_sink ? SDL_strdup(default_sink_path) : NULL; + char *current_default_source = check_default_source ? SDL_strdup(default_source_path) : NULL; + default_sink_changed = default_source_changed = SDL_FALSE; PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - CheckDefaultDevice(&prev_default_sink_index, default_sink_index); - CheckDefaultDevice(&prev_default_source_index, default_source_index); + check_default_sink = CheckDefaultDevice(check_default_sink, current_default_sink); + check_default_source = CheckDefaultDevice(check_default_source, current_default_source); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); + // free our copies (which will be NULL if nothing changed) + SDL_free(current_default_sink); + SDL_free(current_default_source); + + // set these to true if we didn't handle the change OR there was _another_ change while we were working unlocked. + default_sink_changed = (default_sink_changed || check_default_sink); + default_source_changed = (default_source_changed || check_default_source); } if (op) { @@ -880,8 +936,6 @@ static int SDLCALL HotplugThread(void *data) PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); return 0; - - } static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) @@ -890,29 +944,33 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); - WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, (void *)((intptr_t)SDL_TRUE))); - WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, (void *)((intptr_t)SDL_TRUE))); + WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, NULL)); + WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, NULL)); PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - SDL_AudioDevice *device; - device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)default_sink_index + 1)); - if (device) { - *default_output = device; + if (default_sink_path) { + *default_output = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_sink_path); } - device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)default_source_index + 1)); - if (device) { - *default_capture = device; + if (default_source_path) { + *default_capture = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_source_path); } - /* ok, we have a sane list, let's set up hotplug notifications now... */ + // ok, we have a sane list, let's set up hotplug notifications now... SDL_AtomicSet(&pulseaudio_hotplug_thread_active, 1); pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, ready_sem); // !!! FIXME: this can probably survive in significantly less stack space. SDL_WaitSemaphore(ready_sem); SDL_DestroySemaphore(ready_sem); } -static void PULSEAUDIO_Deinitialize(void) +static void PULSEAUDIO_FreeDeviceHandle(SDL_AudioDevice *device) +{ + PulseDeviceHandle *handle = (PulseDeviceHandle *) device->handle; + SDL_free(handle->device_path); + SDL_free(handle); +} + +static void PULSEAUDIO_DeinitializeStart(void) { if (pulseaudio_hotplug_thread) { PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); @@ -922,16 +980,18 @@ static void PULSEAUDIO_Deinitialize(void) SDL_WaitThread(pulseaudio_hotplug_thread, NULL); pulseaudio_hotplug_thread = NULL; } +} +static void PULSEAUDIO_Deinitialize(void) +{ DisconnectFromPulseServer(); SDL_free(default_sink_path); default_sink_path = NULL; + default_sink_changed = SDL_FALSE; SDL_free(default_source_path); default_source_path = NULL; - - default_source_index = 0; - default_sink_index = 0; + default_source_changed = SDL_FALSE; UnloadPulseAudioLibrary(); } @@ -947,25 +1007,26 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) include_monitors = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, SDL_FALSE); - /* Set the function pointers */ impl->DetectDevices = PULSEAUDIO_DetectDevices; impl->OpenDevice = PULSEAUDIO_OpenDevice; impl->PlayDevice = PULSEAUDIO_PlayDevice; impl->WaitDevice = PULSEAUDIO_WaitDevice; impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; impl->CloseDevice = PULSEAUDIO_CloseDevice; + impl->DeinitializeStart = PULSEAUDIO_DeinitializeStart; impl->Deinitialize = PULSEAUDIO_Deinitialize; impl->WaitCaptureDevice = PULSEAUDIO_WaitCaptureDevice; impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; impl->FlushCapture = PULSEAUDIO_FlushCapture; + impl->FreeDeviceHandle = PULSEAUDIO_FreeDeviceHandle; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap PULSEAUDIO_bootstrap = { "pulseaudio", "PulseAudio", PULSEAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO */ +#endif // SDL_AUDIO_DRIVER_PULSEAUDIO diff --git a/src/audio/pulseaudio/SDL_pulseaudio.h b/src/audio/pulseaudio/SDL_pulseaudio.h index 10a7d9d1..1d209cb1 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.h +++ b/src/audio/pulseaudio/SDL_pulseaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,18 +29,16 @@ struct SDL_PrivateAudioData { - char *device_name; - - /* pulseaudio structures */ + // pulseaudio structures pa_stream *stream; - /* Raw mixing buffer */ + // Raw mixing buffer Uint8 *mixbuf; - int bytes_requested; /* bytes of data the hardware wants _now_. */ + int bytes_requested; // bytes of data the hardware wants _now_. const Uint8 *capturebuf; int capturelen; }; -#endif /* SDL_pulseaudio_h_ */ +#endif // SDL_pulseaudio_h_ diff --git a/src/audio/qnx/SDL_qsa_audio.c b/src/audio/qnx/SDL_qsa_audio.c index 71037168..6b69670c 100644 --- a/src/audio/qnx/SDL_qsa_audio.c +++ b/src/audio/qnx/SDL_qsa_audio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2021 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 @@ -39,7 +39,7 @@ #include "SDL3/SDL_timer.h" #include "SDL3/SDL_audio.h" #include "../../core/unix/SDL_poll.h" -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_qsa_audio.h" // default channel communication parameters @@ -86,21 +86,19 @@ static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) } // This function waits until it is possible to write a full sound buffer -static void QSA_WaitDevice(SDL_AudioDevice *device) +static int QSA_WaitDevice(SDL_AudioDevice *device) { - int result; - // Setup timeout for playing one fragment equal to 2 seconds // If timeout occurred than something wrong with hardware or driver // For example, Vortex 8820 audio driver stucks on second DAC because // it doesn't exist ! - result = SDL_IOReady(device->hidden->audio_fd, - device->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE, - 2 * 1000); + const int result = SDL_IOReady(device->hidden->audio_fd, + device->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE, + 2 * 1000); switch (result) { case -1: - SDL_SetError("QSA: SDL_IOReady() failed: %s", strerror(errno)); // !!! FIXME: Should we just disconnect the device in this case? - break; + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno)); + return -1; case 0: device->hidden->timeout_on_wait = SDL_TRUE; // !!! FIXME: Should we just disconnect the device in this case? break; @@ -108,6 +106,8 @@ static void QSA_WaitDevice(SDL_AudioDevice *device) device->hidden->timeout_on_wait = SDL_FALSE; break; } + + return 0; } static int QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) @@ -176,7 +176,7 @@ static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static void QSA_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - if (device->hidden->audio_handle != NULL) { + if (device->hidden->audio_handle) { #if _NTO_VERSION < 710 // Finish playing available samples or cancel unread samples during capture snd_pcm_plugin_flush(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK); @@ -206,7 +206,7 @@ static int QSA_OpenDevice(SDL_AudioDevice *device) // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData))); if (device->hidden == NULL) { - return SDL_OutOfMemory(); + return -1; } // Initialize channel transfer parameters to default @@ -275,7 +275,7 @@ static int QSA_OpenDevice(SDL_AudioDevice *device) device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size); if (device->hidden->pcm_buf == NULL) { - return SDL_OutOfMemory(); + return -1; } SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size); diff --git a/src/audio/qnx/SDL_qsa_audio.h b/src/audio/qnx/SDL_qsa_audio.h index 29dba24f..ea537520 100644 --- a/src/audio/qnx/SDL_qsa_audio.h +++ b/src/audio/qnx/SDL_qsa_audio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2021 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 @@ -36,6 +36,5 @@ struct SDL_PrivateAudioData Uint8 *pcm_buf; // Raw mixing buffer }; -#endif /* __SDL_QSA_AUDIO_H__ */ +#endif // __SDL_QSA_AUDIO_H__ -/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/audio/sndio/SDL_sndioaudio.c b/src/audio/sndio/SDL_sndioaudio.c index 0347c38e..ee35d3f3 100644 --- a/src/audio/sndio/SDL_sndioaudio.c +++ b/src/audio/sndio/SDL_sndioaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,7 +36,7 @@ #include #include -#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" #include "SDL_sndioaudio.h" #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC @@ -71,7 +71,7 @@ static void *sndio_handle = NULL; static int load_sndio_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(sndio_handle, fn); - if (*addr == NULL) { + if (!*addr) { return 0; // Don't call SDL_SetError(): SDL_LoadFunction already did. } @@ -110,7 +110,7 @@ static int load_sndio_syms(void) static void UnloadSNDIOLibrary(void) { - if (sndio_handle != NULL) { + if (sndio_handle) { SDL_UnloadObject(sndio_handle); sndio_handle = NULL; } @@ -119,9 +119,9 @@ static void UnloadSNDIOLibrary(void) static int LoadSNDIOLibrary(void) { int retval = 0; - if (sndio_handle == NULL) { + if (!sndio_handle) { sndio_handle = SDL_LoadObject(sndio_library); - if (sndio_handle == NULL) { + if (!sndio_handle) { retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_sndio_syms(); @@ -147,32 +147,31 @@ static int LoadSNDIOLibrary(void) #endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC -static void SNDIO_WaitDevice(SDL_AudioDevice *device) +static int SNDIO_WaitDevice(SDL_AudioDevice *device) { const SDL_bool iscapture = device->iscapture; while (!SDL_AtomicGet(&device->shutdown)) { if (SNDIO_sio_eof(device->hidden->dev)) { - SDL_AudioDeviceDisconnected(device); - return; + return -1; } const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, iscapture ? POLLIN : POLLOUT); if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) { - SDL_AudioDeviceDisconnected(device); - return; + return -1; } const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd); if (iscapture && (revents & POLLIN)) { - return; + break; } else if (!iscapture && (revents & POLLOUT)) { - return; + break; } else if (revents & POLLHUP) { - SDL_AudioDeviceDisconnected(device); - return; + return -1; } } + + return 0; } static int SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) @@ -214,7 +213,7 @@ static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static void SNDIO_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - if (device->hidden->dev != NULL) { + if (device->hidden->dev) { SNDIO_sio_stop(device->hidden->dev); SNDIO_sio_close(device->hidden->dev); } @@ -228,23 +227,23 @@ static void SNDIO_CloseDevice(SDL_AudioDevice *device) static int SNDIO_OpenDevice(SDL_AudioDevice *device) { device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } // !!! FIXME: we really should standardize this on a specific SDL hint. const char *audiodev = SDL_getenv("AUDIODEV"); // Capture devices must be non-blocking for SNDIO_FlushCapture - device->hidden->dev = SNDIO_sio_open(audiodev != NULL ? audiodev : SIO_DEVANY, + device->hidden->dev = SNDIO_sio_open(audiodev ? audiodev : SIO_DEVANY, device->iscapture ? SIO_REC : SIO_PLAY, device->iscapture); - if (device->hidden->dev == NULL) { + if (!device->hidden->dev) { return SDL_SetError("sio_open() failed"); } device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev)); - if (device->hidden->pfd == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->pfd) { + return -1; } struct sio_par par; @@ -308,8 +307,8 @@ static int SNDIO_OpenDevice(SDL_AudioDevice *device) // Allocate mixing buffer device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (device->hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden->mixbuf) { + return -1; } SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); @@ -348,7 +347,6 @@ static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl) impl->Deinitialize = SNDIO_Deinitialize; impl->DetectDevices = SNDIO_DetectDevices; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; return SDL_TRUE; diff --git a/src/audio/sndio/SDL_sndioaudio.h b/src/audio/sndio/SDL_sndioaudio.h index 6a36b7df..11a8d608 100644 --- a/src/audio/sndio/SDL_sndioaudio.h +++ b/src/audio/sndio/SDL_sndioaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,4 +35,4 @@ struct SDL_PrivateAudioData struct pollfd *pfd; // Polling structures for non-blocking sndio devices }; -#endif /* SDL_sndioaudio_h_ */ +#endif // SDL_sndioaudio_h_ diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index 53a54d10..2de08d93 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,6 @@ #include #include -#include "../SDL_audio_c.h" #include "../SDL_audiodev_c.h" #include "../SDL_sysaudio.h" #include "SDL_vitaaudio.h" @@ -63,11 +62,10 @@ static int VITAAUD_OpenDevice(SDL_AudioDevice *device) const SDL_AudioFormat *closefmts; device->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + SDL_calloc(1, sizeof(*device->hidden)); + if (!device->hidden) { + return -1; } - SDL_memset(device->hidden, 0, sizeof(*device->hidden)); closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { @@ -96,7 +94,7 @@ static int VITAAUD_OpenDevice(SDL_AudioDevice *device) 64, so spec->size should be a multiple of 64 as well. */ mixlen = device->buffer_size * NUM_BUFFERS; device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (device->hidden->rawbuf == NULL) { + if (!device->hidden->rawbuf) { return SDL_SetError("Couldn't allocate mixing buffer"); } @@ -136,12 +134,13 @@ static int VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int } // This function waits until it is possible to write a full sound buffer -static void VITAAUD_WaitDevice(SDL_AudioDevice *device) +static int VITAAUD_WaitDevice(SDL_AudioDevice *device) { // !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc. while (!SDL_AtomicGet(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) { SDL_Delay(1); } + return 0; } static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) @@ -163,8 +162,8 @@ static void VITAAUD_CloseDevice(SDL_AudioDevice *device) device->hidden->port = -1; } - if (!device->iscapture && device->hidden->rawbuf != NULL) { - SDL_aligned_free(device->hidden->rawbuf); // this uses memalign(), not SDL_malloc(). + if (!device->iscapture && device->hidden->rawbuf) { + SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc() device->hidden->rawbuf = NULL; } SDL_free(device->hidden); @@ -172,7 +171,7 @@ static void VITAAUD_CloseDevice(SDL_AudioDevice *device) } } -static void VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device) +static int VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device) { // there's only a blocking call to obtain more data, so we'll just sleep as // long as a buffer would run. @@ -180,6 +179,7 @@ static void VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device) while (!SDL_AtomicGet(&device->shutdown) && (SDL_GetTicks() < endticks)) { SDL_Delay(1); } + return 0; } static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) diff --git a/src/audio/vita/SDL_vitaaudio.h b/src/audio/vita/SDL_vitaaudio.h index 1046fd57..35b46e5f 100644 --- a/src/audio/vita/SDL_vitaaudio.h +++ b/src/audio/vita/SDL_vitaaudio.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,14 +28,14 @@ struct SDL_PrivateAudioData { - /* The hardware input/output port. */ + // The hardware input/output port. int port; - /* The raw allocated mixing buffer. */ + // The raw allocated mixing buffer. Uint8 *rawbuf; - /* Individual mixing buffers. */ + // Individual mixing buffers. Uint8 *mixbufs[NUM_BUFFERS]; - /* Index of the next available mixing buffer. */ + // Index of the next available mixing buffer. int next_buffer; }; -#endif /* SDL_vitaaudio_h */ +#endif // SDL_vitaaudio_h diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index 6e442c42..5eae508b 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,6 @@ #include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_immdevice.h" #include "../../thread/SDL_systhread.h" -#include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #define COBJMACROS @@ -94,7 +93,7 @@ static void ManagementThreadMainloop(void) int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result) { // We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock. - if ((wait_on_result != NULL) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) { + if ((wait_on_result) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) { *wait_on_result = task(userdata); return 0; // completed! } @@ -105,7 +104,7 @@ int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, in ManagementThreadPendingTask *pending = SDL_calloc(1, sizeof(ManagementThreadPendingTask)); if (!pending) { - return SDL_OutOfMemory(); + return -1; } pending->fn = task; @@ -125,11 +124,11 @@ int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, in // add to end of task list. ManagementThreadPendingTask *prev = NULL; - for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks); i != NULL; i = i->next) { + for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks); i; i = i->next) { prev = i; } - if (prev != NULL) { + if (prev) { prev->next = pending; } else { SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, pending); @@ -265,6 +264,7 @@ static int mgmtthrtask_DetectDevices(void *userdata) static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { int rc; + // this blocks because it needs to finish before the audio subsystem inits mgmtthrtask_DetectDevicesData data = { default_output, default_capture }; WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc); } @@ -277,26 +277,31 @@ static int mgmtthrtask_DisconnectDevice(void *userdata) void WASAPI_DisconnectDevice(SDL_AudioDevice *device) { - // this runs async, so it can hold the device lock from the management thread. - WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL); + int rc; // block on this; don't disconnect while holding the device lock! + WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, &rc); } static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err) { if (err == S_OK) { return SDL_FALSE; - } - - if (err == AUDCLNT_E_DEVICE_INVALIDATED) { + } else if (err == AUDCLNT_E_DEVICE_INVALIDATED) { device->hidden->device_lost = SDL_TRUE; } else { - IAudioClient_Stop(device->hidden->client); - WASAPI_DisconnectDevice(device); + device->hidden->device_dead = SDL_TRUE; } return SDL_TRUE; } +static int mgmtthrtask_StopAndReleaseClient(void *userdata) +{ + IAudioClient *client = (IAudioClient *) userdata; + IAudioClient_Stop(client); + IAudioClient_Release(client); + return 0; +} + static int mgmtthrtask_ReleaseCaptureClient(void *userdata) { IAudioCaptureClient_Release((IAudioCaptureClient *)userdata); @@ -309,56 +314,68 @@ static int mgmtthrtask_ReleaseRenderClient(void *userdata) return 0; } -static int mgmtthrtask_ResetWasapiDevice(void *userdata) +static int mgmtthrtask_CoTaskMemFree(void *userdata) { - SDL_AudioDevice *device = (SDL_AudioDevice *)userdata; + CoTaskMemFree(userdata); + return 0; +} - if (!device || !device->hidden) { - return 0; - } - - if (device->hidden->client) { - IAudioClient_Stop(device->hidden->client); - IAudioClient_Release(device->hidden->client); - device->hidden->client = NULL; - } - - if (device->hidden->render) { - // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so - // proxy this to the management thread to be released later. - WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, device->hidden->render, NULL); - device->hidden->render = NULL; - } - - if (device->hidden->capture) { - // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so - // proxy this to the management thread to be released later. - WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, device->hidden->capture, NULL); - device->hidden->capture = NULL; - } - - if (device->hidden->waveformat) { - CoTaskMemFree(device->hidden->waveformat); - device->hidden->waveformat = NULL; - } - - if (device->hidden->activation_handler) { - WASAPI_PlatformDeleteActivationHandler(device->hidden->activation_handler); - device->hidden->activation_handler = NULL; - } - - if (device->hidden->event) { - CloseHandle(device->hidden->event); - device->hidden->event = NULL; - } +static int mgmtthrtask_PlatformDeleteActivationHandler(void *userdata) +{ + WASAPI_PlatformDeleteActivationHandler(userdata); + return 0; +} +static int mgmtthrtask_CloseHandle(void *userdata) +{ + CloseHandle((HANDLE) userdata); return 0; } static void ResetWasapiDevice(SDL_AudioDevice *device) { - int rc; - WASAPI_ProxyToManagementThread(mgmtthrtask_ResetWasapiDevice, device, &rc); + if (!device || !device->hidden) { + return; + } + + // just queue up all the tasks in the management thread and don't block. + // We don't care when any of these actually get free'd. + + if (device->hidden->client) { + IAudioClient *client = device->hidden->client; + device->hidden->client = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL); + } + + if (device->hidden->render) { + IAudioRenderClient *render = device->hidden->render; + device->hidden->render = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL); + } + + if (device->hidden->capture) { + IAudioCaptureClient *capture = device->hidden->capture; + device->hidden->capture = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL); + } + + if (device->hidden->waveformat) { + void *ptr = device->hidden->waveformat; + device->hidden->waveformat = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL); + } + + if (device->hidden->activation_handler) { + void *activation_handler = device->hidden->activation_handler; + device->hidden->activation_handler = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_PlatformDeleteActivationHandler, activation_handler, NULL); + } + + if (device->hidden->event) { + HANDLE event = device->hidden->event; + device->hidden->event = NULL; + WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL); + } } static int mgmtthrtask_ActivateDevice(void *userdata) @@ -368,10 +385,13 @@ static int mgmtthrtask_ActivateDevice(void *userdata) static int ActivateWasapiDevice(SDL_AudioDevice *device) { - int rc; + // this blocks because we're either being notified from a background thread or we're running during device open, + // both of which won't deadlock vs the device thread. + int rc = -1; return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0; } +// do not call when holding the device lock! static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device) { ResetWasapiDevice(device); // dump the lost device's handles. @@ -387,10 +407,18 @@ static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device) return SDL_TRUE; // okay, carry on with new device details! } +// do not call when holding the device lock! static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device) { if (SDL_AtomicGet(&device->shutdown)) { return SDL_FALSE; // already failed. + } else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit + IAudioClient_Stop(device->hidden->client); + WASAPI_DisconnectDevice(device); + SDL_assert(SDL_AtomicGet(&device->shutdown)); // so we don't come back through here. + return SDL_FALSE; // already failed. + } else if (SDL_AtomicGet(&device->zombie)) { + return SDL_FALSE; // we're already dead, so just leave and let the Zombie implementations take over. } else if (!device->hidden->client) { return SDL_TRUE; // still waiting for activation. } @@ -403,9 +431,12 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) // get an endpoint buffer from WASAPI. BYTE *buffer = NULL; - if (RecoverWasapiIfLost(device) && device->hidden->render) { + if (device->hidden->render) { if (WasapiFailed(device, IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer))) { SDL_assert(buffer == NULL); + if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow. + *buffer_size = 0; // we'll recover during WaitDevice and try again. + } } } @@ -414,15 +445,16 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static int WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - if (device->hidden->render != NULL) { // definitely activated? + if (device->hidden->render) { // definitely activated? // WasapiFailed() will mark the device for reacquisition or removal elsewhere. WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0)); } return 0; } -static void WASAPI_WaitDevice(SDL_AudioDevice *device) +static int WASAPI_WaitDevice(SDL_AudioDevice *device) { + // WaitDevice does not hold the device lock, so check for recovery/disconnect details here. while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) { DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE); if (waitResult == WAIT_OBJECT_0) { @@ -439,9 +471,11 @@ static void WASAPI_WaitDevice(SDL_AudioDevice *device) } else if (waitResult != WAIT_TIMEOUT) { //SDL_Log("WASAPI FAILED EVENT!");*/ IAudioClient_Stop(device->hidden->client); - WASAPI_DisconnectDevice(device); + return -1; } } + + return 0; } static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) @@ -450,10 +484,10 @@ static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int b UINT32 frames = 0; DWORD flags = 0; - while (RecoverWasapiIfLost(device) && device->hidden->capture) { - HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); + while (device->hidden->capture) { + const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); if (ret == AUDCLNT_S_BUFFER_EMPTY) { - return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL + return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3. } WasapiFailed(device, ret); // mark device lost/failed if necessary. @@ -472,8 +506,7 @@ static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int b SDL_memcpy(buffer, ptr, cpy); } - ret = IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames); - WasapiFailed(device, ret); // mark device lost/failed if necessary. + WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames)); return cpy; } @@ -537,7 +570,7 @@ static int mgmtthrtask_PrepDevice(void *userdata) device->hidden->event = CreateEventW(NULL, 0, 0, NULL); #endif - if (device->hidden->event == NULL) { + if (!device->hidden->event) { return WIN_SetError("WASAPI can't create an event handle"); } @@ -666,8 +699,8 @@ static int WASAPI_OpenDevice(SDL_AudioDevice *device) { // Initialize all variables that we clean on shutdown device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return SDL_OutOfMemory(); + if (!device->hidden) { + return -1; } else if (ActivateWasapiDevice(device) < 0) { return -1; // already set error. } @@ -702,6 +735,18 @@ static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device) WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc); } +static int mgmtthrtask_DeinitializeStart(void *userdata) +{ + WASAPI_PlatformDeinitializeStart(); + return 0; +} + +static void WASAPI_DeinitializeStart(void) +{ + int rc; + WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc); +} + static void WASAPI_Deinitialize(void) { DeinitManagementThread(); @@ -724,6 +769,7 @@ static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl) impl->CaptureFromDevice = WASAPI_CaptureFromDevice; impl->FlushCapture = WASAPI_FlushCapture; impl->CloseDevice = WASAPI_CloseDevice; + impl->DeinitializeStart = WASAPI_DeinitializeStart; impl->Deinitialize = WASAPI_Deinitialize; impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle; diff --git a/src/audio/wasapi/SDL_wasapi.h b/src/audio/wasapi/SDL_wasapi.h index 7ea47ccb..45a81735 100644 --- a/src/audio/wasapi/SDL_wasapi.h +++ b/src/audio/wasapi/SDL_wasapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,22 +41,24 @@ struct SDL_PrivateAudioData SDL_bool coinitialized; int framesize; SDL_bool device_lost; + SDL_bool device_dead; void *activation_handler; }; -/* win32 and winrt implementations call into these. */ +// win32 and winrt implementations call into these. int WASAPI_PrepDevice(SDL_AudioDevice *device); -void WASAPI_DisconnectDevice(SDL_AudioDevice *device); +void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this! // BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock. typedef int (*ManagementThreadTask)(void *userdata); int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete); -/* These are functions that are implemented differently for Windows vs WinRT. */ +// These are functions that are implemented differently for Windows vs WinRT. // UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD. int WASAPI_PlatformInit(void); void WASAPI_PlatformDeinit(void); +void WASAPI_PlatformDeinitializeStart(void); void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); int WASAPI_ActivateDevice(SDL_AudioDevice *device); void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread. @@ -68,4 +70,4 @@ void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device); } #endif -#endif /* SDL_wasapi_h_ */ +#endif // SDL_wasapi_h_ diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c index 719ac94a..fd84394f 100644 --- a/src/audio/wasapi/SDL_wasapi_win32.c +++ b/src/audio/wasapi/SDL_wasapi_win32.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,30 +30,70 @@ #include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_immdevice.h" -#include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include #include "SDL_wasapi.h" -/* handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). */ +// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). static HMODULE libavrt = NULL; typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL; static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL; -/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */ +static SDL_bool immdevice_initialized = SDL_FALSE; + +// Some GUIDs we need to know without linking to libraries that aren't available before Vista. static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +static int mgmtthrtask_AudioDeviceDisconnected(void *userdata) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; + SDL_AudioDeviceDisconnected(device); + UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes. + return 0; +} + +static void WASAPI_AudioDeviceDisconnected(SDL_AudioDevice *device) +{ + // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock. + if (device) { + RefPhysicalAudioDevice(device); // make sure this lives until the task completes. + WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL); + } +} + +static int mgmtthrtask_DefaultAudioDeviceChanged(void *userdata) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *) userdata; + SDL_DefaultAudioDeviceChanged(device); + UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes. + return 0; +} + +static void WASAPI_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) +{ + // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock. + if (new_default_device) { + RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes. + WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL); + } +} + int WASAPI_PlatformInit(void) { - if (SDL_IMMDevice_Init() < 0) { // this will call WIN_CoInitialize for us! - return -1; /* This is set by SDL_IMMDevice_Init */ + const SDL_IMMDevice_callbacks callbacks = { WASAPI_AudioDeviceDisconnected, WASAPI_DefaultAudioDeviceChanged }; + if (FAILED(WIN_CoInitialize())) { + return SDL_SetError("CoInitialize() failed"); + } else if (SDL_IMMDevice_Init(&callbacks) < 0) { + return -1; // Error string is set by SDL_IMMDevice_Init } - libavrt = LoadLibrary(TEXT("avrt.dll")); /* this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! */ + immdevice_initialized = SDL_TRUE; + + libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! if (libavrt) { pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW"); pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics"); @@ -62,6 +102,14 @@ int WASAPI_PlatformInit(void) return 0; } +static void StopWasapiHotplug(void) +{ + if (immdevice_initialized) { + SDL_IMMDevice_Quit(); + immdevice_initialized = SDL_FALSE; + } +} + void WASAPI_PlatformDeinit(void) { if (libavrt) { @@ -72,17 +120,24 @@ void WASAPI_PlatformDeinit(void) pAvSetMmThreadCharacteristicsW = NULL; pAvRevertMmThreadCharacteristics = NULL; - SDL_IMMDevice_Quit(); // This will call WIN_CoUninitialize for us! + StopWasapiHotplug(); + + WIN_CoUninitialize(); +} + +void WASAPI_PlatformDeinitializeStart(void) +{ + StopWasapiHotplug(); } void WASAPI_PlatformThreadInit(SDL_AudioDevice *device) { - /* this thread uses COM. */ - if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */ + // this thread uses COM. + if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked! device->hidden->coinitialized = SDL_TRUE; } - /* Set this thread to very high "Pro Audio" priority. */ + // Set this thread to very high "Pro Audio" priority. if (pAvSetMmThreadCharacteristicsW) { DWORD idx = 0; device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx); @@ -94,7 +149,7 @@ void WASAPI_PlatformThreadInit(SDL_AudioDevice *device) void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device) { - /* Set this thread back to normal priority. */ + // Set this thread back to normal priority. if (device->hidden->task && pAvRevertMmThreadCharacteristics) { pAvRevertMmThreadCharacteristics(device->hidden->task); device->hidden->task = NULL; @@ -111,10 +166,10 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *device) IMMDevice *immdevice = NULL; if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) { device->hidden->client = NULL; - return -1; /* This is already set by SDL_IMMDevice_Get */ + return -1; // This is already set by SDL_IMMDevice_Get } - /* this is _not_ async in standard win32, yay! */ + // this is _not_ async in standard win32, yay! HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client); IMMDevice_Release(immdevice); @@ -124,11 +179,11 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *device) } SDL_assert(device->hidden->client != NULL); - if (WASAPI_PrepDevice(device) == -1) { /* not async, fire it right away. */ + if (WASAPI_PrepDevice(device) == -1) { // not async, fire it right away. return -1; } - return 0; /* good to go. */ + return 0; // good to go. } void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) @@ -138,7 +193,7 @@ void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice void WASAPI_PlatformDeleteActivationHandler(void *handler) { - /* not asynchronous. */ + // not asynchronous. SDL_assert(!"This function should have only been called on WinRT."); } @@ -147,4 +202,4 @@ void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device) SDL_IMMDevice_FreeDeviceHandle(device); } -#endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */ +#endif // SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp index a4030f39..0af821d7 100644 --- a/src/audio/wasapi/SDL_wasapi_winrt.cpp +++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,7 +36,6 @@ extern "C" { #include "../../core/windows/SDL_windows.h" -#include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" } @@ -56,7 +55,7 @@ static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4 static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata) { - return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE; + return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0); } static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid) @@ -220,7 +219,7 @@ int WASAPI_PlatformInit(void) return 0; } -void WASAPI_PlatformDeinit(void) +static void StopWasapiHotplug(void) { delete playback_device_event_handler; playback_device_event_handler = nullptr; @@ -228,6 +227,17 @@ void WASAPI_PlatformDeinit(void) capture_device_event_handler = nullptr; } +void WASAPI_PlatformDeinit(void) +{ + StopWasapiHotplug(); +} + +void WASAPI_PlatformDeinitializeStart(void) +{ + StopWasapiHotplug(); +} + + void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { Platform::String ^ defdevid; diff --git a/src/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c new file mode 100644 index 00000000..b9e33974 --- /dev/null +++ b/src/core/SDL_core_unsupported.c @@ -0,0 +1,235 @@ +/* + Simple DirectMedia Layer + 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 + 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_VIDEO_DRIVER_X11 + +DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) +{ +} + +#endif + +#ifndef __LINUX__ + +DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority); +int SDL_LinuxSetThreadPriority(Sint64 threadID, int priority) +{ + (void)threadID; + (void)priority; + return SDL_Unsupported(); +} + +DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); +int SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy) +{ + (void)threadID; + (void)sdlPriority; + (void)schedPolicy; + return SDL_Unsupported(); +} + +#endif + +#ifndef __GDK__ + +DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); +void SDL_GDKSuspendComplete(void) +{ + SDL_Unsupported(); +} + +DECLSPEC int SDLCALL SDL_GDKGetDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */ +int SDL_GDKGetDefaultUser(void *outUserHandle) +{ + return SDL_Unsupported(); +} + +#endif + +#if !(defined(__WIN32__) || defined(__WINRT__) || defined(__GDK__)) + +DECLSPEC int SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); +int SDL_RegisterApp(const char *name, Uint32 style, void *hInst) +{ + (void)name; + (void)style; + (void)hInst; + return SDL_Unsupported(); +} + +DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); /* SDL_WindowsMessageHook callback */ +void SDL_SetWindowsMessageHook(void *callback, void *userdata) +{ + (void)callback; + (void)userdata; + SDL_Unsupported(); +} + +DECLSPEC void SDLCALL SDL_UnregisterApp(void); +void SDL_UnregisterApp(void) +{ + SDL_Unsupported(); +} + +#endif + +#ifndef __WINRT__ + +/* Returns SDL_WinRT_DeviceFamily enum */ +DECLSPEC int SDLCALL SDL_WinRTGetDeviceFamily(void); +int SDL_WinRTGetDeviceFamily() +{ + SDL_Unsupported(); + return 0; /* SDL_WINRT_DEVICEFAMILY_UNKNOWN */ +} + +DECLSPEC const wchar_t *SDLCALL SDL_WinRTGetFSPathUNICODE(int pathType); /* SDL_WinRT_Path pathType */ +const wchar_t *SDL_WinRTGetFSPathUNICODE(int pathType) +{ + (void)pathType; + SDL_Unsupported(); + return NULL; +} + +DECLSPEC const char *SDLCALL SDL_WinRTGetFSPathUTF8(int pathType); /* SDL_WinRT_Path pathType */ +const char *SDL_WinRTGetFSPathUTF8(int pathType) +{ + (void)pathType; + SDL_Unsupported(); + return NULL; +} +#endif + +#ifndef __ANDROID__ + +DECLSPEC void SDLCALL SDL_AndroidBackButton(void); +void SDL_AndroidBackButton() +{ + SDL_Unsupported(); +} + +DECLSPEC void *SDLCALL SDL_AndroidGetActivity(void); +void *SDL_AndroidGetActivity() +{ + SDL_Unsupported(); + return NULL; +} + +DECLSPEC const char *SDLCALL SDL_AndroidGetExternalStoragePath(void); +const char* SDL_AndroidGetExternalStoragePath() +{ + SDL_Unsupported(); + return NULL; +} + +DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(Uint32 *state); +int SDL_AndroidGetExternalStorageState(Uint32 *state) +{ + (void)state; + return SDL_Unsupported(); +} +DECLSPEC const char *SDLCALL SDL_AndroidGetInternalStoragePath(void); +const char *SDL_AndroidGetInternalStoragePath() +{ + SDL_Unsupported(); + return NULL; +} + +DECLSPEC void *SDLCALL SDL_AndroidGetJNIEnv(void); +void *SDL_AndroidGetJNIEnv() +{ + SDL_Unsupported(); + return NULL; +} + +DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission); +SDL_bool SDL_AndroidRequestPermission(const char *permission) +{ + (void)permission; + SDL_Unsupported(); + return SDL_FALSE; +} + +DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param); +int SDL_AndroidSendMessage(Uint32 command, int param) +{ + (void)command; + (void)param; + return SDL_Unsupported(); +} + +DECLSPEC int SDLCALL SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset); +int SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset) +{ + (void)message; + (void)duration; + (void)gravity; + (void)xoffset; + (void)yoffset; + return SDL_Unsupported(); +} + +DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); +int SDL_GetAndroidSDKVersion() +{ + return SDL_Unsupported(); +} + +DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void); +SDL_bool SDL_IsAndroidTV() +{ + SDL_Unsupported(); + return SDL_FALSE; +} + +DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void); +SDL_bool SDL_IsChromebook() +{ + SDL_Unsupported(); + return SDL_FALSE; +} + +DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void); +SDL_bool SDL_IsDeXMode(void) +{ + SDL_Unsupported(); + return SDL_FALSE; +} + +DECLSPEC Sint32 SDLCALL JNI_OnLoad(void *vm, void *reserved); +Sint32 JNI_OnLoad(void *vm, void *reserved) +{ + (void)vm; + (void)reserved; + SDL_Unsupported(); + return -1; /* JNI_ERR */ +} +#endif + +#if defined(__XBOXONE__) || defined(__XBOXSERIES__) +char *SDL_GetUserFolder(SDL_Folder folder) +{ + (void)folder; + SDL_Unsupported(); + return NULL; +} +#endif diff --git a/src/core/SDL_runapp.c b/src/core/SDL_runapp.c index 02e25c69..52c7ede1 100644 --- a/src/core/SDL_runapp.c +++ b/src/core/SDL_runapp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,15 +27,14 @@ DECLSPEC int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) { - char empty[1] = {0}; - char* argvdummy[2] = { empty, NULL }; - (void)reserved; - if(argv == NULL) + if(!argv) { - argc = 0; /* make sure argv isn't NULL, in case some user code doesn't like that */ + static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + static char* argvdummy[2] = { dummyargv0, NULL }; + argc = 1; argv = argvdummy; } diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 41a0ad00..b4ee8b7a 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -434,12 +434,12 @@ JNIEnv *Android_JNI_GetEnv(void) { /* Get JNIEnv from the Thread local storage */ JNIEnv *env = pthread_getspecific(mThreadKey); - if (env == NULL) { + if (!env) { /* If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread() */ int status; /* There should be a JVM */ - if (mJavaVM == NULL) { + if (!mJavaVM) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); return NULL; } @@ -468,7 +468,7 @@ int Android_JNI_SetupThread(void) int status; /* There should be a JVM */ - if (mJavaVM == NULL) { + if (!mJavaVM) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); return 0; } @@ -494,7 +494,7 @@ static void Android_JNI_ThreadDestroyed(void *value) { /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ JNIEnv *env = (JNIEnv *)value; - if (env != NULL) { + if (env) { (*mJavaVM)->DetachCurrentThread(mJavaVM); Android_JNI_SetEnv(NULL); } @@ -520,7 +520,7 @@ static void Android_JNI_CreateKey_once(void) static void register_methods(JNIEnv *env, const char *classname, JNINativeMethod *methods, int nb) { jclass clazz = (*env)->FindClass(env, classname); - if (clazz == NULL || (*env)->RegisterNatives(env, clazz, methods, nb) < 0) { + if (!clazz || (*env)->RegisterNatives(env, clazz, methods, nb) < 0) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to register methods of %s", classname); return; } @@ -573,6 +573,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()"); + /* Start with a clean slate */ + SDL_ClearError(); + /* * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this @@ -582,28 +585,28 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl /* Save JNIEnv of SDLActivity */ Android_JNI_SetEnv(env); - if (mJavaVM == NULL) { + if (!mJavaVM) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM"); } /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'. * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. ) */ - if (Android_ActivityMutex == NULL) { + if (!Android_ActivityMutex) { Android_ActivityMutex = SDL_CreateMutex(); /* Could this be created twice if onCreate() is called a second time ? */ } - if (Android_ActivityMutex == NULL) { + if (!Android_ActivityMutex) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex"); } Android_PauseSem = SDL_CreateSemaphore(0); - if (Android_PauseSem == NULL) { + if (!Android_PauseSem) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_PauseSem semaphore"); } Android_ResumeSem = SDL_CreateSemaphore(0); - if (Android_ResumeSem == NULL) { + if (!Android_ResumeSem) { __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ResumeSem semaphore"); } @@ -878,7 +881,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( jstring filename) { const char *path = (*env)->GetStringUTFChars(env, filename, NULL); - SDL_SendDropFile(NULL, path); + SDL_SendDropFile(NULL, NULL, path); (*env)->ReleaseStringUTFChars(env, filename, path); SDL_SendDropComplete(NULL); } @@ -1006,24 +1009,28 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, jstring name, jint device_id) { +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES if (SDL_GetCurrentAudioDriver() != NULL) { void *handle = (void *)((size_t)device_id); if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddAudioDevice(is_capture ? SDL_TRUE : SDL_FALSE, SDL_strdup(utf8name), NULL, handle); + SDL_AddAudioDevice(is_capture, SDL_strdup(utf8name), NULL, handle); (*env)->ReleaseStringUTFChars(env, name, utf8name); } } +#endif } JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, jint device_id) { +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES if (SDL_GetCurrentAudioDriver() != NULL) { SDL_Log("Removing device with handle %d, capture %d", device_id, is_capture); SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); } +#endif } /* Paddown */ @@ -1068,7 +1075,7 @@ JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer ? SDL_TRUE : SDL_FALSE, button_mask, naxes, axis_mask, nhats); + retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer, button_mask, naxes, axis_mask, nhats); (*env)->ReleaseStringUTFChars(env, device_name, name); (*env)->ReleaseStringUTFChars(env, device_desc, desc); @@ -1610,7 +1617,7 @@ int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device) __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output"); result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midAudioOpen, spec->freq, audioformat, spec->channels, device->sample_frames, device_id); } - if (result == NULL) { + if (!result) { /* Error during audio initialization, error printed from Java */ return SDL_SetError("Java-side initialization failed"); } @@ -1671,7 +1678,7 @@ int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device) return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat); } - if (jbufobj == NULL) { + if (!jbufobj) { __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer"); return SDL_OutOfMemory(); } @@ -1942,7 +1949,7 @@ static void Internal_Android_Create_AssetManager() javaAssetManagerRef = (*env)->NewGlobalRef(env, javaAssetManager); asset_manager = AAssetManager_fromJava(env, javaAssetManagerRef); - if (asset_manager == NULL) { + if (!asset_manager) { (*env)->DeleteGlobalRef(env, javaAssetManagerRef); Android_JNI_ExceptionOccurred(SDL_TRUE); } @@ -1966,17 +1973,17 @@ int Android_JNI_FileOpen(SDL_RWops *ctx, AAsset *asset = NULL; ctx->hidden.androidio.asset = NULL; - if (asset_manager == NULL) { + if (!asset_manager) { Internal_Android_Create_AssetManager(); } - if (asset_manager == NULL) { - return -1; + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); } asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); - if (asset == NULL) { - return -1; + if (!asset) { + return SDL_SetError("Couldn't open asset '%s'", fileName); } ctx->hidden.androidio.asset = (void *)asset; @@ -2047,14 +2054,13 @@ char *Android_JNI_GetClipboardText(void) (*env)->DeleteLocalRef(env, string); } - return (text == NULL) ? SDL_strdup("") : text; + return (!text) ? SDL_strdup("") : text; } SDL_bool Android_JNI_HasClipboardText(void) { JNIEnv *env = Android_JNI_GetEnv(); - jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText); - return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE; + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText); } /* returns 0 on success or -1 on error (others undefined then) @@ -2233,7 +2239,7 @@ int Android_JNI_SuspendScreenSaver(SDL_bool suspend) return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1); } -void Android_JNI_ShowTextInput(SDL_Rect *inputRect) +void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect) { JNIEnv *env = Android_JNI_GetEnv(); (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput, @@ -2243,7 +2249,7 @@ void Android_JNI_ShowTextInput(SDL_Rect *inputRect) inputRect->h); } -void Android_JNI_HideTextInput(void) +void Android_JNI_HideScreenKeyboard(void) { /* has to match Activity constant */ const int COMMAND_TEXTEDIT_HIDE = 3; @@ -2368,7 +2374,7 @@ void *SDL_AndroidGetActivity(void) /* See SDL_system.h for caveats on using this function. */ JNIEnv *env = Android_JNI_GetEnv(); - if (env == NULL) { + if (!env) { return NULL; } @@ -2422,7 +2428,7 @@ const char *SDL_AndroidGetInternalStoragePath(void) { static char *s_AndroidInternalFilesPath = NULL; - if (s_AndroidInternalFilesPath == NULL) { + if (!s_AndroidInternalFilesPath) { struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); jmethodID mid; jobject context; @@ -2521,7 +2527,7 @@ const char *SDL_AndroidGetExternalStoragePath(void) { static char *s_AndroidExternalFilesPath = NULL; - if (s_AndroidExternalFilesPath == NULL) { + if (!s_AndroidExternalFilesPath) { struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); jmethodID mid; jobject context; @@ -2677,16 +2683,16 @@ int Android_JNI_GetLocale(char *buf, size_t buflen) /* Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED) */ Internal_Android_Destroy_AssetManager(); - if (asset_manager == NULL) { + if (!asset_manager) { Internal_Android_Create_AssetManager(); } - if (asset_manager == NULL) { + if (!asset_manager) { return -1; } cfg = AConfiguration_new(); - if (cfg == NULL) { + if (!cfg) { return -1; } diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index bc090b88..af8ed1d4 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,9 @@ extern "C" { #include "../../audio/SDL_sysaudio.h" +// this appears to be broken right now (on Android, not SDL, I think...?). +#define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0 + /* Interface from the SDL library into the Android Java activity */ extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen); @@ -40,8 +43,8 @@ extern void Android_JNI_MinizeWindow(void); extern SDL_bool Android_JNI_ShouldMinimizeOnFocusLoss(void); extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]); -extern void Android_JNI_ShowTextInput(SDL_Rect *inputRect); -extern void Android_JNI_HideTextInput(void); +extern void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect); +extern void Android_JNI_HideScreenKeyboard(void); extern SDL_bool Android_JNI_IsScreenKeyboardShown(void); extern ANativeWindow *Android_JNI_GetNativeWindow(void); diff --git a/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/src/core/freebsd/SDL_evdev_kbd_freebsd.c index a30988c8..2c1df4a8 100644 --- a/src/core/freebsd/SDL_evdev_kbd_freebsd.c +++ b/src/core/freebsd/SDL_evdev_kbd_freebsd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -87,7 +87,7 @@ static void kbd_cleanup(void) { struct mouse_info mData; SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state; - if (kbd == NULL) { + if (!kbd) { return; } kbd_cleanup_state = NULL; @@ -178,7 +178,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) { int tabidx; - if (kbd_cleanup_state != NULL) { + if (kbd_cleanup_state) { return; } kbd_cleanup_state = kbd; @@ -230,7 +230,7 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) SDL_zero(mData); mData.operation = MOUSE_HIDE; kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state)); - if (kbd == NULL) { + if (!kbd) { return NULL; } @@ -296,7 +296,7 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) { struct mouse_info mData; - if (kbd == NULL) { + if (!kbd) { return; } SDL_zero(mData); @@ -320,6 +320,18 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) SDL_free(kbd); } +void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted) +{ +} + +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +{ +} + +void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state) +{ +} + /* * Helper Functions. */ @@ -474,7 +486,7 @@ void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *kbd, unsigned int keycode, unsigned int final_key_state; unsigned int map_from_key_sym; - if (kbd == NULL) { + if (!kbd) { return; } diff --git a/src/core/gdk/SDL_gdk.cpp b/src/core/gdk/SDL_gdk.cpp index c2dd14e2..2c4469b4 100644 --- a/src/core/gdk/SDL_gdk.cpp +++ b/src/core/gdk/SDL_gdk.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/gdk/SDL_gdk.h b/src/core/gdk/SDL_gdk.h index 645b59a2..d500c41f 100644 --- a/src/core/gdk/SDL_gdk.h +++ b/src/core/gdk/SDL_gdk.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/haiku/SDL_BApp.h b/src/core/haiku/SDL_BApp.h index 6deaff2e..39fec778 100644 --- a/src/core/haiku/SDL_BApp.h +++ b/src/core/haiku/SDL_BApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/haiku/SDL_BeApp.cc b/src/core/haiku/SDL_BeApp.cc index 16eb58af..ccb9a4db 100644 --- a/src/core/haiku/SDL_BeApp.cc +++ b/src/core/haiku/SDL_BeApp.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -69,7 +69,7 @@ public: entry_ref entryRef; for (int32 i = 0; message->FindRef("refs", i, &entryRef) == B_OK; i++) { BPath referencePath = BPath(&entryRef); - SDL_SendDropFile(NULL, referencePath.Path()); + SDL_SendDropFile(NULL, NULL, referencePath.Path()); } return; } @@ -108,13 +108,13 @@ static int StartBeLooper() { if (!be_app) { SDL_AppThread = SDL_CreateThreadInternal(StartBeApp, "SDLApplication", 0, NULL); - if (SDL_AppThread == NULL) { + if (!SDL_AppThread) { return SDL_SetError("Couldn't create BApplication thread"); } do { SDL_Delay(10); - } while ((be_app == NULL) || be_app->IsLaunching()); + } while ((!be_app) || be_app->IsLaunching()); } /* Change working directory to that of executable */ @@ -167,7 +167,7 @@ void SDL_QuitBeApp(void) SDL_Looper->Lock(); SDL_Looper->Quit(); SDL_Looper = NULL; - if (SDL_AppThread != NULL) { + if (SDL_AppThread) { if (be_app != NULL) { /* Not tested */ be_app->PostMessage(B_QUIT_REQUESTED); } diff --git a/src/core/haiku/SDL_BeApp.h b/src/core/haiku/SDL_BeApp.h index ec8bd7fa..fde331ae 100644 --- a/src/core/haiku/SDL_BeApp.h +++ b/src/core/haiku/SDL_BeApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index ba961be0..f7f5b6d6 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -96,7 +96,7 @@ static int LoadDBUSSyms(void) static void UnloadDBUSLibrary(void) { - if (dbus_handle != NULL) { + if (dbus_handle) { SDL_UnloadObject(dbus_handle); dbus_handle = NULL; } @@ -105,9 +105,9 @@ static void UnloadDBUSLibrary(void) static int LoadDBUSLibrary(void) { int retval = 0; - if (dbus_handle == NULL) { + if (!dbus_handle) { dbus_handle = SDL_LoadObject(dbus_library); - if (dbus_handle == NULL) { + if (!dbus_handle) { retval = -1; /* Don't call SDL_SetError(): SDL_LoadObject already did. */ } else { @@ -183,14 +183,13 @@ void SDL_DBus_Quit(void) dbus.connection_close(dbus.session_conn); dbus.connection_unref(dbus.session_conn); } -/* Don't do this - bug 3950 - dbus_shutdown() is a debug feature which closes all global resources in the dbus library. Calling this should be done by the app, not a library, because if there are multiple users of dbus in the process then SDL could shut it down even though another part is using it. -*/ -#if 0 - if (dbus.shutdown) { - dbus.shutdown(); + + if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, SDL_FALSE)) { + if (dbus.shutdown) { + dbus.shutdown(); + } } -#endif + SDL_zero(dbus); UnloadDBUSLibrary(); SDL_free(inhibit_handle); @@ -199,7 +198,7 @@ void SDL_DBus_Quit(void) SDL_DBusContext *SDL_DBus_GetContext(void) { - if (dbus_handle == NULL || !dbus.session_conn) { + if (!dbus_handle || !dbus.session_conn) { SDL_DBus_Init(); } @@ -360,7 +359,7 @@ SDL_bool SDL_DBus_QueryProperty(const char *node, const char *path, const char * void SDL_DBus_ScreensaverTickle(void) { - if (screensaver_cookie == 0 && inhibit_handle == NULL) { /* no need to tickle if we're inhibiting. */ + if (screensaver_cookie == 0 && !inhibit_handle) { /* no need to tickle if we're inhibiting. */ /* org.gnome.ScreenSaver is the legacy interface, but it'll either do nothing or just be a second harmless tickle on newer systems, so we leave it for now. */ SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID); SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID); @@ -428,7 +427,7 @@ SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit) { const char *default_inhibit_reason = "Playing a game"; - if ((inhibit && (screensaver_cookie != 0 || inhibit_handle != NULL)) || (!inhibit && (screensaver_cookie == 0 && inhibit_handle == NULL))) { + if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) { return SDL_TRUE; } @@ -452,12 +451,12 @@ SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit) const char *key = "reason"; const char *reply = NULL; const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME); - if (reason == NULL || !reason[0]) { + if (!reason || !reason[0]) { reason = default_inhibit_reason; } msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit"); - if (msg == NULL) { + if (!msg) { return SDL_FALSE; } @@ -496,10 +495,10 @@ SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit) if (inhibit) { const char *app = SDL_GetHint(SDL_HINT_APP_NAME); const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME); - if (app == NULL || !app[0]) { + if (!app || !app[0]) { app = "My SDL application"; } - if (reason == NULL || !reason[0]) { + if (!reason || !reason[0]) { reason = default_inhibit_reason; } @@ -508,7 +507,7 @@ SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit) DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) { return SDL_FALSE; } - return (screensaver_cookie != 0) ? SDL_TRUE : SDL_FALSE; + return (screensaver_cookie != 0); } else { if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) { return SDL_FALSE; diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 7a6feeb3..c37de56a 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c index c0bde0d7..f7e26b12 100644 --- a/src/core/linux/SDL_evdev.c +++ b/src/core/linux/SDL_evdev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -73,6 +73,7 @@ typedef struct SDL_evdevlist_item { char *path; int fd; + int udev_class; /* TODO: use this for every device, not just touchscreen */ SDL_bool out_of_sync; @@ -155,12 +156,21 @@ static int SDL_EVDEV_SetRelativeMouseMode(SDL_bool enabled) return 0; } +static void SDL_EVDEV_UpdateKeyboardMute(void) +{ + if (SDL_EVDEV_GetDeviceCount(SDL_UDEV_DEVICE_KEYBOARD) > 0) { + SDL_EVDEV_kbd_set_muted(_this->kbd, SDL_TRUE); + } else { + SDL_EVDEV_kbd_set_muted(_this->kbd, SDL_FALSE); + } +} + int SDL_EVDEV_Init(void) { - if (_this == NULL) { + if (!_this) { _this = (SDL_EVDEV_PrivateData *)SDL_calloc(1, sizeof(*_this)); - if (_this == NULL) { - return SDL_OutOfMemory(); + if (!_this) { + return -1; } #ifdef SDL_USE_LIBUDEV @@ -208,6 +218,8 @@ int SDL_EVDEV_Init(void) #endif /* SDL_USE_LIBUDEV */ _this->kbd = SDL_EVDEV_kbd_init(); + + SDL_EVDEV_UpdateKeyboardMute(); } SDL_GetMouse()->SetRelativeMouseMode = SDL_EVDEV_SetRelativeMouseMode; @@ -219,7 +231,7 @@ int SDL_EVDEV_Init(void) void SDL_EVDEV_Quit(void) { - if (_this == NULL) { + if (!_this) { return; } @@ -231,13 +243,13 @@ void SDL_EVDEV_Quit(void) SDL_UDEV_Quit(); #endif /* SDL_USE_LIBUDEV */ - SDL_EVDEV_kbd_quit(_this->kbd); - /* Remove existing devices */ - while (_this->first != NULL) { + while (_this->first) { SDL_EVDEV_device_removed(_this->first->path); } + SDL_EVDEV_kbd_quit(_this->kbd); + SDL_assert(_this->first == NULL); SDL_assert(_this->last == NULL); SDL_assert(_this->num_devices == 0); @@ -251,7 +263,7 @@ void SDL_EVDEV_Quit(void) static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class, const char *dev_path) { - if (dev_path == NULL) { + if (!dev_path) { return; } @@ -276,6 +288,27 @@ static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_cl } #endif /* SDL_USE_LIBUDEV */ +void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, + void (*acquire_callback)(void*), void *acquire_callback_data) +{ + SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd, + release_callback, release_callback_data, + acquire_callback, acquire_callback_data); +} + +int SDL_EVDEV_GetDeviceCount(int device_class) +{ + SDL_evdevlist_item *item; + int count = 0; + + for (item = _this->first; item; item = item->next) { + if ((item->udev_class & device_class) == device_class) { + ++count; + } + } + return count; +} + void SDL_EVDEV_Poll(void) { struct input_event events[32]; @@ -294,9 +327,11 @@ void SDL_EVDEV_Poll(void) SDL_UDEV_Poll(); #endif + SDL_EVDEV_kbd_update(_this->kbd); + mouse = SDL_GetMouse(); - for (item = _this->first; item != NULL; item = item->next) { + for (item = _this->first; item; item = item->next) { while ((len = read(item->fd, events, sizeof(events))) > 0) { len /= sizeof(events[0]); for (i = 0; i < len; ++i) { @@ -608,8 +643,8 @@ static int SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) } item->touchscreen_data = SDL_calloc(1, sizeof(*item->touchscreen_data)); - if (item->touchscreen_data == NULL) { - return SDL_OutOfMemory(); + if (!item->touchscreen_data) { + return -1; } ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name); @@ -619,9 +654,9 @@ static int SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) } item->touchscreen_data->name = SDL_strdup(name); - if (item->touchscreen_data->name == NULL) { + if (!item->touchscreen_data->name) { SDL_free(item->touchscreen_data); - return SDL_OutOfMemory(); + return -1; } ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info); @@ -674,10 +709,10 @@ static int SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class) item->touchscreen_data->slots = SDL_calloc( item->touchscreen_data->max_slots, sizeof(*item->touchscreen_data->slots)); - if (item->touchscreen_data->slots == NULL) { + if (!item->touchscreen_data->slots) { SDL_free(item->touchscreen_data->name); SDL_free(item->touchscreen_data); - return SDL_OutOfMemory(); + return -1; } for (i = 0; i < item->touchscreen_data->max_slots; i++) { @@ -735,7 +770,7 @@ static void SDL_EVDEV_sync_device(SDL_evdevlist_item *item) sizeof(*mt_req_values) * item->touchscreen_data->max_slots; mt_req_code = SDL_calloc(1, mt_req_size); - if (mt_req_code == NULL) { + if (!mt_req_code) { return; } @@ -840,15 +875,15 @@ static int SDL_EVDEV_device_added(const char *dev_path, int udev_class) unsigned long relbit[NBITS(REL_MAX)] = { 0 }; /* Check to make sure it's not already in list. */ - for (item = _this->first; item != NULL; item = item->next) { + for (item = _this->first; item; item = item->next) { if (SDL_strcmp(dev_path, item->path) == 0) { return -1; /* already have this one */ } } item = (SDL_evdevlist_item *)SDL_calloc(1, sizeof(SDL_evdevlist_item)); - if (item == NULL) { - return SDL_OutOfMemory(); + if (!item) { + return -1; } item->fd = open(dev_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); @@ -858,12 +893,14 @@ static int SDL_EVDEV_device_added(const char *dev_path, int udev_class) } item->path = SDL_strdup(dev_path); - if (item->path == NULL) { + if (!item->path) { close(item->fd); SDL_free(item); - return SDL_OutOfMemory(); + return -1; } + item->udev_class = udev_class; + if (ioctl(item->fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0) { item->relative_mouse = test_bit(REL_X, relbit) && test_bit(REL_Y, relbit); item->high_res_wheel = test_bit(REL_WHEEL_HI_RES, relbit); @@ -891,7 +928,7 @@ static int SDL_EVDEV_device_added(const char *dev_path, int udev_class) } } - if (_this->last == NULL) { + if (!_this->last) { _this->first = _this->last = item; } else { _this->last->next = item; @@ -900,6 +937,8 @@ static int SDL_EVDEV_device_added(const char *dev_path, int udev_class) SDL_EVDEV_sync_device(item); + SDL_EVDEV_UpdateKeyboardMute(); + return _this->num_devices++; } @@ -908,10 +947,10 @@ static int SDL_EVDEV_device_removed(const char *dev_path) SDL_evdevlist_item *item; SDL_evdevlist_item *prev = NULL; - for (item = _this->first; item != NULL; item = item->next) { + for (item = _this->first; item; item = item->next) { /* found it, remove it. */ if (SDL_strcmp(dev_path, item->path) == 0) { - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(_this->first == item); @@ -926,6 +965,7 @@ static int SDL_EVDEV_device_removed(const char *dev_path) close(item->fd); SDL_free(item->path); SDL_free(item); + SDL_EVDEV_UpdateKeyboardMute(); _this->num_devices--; return 0; } diff --git a/src/core/linux/SDL_evdev.h b/src/core/linux/SDL_evdev.h index ba58b91a..fc7c4bd5 100644 --- a/src/core/linux/SDL_evdev.h +++ b/src/core/linux/SDL_evdev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,6 +30,9 @@ struct input_event; extern int SDL_EVDEV_Init(void); extern void SDL_EVDEV_Quit(void); +extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, + void (*acquire_callback)(void*), void *acquire_callback_data); +extern int SDL_EVDEV_GetDeviceCount(int device_class); extern void SDL_EVDEV_Poll(void); extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event); diff --git a/src/core/linux/SDL_evdev_capabilities.c b/src/core/linux/SDL_evdev_capabilities.c index 96653f64..865abf4f 100644 --- a/src/core/linux/SDL_evdev_capabilities.c +++ b/src/core/linux/SDL_evdev_capabilities.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/src/core/linux/SDL_evdev_capabilities.h b/src/core/linux/SDL_evdev_capabilities.h index 94afee79..49df8da7 100644 --- a/src/core/linux/SDL_evdev_capabilities.h +++ b/src/core/linux/SDL_evdev_capabilities.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2020 Collabora Ltd. This software is provided 'as-is', without any express or implied @@ -53,6 +53,7 @@ typedef enum SDL_UDEV_DEVICE_ACCELEROMETER = 0x0020, SDL_UDEV_DEVICE_TOUCHPAD = 0x0040, SDL_UDEV_DEVICE_HAS_KEYS = 0x0080, + SDL_UDEV_DEVICE_VIDEO_CAPTURE = 0x0100, } SDL_UDEV_deviceclass; #define BITS_PER_LONG (sizeof(unsigned long) * 8) diff --git a/src/core/linux/SDL_evdev_kbd.c b/src/core/linux/SDL_evdev_kbd.c index 2754e51e..7c81b544 100644 --- a/src/core/linux/SDL_evdev_kbd.c +++ b/src/core/linux/SDL_evdev_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -84,6 +84,7 @@ static fn_handler_fn *fn_handler[] = { struct SDL_EVDEV_keyboard_state { int console_fd; + SDL_bool muted; int old_kbd_mode; unsigned short **key_maps; unsigned char shift_down[NR_SHIFT]; /* shift state counters.. */ @@ -98,6 +99,10 @@ struct SDL_EVDEV_keyboard_state char shift_state; char text[128]; unsigned int text_len; + void (*vt_release_callback)(void *); + void *vt_release_callback_data; + void (*vt_acquire_callback)(void *); + void *vt_acquire_callback_data; }; #ifdef DUMP_ACCENTS @@ -168,7 +173,7 @@ static int fatal_signals[] = { static void kbd_cleanup(void) { SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state; - if (kbd == NULL) { + if (!kbd) { return; } kbd_cleanup_state = NULL; @@ -253,7 +258,7 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) { int tabidx; - if (kbd_cleanup_state != NULL) { + if (kbd_cleanup_state) { return; } kbd_cleanup_state = kbd; @@ -295,6 +300,126 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd) } } +enum { + VT_SIGNAL_NONE, + VT_SIGNAL_RELEASE, + VT_SIGNAL_ACQUIRE, +}; +static int vt_release_signal; +static int vt_acquire_signal; +static SDL_AtomicInt vt_signal_pending; + +typedef void (*signal_handler)(int signum); + +static void kbd_vt_release_signal_action(int signum) +{ + SDL_AtomicSet(&vt_signal_pending, VT_SIGNAL_RELEASE); +} + +static void kbd_vt_acquire_signal_action(int signum) +{ + SDL_AtomicSet(&vt_signal_pending, VT_SIGNAL_ACQUIRE); +} + +static SDL_bool setup_vt_signal(int signum, signal_handler handler) +{ + struct sigaction *old_action_p; + struct sigaction new_action; + old_action_p = &(old_sigaction[signum]); + SDL_zero(new_action); + new_action.sa_handler = handler; + new_action.sa_flags = SA_RESTART; + if (sigaction(signum, &new_action, old_action_p) < 0) { + return SDL_FALSE; + } + if (old_action_p->sa_handler != SIG_DFL) { + /* This signal is already in use */ + sigaction(signum, old_action_p, NULL); + return SDL_FALSE; + } + return SDL_TRUE; +} + +static int find_free_signal(signal_handler handler) +{ +#ifdef SIGRTMIN + int i; + + for (i = SIGRTMIN + 2; i <= SIGRTMAX; ++i) { + if (setup_vt_signal(i, handler)) { + return i; + } + } +#endif + if (setup_vt_signal(SIGUSR1, handler)) { + return SIGUSR1; + } + if (setup_vt_signal(SIGUSR2, handler)) { + return SIGUSR2; + } + return 0; +} + +static void kbd_vt_quit(int console_fd) +{ + struct vt_mode mode; + + if (vt_release_signal) { + sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL); + vt_release_signal = 0; + } + if (vt_acquire_signal) { + sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL); + vt_acquire_signal = 0; + } + + SDL_zero(mode); + mode.mode = VT_AUTO; + ioctl(console_fd, VT_SETMODE, &mode); +} + +static int kbd_vt_init(int console_fd) +{ + struct vt_mode mode; + + vt_release_signal = find_free_signal(kbd_vt_release_signal_action); + vt_acquire_signal = find_free_signal(kbd_vt_acquire_signal_action); + if (!vt_release_signal || !vt_acquire_signal ) { + kbd_vt_quit(console_fd); + return -1; + } + + SDL_zero(mode); + mode.mode = VT_PROCESS; + mode.relsig = vt_release_signal; + mode.acqsig = vt_acquire_signal; + mode.frsig = SIGIO; + if (ioctl(console_fd, VT_SETMODE, &mode) < 0) { + kbd_vt_quit(console_fd); + return -1; + } + return 0; +} + +static void kbd_vt_update(SDL_EVDEV_keyboard_state *state) +{ + int signal_pending = SDL_AtomicGet(&vt_signal_pending); + if (signal_pending != VT_SIGNAL_NONE) { + if (signal_pending == VT_SIGNAL_RELEASE) { + if (state->vt_release_callback) { + state->vt_release_callback(state->vt_release_callback_data); + } + ioctl(state->console_fd, VT_RELDISP, 1); + } else { + if (state->vt_acquire_callback) { + state->vt_acquire_callback(state->vt_acquire_callback_data); + } + ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ); + } + SDL_AtomicCAS(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE); + } +} + SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) { SDL_EVDEV_keyboard_state *kbd; @@ -303,7 +428,7 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) char shift_state[sizeof(long)] = { TIOCL_GETSHIFTSTATE, 0 }; kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(*kbd)); - if (kbd == NULL) { + if (!kbd) { return NULL; } @@ -332,35 +457,77 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) ioctl(kbd->console_fd, KDSKBMODE, K_UNICODE); } - /* Allow inhibiting keyboard mute with env. variable for debugging etc. */ - if (SDL_getenv("SDL_INPUT_LINUX_KEEP_KBD") == NULL) { - /* Mute the keyboard so keystrokes only generate evdev events - * and do not leak through to the console - */ - ioctl(kbd->console_fd, KDSKBMODE, K_OFF); + kbd_vt_init(kbd->console_fd); - /* Make sure to restore keyboard if application fails to call - * SDL_Quit before exit or fatal signal is raised. - */ - if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) { - kbd_register_emerg_cleanup(kbd); - } - } return kbd; } +void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted) +{ + if (!state) { + return; + } + + if (muted == state->muted) { + return; + } + + if (muted) { + /* Allow inhibiting keyboard mute with env. variable for debugging etc. */ + if (SDL_getenv("SDL_INPUT_LINUX_KEEP_KBD") == NULL) { + /* Mute the keyboard so keystrokes only generate evdev events + * and do not leak through to the console + */ + ioctl(state->console_fd, KDSKBMODE, K_OFF); + + /* Make sure to restore keyboard if application fails to call + * SDL_Quit before exit or fatal signal is raised. + */ + if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) { + kbd_register_emerg_cleanup(state); + } + } + } else { + kbd_unregister_emerg_cleanup(); + + /* Restore the original keyboard mode */ + ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode); + } + state->muted = muted; +} + +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +{ + if (state == NULL) { + return; + } + + state->vt_release_callback = release_callback; + state->vt_release_callback_data = release_callback_data; + state->vt_acquire_callback = acquire_callback; + state->vt_acquire_callback_data = acquire_callback_data; +} + +void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state) +{ + if (!state) { + return; + } + + kbd_vt_update(state); +} + void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state) { if (state == NULL) { return; } - kbd_unregister_emerg_cleanup(); + SDL_EVDEV_kbd_set_muted(state, SDL_FALSE); + + kbd_vt_quit(state->console_fd); if (state->console_fd >= 0) { - /* Restore the original keyboard mode */ - ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode); - close(state->console_fd); state->console_fd = -1; } @@ -725,7 +892,7 @@ void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode unsigned short *key_map; unsigned short keysym; - if (state == NULL) { + if (!state) { return; } @@ -733,7 +900,7 @@ void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode shift_final = (state->shift_state | state->slockstate) ^ state->lockstate; key_map = state->key_maps[shift_final]; - if (key_map == NULL) { + if (!key_map) { /* Unsupported shift state (e.g. ctrl = 4, alt = 8), just reset to the default state */ state->shift_state = 0; state->slockstate = 0; @@ -808,6 +975,18 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void) return NULL; } +void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted) +{ +} + +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +{ +} + +void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state) +{ +} + void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down) { } diff --git a/src/core/linux/SDL_evdev_kbd.h b/src/core/linux/SDL_evdev_kbd.h index 6d592ef2..7c38bc9d 100644 --- a/src/core/linux/SDL_evdev_kbd.h +++ b/src/core/linux/SDL_evdev_kbd.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,6 +26,9 @@ struct SDL_EVDEV_keyboard_state; typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state; extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void); +extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted); +extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data); +extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state); extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down); extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state); diff --git a/src/core/linux/SDL_evdev_kbd_default_accents.h b/src/core/linux/SDL_evdev_kbd_default_accents.h index 1bf49c1b..259e6572 100644 --- a/src/core/linux/SDL_evdev_kbd_default_accents.h +++ b/src/core/linux/SDL_evdev_kbd_default_accents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_evdev_kbd_default_keymap.h b/src/core/linux/SDL_evdev_kbd_default_keymap.h index db767cb1..790dac9b 100644 --- a/src/core/linux/SDL_evdev_kbd_default_keymap.h +++ b/src/core/linux/SDL_evdev_kbd_default_keymap.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_fcitx.c b/src/core/linux/SDL_fcitx.c index d7cfe6be..a3de3c68 100644 --- a/src/core/linux/SDL_fcitx.c +++ b/src/core/linux/SDL_fcitx.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,7 +29,6 @@ #ifdef SDL_VIDEO_DRIVER_X11 #include "../../video/x11/SDL_x11video.h" #endif -#include #define FCITX_DBUS_SERVICE "org.freedesktop.portal.Fcitx" @@ -212,26 +211,11 @@ static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *m Sint32 start_pos, end_pos; size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text, &start_pos, &end_pos); if (text_bytes) { - if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE)) { - if (start_pos == -1) { - Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg); - start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1; - } - SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1); - } else { - char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; - size_t i = 0; - size_t cursor = 0; - while (i < text_bytes) { - const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf)); - const size_t chars = SDL_utf8strlen(buf); - - SDL_SendEditingText(buf, cursor, chars); - - i += sz; - cursor += chars; - } + if (start_pos == -1) { + Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg); + start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1; } + SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1); SDL_free(text); } else { SDL_SendEditingText("", 0, 0); @@ -427,7 +411,6 @@ SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state) void SDL_Fcitx_UpdateTextRect(const SDL_Rect *rect) { SDL_Window *focused_win = NULL; - SDL_SysWMinfo info; int x = 0, y = 0; SDL_Rect *cursor = &fcitx_client.cursor_rect; @@ -436,23 +419,24 @@ void SDL_Fcitx_UpdateTextRect(const SDL_Rect *rect) } focused_win = SDL_GetKeyboardFocus(); - if (focused_win == NULL) { + if (!focused_win) { return; } SDL_GetWindowPosition(focused_win, &x, &y); - if (SDL_GetWindowWMInfo(focused_win, &info, SDL_SYSWM_CURRENT_VERSION) == 0) { -#ifdef SDL_ENABLE_SYSWM_X11 - if (info.subsystem == SDL_SYSWM_X11) { - Display *x_disp = info.info.x11.display; - int x_screen = info.info.x11.screen; - Window x_win = info.info.x11.window; - Window unused; +#ifdef SDL_VIDEO_DRIVER_X11 + { + SDL_PropertiesID props = SDL_GetWindowProperties(focused_win); + Display *x_disp = (Display *)SDL_GetProperty(props, SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER, NULL); + int x_screen = SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_SCREEN_NUMBER, 0); + Window x_win = SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER, 0); + Window unused; + if (x_disp && x_win) { X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused); } -#endif } +#endif if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) { /* move to bottom left */ diff --git a/src/core/linux/SDL_fcitx.h b/src/core/linux/SDL_fcitx.h index 2d029a1f..44ee0533 100644 --- a/src/core/linux/SDL_fcitx.h +++ b/src/core/linux/SDL_fcitx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_ibus.c b/src/core/linux/SDL_ibus.c index 92475c25..a919735e 100644 --- a/src/core/linux/SDL_ibus.c +++ b/src/core/linux/SDL_ibus.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,7 +32,6 @@ #ifdef SDL_VIDEO_DRIVER_X11 #include "../../video/x11/SDL_x11video.h" #endif -#include #include #include @@ -113,7 +112,7 @@ static SDL_bool IBus_EnterVariant(DBusConnection *conn, DBusMessageIter *iter, S } dbus->message_iter_get_basic(inside, &struct_id); - if (struct_id == NULL || SDL_strncmp(struct_id, struct_id, id_size) != 0) { + if (!struct_id || SDL_strncmp(struct_id, struct_id, id_size) != 0) { return SDL_FALSE; } return SDL_TRUE; @@ -252,38 +251,23 @@ static DBusHandlerResult IBus_MessageHandler(DBusConnection *conn, DBusMessage * text = IBus_GetVariantText(conn, &iter, dbus); if (text) { - if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE)) { - Uint32 pos, start_pos, end_pos; - SDL_bool has_pos = SDL_FALSE; - SDL_bool has_dec_pos = SDL_FALSE; + Uint32 pos, start_pos, end_pos; + SDL_bool has_pos = SDL_FALSE; + SDL_bool has_dec_pos = SDL_FALSE; + dbus->message_iter_init(msg, &iter); + has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos); + if (!has_dec_pos) { dbus->message_iter_init(msg, &iter); - has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos); - if (!has_dec_pos) { - dbus->message_iter_init(msg, &iter); - has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos); - } + has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos); + } - if (has_dec_pos) { - SDL_SendEditingText(text, start_pos, end_pos - start_pos); - } else if (has_pos) { - SDL_SendEditingText(text, pos, -1); - } else { - SDL_SendEditingText(text, -1, -1); - } + if (has_dec_pos) { + SDL_SendEditingText(text, start_pos, end_pos - start_pos); + } else if (has_pos) { + SDL_SendEditingText(text, pos, -1); } else { - char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; - size_t text_bytes = SDL_strlen(text), i = 0; - size_t cursor = 0; - - do { - const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf)); - const size_t chars = SDL_utf8strlen(buf); - - SDL_SendEditingText(buf, cursor, chars); - i += sz; - cursor += chars; - } while (i < text_bytes); + SDL_SendEditingText(text, -1, -1); } } @@ -307,7 +291,7 @@ static char *IBus_ReadAddressFromFile(const char *file_path) FILE *addr_file; addr_file = fopen(file_path, "r"); - if (addr_file == NULL) { + if (!addr_file) { return NULL; } @@ -352,7 +336,7 @@ static char *IBus_GetDBusAddressFilename(void) } dbus = SDL_DBus_GetContext(); - if (dbus == NULL) { + if (!dbus) { return NULL; } @@ -366,7 +350,7 @@ static char *IBus_GetDBusAddressFilename(void) and look up the address from a filepath using all those bits, eek. */ disp_env = SDL_getenv("DISPLAY"); - if (disp_env == NULL || !*disp_env) { + if (!disp_env || !*disp_env) { display = SDL_strdup(":0.0"); } else { display = SDL_strdup(disp_env); @@ -376,7 +360,7 @@ static char *IBus_GetDBusAddressFilename(void) disp_num = SDL_strrchr(display, ':'); screen_num = SDL_strrchr(display, '.'); - if (disp_num == NULL) { + if (!disp_num) { SDL_free(display); return NULL; } @@ -390,7 +374,7 @@ static char *IBus_GetDBusAddressFilename(void) if (!*host) { const char *session = SDL_getenv("XDG_SESSION_TYPE"); - if (session != NULL && SDL_strcmp(session, "wayland") == 0) { + if (session && SDL_strcmp(session, "wayland") == 0) { host = "unix-wayland"; } else { host = "unix"; @@ -404,7 +388,7 @@ static char *IBus_GetDBusAddressFilename(void) SDL_strlcpy(config_dir, conf_env, sizeof(config_dir)); } else { const char *home_env = SDL_getenv("HOME"); - if (home_env == NULL || !*home_env) { + if (!home_env || !*home_env) { SDL_free(display); return NULL; } @@ -413,7 +397,7 @@ static char *IBus_GetDBusAddressFilename(void) key = SDL_DBus_GetLocalMachineId(); - if (key == NULL) { + if (!key) { SDL_free(display); return NULL; } @@ -474,7 +458,7 @@ static SDL_bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) ibus_input_interface = IBUS_INPUT_INTERFACE; ibus_conn = dbus->connection_open_private(addr, NULL); - if (ibus_conn == NULL) { + if (!ibus_conn) { return SDL_FALSE; /* oh well. */ } @@ -514,7 +498,7 @@ static SDL_bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr) static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus) { - if (dbus == NULL) { + if (!dbus) { return SDL_FALSE; } @@ -534,7 +518,7 @@ static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus) struct inotify_event *event = (struct inotify_event *)p; if (event->len > 0) { char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/'); - if (addr_file_no_path == NULL) { + if (!addr_file_no_path) { return SDL_FALSE; } @@ -571,19 +555,21 @@ SDL_bool SDL_IBus_Init(void) char *addr; char *addr_file_dir; - if (addr_file == NULL) { + if (!addr_file) { return SDL_FALSE; } - /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */ - ibus_addr_file = SDL_strdup(addr_file); - addr = IBus_ReadAddressFromFile(addr_file); - if (addr == NULL) { + if (!addr) { SDL_free(addr_file); return SDL_FALSE; } + if (ibus_addr_file) { + SDL_free(ibus_addr_file); + } + ibus_addr_file = SDL_strdup(addr_file); + if (inotify_fd < 0) { inotify_fd = inotify_init(); fcntl(inotify_fd, F_SETFL, O_NONBLOCK); @@ -660,7 +646,7 @@ static void IBus_SimpleMessage(const char *method) { SDL_DBusContext *dbus = SDL_DBus_GetContext(); - if ((input_ctx_path != NULL) && (IBus_CheckConnection(dbus))) { + if ((input_ctx_path) && (IBus_CheckConnection(dbus))) { SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, method, DBUS_TYPE_INVALID); } } @@ -696,13 +682,12 @@ SDL_bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state) SDL_IBus_UpdateTextRect(NULL); - return result ? SDL_TRUE : SDL_FALSE; + return (result != 0); } void SDL_IBus_UpdateTextRect(const SDL_Rect *rect) { SDL_Window *focused_win; - SDL_SysWMinfo info; int x = 0, y = 0; SDL_DBusContext *dbus; @@ -711,24 +696,25 @@ void SDL_IBus_UpdateTextRect(const SDL_Rect *rect) } focused_win = SDL_GetKeyboardFocus(); - if (focused_win == NULL) { + if (!focused_win) { return; } SDL_GetWindowPosition(focused_win, &x, &y); - if (SDL_GetWindowWMInfo(focused_win, &info, SDL_SYSWM_CURRENT_VERSION) == 0) { -#ifdef SDL_ENABLE_SYSWM_X11 - if (info.subsystem == SDL_SYSWM_X11) { - Display *x_disp = info.info.x11.display; - int x_screen = info.info.x11.screen; - Window x_win = info.info.x11.window; - Window unused; +#ifdef SDL_VIDEO_DRIVER_X11 + { + SDL_PropertiesID props = SDL_GetWindowProperties(focused_win); + Display *x_disp = (Display *)SDL_GetProperty(props, SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER, NULL); + int x_screen = SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_SCREEN_NUMBER, 0); + Window x_win = SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER, 0); + Window unused; + if (x_disp && x_win) { X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused); } -#endif } +#endif x += ibus_cursor_rect.x; y += ibus_cursor_rect.y; diff --git a/src/core/linux/SDL_ibus.h b/src/core/linux/SDL_ibus.h index 560aa126..377a9fc2 100644 --- a/src/core/linux/SDL_ibus.h +++ b/src/core/linux/SDL_ibus.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_ime.c b/src/core/linux/SDL_ime.c index 45bc1c6c..752c3ab9 100644 --- a/src/core/linux/SDL_ime.c +++ b/src/core/linux/SDL_ime.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -56,9 +56,9 @@ static void InitIME(void) /* See if fcitx IME support is being requested */ #ifdef HAVE_FCITX - if (SDL_IME_Init_Real == NULL && + if (!SDL_IME_Init_Real && ((im_module && SDL_strcmp(im_module, "fcitx") == 0) || - (im_module == NULL && xmodifiers && SDL_strstr(xmodifiers, "@im=fcitx") != NULL))) { + (!im_module && xmodifiers && SDL_strstr(xmodifiers, "@im=fcitx") != NULL))) { SDL_IME_Init_Real = SDL_Fcitx_Init; SDL_IME_Quit_Real = SDL_Fcitx_Quit; SDL_IME_SetFocus_Real = SDL_Fcitx_SetFocus; @@ -71,7 +71,7 @@ static void InitIME(void) /* default to IBus */ #ifdef HAVE_IBUS_IBUS_H - if (SDL_IME_Init_Real == NULL) { + if (!SDL_IME_Init_Real) { SDL_IME_Init_Real = SDL_IBus_Init; SDL_IME_Quit_Real = SDL_IBus_Quit; SDL_IME_SetFocus_Real = SDL_IBus_SetFocus; diff --git a/src/core/linux/SDL_ime.h b/src/core/linux/SDL_ime.h index 3b21b502..f0ac7782 100644 --- a/src/core/linux/SDL_ime.h +++ b/src/core/linux/SDL_ime.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_sandbox.c b/src/core/linux/SDL_sandbox.c index f0e0cb79..413148fc 100644 --- a/src/core/linux/SDL_sandbox.c +++ b/src/core/linux/SDL_sandbox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2022 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/src/core/linux/SDL_sandbox.h b/src/core/linux/SDL_sandbox.h index 6824c156..8339c90b 100644 --- a/src/core/linux/SDL_sandbox.h +++ b/src/core/linux/SDL_sandbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2022 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/src/core/linux/SDL_system_theme.c b/src/core/linux/SDL_system_theme.c index 89077a03..20b24122 100644 --- a/src/core/linux/SDL_system_theme.c +++ b/src/core/linux/SDL_system_theme.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -115,12 +115,12 @@ SDL_bool SDL_SystemTheme_Init(void) system_theme_data.theme = SDL_SYSTEM_THEME_UNKNOWN; system_theme_data.dbus = dbus; - if (dbus == NULL) { + if (!dbus) { return SDL_FALSE; } msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, PORTAL_METHOD); - if (msg != NULL) { + if (msg) { if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) { DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL); if (reply) { diff --git a/src/core/linux/SDL_system_theme.h b/src/core/linux/SDL_system_theme.h index 8d521faa..396b3a76 100644 --- a/src/core/linux/SDL_system_theme.h +++ b/src/core/linux/SDL_system_theme.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/linux/SDL_threadprio.c b/src/core/linux/SDL_threadprio.c index 23b44733..cc8d6e47 100644 --- a/src/core/linux/SDL_threadprio.c +++ b/src/core/linux/SDL_threadprio.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -111,19 +111,19 @@ static void rtkit_initialize(void) dbus_conn = get_rtkit_dbus_connection(); /* Try getting minimum nice level: this is often greater than PRIO_MIN (-20). */ - if (dbus_conn == NULL || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel", DBUS_TYPE_INT32, &rtkit_min_nice_level)) { rtkit_min_nice_level = -20; } /* Try getting maximum realtime priority: this can be less than the POSIX default (99). */ - if (dbus_conn == NULL || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority", DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) { rtkit_max_realtime_priority = 99; } /* Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL */ - if (dbus_conn == NULL || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", + if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax", DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) { rtkit_max_rttime_usec = 200000; } @@ -202,7 +202,7 @@ static SDL_bool rtkit_setpriority_nice(pid_t thread, int nice_level) nice = rtkit_min_nice_level; } - if (dbus_conn == NULL || !SDL_DBus_CallMethodOnConnection(dbus_conn, + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID", DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID, DBUS_TYPE_INVALID)) { @@ -233,7 +233,7 @@ static SDL_bool rtkit_setpriority_realtime(pid_t thread, int rt_priority) // go through to determine whether it really needs to fail or not. rtkit_initialize_realtime_thread(); - if (dbus_conn == NULL || !SDL_DBus_CallMethodOnConnection(dbus_conn, + if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID", DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID, DBUS_TYPE_INVALID)) { diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c index 9e72d79e..aa12289e 100644 --- a/src/core/linux/SDL_udev.c +++ b/src/core/linux/SDL_udev.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,12 +42,15 @@ static SDL_UDEV_PrivateData *_this = NULL; static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr); static int SDL_UDEV_load_syms(void); static SDL_bool SDL_UDEV_hotplug_update_available(void); +static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len); +static int guess_device_class(struct udev_device *dev); +static int device_class(struct udev_device *dev); static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev); static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(_this->udev_handle, fn); - if (*addr == NULL) { + if (!*addr) { /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ return SDL_FALSE; } @@ -96,7 +99,7 @@ static int SDL_UDEV_load_syms(void) static SDL_bool SDL_UDEV_hotplug_update_available(void) { - if (_this->udev_mon != NULL) { + if (_this->udev_mon) { const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon); if (SDL_IOReady(fd, SDL_IOR_READ, 0)) { return SDL_TRUE; @@ -109,10 +112,10 @@ int SDL_UDEV_Init(void) { int retval = 0; - if (_this == NULL) { + if (!_this) { _this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this)); - if (_this == NULL) { - return SDL_OutOfMemory(); + if (!_this) { + return -1; } retval = SDL_UDEV_LoadLibrary(); @@ -126,19 +129,20 @@ int SDL_UDEV_Init(void) */ _this->udev = _this->syms.udev_new(); - if (_this->udev == NULL) { + if (!_this->udev) { SDL_UDEV_Quit(); return SDL_SetError("udev_new() failed"); } _this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev"); - if (_this->udev_mon == NULL) { + if (!_this->udev_mon) { SDL_UDEV_Quit(); return SDL_SetError("udev_monitor_new_from_netlink() failed"); } _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL); _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL); + _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL); _this->syms.udev_monitor_enable_receiving(_this->udev_mon); /* Do an initial scan of existing devices */ @@ -152,7 +156,7 @@ int SDL_UDEV_Init(void) void SDL_UDEV_Quit(void) { - if (_this == NULL) { + if (!_this) { return; } @@ -160,17 +164,17 @@ void SDL_UDEV_Quit(void) if (_this->ref_count < 1) { - if (_this->udev_mon != NULL) { + if (_this->udev_mon) { _this->syms.udev_monitor_unref(_this->udev_mon); _this->udev_mon = NULL; } - if (_this->udev != NULL) { + if (_this->udev) { _this->syms.udev_unref(_this->udev); _this->udev = NULL; } /* Remove existing devices */ - while (_this->first != NULL) { + while (_this->first) { SDL_UDEV_CallbackList *item = _this->first; _this->first = _this->first->next; SDL_free(item); @@ -188,25 +192,26 @@ int SDL_UDEV_Scan(void) struct udev_list_entry *devs = NULL; struct udev_list_entry *item = NULL; - if (_this == NULL) { + if (!_this) { return 0; } enumerate = _this->syms.udev_enumerate_new(_this->udev); - if (enumerate == NULL) { + if (!enumerate) { SDL_UDEV_Quit(); return SDL_SetError("udev_enumerate_new() failed"); } _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input"); _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound"); + _this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux"); _this->syms.udev_enumerate_scan_devices(enumerate); devs = _this->syms.udev_enumerate_get_list_entry(enumerate); for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) { const char *path = _this->syms.udev_list_entry_get_name(item); struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path); - if (dev != NULL) { + if (dev) { device_event(SDL_UDEV_DEVICEADDED, dev); _this->syms.udev_device_unref(dev); } @@ -216,19 +221,19 @@ int SDL_UDEV_Scan(void) return 0; } -SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version) +SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class) { struct udev_enumerate *enumerate = NULL; struct udev_list_entry *devs = NULL; struct udev_list_entry *item = NULL; SDL_bool found = SDL_FALSE; - if (_this == NULL) { + if (!_this) { return SDL_FALSE; } enumerate = _this->syms.udev_enumerate_new(_this->udev); - if (enumerate == NULL) { + if (!enumerate) { SDL_SetError("udev_enumerate_new() failed"); return SDL_FALSE; } @@ -238,28 +243,34 @@ SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 for (item = devs; item && !found; item = _this->syms.udev_list_entry_get_next(item)) { const char *path = _this->syms.udev_list_entry_get_name(item); struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path); - if (dev != NULL) { + if (dev) { const char *val = NULL; const char *existing_path; existing_path = _this->syms.udev_device_get_devnode(dev); if (existing_path && SDL_strcmp(device_path, existing_path) == 0) { + int class_temp; found = SDL_TRUE; val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID"); - if (val != NULL) { + if (val) { *vendor = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID"); - if (val != NULL) { + if (val) { *product = (Uint16)SDL_strtol(val, NULL, 16); } val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION"); - if (val != NULL) { + if (val) { *version = (Uint16)SDL_strtol(val, NULL, 16); } + + class_temp = device_class(dev); + if (class_temp) { + *class = class_temp; + } } _this->syms.udev_device_unref(dev); } @@ -271,11 +282,11 @@ SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 void SDL_UDEV_UnloadLibrary(void) { - if (_this == NULL) { + if (!_this) { return; } - if (_this->udev_handle != NULL) { + if (_this->udev_handle) { SDL_UnloadObject(_this->udev_handle); _this->udev_handle = NULL; } @@ -285,7 +296,7 @@ int SDL_UDEV_LoadLibrary(void) { int retval = 0, i; - if (_this == NULL) { + if (!_this) { return SDL_SetError("UDEV not initialized"); } @@ -296,9 +307,9 @@ int SDL_UDEV_LoadLibrary(void) #ifdef SDL_UDEV_DYNAMIC /* Check for the build environment's libudev first */ - if (_this->udev_handle == NULL) { + if (!_this->udev_handle) { _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC); - if (_this->udev_handle != NULL) { + if (_this->udev_handle) { retval = SDL_UDEV_load_syms(); if (retval < 0) { SDL_UDEV_UnloadLibrary(); @@ -307,10 +318,10 @@ int SDL_UDEV_LoadLibrary(void) } #endif - if (_this->udev_handle == NULL) { + if (!_this->udev_handle) { for (i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) { _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]); - if (_this->udev_handle != NULL) { + if (_this->udev_handle) { retval = SDL_UDEV_load_syms(); if (retval < 0) { SDL_UDEV_UnloadLibrary(); @@ -320,7 +331,7 @@ int SDL_UDEV_LoadLibrary(void) } } - if (_this->udev_handle == NULL) { + if (!_this->udev_handle) { retval = -1; /* Don't call SDL_SetError(): SDL_LoadObject already did. */ } @@ -339,7 +350,7 @@ static void get_caps(struct udev_device *dev, struct udev_device *pdev, const ch SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask)); value = _this->syms.udev_device_get_sysattr_value(pdev, attr); - if (value == NULL) { + if (!value) { return; } @@ -374,7 +385,7 @@ static int guess_device_class(struct udev_device *dev) while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) { pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); } - if (pdev == NULL) { + if (!pdev) { return 0; } @@ -391,43 +402,45 @@ static int guess_device_class(struct udev_device *dev) &bitmask_rel[0]); } -static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) +static int device_class(struct udev_device *dev) { const char *subsystem; const char *val = NULL; int devclass = 0; - const char *path; - SDL_UDEV_CallbackList *item; - - path = _this->syms.udev_device_get_devnode(dev); - if (path == NULL) { - return; - } subsystem = _this->syms.udev_device_get_subsystem(dev); + if (!subsystem) { + return 0; + } + if (SDL_strcmp(subsystem, "sound") == 0) { devclass = SDL_UDEV_DEVICE_SOUND; + } else if (SDL_strcmp(subsystem, "video4linux") == 0) { + val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES"); + if (val && SDL_strcasestr(val, "capture")) { + devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE; + } } else if (SDL_strcmp(subsystem, "input") == 0) { /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */ val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); - if (val != NULL && SDL_strcmp(val, "1") == 0) { + if (val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_JOYSTICK; } val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER"); if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE) && - val != NULL && SDL_strcmp(val, "1") == 0) { + val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_JOYSTICK; } val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); - if (val != NULL && SDL_strcmp(val, "1") == 0) { + if (val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_MOUSE; } val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"); - if (val != NULL && SDL_strcmp(val, "1") == 0) { + if (val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; } @@ -438,39 +451,54 @@ static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183 */ val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY"); - if (val != NULL && SDL_strcmp(val, "1") == 0) { + if (val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_HAS_KEYS; } val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"); - if (val != NULL && SDL_strcmp(val, "1") == 0) { + if (val && SDL_strcmp(val, "1") == 0) { devclass |= SDL_UDEV_DEVICE_KEYBOARD; } if (devclass == 0) { /* Fall back to old style input classes */ val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS"); - if (val != NULL) { + if (val) { if (SDL_strcmp(val, "joystick") == 0) { devclass = SDL_UDEV_DEVICE_JOYSTICK; } else if (SDL_strcmp(val, "mouse") == 0) { devclass = SDL_UDEV_DEVICE_MOUSE; } else if (SDL_strcmp(val, "kbd") == 0) { devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD; - } else { - return; } } else { /* We could be linked with libudev on a system that doesn't have udev running */ devclass = guess_device_class(dev); } } - } else { + } + + return devclass; +} + +static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) +{ + int devclass = 0; + const char *path; + SDL_UDEV_CallbackList *item; + + path = _this->syms.udev_device_get_devnode(dev); + if (!path) { return; } + devclass = device_class(dev); + if (!devclass) { + return; + } + /* Process callbacks */ - for (item = _this->first; item != NULL; item = item->next) { + for (item = _this->first; item; item = item->next) { item->callback(type, devclass, path); } } @@ -480,13 +508,13 @@ void SDL_UDEV_Poll(void) struct udev_device *dev = NULL; const char *action = NULL; - if (_this == NULL) { + if (!_this) { return; } while (SDL_UDEV_hotplug_update_available()) { dev = _this->syms.udev_monitor_receive_device(_this->udev_mon); - if (dev == NULL) { + if (!dev) { break; } action = _this->syms.udev_device_get_action(dev); @@ -507,20 +535,20 @@ int SDL_UDEV_AddCallback(SDL_UDEV_Callback cb) { SDL_UDEV_CallbackList *item; item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList)); - if (item == NULL) { - return SDL_OutOfMemory(); + if (!item) { + return -1; } item->callback = cb; - if (_this->last == NULL) { + if (!_this->last) { _this->first = _this->last = item; } else { _this->last->next = item; _this->last = item; } - return 1; + return 0; } void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb) @@ -528,14 +556,14 @@ void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb) SDL_UDEV_CallbackList *item; SDL_UDEV_CallbackList *prev = NULL; - if (_this == NULL) { + if (!_this) { return; } - for (item = _this->first; item != NULL; item = item->next) { + for (item = _this->first; item; item = item->next) { /* found it, remove it. */ if (item->callback == cb) { - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(_this->first == item); diff --git a/src/core/linux/SDL_udev.h b/src/core/linux/SDL_udev.h index 2f44e929..b3972d67 100644 --- a/src/core/linux/SDL_udev.h +++ b/src/core/linux/SDL_udev.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,7 +35,7 @@ #include /** - * \brief Device type + * Device type */ typedef enum @@ -102,7 +102,7 @@ extern void SDL_UDEV_UnloadLibrary(void); extern int SDL_UDEV_LoadLibrary(void); extern void SDL_UDEV_Poll(void); extern int SDL_UDEV_Scan(void); -extern SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version); +extern SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class); extern int SDL_UDEV_AddCallback(SDL_UDEV_Callback cb); extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb); extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void); diff --git a/src/core/n3ds/SDL_n3ds.c b/src/core/n3ds/SDL_n3ds.c index 0c1d8b4b..46610d19 100644 --- a/src/core/n3ds/SDL_n3ds.c +++ b/src/core/n3ds/SDL_n3ds.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/ngage/SDL_ngage_runapp.cpp b/src/core/ngage/SDL_ngage_runapp.cpp index 6c3e4b04..b2eca988 100644 --- a/src/core/ngage/SDL_ngage_runapp.cpp +++ b/src/core/ngage/SDL_ngage_runapp.cpp @@ -57,7 +57,7 @@ SDL_RunApp(int argc_, char* argv_[], SDL_main_func mainFunction, void * reserved newHeap = User::ChunkHeap(NULL, heapSize, heapSize, KMinHeapGrowBy); - if (newHeap == NULL) { + if (!newHeap) { ret = 3; goto cleanup; } else { diff --git a/src/core/openbsd/SDL_wscons.h b/src/core/openbsd/SDL_wscons.h index e53e5742..40e102d0 100644 --- a/src/core/openbsd/SDL_wscons.h +++ b/src/core/openbsd/SDL_wscons.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/openbsd/SDL_wscons_kbd.c b/src/core/openbsd/SDL_wscons_kbd.c index f54f1d39..6832cc83 100644 --- a/src/core/openbsd/SDL_wscons_kbd.c +++ b/src/core/openbsd/SDL_wscons_kbd.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -419,7 +419,7 @@ static SDL_WSCONS_input_data *SDL_WSCONS_Init_Keyboard(const char *dev) #endif SDL_WSCONS_input_data *input = (SDL_WSCONS_input_data *)SDL_calloc(1, sizeof(SDL_WSCONS_input_data)); - if (input == NULL) { + if (!input) { return input; } input->fd = open(dev, O_RDWR | O_NONBLOCK | O_CLOEXEC); @@ -429,7 +429,7 @@ static SDL_WSCONS_input_data *SDL_WSCONS_Init_Keyboard(const char *dev) return NULL; } input->keymap.map = SDL_calloc(sizeof(struct wscons_keymap), KS_NUMKEYCODES); - if (input->keymap.map == NULL) { + if (!input->keymap.map) { SDL_free(input); return NULL; } @@ -579,7 +579,7 @@ static void updateKeyboard(SDL_WSCONS_input_data *input) keysym_t *group; keysym_t ksym, result; - if (input == NULL) { + if (!input) { return; } if ((n = read(input->fd, events, sizeof(events))) > 0) { @@ -923,7 +923,7 @@ void SDL_WSCONS_PumpEvents() for (i = 0; i < 4; i++) { updateKeyboard(inputs[i]); } - if (mouseInputData != NULL) { + if (mouseInputData) { updateMouse(mouseInputData); } } diff --git a/src/core/openbsd/SDL_wscons_mouse.c b/src/core/openbsd/SDL_wscons_mouse.c index c3378153..3d3de955 100644 --- a/src/core/openbsd/SDL_wscons_mouse.c +++ b/src/core/openbsd/SDL_wscons_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,7 +41,7 @@ SDL_WSCONS_mouse_input_data *SDL_WSCONS_Init_Mouse() #endif SDL_WSCONS_mouse_input_data *mouseInputData = SDL_calloc(1, sizeof(SDL_WSCONS_mouse_input_data)); - if (mouseInputData == NULL) { + if (!mouseInputData) { return NULL; } mouseInputData->fd = open("/dev/wsmouse", O_RDWR | O_NONBLOCK | O_CLOEXEC); @@ -125,7 +125,7 @@ void updateMouse(SDL_WSCONS_mouse_input_data *inputData) void SDL_WSCONS_Quit_Mouse(SDL_WSCONS_mouse_input_data *inputData) { - if (inputData == NULL) { + if (!inputData) { return; } close(inputData->fd); diff --git a/src/core/ps2/SDL_ps2.c b/src/core/ps2/SDL_ps2.c index 5149c90e..b0a6f549 100644 --- a/src/core/ps2/SDL_ps2.c +++ b/src/core/ps2/SDL_ps2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/psp/SDL_psp.c b/src/core/psp/SDL_psp.c index d66ed88d..2b47c5b1 100644 --- a/src/core/psp/SDL_psp.c +++ b/src/core/psp/SDL_psp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/unix/SDL_appid.c b/src/core/unix/SDL_appid.c index c7cddc5b..fd2f2dc3 100644 --- a/src/core/unix/SDL_appid.c +++ b/src/core/unix/SDL_appid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/unix/SDL_appid.h b/src/core/unix/SDL_appid.h index d5acb009..976788cb 100644 --- a/src/core/unix/SDL_appid.h +++ b/src/core/unix/SDL_appid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer -Copyright (C) 1997-2023 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 diff --git a/src/core/unix/SDL_poll.c b/src/core/unix/SDL_poll.c index d4c6e2ab..2ff46099 100644 --- a/src/core/unix/SDL_poll.c +++ b/src/core/unix/SDL_poll.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/unix/SDL_poll.h b/src/core/unix/SDL_poll.h index b3f261d1..eef1d64b 100644 --- a/src/core/unix/SDL_poll.h +++ b/src/core/unix/SDL_poll.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/SDL_directx.h b/src/core/windows/SDL_directx.h index 06ff7978..6736d8d7 100644 --- a/src/core/windows/SDL_directx.h +++ b/src/core/windows/SDL_directx.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/SDL_hid.c b/src/core/windows/SDL_hid.c index 3129ac27..6cea5344 100644 --- a/src/core/windows/SDL_hid.c +++ b/src/core/windows/SDL_hid.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -58,9 +58,9 @@ int WIN_LoadHIDDLL(void) SDL_HidP_GetValueCaps = (HidP_GetValueCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetValueCaps"); SDL_HidP_MaxDataListLength = (HidP_MaxDataListLength_t)GetProcAddress(s_pHIDDLL, "HidP_MaxDataListLength"); SDL_HidP_GetData = (HidP_GetData_t)GetProcAddress(s_pHIDDLL, "HidP_GetData"); - if (SDL_HidD_GetManufacturerString == NULL || SDL_HidD_GetProductString == NULL || - SDL_HidP_GetCaps == NULL || SDL_HidP_GetButtonCaps == NULL || - SDL_HidP_GetValueCaps == NULL || SDL_HidP_MaxDataListLength == NULL || SDL_HidP_GetData == NULL) { + if (!SDL_HidD_GetManufacturerString || !SDL_HidD_GetProductString || + !SDL_HidP_GetCaps || !SDL_HidP_GetButtonCaps || + !SDL_HidP_GetValueCaps || !SDL_HidP_MaxDataListLength || !SDL_HidP_GetData) { WIN_UnloadHIDDLL(); return -1; } diff --git a/src/core/windows/SDL_hid.h b/src/core/windows/SDL_hid.h index c0bb56b2..4dbdd757 100644 --- a/src/core/windows/SDL_hid.h +++ b/src/core/windows/SDL_hid.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index 780eff40..2e956b47 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -37,6 +37,7 @@ static const ERole SDL_IMMDevice_role = eConsole; /* !!! FIXME: should this be e /* This is global to the WASAPI target, to handle hotplug and default device lookup. */ static IMMDeviceEnumerator *enumerator = NULL; +static SDL_IMMDevice_callbacks immcallbacks; /* PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. */ #ifdef PropVariantInit @@ -102,7 +103,7 @@ static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSI } PropVariantClear(&var); if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_GUID, &var))) { - CLSIDFromString(var.pwszVal, guid); + (void)CLSIDFromString(var.pwszVal, guid); } PropVariantClear(&var); IPropertyStore_Release(props); @@ -132,16 +133,26 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char * // see if we already have this one first. SDL_AudioDevice *device = SDL_IMMDevice_FindByDevID(devid); + if (device) { + if (SDL_AtomicGet(&device->zombie)) { + // whoa, it came back! This can happen if you unplug and replug USB headphones while we're still keeping the SDL object alive. + // Kill this device's IMMDevice id; the device will go away when the app closes it, or maybe a new default device is chosen + // (possibly this reconnected device), so we just want to make sure IMMDevice doesn't try to find the old device by the existing ID string. + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; + SDL_free(handle->immdevice_id); + handle->immdevice_id = NULL; + device = NULL; // add a new device, below. + } + } + if (!device) { // handle is freed by SDL_IMMDevice_FreeDeviceHandle! SDL_IMMDevice_HandleData *handle = SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); if (!handle) { - SDL_OutOfMemory(); return NULL; } handle->immdevice_id = SDL_wcsdup(devid); if (!handle->immdevice_id) { - SDL_OutOfMemory(); SDL_free(handle); return NULL; } @@ -154,6 +165,10 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char * spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); device = SDL_AddAudioDevice(iscapture, devname, &spec, handle); + if (!device) { + SDL_free(handle->immdevice_id); + SDL_free(handle); + } } return device; @@ -204,7 +219,7 @@ static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationCl static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (role == SDL_IMMDevice_role) { - SDL_DefaultAudioDeviceChanged(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + immcallbacks.default_audio_device_changed(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); } return S_OK; } @@ -244,7 +259,7 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM SDL_free(utf8dev); } } else { - SDL_AudioDeviceDisconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + immcallbacks.audio_device_disconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); } } IMMEndpoint_Release(endpoint); @@ -273,7 +288,7 @@ static const IMMNotificationClientVtbl notification_client_vtbl = { static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; -int SDL_IMMDevice_Init(void) +int SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) { HRESULT ret; @@ -291,6 +306,20 @@ int SDL_IMMDevice_Init(void) WIN_CoUninitialize(); return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret); } + + if (callbacks) { + SDL_copyp(&immcallbacks, callbacks); + } else { + SDL_zero(immcallbacks); + } + + if (!immcallbacks.audio_device_disconnected) { + immcallbacks.audio_device_disconnected = SDL_AudioDeviceDisconnected; + } + if (!immcallbacks.default_audio_device_changed) { + immcallbacks.default_audio_device_changed = SDL_DefaultAudioDeviceChanged; + } + return 0; } @@ -302,6 +331,8 @@ void SDL_IMMDevice_Quit(void) enumerator = NULL; } + SDL_zero(immcallbacks); + WIN_CoUninitialize(); } diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index a190a7cf..3dcd45eb 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,7 +28,13 @@ typedef struct SDL_AudioDevice SDL_AudioDevice; // this is defined in src/audio/SDL_sysaudio.h -int SDL_IMMDevice_Init(void); +typedef struct SDL_IMMDevice_callbacks +{ + void (*audio_device_disconnected)(SDL_AudioDevice *device); + void (*default_audio_device_changed)(SDL_AudioDevice *new_default_device); +} SDL_IMMDevice_callbacks; + +int SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks); void SDL_IMMDevice_Quit(void); int SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, SDL_bool iscapture); void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c index 38986270..0e6b474e 100644 --- a/src/core/windows/SDL_windows.c +++ b/src/core/windows/SDL_windows.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -283,7 +283,7 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) } strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR)); - if (strw == NULL) { + if (!strw) { RegCloseKey(hkey); return WIN_StringToUTF8(name); /* oh well. */ } @@ -388,7 +388,7 @@ DECLSPEC int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_fu (void)_argc; (void)_argv; (void)reserved; argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argvw == NULL) { + if (!argvw) { return OutOfMemory(); } @@ -399,13 +399,13 @@ DECLSPEC int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_fu /* Parse it into argv and argc */ argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (argv == NULL) { + if (!argv) { return OutOfMemory(); } for (i = 0; i < argc; ++i) { DWORD len; char *arg = WIN_StringToUTF8W(argvw[i]); - if (arg == NULL) { + if (!arg) { return OutOfMemory(); } len = (DWORD)SDL_strlen(arg); diff --git a/src/core/windows/SDL_windows.h b/src/core/windows/SDL_windows.h index 89289383..61044956 100644 --- a/src/core/windows/SDL_windows.h +++ b/src/core/windows/SDL_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/SDL_xinput.c b/src/core/windows/SDL_xinput.c index 01418d9f..36c9d818 100644 --- a/src/core/windows/SDL_xinput.c +++ b/src/core/windows/SDL_xinput.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,6 +30,7 @@ extern "C" { XInputGetState_t SDL_XInputGetState = NULL; XInputSetState_t SDL_XInputSetState = NULL; XInputGetCapabilities_t SDL_XInputGetCapabilities = NULL; +XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx = NULL; XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation = NULL; DWORD SDL_XInputVersion = 0; @@ -106,13 +107,15 @@ int WIN_LoadXInputDLL(void) /* 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think... */ SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, (LPCSTR)100); - if (SDL_XInputGetState == NULL) { + if (!SDL_XInputGetState) { SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, "XInputGetState"); } SDL_XInputSetState = (XInputSetState_t)GetProcAddress(s_pXInputDLL, "XInputSetState"); SDL_XInputGetCapabilities = (XInputGetCapabilities_t)GetProcAddress(s_pXInputDLL, "XInputGetCapabilities"); + /* 108 is the ordinal for _XInputGetCapabilitiesEx, which additionally returns VID/PID of the controller. */ + SDL_XInputGetCapabilitiesEx = (XInputGetCapabilitiesEx_t)GetProcAddress(s_pXInputDLL, (LPCSTR)108); SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)GetProcAddress(s_pXInputDLL, "XInputGetBatteryInformation"); - if (SDL_XInputGetState == NULL || SDL_XInputSetState == NULL || SDL_XInputGetCapabilities == NULL) { + if (!SDL_XInputGetState || !SDL_XInputSetState || !SDL_XInputGetCapabilities) { WIN_UnloadXInputDLL(); return -1; } diff --git a/src/core/windows/SDL_xinput.h b/src/core/windows/SDL_xinput.h index 1d59a879..d9c840db 100644 --- a/src/core/windows/SDL_xinput.h +++ b/src/core/windows/SDL_xinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,6 +28,7 @@ #ifdef HAVE_XINPUT_H #if defined(__XBOXONE__) || defined(__XBOXSERIES__) /* Xbox supports an XInput wrapper which is a C++-only header... */ +#include /* Required to compile with recent MSVC... */ #include using namespace XInputOnGameInput; #else @@ -44,6 +45,9 @@ using namespace XInputOnGameInput; #ifndef XINPUT_CAPS_FFB_SUPPORTED #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef XINPUT_CAPS_WIRELESS +#define XINPUT_CAPS_WIRELESS 0x0002 +#endif #ifndef XINPUT_DEVSUBTYPE_UNKNOWN #define XINPUT_DEVSUBTYPE_UNKNOWN 0x00 @@ -163,28 +167,8 @@ extern "C" { /* typedef's for XInput structs we use */ -#ifndef HAVE_XINPUT_GAMEPAD_EX -typedef struct -{ - WORD wButtons; - BYTE bLeftTrigger; - BYTE bRightTrigger; - SHORT sThumbLX; - SHORT sThumbLY; - SHORT sThumbRX; - SHORT sThumbRY; - DWORD dwPaddingReserved; -} XINPUT_GAMEPAD_EX; -#endif - -#ifndef HAVE_XINPUT_STATE_EX -typedef struct -{ - DWORD dwPacketNumber; - XINPUT_GAMEPAD_EX Gamepad; -} XINPUT_STATE_EX; -#endif +/* This is the same as XINPUT_BATTERY_INFORMATION, but always defined instead of just if WIN32_WINNT >= _WIN32_WINNT_WIN8 */ typedef struct { BYTE BatteryType; @@ -204,6 +188,12 @@ typedef struct SHORT sThumbRY; } XINPUT_GAMEPAD; +typedef struct +{ + DWORD dwPacketNumber; + XINPUT_GAMEPAD Gamepad; +} XINPUT_STATE; + typedef struct { WORD wLeftMotorSpeed; @@ -221,10 +211,21 @@ typedef struct #endif /* HAVE_XINPUT_H */ +/* This struct is not defined in XInput headers. */ +typedef struct _XINPUT_CAPABILITIES_EX +{ + XINPUT_CAPABILITIES Capabilities; + WORD VendorId; + WORD ProductId; + WORD ProductVersion; + WORD unk1; + DWORD unk2; +} XINPUT_CAPABILITIES_EX, *PXINPUT_CAPABILITIES_EX; + /* Forward decl's for XInput API's we load dynamically and use if available */ typedef DWORD(WINAPI *XInputGetState_t)( DWORD dwUserIndex, /* [in] Index of the gamer associated with the device */ - XINPUT_STATE_EX *pState /* [out] Receives the current state */ + XINPUT_STATE *pState /* [out] Receives the current state */ ); typedef DWORD(WINAPI *XInputSetState_t)( @@ -238,6 +239,14 @@ typedef DWORD(WINAPI *XInputGetCapabilities_t)( XINPUT_CAPABILITIES *pCapabilities /* [out] Receives the capabilities */ ); +/* Only available in XInput 1.4 that is shipped with Windows 8 and newer. */ +typedef DWORD(WINAPI *XInputGetCapabilitiesEx_t)( + DWORD dwReserved, /* [in] Must be 1 */ + DWORD dwUserIndex, /* [in] Index of the gamer associated with the device */ + DWORD dwFlags, /* [in] Input flags that identify the device type */ + XINPUT_CAPABILITIES_EX *pCapabilitiesEx /* [out] Receives the capabilities */ +); + typedef DWORD(WINAPI *XInputGetBatteryInformation_t)( DWORD dwUserIndex, BYTE devType, @@ -249,6 +258,7 @@ extern void WIN_UnloadXInputDLL(void); extern XInputGetState_t SDL_XInputGetState; extern XInputSetState_t SDL_XInputSetState; extern XInputGetCapabilities_t SDL_XInputGetCapabilities; +extern XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx; extern XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation; extern DWORD SDL_XInputVersion; /* ((major << 16) & 0xFF00) | (minor & 0xFF) */ @@ -260,6 +270,7 @@ extern DWORD SDL_XInputVersion; /* ((major << 16) & 0xFF00) | (minor & 0xFF) */ #define XINPUTGETSTATE SDL_XInputGetState #define XINPUTSETSTATE SDL_XInputSetState #define XINPUTGETCAPABILITIES SDL_XInputGetCapabilities +#define XINPUTGETCAPABILITIESEX SDL_XInputGetCapabilitiesEx #define XINPUTGETBATTERYINFORMATION SDL_XInputGetBatteryInformation #endif /* SDL_xinput_h_ */ diff --git a/src/core/windows/pch.c b/src/core/windows/pch.c index 2a7ba0c1..7e84b1a4 100644 --- a/src/core/windows/pch.c +++ b/src/core/windows/pch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/pch_cpp.cpp b/src/core/windows/pch_cpp.cpp index 2a7ba0c1..7e84b1a4 100644 --- a/src/core/windows/pch_cpp.cpp +++ b/src/core/windows/pch_cpp.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/windows/version.rc b/src/core/windows/version.rc index 259a1f0d..84869e1c 100644 --- a/src/core/windows/version.rc +++ b/src/core/windows/version.rc @@ -25,7 +25,7 @@ BEGIN VALUE "FileDescription", "SDL\0" VALUE "FileVersion", "3, 0, 0, 0\0" VALUE "InternalName", "SDL\0" - VALUE "LegalCopyright", "Copyright (C) 2023 Sam Lantinga\0" + VALUE "LegalCopyright", "Copyright (C) 2024 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" VALUE "ProductVersion", "3, 0, 0, 0\0" diff --git a/src/core/winrt/SDL_winrtapp_common.cpp b/src/core/winrt/SDL_winrtapp_common.cpp index 5ccb9158..c20f5242 100644 --- a/src/core/winrt/SDL_winrtapp_common.cpp +++ b/src/core/winrt/SDL_winrtapp_common.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/winrt/SDL_winrtapp_common.h b/src/core/winrt/SDL_winrtapp_common.h index 635fb916..83cfd776 100644 --- a/src/core/winrt/SDL_winrtapp_common.h +++ b/src/core/winrt/SDL_winrtapp_common.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/winrt/SDL_winrtapp_direct3d.cpp b/src/core/winrt/SDL_winrtapp_direct3d.cpp index 3bd87ccd..404fa736 100644 --- a/src/core/winrt/SDL_winrtapp_direct3d.cpp +++ b/src/core/winrt/SDL_winrtapp_direct3d.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -296,11 +296,8 @@ void SDL_WinRTApp::SetWindow(CoreWindow ^ window) ref new TypedEventHandler(this, &SDL_WinRTApp::OnMouseMoved); #endif - window->KeyDown += - ref new TypedEventHandler(this, &SDL_WinRTApp::OnKeyDown); - - window->KeyUp += - ref new TypedEventHandler(this, &SDL_WinRTApp::OnKeyUp); + window->Dispatcher->AcceleratorKeyActivated += + ref new TypedEventHandler(this, &SDL_WinRTApp::OnAcceleratorKeyActivated); window->CharacterReceived += ref new TypedEventHandler(this, &SDL_WinRTApp::OnCharacterReceived); @@ -343,7 +340,7 @@ void SDL_WinRTApp::Run() // representation of command line arguments. int argc = 1; char **argv = (char **)SDL_malloc(2 * sizeof(*argv)); - if (argv == NULL) { + if (!argv) { return; } argv[0] = SDL_strdup("WinRTApp"); @@ -720,19 +717,14 @@ void SDL_WinRTApp::OnMouseMoved(MouseDevice ^ mouseDevice, MouseEventArgs ^ args WINRT_ProcessMouseMovedEvent(WINRT_GlobalSDLWindow, args); } -void SDL_WinRTApp::OnKeyDown(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::KeyEventArgs ^ args) +void SDL_WinRTApp::OnAcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher ^ sender, Windows::UI::Core::AcceleratorKeyEventArgs ^ args) { - WINRT_ProcessKeyDownEvent(args); -} - -void SDL_WinRTApp::OnKeyUp(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::KeyEventArgs ^ args) -{ - WINRT_ProcessKeyUpEvent(args); + WINRT_ProcessAcceleratorKeyActivated(args); } void SDL_WinRTApp::OnCharacterReceived(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::CharacterReceivedEventArgs ^ args) { - WINRT_ProcessCharacterReceivedEvent(args); + WINRT_ProcessCharacterReceivedEvent(WINRT_GlobalSDLWindow, args); } template diff --git a/src/core/winrt/SDL_winrtapp_direct3d.h b/src/core/winrt/SDL_winrtapp_direct3d.h index 30867a69..9fb21f32 100644 --- a/src/core/winrt/SDL_winrtapp_direct3d.h +++ b/src/core/winrt/SDL_winrtapp_direct3d.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -71,8 +71,7 @@ ref class SDL_WinRTApp sealed : public Windows::ApplicationModel::Core::IFramewo void OnPointerEntered(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args); void OnPointerExited(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args); void OnMouseMoved(Windows::Devices::Input::MouseDevice ^ mouseDevice, Windows::Devices::Input::MouseEventArgs ^ args); - void OnKeyDown(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::KeyEventArgs ^ args); - void OnKeyUp(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::KeyEventArgs ^ args); + void OnAcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher ^ sender, Windows::UI::Core::AcceleratorKeyEventArgs ^ args); void OnCharacterReceived(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::CharacterReceivedEventArgs ^ args); #if NTDDI_VERSION >= NTDDI_WIN10 diff --git a/src/core/winrt/SDL_winrtapp_xaml.cpp b/src/core/winrt/SDL_winrtapp_xaml.cpp index 9998e0ea..81a54515 100644 --- a/src/core/winrt/SDL_winrtapp_xaml.cpp +++ b/src/core/winrt/SDL_winrtapp_xaml.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/core/winrt/SDL_winrtapp_xaml.h b/src/core/winrt/SDL_winrtapp_xaml.h index 2d4de646..b321cc83 100644 --- a/src/core/winrt/SDL_winrtapp_xaml.h +++ b/src/core/winrt/SDL_winrtapp_xaml.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index 7eeb14e0..2f0dc1e5 100644 --- a/src/cpuinfo/SDL_cpuinfo.c +++ b/src/cpuinfo/SDL_cpuinfo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -1011,16 +1011,19 @@ int SDL_GetSystemRAM(void) #endif #ifdef HAVE_SYSCTLBYNAME if (SDL_SystemRAM <= 0) { -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__DragonFly__) -#ifdef HW_REALMEM +#ifdef HW_PHYSMEM64 + /* (64-bit): NetBSD since 2003, OpenBSD */ + int mib[2] = { CTL_HW, HW_PHYSMEM64 }; +#elif defined(HW_REALMEM) + /* (64-bit): FreeBSD since 2005, DragonFly */ int mib[2] = { CTL_HW, HW_REALMEM }; -#else - /* might only report up to 2 GiB */ - int mib[2] = { CTL_HW, HW_PHYSMEM }; -#endif /* HW_REALMEM */ -#else +#elif defined(HW_MEMSIZE) + /* (64-bit): Darwin */ int mib[2] = { CTL_HW, HW_MEMSIZE }; -#endif /* __FreeBSD__ || __FreeBSD_kernel__ */ +#else + /* (32-bit): very old BSD, might only report up to 2 GiB */ + int mib[2] = { CTL_HW, HW_PHYSMEM }; +#endif /* HW_PHYSMEM64 */ Uint64 memsize = 0; size_t len = sizeof(memsize); diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c index e85437c7..3900b6ca 100644 --- a/src/dynapi/SDL_dynapi.c +++ b/src/dynapi/SDL_dynapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -21,6 +21,7 @@ #include "build_config/SDL_build_config.h" #include "SDL_dynapi.h" +#include "SDL_dynapi_unsupported.h" #if SDL_DYNAMIC_API @@ -36,7 +37,6 @@ /* These headers have system specific definitions, so aren't included above */ -#include #include /* This is the version of the dynamic API. This doesn't match the SDL version @@ -143,6 +143,16 @@ static void SDL_InitDynamicAPI(void); va_end(ap); \ return retval; \ } \ + _static size_t SDLCALL SDL_RWprintf##name(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + { \ + size_t retval; \ + va_list ap; \ + initcall; \ + va_start(ap, fmt); \ + retval = jump_table.SDL_RWvprintf(context, fmt, ap); \ + va_end(ap); \ + return retval; \ + } \ _static void SDLCALL SDL_Log##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ va_list ap; \ @@ -274,6 +284,26 @@ static int SDLCALL SDL_asprintf_LOGSDLCALLS(char **strp, SDL_PRINTF_FORMAT_STRIN va_end(ap); return retval; } +static int SDLCALL SDL_swprintf_LOGSDLCALLS(SDL_OUT_Z_CAP(maxlen) wchar_t *buf, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ...) +{ + int retval; + va_list ap; + SDL_Log_REAL("SDL3CALL SDL_swprintf"); + va_start(ap, fmt); + retval = SDL_vswprintf_REAL(buf, maxlen, fmt, ap); + va_end(ap); + return retval; +} +_static size_t SDLCALL SDL_RWprintf_LOGSDLCALLS(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + size_t retval; + va_list ap; + SDL_Log_REAL("SDL3CALL SDL_RWprintf"); + va_start(ap, fmt); + retval = SDL_RWvprintf_REAL(context, fmt, ap); + va_end(ap); + return retval; +} static void SDLCALL SDL_Log_LOGSDLCALLS(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) { va_list ap; @@ -388,7 +418,7 @@ static SDL_INLINE void *get_sdlapi_entry(const char *fname, const char *sym) void *retval = NULL; if (lib) { retval = (void *) GetProcAddress(lib, sym); - if (retval == NULL) { + if (!retval) { FreeLibrary(lib); } } @@ -401,9 +431,9 @@ static SDL_INLINE void *get_sdlapi_entry(const char *fname, const char *sym) { void *lib = dlopen(fname, RTLD_NOW | RTLD_LOCAL); void *retval = NULL; - if (lib != NULL) { + if (lib) { retval = dlsym(lib, sym); - if (retval == NULL) { + if (!retval) { dlclose(lib); } } @@ -523,4 +553,17 @@ static void SDL_InitDynamicAPI(void) #endif } +#else /* SDL_DYNAMIC_API */ + +#include + +Sint32 SDL_DYNAPI_entry(Uint32 apiver, void *table, Uint32 tablesize); +Sint32 SDL_DYNAPI_entry(Uint32 apiver, void *table, Uint32 tablesize) +{ + (void)apiver; + (void)table; + (void)tablesize; + return -1; /* not compatible. */ +} + #endif /* SDL_DYNAMIC_API */ diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h index 1f0fd4b3..579cdf49 100644 --- a/src/dynapi/SDL_dynapi.h +++ b/src/dynapi/SDL_dynapi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 54467c5f..f8900f68 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -57,7 +57,6 @@ SDL3_0.0.0 { SDL_CreateRWLock; SDL_CreateRenderer; SDL_CreateSemaphore; - SDL_CreateShapedWindow; SDL_CreateSoftwareRenderer; SDL_CreateSurface; SDL_CreateSurfaceFrom; @@ -69,8 +68,7 @@ SDL3_0.0.0 { SDL_CreateThreadWithStackSize; SDL_CreateWindow; SDL_CreateWindowAndRenderer; - SDL_CreateWindowFrom; - SDL_CreateWindowWithPosition; + SDL_CreateWindowWithProperties; SDL_CursorVisible; SDL_DXGIGetOutputInfo; SDL_DelEventWatch; @@ -109,9 +107,9 @@ SDL3_0.0.0 { SDL_FlashWindow; SDL_FlushEvent; SDL_FlushEvents; + SDL_FlushRenderer; SDL_GDKGetTaskQueue; SDL_GDKSuspendComplete; - SDL_GL_BindTexture; SDL_GL_CreateContext; SDL_GL_DeleteContext; SDL_GL_ExtensionSupported; @@ -126,7 +124,6 @@ SDL3_0.0.0 { SDL_GL_SetAttribute; SDL_GL_SetSwapInterval; SDL_GL_SwapWindow; - SDL_GL_UnbindTexture; SDL_GL_UnloadLibrary; SDL_GUIDFromString; SDL_GUIDToString; @@ -165,7 +162,6 @@ SDL3_0.0.0 { SDL_GetDisplayUsableBounds; SDL_GetDisplays; SDL_GetError; - SDL_GetErrorMsg; SDL_GetEventFilter; SDL_GetFullscreenDisplayModes; SDL_GetGamepadAppleSFSymbolsNameForAxis; @@ -190,7 +186,6 @@ SDL3_0.0.0 { SDL_GetGamepadJoystick; SDL_GetGamepadMapping; SDL_GetGamepadMappingForGUID; - SDL_GetGamepadMappingForIndex; SDL_GetGamepadName; SDL_GetGamepadPath; SDL_GetGamepadPlayerIndex; @@ -251,14 +246,12 @@ SDL3_0.0.0 { SDL_GetMouseState; SDL_GetNaturalDisplayOrientation; SDL_GetNumAllocations; - SDL_GetNumGamepadMappings; SDL_GetNumGamepadTouchpadFingers; SDL_GetNumGamepadTouchpads; SDL_GetNumJoystickAxes; SDL_GetNumJoystickButtons; SDL_GetNumJoystickHats; SDL_GetNumRenderDrivers; - SDL_GetNumTouchDevices; SDL_GetNumTouchFingers; SDL_GetNumVideoDrivers; SDL_GetOriginalMemoryFunctions; @@ -286,8 +279,6 @@ SDL3_0.0.0 { SDL_GetRelativeMouseMode; SDL_GetRelativeMouseState; SDL_GetRenderClipRect; - SDL_GetRenderD3D11Device; - SDL_GetRenderD3D9Device; SDL_GetRenderDrawBlendMode; SDL_GetRenderDrawColor; SDL_GetRenderDriver; @@ -317,7 +308,6 @@ SDL3_0.0.0 { SDL_GetSensorNonPortableType; SDL_GetSensorType; SDL_GetSensors; - SDL_GetShapedWindowMode; SDL_GetSurfaceAlphaMod; SDL_GetSurfaceBlendMode; SDL_GetSurfaceClipRect; @@ -330,19 +320,15 @@ SDL3_0.0.0 { SDL_GetTextureBlendMode; SDL_GetTextureColorMod; SDL_GetTextureScaleMode; - SDL_GetTextureUserData; SDL_GetThreadID; SDL_GetThreadName; SDL_GetTicks; SDL_GetTicksNS; - SDL_GetTouchDevice; SDL_GetTouchDeviceType; SDL_GetTouchFinger; - SDL_GetTouchName; SDL_GetVersion; SDL_GetVideoDriver; SDL_GetWindowBordersSize; - SDL_GetWindowData; SDL_GetWindowDisplayScale; SDL_GetWindowFlags; SDL_GetWindowFromID; @@ -364,7 +350,6 @@ SDL3_0.0.0 { SDL_GetWindowSizeInPixels; SDL_GetWindowSurface; SDL_GetWindowTitle; - SDL_GetWindowWMInfo; SDL_GetYUVConversionMode; SDL_GetYUVConversionModeForResolution; SDL_HapticClose; @@ -426,7 +411,6 @@ SDL3_0.0.0 { SDL_IsDeXMode; SDL_IsGamepad; SDL_IsJoystickVirtual; - SDL_IsShapedWindow; SDL_IsTablet; SDL_JoystickConnected; SDL_JoystickEventsEnabled; @@ -521,10 +505,8 @@ SDL3_0.0.0 { SDL_RenderCoordinatesToWindow; SDL_RenderFillRect; SDL_RenderFillRects; - SDL_RenderFlush; SDL_RenderGeometry; SDL_RenderGeometryRaw; - SDL_RenderGetD3D12Device; SDL_RenderLine; SDL_RenderLines; SDL_RenderPoint; @@ -601,11 +583,9 @@ SDL3_0.0.0 { SDL_SetTextureBlendMode; SDL_SetTextureColorMod; SDL_SetTextureScaleMode; - SDL_SetTextureUserData; SDL_SetThreadPriority; SDL_SetWindowAlwaysOnTop; SDL_SetWindowBordered; - SDL_SetWindowData; SDL_SetWindowFullscreen; SDL_SetWindowFullscreenMode; SDL_SetWindowGrab; @@ -621,7 +601,6 @@ SDL3_0.0.0 { SDL_SetWindowOpacity; SDL_SetWindowPosition; SDL_SetWindowResizable; - SDL_SetWindowShape; SDL_SetWindowSize; SDL_SetWindowTitle; SDL_SetWindowsMessageHook; @@ -632,7 +611,6 @@ SDL3_0.0.0 { SDL_ShowWindow; SDL_SignalCondition; SDL_SoftStretch; - SDL_SoftStretchLinear; SDL_StartTextInput; SDL_StopTextInput; SDL_SurfaceHasColorKey; @@ -883,7 +861,6 @@ SDL3_0.0.0 { SDL_MixAudioFormat; SDL_ConvertAudioSamples; SDL_GetSilenceValueForFormat; - SDL_LoadWAV; SDL_PauseAudioDevice; SDL_ResumeAudioDevice; SDL_AudioDevicePaused; @@ -907,6 +884,84 @@ SDL3_0.0.0 { SDL_SetAudioStreamFrequencyRatio; SDL_SetAudioPostmixCallback; SDL_GetAudioStreamQueued; + SDL_CreateProperties; + SDL_LockProperties; + SDL_UnlockProperties; + SDL_SetProperty; + SDL_GetProperty; + SDL_DestroyProperties; + SDL_GetAudioStreamProperties; + SDL_GetGamepadProperties; + SDL_GetJoystickProperties; + SDL_GetRendererProperties; + SDL_GetTextureProperties; + SDL_GetRWProperties; + SDL_GetSensorProperties; + SDL_GetSurfaceProperties; + SDL_GetWindowProperties; + SDL_ClearProperty; + SDL_EnterAppMainCallbacks; + SDL_RWprintf; + SDL_RWvprintf; + SDL_AllocateEventMemory; + SDL_GetDisplayProperties; + SDL_SetPropertyWithCleanup; + SDL_SetX11EventHook; + SDL_GetGlobalProperties; + SDL_OpenVideoCapture; + SDL_SetVideoCaptureSpec; + SDL_OpenVideoCaptureWithSpec; + SDL_GetVideoCaptureDeviceName; + SDL_GetVideoCaptureSpec; + SDL_GetVideoCaptureFormat; + SDL_GetNumVideoCaptureFormats; + SDL_GetVideoCaptureFrameSize; + SDL_GetNumVideoCaptureFrameSizes; + SDL_GetVideoCaptureStatus; + SDL_StartVideoCapture; + SDL_AcquireVideoCaptureFrame; + SDL_ReleaseVideoCaptureFrame; + SDL_StopVideoCapture; + SDL_CloseVideoCapture; + SDL_GetVideoCaptureDevices; + SDL_GetGamepadButtonLabelForType; + SDL_GetGamepadButtonLabel; + SDL_GetPens; + SDL_GetPenStatus; + SDL_GetPenFromGUID; + SDL_GetPenGUID; + SDL_PenConnected; + SDL_GetPenName; + SDL_GetPenCapabilities; + SDL_GetPenType; + SDL_GetPens; + SDL_GetPenStatus; + SDL_GetPenFromGUID; + SDL_GetPenGUID; + SDL_PenConnected; + SDL_GetPenName; + SDL_GetPenCapabilities; + SDL_GetPenType; + SDL_SetStringProperty; + SDL_SetNumberProperty; + SDL_SetFloatProperty; + SDL_GetPropertyType; + SDL_GetStringProperty; + SDL_GetNumberProperty; + SDL_GetFloatProperty; + SDL_EnumerateProperties; + SDL_SetBooleanProperty; + SDL_GetBooleanProperty; + SDL_CreateTextureWithProperties; + SDL_CreateRendererWithProperties; + SDL_GetGamepadMappings; + SDL_GetTouchDevices; + SDL_GetTouchDeviceName; + SDL_strnstr; + SDL_wcsnstr; + SDL_SyncWindow; + SDL_GetGamepadSteamHandle; + SDL_GetRendererFromTexture; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index da300e29..b092b19e 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -81,7 +81,6 @@ #define SDL_CreateRWLock SDL_CreateRWLock_REAL #define SDL_CreateRenderer SDL_CreateRenderer_REAL #define SDL_CreateSemaphore SDL_CreateSemaphore_REAL -#define SDL_CreateShapedWindow SDL_CreateShapedWindow_REAL #define SDL_CreateSoftwareRenderer SDL_CreateSoftwareRenderer_REAL #define SDL_CreateSurface SDL_CreateSurface_REAL #define SDL_CreateSurfaceFrom SDL_CreateSurfaceFrom_REAL @@ -93,8 +92,7 @@ #define SDL_CreateThreadWithStackSize SDL_CreateThreadWithStackSize_REAL #define SDL_CreateWindow SDL_CreateWindow_REAL #define SDL_CreateWindowAndRenderer SDL_CreateWindowAndRenderer_REAL -#define SDL_CreateWindowFrom SDL_CreateWindowFrom_REAL -#define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL +#define SDL_CreateWindowWithProperties SDL_CreateWindowWithProperties_REAL #define SDL_CursorVisible SDL_CursorVisible_REAL #define SDL_DXGIGetOutputInfo SDL_DXGIGetOutputInfo_REAL #define SDL_DelEventWatch SDL_DelEventWatch_REAL @@ -133,9 +131,9 @@ #define SDL_FlashWindow SDL_FlashWindow_REAL #define SDL_FlushEvent SDL_FlushEvent_REAL #define SDL_FlushEvents SDL_FlushEvents_REAL +#define SDL_FlushRenderer SDL_FlushRenderer_REAL #define SDL_GDKGetTaskQueue SDL_GDKGetTaskQueue_REAL #define SDL_GDKSuspendComplete SDL_GDKSuspendComplete_REAL -#define SDL_GL_BindTexture SDL_GL_BindTexture_REAL #define SDL_GL_CreateContext SDL_GL_CreateContext_REAL #define SDL_GL_DeleteContext SDL_GL_DeleteContext_REAL #define SDL_GL_ExtensionSupported SDL_GL_ExtensionSupported_REAL @@ -150,7 +148,6 @@ #define SDL_GL_SetAttribute SDL_GL_SetAttribute_REAL #define SDL_GL_SetSwapInterval SDL_GL_SetSwapInterval_REAL #define SDL_GL_SwapWindow SDL_GL_SwapWindow_REAL -#define SDL_GL_UnbindTexture SDL_GL_UnbindTexture_REAL #define SDL_GL_UnloadLibrary SDL_GL_UnloadLibrary_REAL #define SDL_GUIDFromString SDL_GUIDFromString_REAL #define SDL_GUIDToString SDL_GUIDToString_REAL @@ -189,7 +186,6 @@ #define SDL_GetDisplayUsableBounds SDL_GetDisplayUsableBounds_REAL #define SDL_GetDisplays SDL_GetDisplays_REAL #define SDL_GetError SDL_GetError_REAL -#define SDL_GetErrorMsg SDL_GetErrorMsg_REAL #define SDL_GetEventFilter SDL_GetEventFilter_REAL #define SDL_GetFullscreenDisplayModes SDL_GetFullscreenDisplayModes_REAL #define SDL_GetGamepadAppleSFSymbolsNameForAxis SDL_GetGamepadAppleSFSymbolsNameForAxis_REAL @@ -214,7 +210,6 @@ #define SDL_GetGamepadJoystick SDL_GetGamepadJoystick_REAL #define SDL_GetGamepadMapping SDL_GetGamepadMapping_REAL #define SDL_GetGamepadMappingForGUID SDL_GetGamepadMappingForGUID_REAL -#define SDL_GetGamepadMappingForIndex SDL_GetGamepadMappingForIndex_REAL #define SDL_GetGamepadName SDL_GetGamepadName_REAL #define SDL_GetGamepadPath SDL_GetGamepadPath_REAL #define SDL_GetGamepadPlayerIndex SDL_GetGamepadPlayerIndex_REAL @@ -275,14 +270,12 @@ #define SDL_GetMouseState SDL_GetMouseState_REAL #define SDL_GetNaturalDisplayOrientation SDL_GetNaturalDisplayOrientation_REAL #define SDL_GetNumAllocations SDL_GetNumAllocations_REAL -#define SDL_GetNumGamepadMappings SDL_GetNumGamepadMappings_REAL #define SDL_GetNumGamepadTouchpadFingers SDL_GetNumGamepadTouchpadFingers_REAL #define SDL_GetNumGamepadTouchpads SDL_GetNumGamepadTouchpads_REAL #define SDL_GetNumJoystickAxes SDL_GetNumJoystickAxes_REAL #define SDL_GetNumJoystickButtons SDL_GetNumJoystickButtons_REAL #define SDL_GetNumJoystickHats SDL_GetNumJoystickHats_REAL #define SDL_GetNumRenderDrivers SDL_GetNumRenderDrivers_REAL -#define SDL_GetNumTouchDevices SDL_GetNumTouchDevices_REAL #define SDL_GetNumTouchFingers SDL_GetNumTouchFingers_REAL #define SDL_GetNumVideoDrivers SDL_GetNumVideoDrivers_REAL #define SDL_GetOriginalMemoryFunctions SDL_GetOriginalMemoryFunctions_REAL @@ -310,8 +303,6 @@ #define SDL_GetRelativeMouseMode SDL_GetRelativeMouseMode_REAL #define SDL_GetRelativeMouseState SDL_GetRelativeMouseState_REAL #define SDL_GetRenderClipRect SDL_GetRenderClipRect_REAL -#define SDL_GetRenderD3D11Device SDL_GetRenderD3D11Device_REAL -#define SDL_GetRenderD3D9Device SDL_GetRenderD3D9Device_REAL #define SDL_GetRenderDrawBlendMode SDL_GetRenderDrawBlendMode_REAL #define SDL_GetRenderDrawColor SDL_GetRenderDrawColor_REAL #define SDL_GetRenderDriver SDL_GetRenderDriver_REAL @@ -341,7 +332,6 @@ #define SDL_GetSensorNonPortableType SDL_GetSensorNonPortableType_REAL #define SDL_GetSensorType SDL_GetSensorType_REAL #define SDL_GetSensors SDL_GetSensors_REAL -#define SDL_GetShapedWindowMode SDL_GetShapedWindowMode_REAL #define SDL_GetSurfaceAlphaMod SDL_GetSurfaceAlphaMod_REAL #define SDL_GetSurfaceBlendMode SDL_GetSurfaceBlendMode_REAL #define SDL_GetSurfaceClipRect SDL_GetSurfaceClipRect_REAL @@ -354,19 +344,15 @@ #define SDL_GetTextureBlendMode SDL_GetTextureBlendMode_REAL #define SDL_GetTextureColorMod SDL_GetTextureColorMod_REAL #define SDL_GetTextureScaleMode SDL_GetTextureScaleMode_REAL -#define SDL_GetTextureUserData SDL_GetTextureUserData_REAL #define SDL_GetThreadID SDL_GetThreadID_REAL #define SDL_GetThreadName SDL_GetThreadName_REAL #define SDL_GetTicks SDL_GetTicks_REAL #define SDL_GetTicksNS SDL_GetTicksNS_REAL -#define SDL_GetTouchDevice SDL_GetTouchDevice_REAL #define SDL_GetTouchDeviceType SDL_GetTouchDeviceType_REAL #define SDL_GetTouchFinger SDL_GetTouchFinger_REAL -#define SDL_GetTouchName SDL_GetTouchName_REAL #define SDL_GetVersion SDL_GetVersion_REAL #define SDL_GetVideoDriver SDL_GetVideoDriver_REAL #define SDL_GetWindowBordersSize SDL_GetWindowBordersSize_REAL -#define SDL_GetWindowData SDL_GetWindowData_REAL #define SDL_GetWindowDisplayScale SDL_GetWindowDisplayScale_REAL #define SDL_GetWindowFlags SDL_GetWindowFlags_REAL #define SDL_GetWindowFromID SDL_GetWindowFromID_REAL @@ -388,7 +374,6 @@ #define SDL_GetWindowSizeInPixels SDL_GetWindowSizeInPixels_REAL #define SDL_GetWindowSurface SDL_GetWindowSurface_REAL #define SDL_GetWindowTitle SDL_GetWindowTitle_REAL -#define SDL_GetWindowWMInfo SDL_GetWindowWMInfo_REAL #define SDL_GetYUVConversionMode SDL_GetYUVConversionMode_REAL #define SDL_GetYUVConversionModeForResolution SDL_GetYUVConversionModeForResolution_REAL #define SDL_HapticClose SDL_HapticClose_REAL @@ -450,7 +435,6 @@ #define SDL_IsDeXMode SDL_IsDeXMode_REAL #define SDL_IsGamepad SDL_IsGamepad_REAL #define SDL_IsJoystickVirtual SDL_IsJoystickVirtual_REAL -#define SDL_IsShapedWindow SDL_IsShapedWindow_REAL #define SDL_IsTablet SDL_IsTablet_REAL #define SDL_JoystickConnected SDL_JoystickConnected_REAL #define SDL_JoystickEventsEnabled SDL_JoystickEventsEnabled_REAL @@ -545,10 +529,8 @@ #define SDL_RenderCoordinatesToWindow SDL_RenderCoordinatesToWindow_REAL #define SDL_RenderFillRect SDL_RenderFillRect_REAL #define SDL_RenderFillRects SDL_RenderFillRects_REAL -#define SDL_RenderFlush SDL_RenderFlush_REAL #define SDL_RenderGeometry SDL_RenderGeometry_REAL #define SDL_RenderGeometryRaw SDL_RenderGeometryRaw_REAL -#define SDL_RenderGetD3D12Device SDL_RenderGetD3D12Device_REAL #define SDL_RenderLine SDL_RenderLine_REAL #define SDL_RenderLines SDL_RenderLines_REAL #define SDL_RenderPoint SDL_RenderPoint_REAL @@ -624,11 +606,9 @@ #define SDL_SetTextureBlendMode SDL_SetTextureBlendMode_REAL #define SDL_SetTextureColorMod SDL_SetTextureColorMod_REAL #define SDL_SetTextureScaleMode SDL_SetTextureScaleMode_REAL -#define SDL_SetTextureUserData SDL_SetTextureUserData_REAL #define SDL_SetThreadPriority SDL_SetThreadPriority_REAL #define SDL_SetWindowAlwaysOnTop SDL_SetWindowAlwaysOnTop_REAL #define SDL_SetWindowBordered SDL_SetWindowBordered_REAL -#define SDL_SetWindowData SDL_SetWindowData_REAL #define SDL_SetWindowFullscreen SDL_SetWindowFullscreen_REAL #define SDL_SetWindowFullscreenMode SDL_SetWindowFullscreenMode_REAL #define SDL_SetWindowGrab SDL_SetWindowGrab_REAL @@ -644,7 +624,6 @@ #define SDL_SetWindowOpacity SDL_SetWindowOpacity_REAL #define SDL_SetWindowPosition SDL_SetWindowPosition_REAL #define SDL_SetWindowResizable SDL_SetWindowResizable_REAL -#define SDL_SetWindowShape SDL_SetWindowShape_REAL #define SDL_SetWindowSize SDL_SetWindowSize_REAL #define SDL_SetWindowTitle SDL_SetWindowTitle_REAL #define SDL_SetWindowsMessageHook SDL_SetWindowsMessageHook_REAL @@ -655,7 +634,6 @@ #define SDL_ShowWindow SDL_ShowWindow_REAL #define SDL_SignalCondition SDL_SignalCondition_REAL #define SDL_SoftStretch SDL_SoftStretch_REAL -#define SDL_SoftStretchLinear SDL_SoftStretchLinear_REAL #define SDL_StartTextInput SDL_StartTextInput_REAL #define SDL_StopTextInput SDL_StopTextInput_REAL #define SDL_SurfaceHasColorKey SDL_SurfaceHasColorKey_REAL @@ -908,7 +886,6 @@ #define SDL_MixAudioFormat SDL_MixAudioFormat_REAL #define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL -#define SDL_LoadWAV SDL_LoadWAV_REAL #define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL #define SDL_ResumeAudioDevice SDL_ResumeAudioDevice_REAL #define SDL_AudioDevicePaused SDL_AudioDevicePaused_REAL @@ -932,3 +909,81 @@ #define SDL_SetAudioStreamFrequencyRatio SDL_SetAudioStreamFrequencyRatio_REAL #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL #define SDL_GetAudioStreamQueued SDL_GetAudioStreamQueued_REAL +#define SDL_CreateProperties SDL_CreateProperties_REAL +#define SDL_LockProperties SDL_LockProperties_REAL +#define SDL_UnlockProperties SDL_UnlockProperties_REAL +#define SDL_SetProperty SDL_SetProperty_REAL +#define SDL_GetProperty SDL_GetProperty_REAL +#define SDL_DestroyProperties SDL_DestroyProperties_REAL +#define SDL_GetAudioStreamProperties SDL_GetAudioStreamProperties_REAL +#define SDL_GetGamepadProperties SDL_GetGamepadProperties_REAL +#define SDL_GetJoystickProperties SDL_GetJoystickProperties_REAL +#define SDL_GetRendererProperties SDL_GetRendererProperties_REAL +#define SDL_GetTextureProperties SDL_GetTextureProperties_REAL +#define SDL_GetRWProperties SDL_GetRWProperties_REAL +#define SDL_GetSensorProperties SDL_GetSensorProperties_REAL +#define SDL_GetSurfaceProperties SDL_GetSurfaceProperties_REAL +#define SDL_GetWindowProperties SDL_GetWindowProperties_REAL +#define SDL_ClearProperty SDL_ClearProperty_REAL +#define SDL_EnterAppMainCallbacks SDL_EnterAppMainCallbacks_REAL +#define SDL_RWprintf SDL_RWprintf_REAL +#define SDL_RWvprintf SDL_RWvprintf_REAL +#define SDL_AllocateEventMemory SDL_AllocateEventMemory_REAL +#define SDL_GetDisplayProperties SDL_GetDisplayProperties_REAL +#define SDL_SetPropertyWithCleanup SDL_SetPropertyWithCleanup_REAL +#define SDL_SetX11EventHook SDL_SetX11EventHook_REAL +#define SDL_GetGlobalProperties SDL_GetGlobalProperties_REAL +#define SDL_OpenVideoCapture SDL_OpenVideoCapture_REAL +#define SDL_SetVideoCaptureSpec SDL_SetVideoCaptureSpec_REAL +#define SDL_OpenVideoCaptureWithSpec SDL_OpenVideoCaptureWithSpec_REAL +#define SDL_GetVideoCaptureDeviceName SDL_GetVideoCaptureDeviceName_REAL +#define SDL_GetVideoCaptureSpec SDL_GetVideoCaptureSpec_REAL +#define SDL_GetVideoCaptureFormat SDL_GetVideoCaptureFormat_REAL +#define SDL_GetNumVideoCaptureFormats SDL_GetNumVideoCaptureFormats_REAL +#define SDL_GetVideoCaptureFrameSize SDL_GetVideoCaptureFrameSize_REAL +#define SDL_GetNumVideoCaptureFrameSizes SDL_GetNumVideoCaptureFrameSizes_REAL +#define SDL_GetVideoCaptureStatus SDL_GetVideoCaptureStatus_REAL +#define SDL_StartVideoCapture SDL_StartVideoCapture_REAL +#define SDL_AcquireVideoCaptureFrame SDL_AcquireVideoCaptureFrame_REAL +#define SDL_ReleaseVideoCaptureFrame SDL_ReleaseVideoCaptureFrame_REAL +#define SDL_StopVideoCapture SDL_StopVideoCapture_REAL +#define SDL_CloseVideoCapture SDL_CloseVideoCapture_REAL +#define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL +#define SDL_GetGamepadButtonLabelForType SDL_GetGamepadButtonLabelForType_REAL +#define SDL_GetGamepadButtonLabel SDL_GetGamepadButtonLabel_REAL +#define SDL_GetPens SDL_GetPens_REAL +#define SDL_GetPenStatus SDL_GetPenStatus_REAL +#define SDL_GetPenFromGUID SDL_GetPenFromGUID_REAL +#define SDL_GetPenGUID SDL_GetPenGUID_REAL +#define SDL_PenConnected SDL_PenConnected_REAL +#define SDL_GetPenName SDL_GetPenName_REAL +#define SDL_GetPenCapabilities SDL_GetPenCapabilities_REAL +#define SDL_GetPenType SDL_GetPenType_REAL +#define SDL_GetPens SDL_GetPens_REAL +#define SDL_GetPenStatus SDL_GetPenStatus_REAL +#define SDL_GetPenFromGUID SDL_GetPenFromGUID_REAL +#define SDL_GetPenGUID SDL_GetPenGUID_REAL +#define SDL_PenConnected SDL_PenConnected_REAL +#define SDL_GetPenName SDL_GetPenName_REAL +#define SDL_GetPenCapabilities SDL_GetPenCapabilities_REAL +#define SDL_GetPenType SDL_GetPenType_REAL +#define SDL_SetStringProperty SDL_SetStringProperty_REAL +#define SDL_SetNumberProperty SDL_SetNumberProperty_REAL +#define SDL_SetFloatProperty SDL_SetFloatProperty_REAL +#define SDL_GetPropertyType SDL_GetPropertyType_REAL +#define SDL_GetStringProperty SDL_GetStringProperty_REAL +#define SDL_GetNumberProperty SDL_GetNumberProperty_REAL +#define SDL_GetFloatProperty SDL_GetFloatProperty_REAL +#define SDL_EnumerateProperties SDL_EnumerateProperties_REAL +#define SDL_SetBooleanProperty SDL_SetBooleanProperty_REAL +#define SDL_GetBooleanProperty SDL_GetBooleanProperty_REAL +#define SDL_CreateTextureWithProperties SDL_CreateTextureWithProperties_REAL +#define SDL_CreateRendererWithProperties SDL_CreateRendererWithProperties_REAL +#define SDL_GetGamepadMappings SDL_GetGamepadMappings_REAL +#define SDL_GetTouchDevices SDL_GetTouchDevices_REAL +#define SDL_GetTouchDeviceName SDL_GetTouchDeviceName_REAL +#define SDL_strnstr SDL_strnstr_REAL +#define SDL_wcsnstr SDL_wcsnstr_REAL +#define SDL_SyncWindow SDL_SyncWindow_REAL +#define SDL_GetGamepadSteamHandle SDL_GetGamepadSteamHandle_REAL +#define SDL_GetRendererFromTexture SDL_GetRendererFromTexture_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a4ffe8d1..42245686 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,6 +43,7 @@ SDL_DYNAPI_PROC(int,SDL_asprintf,(char **a, SDL_PRINTF_FORMAT_STRING const char SDL_DYNAPI_PROC(int,SDL_snprintf,(SDL_OUT_Z_CAP(b) char *a, size_t b, SDL_PRINTF_FORMAT_STRING const char *c, ...),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_swprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, SDL_PRINTF_FORMAT_STRING const wchar_t *c, ...),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_sscanf,(const char *a, SDL_SCANF_FORMAT_STRING const char *b, ...),(a,b),return) +SDL_DYNAPI_PROC(size_t,SDL_RWprintf,(SDL_RWops *a, SDL_PRINTF_FORMAT_STRING const char *b, ...),(a,b),return) #endif #ifdef SDL_CreateThread @@ -65,43 +66,27 @@ SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThreadWithStackSize,(SDL_ThreadFunction a, SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThreadWithStackSize,(SDL_ThreadFunction a, const char *b, const size_t c, void *d),(a,b,c,d),return) #endif -#if defined(__WIN32__) || defined(__GDK__) SDL_DYNAPI_PROC(int,SDL_RegisterApp,(const char *a, Uint32 b, void *c),(a,b,c),return) -SDL_DYNAPI_PROC(ID3D12Device*,SDL_RenderGetD3D12Device,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(void,SDL_SetWindowsMessageHook,(SDL_WindowsMessageHook a, void *b),(a,b),) SDL_DYNAPI_PROC(void,SDL_UnregisterApp,(void),(),) -#endif -#if defined(__WIN32__) || defined(__WINGDK__) SDL_DYNAPI_PROC(SDL_bool,SDL_DXGIGetOutputInfo,(SDL_DisplayID a, int *b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_Direct3D9GetAdapterIndex,(SDL_DisplayID a),(a),return) -SDL_DYNAPI_PROC(ID3D11Device*,SDL_GetRenderD3D11Device,(SDL_Renderer *a),(a),return) -SDL_DYNAPI_PROC(IDirect3DDevice9*,SDL_GetRenderD3D9Device,(SDL_Renderer *a),(a),return) -#endif -#ifdef __GDK__ SDL_DYNAPI_PROC(int,SDL_GDKGetTaskQueue,(XTaskQueueHandle *a),(a),return) SDL_DYNAPI_PROC(void,SDL_GDKSuspendComplete,(void),(),) -#endif -#ifdef __WINRT__ SDL_DYNAPI_PROC(SDL_WinRT_DeviceFamily,SDL_WinRTGetDeviceFamily,(void),(),return) SDL_DYNAPI_PROC(const wchar_t*,SDL_WinRTGetFSPathUNICODE,(SDL_WinRT_Path a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_WinRTGetFSPathUTF8,(SDL_WinRT_Path a),(a),return) -#endif -#ifdef __LINUX__ SDL_DYNAPI_PROC(int,SDL_LinuxSetThreadPriority,(Sint64 a, int b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_LinuxSetThreadPriorityAndPolicy,(Sint64 a, int b, int c),(a,b,c),return) -#endif -#ifdef __IOS__ SDL_DYNAPI_PROC(void,SDL_OnApplicationDidChangeStatusBarOrientation,(void),(),) SDL_DYNAPI_PROC(int,SDL_iPhoneSetAnimationCallback,(SDL_Window *a, int b, void (SDLCALL *c)(void *), void *d),(a,b,c,d),return) SDL_DYNAPI_PROC(void,SDL_iPhoneSetEventPump,(SDL_bool a),(a),) -#endif -#ifdef __ANDROID__ SDL_DYNAPI_PROC(void,SDL_AndroidBackButton,(void),(),) SDL_DYNAPI_PROC(void*,SDL_AndroidGetActivity,(void),(),return) SDL_DYNAPI_PROC(const char*,SDL_AndroidGetExternalStoragePath,(void),(),return) @@ -115,9 +100,8 @@ SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsAndroidTV,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsChromebook,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsDeXMode,(void),(),return) -#endif -SDL_DYNAPI_PROC(void,SDL_AddEventWatch,(SDL_EventFilter a, void *b),(a,b),) +SDL_DYNAPI_PROC(int,SDL_AddEventWatch,(SDL_EventFilter a, void *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMapping,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromRW,(SDL_RWops *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_AddHintCallback,(const char *a, SDL_HintCallback b, void *c),(a,b,c),return) @@ -135,9 +119,9 @@ SDL_DYNAPI_PROC(void,SDL_AtomicUnlock,(SDL_SpinLock *a),(a),) SDL_DYNAPI_PROC(SDL_JoystickID,SDL_AttachVirtualJoystick,(SDL_JoystickType a, int b, int c, int d),(a,b,c,d),return) SDL_DYNAPI_PROC(SDL_JoystickID,SDL_AttachVirtualJoystickEx,(const SDL_VirtualJoystickDesc *a),(a),return) SDL_DYNAPI_PROC(int,SDL_BlitSurface,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, SDL_Rect *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(int,SDL_BlitSurfaceScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, SDL_Rect *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_BlitSurfaceScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUnchecked,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUncheckedScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUncheckedScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_BroadcastCondition,(SDL_Condition *a),(a),return) SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return) SDL_DYNAPI_PROC(void,SDL_CleanupTLS,(void),(),) @@ -163,7 +147,6 @@ SDL_DYNAPI_PROC(SDL_RWops*,SDL_CreateRW,(void),(),return) SDL_DYNAPI_PROC(SDL_RWLock*,SDL_CreateRWLock,(void),(),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateRenderer,(SDL_Window *a, const char *b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Semaphore*,SDL_CreateSemaphore,(Uint32 a),(a),return) -SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateShapedWindow,(const char *a, int b, int c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateSoftwareRenderer,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_CreateSurface,(int a, int b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_CreateSurfaceFrom,(void *a, int b, int c, int d, Uint32 e),(a,b,c,d,e),return) @@ -173,8 +156,7 @@ SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTexture,(SDL_Renderer *a, Uint32 b, int c SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTextureFromSurface,(SDL_Renderer *a, SDL_Surface *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindow,(const char *a, int b, int c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_CreateWindowAndRenderer,(int a, int b, Uint32 c, SDL_Window **d, SDL_Renderer **e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowFrom,(const void *a),(a),return) -SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithProperties,(SDL_PropertiesID a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_CursorVisible,(void),(),return) SDL_DYNAPI_PROC(void,SDL_DelEventWatch,(SDL_EventFilter a, void *b),(a,b),) SDL_DYNAPI_PROC(void,SDL_DelHintCallback,(const char *a, SDL_HintCallback b, void *c),(a,b,c),) @@ -211,7 +193,7 @@ SDL_DYNAPI_PROC(void,SDL_FilterEvents,(SDL_EventFilter a, void *b),(a,b),) SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a, SDL_FlashOperation b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_FlushEvent,(Uint32 a),(a),) SDL_DYNAPI_PROC(void,SDL_FlushEvents,(Uint32 a, Uint32 b),(a,b),) -SDL_DYNAPI_PROC(int,SDL_GL_BindTexture,(SDL_Texture *a, float *b, float *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_FlushRenderer,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(SDL_GLContext,SDL_GL_CreateContext,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GL_DeleteContext,(SDL_GLContext a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_GL_ExtensionSupported,(const char *a),(a),return) @@ -226,7 +208,6 @@ SDL_DYNAPI_PROC(void,SDL_GL_ResetAttributes,(void),(),) SDL_DYNAPI_PROC(int,SDL_GL_SetAttribute,(SDL_GLattr a, int b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GL_SetSwapInterval,(int a),(a),return) SDL_DYNAPI_PROC(int,SDL_GL_SwapWindow,(SDL_Window *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GL_UnbindTexture,(SDL_Texture *a),(a),return) SDL_DYNAPI_PROC(void,SDL_GL_UnloadLibrary,(void),(),) SDL_DYNAPI_PROC(SDL_GUID,SDL_GUIDFromString,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GUIDToString,(SDL_GUID a, char *b, int c),(a,b,c),return) @@ -264,7 +245,6 @@ SDL_DYNAPI_PROC(const char*,SDL_GetDisplayName,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetDisplayUsableBounds,(SDL_DisplayID a, SDL_Rect *b),(a,b),return) SDL_DYNAPI_PROC(SDL_DisplayID*,SDL_GetDisplays,(int *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetError,(void),(),return) -SDL_DYNAPI_PROC(char*,SDL_GetErrorMsg,(char *a, int b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_GetEventFilter,(SDL_EventFilter *a, void **b),(a,b),return) SDL_DYNAPI_PROC(const SDL_DisplayMode**,SDL_GetFullscreenDisplayModes,(SDL_DisplayID a, int *b),(a,b),return) SDL_DYNAPI_PROC(const char*,SDL_GetGamepadAppleSFSymbolsNameForAxis,(SDL_Gamepad *a, SDL_GamepadAxis b),(a,b),return) @@ -289,7 +269,6 @@ SDL_DYNAPI_PROC(Uint16,SDL_GetGamepadInstanceVendor,(SDL_JoystickID a),(a),retur SDL_DYNAPI_PROC(SDL_Joystick*,SDL_GetGamepadJoystick,(SDL_Gamepad *a),(a),return) SDL_DYNAPI_PROC(char*,SDL_GetGamepadMapping,(SDL_Gamepad *a),(a),return) SDL_DYNAPI_PROC(char*,SDL_GetGamepadMappingForGUID,(SDL_JoystickGUID a),(a),return) -SDL_DYNAPI_PROC(char*,SDL_GetGamepadMappingForIndex,(int a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetGamepadName,(SDL_Gamepad *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetGamepadPath,(SDL_Gamepad *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetGamepadPlayerIndex,(SDL_Gamepad *a),(a),return) @@ -350,14 +329,12 @@ SDL_DYNAPI_PROC(SDL_Window*,SDL_GetMouseFocus,(void),(),return) SDL_DYNAPI_PROC(Uint32,SDL_GetMouseState,(float *a, float *b),(a,b),return) SDL_DYNAPI_PROC(SDL_DisplayOrientation,SDL_GetNaturalDisplayOrientation,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumAllocations,(void),(),return) -SDL_DYNAPI_PROC(int,SDL_GetNumGamepadMappings,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetNumGamepadTouchpadFingers,(SDL_Gamepad *a, int b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetNumGamepadTouchpads,(SDL_Gamepad *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumJoystickAxes,(SDL_Joystick *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumJoystickButtons,(SDL_Joystick *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumJoystickHats,(SDL_Joystick *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumRenderDrivers,(void),(),return) -SDL_DYNAPI_PROC(int,SDL_GetNumTouchDevices,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetNumTouchFingers,(SDL_TouchID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumVideoDrivers,(void),(),return) SDL_DYNAPI_PROC(void,SDL_GetOriginalMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),) @@ -414,7 +391,6 @@ SDL_DYNAPI_PROC(const char*,SDL_GetSensorName,(SDL_Sensor *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetSensorNonPortableType,(SDL_Sensor *a),(a),return) SDL_DYNAPI_PROC(SDL_SensorType,SDL_GetSensorType,(SDL_Sensor *a),(a),return) SDL_DYNAPI_PROC(SDL_SensorID*,SDL_GetSensors,(int *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetShapedWindowMode,(SDL_Window *a, SDL_WindowShapeMode *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetSurfaceAlphaMod,(SDL_Surface *a, Uint8 *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetSurfaceBlendMode,(SDL_Surface *a, SDL_BlendMode *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetSurfaceClipRect,(SDL_Surface *a, SDL_Rect *b),(a,b),return) @@ -427,19 +403,15 @@ SDL_DYNAPI_PROC(int,SDL_GetTextureAlphaMod,(SDL_Texture *a, Uint8 *b),(a,b),retu SDL_DYNAPI_PROC(int,SDL_GetTextureBlendMode,(SDL_Texture *a, SDL_BlendMode *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetTextureColorMod,(SDL_Texture *a, Uint8 *b, Uint8 *c, Uint8 *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_GetTextureScaleMode,(SDL_Texture *a, SDL_ScaleMode *b),(a,b),return) -SDL_DYNAPI_PROC(void*,SDL_GetTextureUserData,(SDL_Texture *a),(a),return) SDL_DYNAPI_PROC(SDL_threadID,SDL_GetThreadID,(SDL_Thread *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetThreadName,(SDL_Thread *a),(a),return) SDL_DYNAPI_PROC(Uint64,SDL_GetTicks,(void),(),return) SDL_DYNAPI_PROC(Uint64,SDL_GetTicksNS,(void),(),return) -SDL_DYNAPI_PROC(SDL_TouchID,SDL_GetTouchDevice,(int a),(a),return) SDL_DYNAPI_PROC(SDL_TouchDeviceType,SDL_GetTouchDeviceType,(SDL_TouchID a),(a),return) SDL_DYNAPI_PROC(SDL_Finger*,SDL_GetTouchFinger,(SDL_TouchID a, int b),(a,b),return) -SDL_DYNAPI_PROC(const char*,SDL_GetTouchName,(int a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetVersion,(SDL_version *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetVideoDriver,(int a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetWindowBordersSize,(SDL_Window *a, int *b, int *c, int *d, int *e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(void*,SDL_GetWindowData,(SDL_Window *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(float,SDL_GetWindowDisplayScale,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(Uint32,SDL_GetWindowFlags,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowFromID,(Uint32 a),(a),return) @@ -461,7 +433,6 @@ SDL_DYNAPI_PROC(int,SDL_GetWindowSize,(SDL_Window *a, int *b, int *c),(a,b,c),re SDL_DYNAPI_PROC(int,SDL_GetWindowSizeInPixels,(SDL_Window *a, int *b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_GetWindowSurface,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetWindowTitle,(SDL_Window *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetWindowWMInfo,(SDL_Window *a, SDL_SysWMinfo *b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_YUV_CONVERSION_MODE,SDL_GetYUVConversionMode,(void),(),return) SDL_DYNAPI_PROC(SDL_YUV_CONVERSION_MODE,SDL_GetYUVConversionModeForResolution,(int a, int b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_HapticClose,(SDL_Haptic *a),(a),) @@ -520,7 +491,6 @@ SDL_DYNAPI_PROC(int,SDL_Init,(Uint32 a),(a),return) SDL_DYNAPI_PROC(int,SDL_InitSubSystem,(Uint32 a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsGamepad,(SDL_JoystickID a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsJoystickVirtual,(SDL_JoystickID a),(a),return) -SDL_DYNAPI_PROC(SDL_bool,SDL_IsShapedWindow,(const SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsTablet,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickConnected,(SDL_Joystick *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickEventsEnabled,(void),(),return) @@ -535,15 +505,15 @@ SDL_DYNAPI_PROC(void*,SDL_LoadFile_RW,(SDL_RWops *a, size_t *b, SDL_bool c),(a,b SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_LoadFunction,(void *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void*,SDL_LoadObject,(const char *a),(a),return) SDL_DYNAPI_PROC(void,SDL_LockJoysticks,(void),(),) -SDL_DYNAPI_PROC(int,SDL_LockMutex,(SDL_Mutex *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_LockRWLockForReading,(SDL_RWLock *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_LockRWLockForWriting,(SDL_RWLock *a),(a),return) +SDL_DYNAPI_PROC(void,SDL_LockMutex,(SDL_Mutex *a),(a),) +SDL_DYNAPI_PROC(void,SDL_LockRWLockForReading,(SDL_RWLock *a),(a),) +SDL_DYNAPI_PROC(void,SDL_LockRWLockForWriting,(SDL_RWLock *a),(a),) SDL_DYNAPI_PROC(int,SDL_LockSurface,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(int,SDL_LockTexture,(SDL_Texture *a, const SDL_Rect *b, void **c, int *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_LockTextureToSurface,(SDL_Texture *a, const SDL_Rect *b, SDL_Surface **c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_LogGetOutputFunction,(SDL_LogOutputFunction *a, void **b),(a,b),) SDL_DYNAPI_PROC(SDL_LogPriority,SDL_LogGetPriority,(int a),(a),return) -SDL_DYNAPI_PROC(void,SDL_LogMessageV,(int a, SDL_LogPriority b, const char *c, va_list d),(a,b,c,d),) +SDL_DYNAPI_PROC(void,SDL_LogMessageV,(int a, SDL_LogPriority b, SDL_PRINTF_FORMAT_STRING const char *c, va_list d),(a,b,c,d),) SDL_DYNAPI_PROC(void,SDL_LogResetPriorities,(void),(),) SDL_DYNAPI_PROC(void,SDL_LogSetAllPriority,(SDL_LogPriority a),(a),) SDL_DYNAPI_PROC(void,SDL_LogSetOutputFunction,(SDL_LogOutputFunction a, void *b),(a,b),) @@ -570,7 +540,7 @@ SDL_DYNAPI_PROC(SDL_Joystick*,SDL_OpenJoystick,(SDL_JoystickID a),(a),return) SDL_DYNAPI_PROC(SDL_Sensor*,SDL_OpenSensor,(SDL_SensorID a),(a),return) SDL_DYNAPI_PROC(int,SDL_OpenURL,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PeepEvents,(SDL_Event *a, int b, SDL_eventaction c, Uint32 d, Uint32 e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_PollEvent,(SDL_Event *a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_PollEvent,(SDL_Event *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PostSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, Uint32 c, const void *d, int e, Uint32 f, void *g, int h),(a,b,c,d,e,f,g,h),return) SDL_DYNAPI_PROC(void,SDL_PumpEvents,(void),(),) @@ -603,7 +573,6 @@ SDL_DYNAPI_PROC(int,SDL_RenderCoordinatesFromWindow,(SDL_Renderer *a, float b, f SDL_DYNAPI_PROC(int,SDL_RenderCoordinatesToWindow,(SDL_Renderer *a, float b, float c, float *d, float *e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_RenderFillRect,(SDL_Renderer *a, const SDL_FRect *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_RenderFillRects,(SDL_Renderer *a, const SDL_FRect *b, int c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_RenderFlush,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(int,SDL_RenderGeometry,(SDL_Renderer *a, SDL_Texture *b, const SDL_Vertex *c, int d, const int *e, int f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_RenderGeometryRaw,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const SDL_Color *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return) SDL_DYNAPI_PROC(int,SDL_RenderLine,(SDL_Renderer *a, float b, float c, float d, float e),(a,b,c,d,e),return) @@ -680,11 +649,9 @@ SDL_DYNAPI_PROC(int,SDL_SetTextureAlphaMod,(SDL_Texture *a, Uint8 b),(a,b),retur SDL_DYNAPI_PROC(int,SDL_SetTextureBlendMode,(SDL_Texture *a, SDL_BlendMode b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetTextureColorMod,(SDL_Texture *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_SetTextureScaleMode,(SDL_Texture *a, SDL_ScaleMode b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_SetTextureUserData,(SDL_Texture *a, void *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetThreadPriority,(SDL_ThreadPriority a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetWindowAlwaysOnTop,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowBordered,(SDL_Window *a, SDL_bool b),(a,b),return) -SDL_DYNAPI_PROC(void*,SDL_SetWindowData,(SDL_Window *a, const char *b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetWindowFullscreen,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowFullscreenMode,(SDL_Window *a, const SDL_DisplayMode *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowGrab,(SDL_Window *a, SDL_bool b),(a,b),return) @@ -700,7 +667,6 @@ SDL_DYNAPI_PROC(int,SDL_SetWindowMouseRect,(SDL_Window *a, const SDL_Rect *b),(a SDL_DYNAPI_PROC(int,SDL_SetWindowOpacity,(SDL_Window *a, float b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowPosition,(SDL_Window *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetWindowResizable,(SDL_Window *a, SDL_bool b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_SetWindowShape,(SDL_Window *a, SDL_Surface *b, SDL_WindowShapeMode *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetWindowSize,(SDL_Window *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetWindowTitle,(SDL_Window *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_SetYUVConversionMode,(SDL_YUV_CONVERSION_MODE a),(a),) @@ -709,8 +675,7 @@ SDL_DYNAPI_PROC(int,SDL_ShowMessageBox,(const SDL_MessageBoxData *a, int *b),(a, SDL_DYNAPI_PROC(int,SDL_ShowSimpleMessageBox,(Uint32 a, const char *b, const char *c, SDL_Window *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_ShowWindow,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SignalCondition,(SDL_Condition *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_SoftStretch,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(int,SDL_SoftStretchLinear,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_SoftStretch,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(void,SDL_StartTextInput,(void),(),) SDL_DYNAPI_PROC(void,SDL_StopTextInput,(void),(),) SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasColorKey,(SDL_Surface *a),(a),return) @@ -724,8 +689,8 @@ SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_RWLock *a),(a),return) SDL_DYNAPI_PROC(int,SDL_TryWaitSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(void,SDL_UnloadObject,(void *a),(a),) SDL_DYNAPI_PROC(void,SDL_UnlockJoysticks,(void),(),) -SDL_DYNAPI_PROC(int,SDL_UnlockMutex,(SDL_Mutex *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_RWLock *a),(a),return) +SDL_DYNAPI_PROC(void,SDL_UnlockMutex,(SDL_Mutex *a),(a),) +SDL_DYNAPI_PROC(void,SDL_UnlockRWLock,(SDL_RWLock *a),(a),) SDL_DYNAPI_PROC(void,SDL_UnlockSurface,(SDL_Surface *a),(a),) SDL_DYNAPI_PROC(void,SDL_UnlockTexture,(SDL_Texture *a),(a),) SDL_DYNAPI_PROC(void,SDL_UpdateGamepads,(void),(),) @@ -736,15 +701,15 @@ SDL_DYNAPI_PROC(int,SDL_UpdateTexture,(SDL_Texture *a, const SDL_Rect *b, const SDL_DYNAPI_PROC(int,SDL_UpdateWindowSurface,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(int,SDL_UpdateWindowSurfaceRects,(SDL_Window *a, const SDL_Rect *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_UpdateYUVTexture,(SDL_Texture *a, const SDL_Rect *b, const Uint8 *c, int d, const Uint8 *e, int f, const Uint8 *g, int h),(a,b,c,d,e,f,g,h),return) -SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_CreateSurface,(SDL_Window *a, VkInstance b, VkSurfaceKHR *c),(a,b,c),return) -SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_GetInstanceExtensions,(unsigned int *a, const char **b),(a,b),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_CreateSurface,(SDL_Window *a, VkInstance b, const struct VkAllocationCallbacks *c, VkSurfaceKHR *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(char const* const*,SDL_Vulkan_GetInstanceExtensions,(Uint32 *a),(a),return) SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_Vulkan_GetVkGetInstanceProcAddr,(void),(),return) SDL_DYNAPI_PROC(int,SDL_Vulkan_LoadLibrary,(const char *a),(a),return) SDL_DYNAPI_PROC(void,SDL_Vulkan_UnloadLibrary,(void),(),) SDL_DYNAPI_PROC(int,SDL_WaitCondition,(SDL_Condition *a, SDL_Mutex *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_WaitConditionTimeout,(SDL_Condition *a, SDL_Mutex *b, Sint32 c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_WaitEvent,(SDL_Event *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_WaitEventTimeout,(SDL_Event *a, Sint32 b),(a,b),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_WaitEvent,(SDL_Event *a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_WaitEventTimeout,(SDL_Event *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_WaitSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(int,SDL_WaitSemaphoreTimeout,(SDL_Semaphore *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_WaitThread,(SDL_Thread *a, int *b),(a,b),) @@ -892,10 +857,10 @@ SDL_DYNAPI_PROC(char*,SDL_ultoa,(unsigned long a, char *b, int c),(a,b,c),return SDL_DYNAPI_PROC(size_t,SDL_utf8strlcpy,(SDL_OUT_Z_CAP(c) char *a, const char *b, size_t c),(a,b,c),return) SDL_DYNAPI_PROC(size_t,SDL_utf8strlen,(const char *a),(a),return) SDL_DYNAPI_PROC(size_t,SDL_utf8strnlen,(const char *a, size_t b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_vasprintf,(char **a, const char *b, va_list c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_vsnprintf,(SDL_OUT_Z_CAP(b) char *a, size_t b, const char *c, va_list d),(a,b,c,d),return) -SDL_DYNAPI_PROC(int,SDL_vsscanf,(const char *a, const char *b, va_list c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_vswprintf,(wchar_t *a, size_t b, const wchar_t *c, va_list d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_vasprintf,(char **a, SDL_PRINTF_FORMAT_STRING const char *b, va_list c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_vsnprintf,(SDL_OUT_Z_CAP(b) char *a, size_t b, SDL_PRINTF_FORMAT_STRING const char *c, va_list d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_vsscanf,(const char *a, SDL_SCANF_FORMAT_STRING const char *b, va_list c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_vswprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, const wchar_t *c, va_list d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_wcscasecmp,(const wchar_t *a, const wchar_t *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_wcscmp,(const wchar_t *a, const wchar_t *b),(a,b),return) SDL_DYNAPI_PROC(wchar_t*,SDL_wcsdup,(const wchar_t *a),(a),return) @@ -949,10 +914,10 @@ SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_Audio SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),) SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_OpenAudioDeviceStream,(SDL_AudioDeviceID a, const SDL_AudioSpec *b, SDL_AudioStreamCallback c, void *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, SDL_bool b, SDL_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uint32 *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(const SDL_AudioSpec *a, const Uint8 *b, int c, const SDL_AudioSpec *d, Uint8 **e, int *f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) -SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uint32 *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_ResumeAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_AudioDevicePaused,(SDL_AudioDeviceID a),(a),return) @@ -970,11 +935,80 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS32LE,(SDL_RWops *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS32BE,(SDL_RWops *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS64LE,(SDL_RWops *a, Sint64 b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS64BE,(SDL_RWops *a, Sint64 b),(a,b),return) -#ifdef __GDK__ + SDL_DYNAPI_PROC(int,SDL_GDKGetDefaultUser,(XUserHandle *a),(a),return) -#endif + SDL_DYNAPI_PROC(int,SDL_SetWindowFocusable,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(float,SDL_GetAudioStreamFrequencyRatio,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFrequencyRatio,(SDL_AudioStream *a, float b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamQueued,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_CreateProperties,(void),(),return) +SDL_DYNAPI_PROC(int,SDL_LockProperties,(SDL_PropertiesID a),(a),return) +SDL_DYNAPI_PROC(void,SDL_UnlockProperties,(SDL_PropertiesID a),(a),) +SDL_DYNAPI_PROC(int,SDL_SetProperty,(SDL_PropertiesID a, const char *b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(void*,SDL_GetProperty,(SDL_PropertiesID a, const char *b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(void,SDL_DestroyProperties,(SDL_PropertiesID a),(a),) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetAudioStreamProperties,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGamepadProperties,(SDL_Gamepad *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetJoystickProperties,(SDL_Joystick *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetRendererProperties,(SDL_Renderer *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetTextureProperties,(SDL_Texture *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetRWProperties,(SDL_RWops *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSensorProperties,(SDL_Sensor *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSurfaceProperties,(SDL_Surface *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_EnterAppMainCallbacks,(int a, char *b[], SDL_AppInit_func c, SDL_AppIterate_func d, SDL_AppEvent_func e, SDL_AppQuit_func f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(size_t,SDL_RWvprintf,(SDL_RWops *a, SDL_PRINTF_FORMAT_STRING const char *b, va_list c),(a,b,c),return) +SDL_DYNAPI_PROC(void*,SDL_AllocateEventMemory,(size_t a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetDisplayProperties,(SDL_DisplayID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_SetPropertyWithCleanup,(SDL_PropertiesID a, const char *b, void *c, void (SDLCALL *d)(void *userdata, void *value), void *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return) +SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCapture,(SDL_VideoCaptureDeviceID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_SetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(SDL_VideoCaptureDevice*,SDL_OpenVideoCaptureWithSpec,(SDL_VideoCaptureDeviceID a, const SDL_VideoCaptureSpec *b, SDL_VideoCaptureSpec *c, int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(const char*,SDL_GetVideoCaptureDeviceName,(SDL_VideoCaptureDeviceID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureSpec,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureSpec *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFormat,(SDL_VideoCaptureDevice *a, int b, Uint32 *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFormats,(SDL_VideoCaptureDevice *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetVideoCaptureFrameSize,(SDL_VideoCaptureDevice *a, Uint32 b, int c, int *d, int *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_GetNumVideoCaptureFrameSizes,(SDL_VideoCaptureDevice *a, Uint32 b),(a,b),return) +SDL_DYNAPI_PROC(SDL_VideoCaptureStatus,SDL_GetVideoCaptureStatus,(SDL_VideoCaptureDevice *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_StartVideoCapture,(SDL_VideoCaptureDevice *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_AcquireVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_ReleaseVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL_VideoCaptureFrame *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_StopVideoCapture,(SDL_VideoCaptureDevice *a),(a),return) +SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),) +SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return) +SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabelForType,(SDL_GamepadType a, SDL_GamepadButton b),(a,b),return) +SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabel,(SDL_Gamepad *a, SDL_GamepadButton b),(a,b),return) +SDL_DYNAPI_PROC(SDL_PenID*,SDL_GetPens,(int *a),(a),return) +SDL_DYNAPI_PROC(Uint32,SDL_GetPenStatus,(SDL_PenID a, float *b, float *c, float *d, size_t e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(SDL_PenID,SDL_GetPenFromGUID,(SDL_GUID a),(a),return) +SDL_DYNAPI_PROC(SDL_GUID,SDL_GetPenGUID,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_PenConnected,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(const char*,SDL_GetPenName,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(Uint32,SDL_GetPenCapabilities,(SDL_PenID a, SDL_PenCapabilityInfo *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_PenSubtype,SDL_GetPenType,(SDL_PenID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_SetStringProperty,(SDL_PropertiesID a, const char *b, const char *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetNumberProperty,(SDL_PropertiesID a, const char *b, Sint64 c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetFloatProperty,(SDL_PropertiesID a, const char *b, float c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_PropertyType,SDL_GetPropertyType,(SDL_PropertiesID a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(const char*,SDL_GetStringProperty,(SDL_PropertiesID a, const char *b, const char *c),(a,b,c),return) +SDL_DYNAPI_PROC(Sint64,SDL_GetNumberProperty,(SDL_PropertiesID a, const char *b, Sint64 c),(a,b,c),return) +SDL_DYNAPI_PROC(float,SDL_GetFloatProperty,(SDL_PropertiesID a, const char *b, float c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_EnumerateProperties,(SDL_PropertiesID a, SDL_EnumeratePropertiesCallback b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetBooleanProperty,(SDL_PropertiesID a, const char *b, SDL_bool c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_GetBooleanProperty,(SDL_PropertiesID a, const char *b, SDL_bool c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTextureWithProperties,(SDL_Renderer *a, SDL_PropertiesID b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateRendererWithProperties,(SDL_PropertiesID a),(a),return) +SDL_DYNAPI_PROC(char**,SDL_GetGamepadMappings,(int *a),(a),return) +SDL_DYNAPI_PROC(SDL_TouchID*,SDL_GetTouchDevices,(int *a),(a),return) +SDL_DYNAPI_PROC(const char*,SDL_GetTouchDeviceName,(SDL_TouchID a),(a),return) +SDL_DYNAPI_PROC(char*,SDL_strnstr,(const char *a, const char *b, size_t c),(a,b,c),return) +SDL_DYNAPI_PROC(wchar_t*,SDL_wcsnstr,(const wchar_t *a, const wchar_t *b, size_t c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SyncWindow,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(Uint64,SDL_GetGamepadSteamHandle,(SDL_Gamepad *a),(a),return) +SDL_DYNAPI_PROC(SDL_Renderer*,SDL_GetRendererFromTexture,(SDL_Texture *a),(a),return) diff --git a/src/dynapi/SDL_dynapi_unsupported.h b/src/dynapi/SDL_dynapi_unsupported.h new file mode 100644 index 00000000..5bad9e5a --- /dev/null +++ b/src/dynapi/SDL_dynapi_unsupported.h @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + 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 + 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_unsupported_h_ +#define SDL_dynapi_unsupported_h_ + + +#if !(defined(__WIN32__) || defined(__GDK__)) +typedef struct ID3D12Device ID3D12Device; +typedef void *SDL_WindowsMessageHook; +#endif + +#if !(defined(__WIN32__) || defined(__WINGDK__)) +typedef struct ID3D11Device ID3D11Device; +typedef struct IDirect3DDevice9 IDirect3DDevice9; +#endif + +#ifndef __GDK__ +typedef struct XTaskQueueHandle XTaskQueueHandle; +#endif + +#ifndef __WINRT__ +typedef int SDL_WinRT_DeviceFamily; +typedef int SDL_WinRT_Path; +#endif +#ifndef __GDK__ +typedef struct XUserHandle XUserHandle; +#endif + +#endif diff --git a/src/dynapi/gendynapi.py b/src/dynapi/gendynapi.py index 591f3d63..6f78afc5 100755 --- a/src/dynapi/gendynapi.py +++ b/src/dynapi/gendynapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Simple DirectMedia Layer -# Copyright (C) 1997-2023 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 @@ -134,7 +134,13 @@ def main(): # Discard if it doesn't contain 'SDLCALL' if "SDLCALL" not in func: if args.debug: - print(" Discard: " + func) + print(" Discard, doesn't have SDLCALL: " + func) + continue + + # Discard if it contains 'SDLMAIN_DECLSPEC' (these are not SDL symbols). + if "SDLMAIN_DECLSPEC" in func: + if args.debug: + print(" Discard, has SDLMAIN_DECLSPEC: " + func) continue if args.debug: @@ -144,8 +150,12 @@ def main(): func = func.replace(" SDL_PRINTF_VARARG_FUNC(1)", ""); func = func.replace(" SDL_PRINTF_VARARG_FUNC(2)", ""); func = func.replace(" SDL_PRINTF_VARARG_FUNC(3)", ""); + func = func.replace(" SDL_PRINTF_VARARG_FUNCV(1)", ""); + func = func.replace(" SDL_PRINTF_VARARG_FUNCV(2)", ""); + func = func.replace(" SDL_PRINTF_VARARG_FUNCV(3)", ""); func = func.replace(" SDL_WPRINTF_VARARG_FUNC(3)", ""); func = func.replace(" SDL_SCANF_VARARG_FUNC(2)", ""); + func = func.replace(" SDL_SCANF_VARARG_FUNCV(2)", ""); func = func.replace(" __attribute__((analyzer_noreturn))", ""); func = func.replace(" SDL_MALLOC", ""); func = func.replace(" SDL_ALLOC_SIZE2(1, 2)", ""); @@ -156,6 +166,7 @@ def main(): func = re.sub(" SDL_TRY_ACQUIRE_SHARED\(.*\)", "", func); func = re.sub(" SDL_RELEASE\(.*\)", "", func); func = re.sub(" SDL_RELEASE_SHARED\(.*\)", "", func); + func = re.sub(" SDL_RELEASE_GENERIC\(.*\)", "", func); # Should be a valid function here match = reg_parsing_function.match(func) @@ -372,7 +383,7 @@ def check_comment(): if header != 'SDL_stdinc.h': parameter_name = i['parameter_name'] for n in parameter_name: - if n != "" and "\\param " + n not in comment: + if n != "" and "\\param " + n not in comment and "\\param[out] " + n not in comment: check_comment_header() print(" In file %s: function %s() missing '\\param %s'" % (header, name, n)); diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index bc9b07e6..0c2f20ac 100644 --- a/src/events/SDL_clipboardevents.c +++ b/src/events/SDL_clipboardevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_clipboardevents_c.h b/src/events/SDL_clipboardevents_c.h index ce74e3cb..557bd0d1 100644 --- a/src/events/SDL_clipboardevents_c.h +++ b/src/events/SDL_clipboardevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_displayevents.c b/src/events/SDL_displayevents.c index edccb033..14c29f30 100644 --- a/src/events/SDL_displayevents.c +++ b/src/events/SDL_displayevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,7 +28,7 @@ int SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, { int posted; - if (display == NULL || display->id == 0) { + if (!display || display->id == 0) { return 0; } switch (displayevent) { @@ -54,8 +54,8 @@ int SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent, } switch (displayevent) { - case SDL_EVENT_DISPLAY_CONNECTED: - SDL_OnDisplayConnected(display); + case SDL_EVENT_DISPLAY_ADDED: + SDL_OnDisplayAdded(display); break; default: break; diff --git a/src/events/SDL_displayevents_c.h b/src/events/SDL_displayevents_c.h index ac209473..c93d9ffa 100644 --- a/src/events/SDL_displayevents_c.h +++ b/src/events/SDL_displayevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_dropevents.c b/src/events/SDL_dropevents.c index 5aa4a992..610652c2 100644 --- a/src/events/SDL_dropevents.c +++ b/src/events/SDL_dropevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,7 +27,7 @@ #include "../video/SDL_sysvideo.h" /* for SDL_Window internals. */ -static int SDL_SendDrop(SDL_Window *window, const SDL_EventType evtype, const char *data, float x, float y) +static int SDL_SendDrop(SDL_Window *window, const SDL_EventType evtype, const char *source, const char *data, float x, float y) { static SDL_bool app_is_dropping = SDL_FALSE; static float last_drop_x = 0; @@ -58,7 +58,18 @@ static int SDL_SendDrop(SDL_Window *window, const SDL_EventType evtype, const ch SDL_zero(event); event.type = evtype; event.common.timestamp = 0; - event.drop.file = data ? SDL_strdup(data) : NULL; + if (source) { + event.drop.source = SDL_strdup(source); + } + if (data) { + size_t size = SDL_strlen(data) + 1; + event.drop.data = (char *)SDL_AllocateEventMemory(size); + if (!event.drop.data) { + SDL_free(event.drop.source); + return 0; + } + SDL_memcpy(event.drop.data, data, size); + } event.drop.windowID = window ? window->id : 0; if (evtype == SDL_EVENT_DROP_POSITION) { @@ -83,23 +94,22 @@ static int SDL_SendDrop(SDL_Window *window, const SDL_EventType evtype, const ch return posted; } -int SDL_SendDropFile(SDL_Window *window, const char *file) +int SDL_SendDropFile(SDL_Window *window, const char *source, const char *file) { - return SDL_SendDrop(window, SDL_EVENT_DROP_FILE, file, 0, 0); + return SDL_SendDrop(window, SDL_EVENT_DROP_FILE, source, file, 0, 0); } -int SDL_SendDropPosition(SDL_Window *window, const char *file, float x, float y) +int SDL_SendDropPosition(SDL_Window *window, float x, float y) { - /* Don't send 'file' since this is an malloc per position, which may be forgotten to be freed */ - return SDL_SendDrop(window, SDL_EVENT_DROP_POSITION, NULL, x, y); + return SDL_SendDrop(window, SDL_EVENT_DROP_POSITION, NULL, NULL, x, y); } int SDL_SendDropText(SDL_Window *window, const char *text) { - return SDL_SendDrop(window, SDL_EVENT_DROP_TEXT, text, 0, 0); + return SDL_SendDrop(window, SDL_EVENT_DROP_TEXT, NULL, text, 0, 0); } int SDL_SendDropComplete(SDL_Window *window) { - return SDL_SendDrop(window, SDL_EVENT_DROP_COMPLETE, NULL, 0, 0); + return SDL_SendDrop(window, SDL_EVENT_DROP_COMPLETE, NULL, NULL, 0, 0); } diff --git a/src/events/SDL_dropevents_c.h b/src/events/SDL_dropevents_c.h index 9a485cd0..4379d68c 100644 --- a/src/events/SDL_dropevents_c.h +++ b/src/events/SDL_dropevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,8 +23,8 @@ #ifndef SDL_dropevents_c_h_ #define SDL_dropevents_c_h_ -extern int SDL_SendDropFile(SDL_Window *window, const char *file); -extern int SDL_SendDropPosition(SDL_Window *window, const char *file, float x, float y); +extern int SDL_SendDropFile(SDL_Window *window, const char *source, const char *file); +extern int SDL_SendDropPosition(SDL_Window *window, float x, float y); extern int SDL_SendDropText(SDL_Window *window, const char *text); extern int SDL_SendDropComplete(SDL_Window *window); diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 055009fb..2a346f03 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,6 +24,7 @@ #include "SDL_events_c.h" #include "../SDL_hints_c.h" +#include "../audio/SDL_audio_c.h" #include "../timer/SDL_timer_c.h" #ifndef SDL_JOYSTICK_DISABLED #include "../joystick/SDL_joystick_c.h" @@ -32,7 +33,6 @@ #include "../sensor/SDL_sensor_c.h" #endif #include "../video/SDL_sysvideo.h" -#include #undef SDL_PRIs64 #if (defined(__WIN32__) || defined(__GDK__)) && !defined(__CYGWIN__) @@ -61,6 +61,7 @@ static int SDL_event_watchers_count = 0; static SDL_bool SDL_event_watchers_dispatching = SDL_FALSE; static SDL_bool SDL_event_watchers_removed = SDL_FALSE; static SDL_AtomicInt SDL_sentinel_pending; +static Uint32 SDL_last_event_id = 0; typedef struct { @@ -74,17 +75,10 @@ static Uint32 SDL_userevents = SDL_EVENT_USER; typedef struct SDL_EventEntry { SDL_Event event; - SDL_SysWMmsg msg; struct SDL_EventEntry *prev; struct SDL_EventEntry *next; } SDL_EventEntry; -typedef struct SDL_SysWMEntry -{ - SDL_SysWMmsg msg; - struct SDL_SysWMEntry *next; -} SDL_SysWMEntry; - static struct { SDL_Mutex *lock; @@ -94,9 +88,76 @@ static struct SDL_EventEntry *head; SDL_EventEntry *tail; SDL_EventEntry *free; - SDL_SysWMEntry *wmmsg_used; - SDL_SysWMEntry *wmmsg_free; -} SDL_EventQ = { NULL, SDL_FALSE, { 0 }, 0, NULL, NULL, NULL, NULL, NULL }; +} SDL_EventQ = { NULL, SDL_FALSE, { 0 }, 0, NULL, NULL, NULL }; + +typedef struct SDL_EventMemory +{ + Uint32 eventID; + void *memory; + struct SDL_EventMemory *next; +} SDL_EventMemory; + +static SDL_Mutex *SDL_event_memory_lock; +static SDL_EventMemory *SDL_event_memory_head; +static SDL_EventMemory *SDL_event_memory_tail; + +void *SDL_AllocateEventMemory(size_t size) +{ + void *memory = SDL_malloc(size); + if (!memory) { + return NULL; + } + + SDL_LockMutex(SDL_event_memory_lock); + { + SDL_EventMemory *entry = (SDL_EventMemory *)SDL_malloc(sizeof(*entry)); + if (entry) { + entry->eventID = SDL_last_event_id; + entry->memory = memory; + entry->next = NULL; + + if (SDL_event_memory_tail) { + SDL_event_memory_tail->next = entry; + } else { + SDL_event_memory_head = entry; + } + SDL_event_memory_tail = entry; + } else { + SDL_free(memory); + memory = NULL; + } + } + SDL_UnlockMutex(SDL_event_memory_lock); + + return memory; +} + +static void SDL_FlushEventMemory(Uint32 eventID) +{ + SDL_LockMutex(SDL_event_memory_lock); + { + if (SDL_event_memory_head) { + while (SDL_event_memory_head) { + SDL_EventMemory *entry = SDL_event_memory_head; + + if (eventID && (Sint32)(eventID - entry->eventID) < 0) { + break; + } + + /* If you crash here, your application has memory corruption + * or freed memory in an event, which is no longer necessary. + */ + SDL_event_memory_head = entry->next; + SDL_free(entry->memory); + SDL_free(entry); + } + if (!SDL_event_memory_head) { + SDL_event_memory_tail = NULL; + } + } + } + SDL_UnlockMutex(SDL_event_memory_lock); +} #ifndef SDL_JOYSTICK_DISABLED @@ -129,8 +190,7 @@ static void SDLCALL SDL_PollSentinelChanged(void *userdata, const char *name, co * Verbosity of logged events as defined in SDL_HINT_EVENT_LOGGING: * - 0: (default) no logging * - 1: logging of most events - * - 2: as above, plus mouse and finger motion - * - 3: as above, plus SDL_SysWMEvents + * - 2: as above, plus mouse, pen, and finger motion */ static int SDL_EventLoggingVerbosity = 0; @@ -144,21 +204,17 @@ static void SDL_LogEvent(const SDL_Event *event) char name[64]; char details[128]; - /* sensor/mouse/finger motion are spammy, ignore these if they aren't demanded. */ + /* sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. */ if ((SDL_EventLoggingVerbosity < 2) && ((event->type == SDL_EVENT_MOUSE_MOTION) || (event->type == SDL_EVENT_FINGER_MOTION) || + (event->type == SDL_EVENT_PEN_MOTION) || (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || (event->type == SDL_EVENT_SENSOR_UPDATE))) { return; } - /* window manager events are even more spammy, and don't provide much useful info. */ - if ((SDL_EventLoggingVerbosity < 3) && (event->type == SDL_EVENT_SYSWM)) { - return; - } - /* this is to make (void)SDL_snprintf() calls cleaner. */ #define uint unsigned int @@ -222,8 +278,8 @@ static void SDL_LogEvent(const SDL_Event *event) (uint)event->display.timestamp, (uint)event->display.displayID, name, (int)event->display.data1); \ break SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ORIENTATION); - SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONNECTED); - SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_DISCONNECTED); + SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_ADDED); + SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_REMOVED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_MOVED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED); #undef SDL_DISPLAYEVENT_CASE @@ -245,6 +301,8 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESTORED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MOUSE_ENTER); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MOUSE_LEAVE); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PEN_ENTER); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PEN_LEAVE); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_FOCUS_GAINED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_FOCUS_LOST); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_CLOSE_REQUESTED); @@ -254,14 +312,11 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_OCCLUDED); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ENTER_FULLSCREEN); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED); #undef SDL_WINDOWEVENT_CASE - SDL_EVENT_CASE(SDL_EVENT_SYSWM) - /* !!! FIXME: we don't delve further at the moment. */ - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u)", (uint)event->syswm.timestamp); - break; - #define PRINT_KEY_EVENT(event) \ (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u state=%s repeat=%s scancode=%u keycode=%u mod=%u)", \ (uint)event->key.timestamp, (uint)event->key.windowID, \ @@ -378,6 +433,9 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_REMAPPED) PRINT_GAMEPADDEV_EVENT(event); break; + SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED) + PRINT_GAMEPADDEV_EVENT(event); + break; #undef PRINT_GAMEPADDEV_EVENT #define PRINT_CTOUCHPAD_EVENT(event) \ @@ -418,7 +476,54 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_FINGER_EVENT -#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (file='%s' timestamp=%u windowid=%u x=%f y=%f)", event->drop.file, (uint)event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) +#define PRINT_PTIP_EVENT(event) \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u tip=%u state=%s x=%g y=%g)", \ + (uint)event->ptip.timestamp, (uint)event->ptip.windowID, \ + (uint)event->ptip.which, (uint)event->ptip.tip, \ + event->ptip.state == SDL_PRESSED ? "down" : "up", \ + event->ptip.x, event->ptip.y) + SDL_EVENT_CASE(SDL_EVENT_PEN_DOWN) + PRINT_PTIP_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PEN_UP) + PRINT_PTIP_EVENT(event); + break; +#undef PRINT_PTIP_EVENT + + SDL_EVENT_CASE(SDL_EVENT_PEN_MOTION) + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%08x x=%g y=%g [%g, %g, %g, %g, %g, %g])", + (uint)event->pmotion.timestamp, (uint)event->pmotion.windowID, + (uint)event->pmotion.which, (uint)event->pmotion.pen_state, + event->pmotion.x, event->pmotion.y, + event->pmotion.axes[SDL_PEN_AXIS_PRESSURE], + event->pmotion.axes[SDL_PEN_AXIS_XTILT], + event->pmotion.axes[SDL_PEN_AXIS_YTILT], + event->pmotion.axes[SDL_PEN_AXIS_DISTANCE], + event->pmotion.axes[SDL_PEN_AXIS_ROTATION], + event->pmotion.axes[SDL_PEN_AXIS_SLIDER]); + break; + +#define PRINT_PBUTTON_EVENT(event) \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u tip=%u state=%s x=%g y=%g axes=[%g, %g, %g, %g, %g, %g])", \ + (uint)event->pbutton.timestamp, (uint)event->pbutton.windowID, \ + (uint)event->pbutton.which, (uint)event->pbutton.button, \ + event->pbutton.state == SDL_PRESSED ? "pressed" : "released", \ + event->pbutton.x, event->pbutton.y, \ + event->pbutton.axes[SDL_PEN_AXIS_PRESSURE], \ + event->pbutton.axes[SDL_PEN_AXIS_XTILT], \ + event->pbutton.axes[SDL_PEN_AXIS_YTILT], \ + event->pbutton.axes[SDL_PEN_AXIS_DISTANCE], \ + event->pbutton.axes[SDL_PEN_AXIS_ROTATION], \ + event->pbutton.axes[SDL_PEN_AXIS_SLIDER]) + SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_DOWN) + PRINT_PBUTTON_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_UP) + PRINT_PBUTTON_EVENT(event); + break; +#undef PRINT_PBUTTON_EVENT + +#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%u windowid=%u x=%f y=%f)", event->drop.data, (uint)event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y) SDL_EVENT_CASE(SDL_EVENT_DROP_FILE) PRINT_DROP_EVENT(event); break; @@ -476,14 +581,11 @@ static void SDL_LogEvent(const SDL_Event *event) #undef uint } -/* Public functions */ - void SDL_StopEventLoop(void) { const char *report = SDL_GetHint("SDL_EVENT_QUEUE_STATISTICS"); int i; SDL_EventEntry *entry; - SDL_SysWMEntry *wmmsg; SDL_LockMutex(SDL_EventQ.lock); @@ -505,32 +607,26 @@ void SDL_StopEventLoop(void) SDL_free(entry); entry = next; } - for (wmmsg = SDL_EventQ.wmmsg_used; wmmsg;) { - SDL_SysWMEntry *next = wmmsg->next; - SDL_free(wmmsg); - wmmsg = next; - } - for (wmmsg = SDL_EventQ.wmmsg_free; wmmsg;) { - SDL_SysWMEntry *next = wmmsg->next; - SDL_free(wmmsg); - wmmsg = next; - } SDL_AtomicSet(&SDL_EventQ.count, 0); SDL_EventQ.max_events_seen = 0; SDL_EventQ.head = NULL; SDL_EventQ.tail = NULL; SDL_EventQ.free = NULL; - SDL_EventQ.wmmsg_used = NULL; - SDL_EventQ.wmmsg_free = NULL; SDL_AtomicSet(&SDL_sentinel_pending, 0); + SDL_FlushEventMemory(0); + /* Clear disabled event state */ for (i = 0; i < SDL_arraysize(SDL_disabled_events); ++i) { SDL_free(SDL_disabled_events[i]); SDL_disabled_events[i] = NULL; } + if (SDL_event_memory_lock) { + SDL_DestroyMutex(SDL_event_memory_lock); + SDL_event_memory_lock = NULL; + } if (SDL_event_watchers_lock) { SDL_DestroyMutex(SDL_event_watchers_lock); SDL_event_watchers_lock = NULL; @@ -576,12 +672,19 @@ int SDL_StartEventLoop(void) return -1; } } + + if (SDL_event_memory_lock == NULL) { + SDL_event_memory_lock = SDL_CreateMutex(); + if (SDL_event_memory_lock == NULL) { + SDL_UnlockMutex(SDL_EventQ.lock); + return -1; + } + } #endif /* !SDL_THREADS_DISABLED */ /* Process most event types */ SDL_SetEventEnabled(SDL_EVENT_TEXT_INPUT, SDL_FALSE); SDL_SetEventEnabled(SDL_EVENT_TEXT_EDITING, SDL_FALSE); - SDL_SetEventEnabled(SDL_EVENT_SYSWM, SDL_FALSE); #if 0 /* Leave these events enabled so apps can respond to items being dragged onto them at startup */ SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, SDL_FALSE); SDL_SetEventEnabled(SDL_EVENT_DROP_TEXT, SDL_FALSE); @@ -618,12 +721,9 @@ static int SDL_AddEvent(SDL_Event *event) SDL_LogEvent(event); } - entry->event = *event; + SDL_copyp(&entry->event, event); if (event->type == SDL_EVENT_POLL_SENTINEL) { SDL_AtomicAdd(&SDL_sentinel_pending, 1); - } else if (event->type == SDL_EVENT_SYSWM) { - entry->msg = *event->syswm.msg; - entry->event.syswm.msg = &entry->msg; } if (SDL_EventQ.tail) { @@ -644,6 +744,8 @@ static int SDL_AddEvent(SDL_Event *event) SDL_EventQ.max_events_seen = final_count; } + ++SDL_last_event_id; + return 1; } @@ -723,43 +825,14 @@ static int SDL_PeepEventsInternal(SDL_Event *events, int numevents, SDL_eventact } } else { SDL_EventEntry *entry, *next; - SDL_SysWMEntry *wmmsg, *wmmsg_next; Uint32 type; - if (action == SDL_GETEVENT) { - /* Clean out any used wmmsg data - FIXME: Do we want to retain the data for some period of time? - */ - for (wmmsg = SDL_EventQ.wmmsg_used; wmmsg; wmmsg = wmmsg_next) { - wmmsg_next = wmmsg->next; - wmmsg->next = SDL_EventQ.wmmsg_free; - SDL_EventQ.wmmsg_free = wmmsg; - } - SDL_EventQ.wmmsg_used = NULL; - } - for (entry = SDL_EventQ.head; entry && (events == NULL || used < numevents); entry = next) { next = entry->next; type = entry->event.type; if (minType <= type && type <= maxType) { if (events) { - events[used] = entry->event; - if (entry->event.type == SDL_EVENT_SYSWM) { - /* We need to copy the wmmsg somewhere safe. - For now we'll guarantee it's valid at least until - the next call to SDL_PeepEvents() - */ - if (SDL_EventQ.wmmsg_free) { - wmmsg = SDL_EventQ.wmmsg_free; - SDL_EventQ.wmmsg_free = wmmsg->next; - } else { - wmmsg = (SDL_SysWMEntry *)SDL_malloc(sizeof(*wmmsg)); - } - wmmsg->msg = *entry->event.syswm.msg; - wmmsg->next = SDL_EventQ.wmmsg_used; - SDL_EventQ.wmmsg_used = wmmsg; - events[used].syswm.msg = &wmmsg->msg; - } + SDL_copyp(&events[used], &entry->event); if (action == SDL_GETEVENT) { SDL_CutEvent(entry); @@ -817,9 +890,6 @@ void SDL_FlushEvents(Uint32 minType, Uint32 maxType) { SDL_EventEntry *entry, *next; Uint32 type; - /* !!! FIXME: we need to manually SDL_free() the strings in TEXTINPUT and - drag'n'drop events if we're flushing them without passing them to the - app, but I don't know if this is the right place to do that. */ /* Make sure the events are current */ #if 0 @@ -853,6 +923,12 @@ static void SDL_PumpEventsInternal(SDL_bool push_sentinel) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); + /* Free old event memory */ + /*SDL_FlushEventMemory(SDL_last_event_id - SDL_MAX_QUEUED_EVENTS);*/ + if (SDL_AtomicGet(&SDL_EventQ.count) == 0) { + SDL_FlushEventMemory(SDL_last_event_id); + } + /* Release any keys held down from last frame */ SDL_ReleaseAutoReleaseKeys(); @@ -861,6 +937,10 @@ static void SDL_PumpEventsInternal(SDL_bool push_sentinel) _this->PumpEvents(_this); } +#ifndef SDL_AUDIO_DISABLED + SDL_UpdateAudio(); +#endif + #ifndef SDL_SENSOR_DISABLED /* Check for sensor state change */ if (SDL_update_sensors) { @@ -898,7 +978,7 @@ void SDL_PumpEvents(void) /* Public functions */ -int SDL_PollEvent(SDL_Event *event) +SDL_bool SDL_PollEvent(SDL_Event *event) { return SDL_WaitEventTimeoutNS(event, 0); } @@ -1009,14 +1089,14 @@ static SDL_Window *SDL_find_active_window(SDL_VideoDevice *_this) return NULL; } -int SDL_WaitEvent(SDL_Event *event) +SDL_bool SDL_WaitEvent(SDL_Event *event) { return SDL_WaitEventTimeoutNS(event, -1); } -int SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS) +SDL_bool SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS) { - Uint64 timeoutNS; + Sint64 timeoutNS; if (timeoutMS > 0) { timeoutNS = SDL_MS_TO_NS(timeoutMS); @@ -1026,54 +1106,14 @@ int SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS) return SDL_WaitEventTimeoutNS(event, timeoutNS); } -int SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) +SDL_bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); SDL_Window *wakeup_window; Uint64 start, expiration; - SDL_bool include_sentinel = (timeoutNS == 0) ? SDL_TRUE : SDL_FALSE; + SDL_bool include_sentinel = (timeoutNS == 0); int result; - /* If there isn't a poll sentinel event pending, pump events and add one */ - if (SDL_AtomicGet(&SDL_sentinel_pending) == 0) { - SDL_PumpEventsInternal(SDL_TRUE); - } - - /* First check for existing events */ - result = SDL_PeepEventsInternal(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST, include_sentinel); - if (result < 0) { - return 0; - } - if (include_sentinel) { - if (event) { - if (event->type == SDL_EVENT_POLL_SENTINEL) { - /* Reached the end of a poll cycle, and not willing to wait */ - return 0; - } - } else { - /* Need to peek the next event to check for sentinel */ - SDL_Event dummy; - - if (SDL_PeepEventsInternal(&dummy, 1, SDL_PEEKEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST, SDL_TRUE) && - dummy.type == SDL_EVENT_POLL_SENTINEL) { - SDL_PeepEventsInternal(&dummy, 1, SDL_GETEVENT, SDL_EVENT_POLL_SENTINEL, SDL_EVENT_POLL_SENTINEL, SDL_TRUE); - /* Reached the end of a poll cycle, and not willing to wait */ - return 0; - } - } - } - if (result == 0) { - if (timeoutNS == 0) { - /* No events available, and not willing to wait */ - return 0; - } - } else { - /* Has existing events */ - return 1; - } - /* We should have completely handled timeoutNS == 0 above */ - SDL_assert(timeoutNS != 0); - if (timeoutNS > 0) { start = SDL_GetTicksNS(); expiration = start + timeoutNS; @@ -1082,36 +1122,80 @@ int SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) expiration = 0; } + /* If there isn't a poll sentinel event pending, pump events and add one */ + if (SDL_AtomicGet(&SDL_sentinel_pending) == 0) { + SDL_PumpEventsInternal(SDL_TRUE); + } + + /* First check for existing events */ + result = SDL_PeepEventsInternal(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST, include_sentinel); + if (result < 0) { + return SDL_FALSE; + } + if (include_sentinel) { + if (event) { + if (event->type == SDL_EVENT_POLL_SENTINEL) { + /* Reached the end of a poll cycle, and not willing to wait */ + return SDL_FALSE; + } + } else { + /* Need to peek the next event to check for sentinel */ + SDL_Event dummy; + + if (SDL_PeepEventsInternal(&dummy, 1, SDL_PEEKEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST, SDL_TRUE) && + dummy.type == SDL_EVENT_POLL_SENTINEL) { + SDL_PeepEventsInternal(&dummy, 1, SDL_GETEVENT, SDL_EVENT_POLL_SENTINEL, SDL_EVENT_POLL_SENTINEL, SDL_TRUE); + /* Reached the end of a poll cycle, and not willing to wait */ + return SDL_FALSE; + } + } + } + if (result == 0) { + if (timeoutNS == 0) { + /* No events available, and not willing to wait */ + return SDL_FALSE; + } + } else { + /* Has existing events */ + return SDL_TRUE; + } + /* We should have completely handled timeoutNS == 0 above */ + SDL_assert(timeoutNS != 0); + if (_this && _this->WaitEventTimeout && _this->SendWakeupEvent && !SDL_events_need_polling()) { /* Look if a shown window is available to send the wakeup event. */ wakeup_window = SDL_find_active_window(_this); if (wakeup_window) { - int status = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, start, timeoutNS); - - /* There may be implementation-defined conditions where the backend cannot - reliably wait for the next event. If that happens, fall back to polling. */ - if (status >= 0) { - return status; + result = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, start, timeoutNS); + if (result > 0) { + return SDL_TRUE; + } else if (result == 0) { + return SDL_FALSE; + } else { + /* There may be implementation-defined conditions where the backend cannot + * reliably wait for the next event. If that happens, fall back to polling. + */ } } } for (;;) { SDL_PumpEventsInternal(SDL_TRUE); - switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST)) { - case -1: - return 0; - case 0: - if (timeoutNS > 0 && SDL_GetTicksNS() >= expiration) { - /* Timeout expired and no events */ - return 0; - } - SDL_Delay(1); - break; - default: - /* Has events */ - return 1; + + if (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) > 0) { + return SDL_TRUE; } + + Uint64 delay = SDL_MS_TO_NS(1); + if (timeoutNS > 0) { + Uint64 now = SDL_GetTicksNS(); + if (now >= expiration) { + /* Timeout expired and no events */ + return SDL_FALSE; + } + delay = SDL_min((expiration - now), delay); + } + SDL_DelayNS(delay); } } @@ -1195,8 +1279,10 @@ SDL_bool SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata) return event_ok.callback ? SDL_TRUE : SDL_FALSE; } -void SDL_AddEventWatch(SDL_EventFilter filter, void *userdata) +int SDL_AddEventWatch(SDL_EventFilter filter, void *userdata) { + int result = 0; + SDL_LockMutex(SDL_event_watchers_lock); { SDL_EventWatcher *event_watchers; @@ -1211,9 +1297,13 @@ void SDL_AddEventWatch(SDL_EventFilter filter, void *userdata) watcher->userdata = userdata; watcher->removed = SDL_FALSE; ++SDL_event_watchers_count; + } else { + result = -1; } } SDL_UnlockMutex(SDL_event_watchers_lock); + + return result; } void SDL_DelEventWatch(SDL_EventFilter filter, void *userdata) @@ -1270,7 +1360,14 @@ void SDL_SetEventEnabled(Uint32 type, SDL_bool enabled) if (enabled != current_state) { if (enabled) { +#ifdef _MSC_VER /* Visual Studio analyzer can't tell that SDL_disabled_events[hi] isn't NULL if enabled is true */ +#pragma warning(push) +#pragma warning(disable : 6011) +#endif SDL_disabled_events[hi]->bits[lo / 32] &= ~(1 << (lo & 31)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif /* Gamepad events depend on joystick events */ switch (type) { @@ -1354,23 +1451,6 @@ int SDL_SendAppEvent(SDL_EventType eventType) return posted; } -int SDL_SendSysWMEvent(SDL_SysWMmsg *message) -{ - int posted; - - posted = 0; - if (SDL_EventEnabled(SDL_EVENT_SYSWM)) { - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = SDL_EVENT_SYSWM; - event.common.timestamp = 0; - event.syswm.msg = message; - posted = (SDL_PushEvent(&event) > 0); - } - /* Update internal event state */ - return posted; -} - int SDL_SendKeymapChangedEvent(void) { return SDL_SendAppEvent(SDL_EVENT_KEYMAP_CHANGED); diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index 44c3eb8d..181583d7 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,7 +41,6 @@ extern void SDL_StopEventLoop(void); extern void SDL_QuitInterrupt(void); extern int SDL_SendAppEvent(SDL_EventType eventType); -extern int SDL_SendSysWMEvent(SDL_SysWMmsg *message); extern int SDL_SendKeymapChangedEvent(void); extern int SDL_SendLocaleChangedEvent(void); extern int SDL_SendSystemThemeChangedEvent(void); diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c index 59968b55..aba50237 100644 --- a/src/events/SDL_keyboard.c +++ b/src/events/SDL_keyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -778,7 +778,7 @@ int SDL_SetKeyboardFocus(SDL_Window *window) } } - if (keyboard->focus && window == NULL) { + if (keyboard->focus && !window) { /* We won't get anymore keyboard messages, so reset keyboard state */ SDL_ResetKeyboard(); } @@ -1063,7 +1063,7 @@ int SDL_SendKeyboardText(const char *text) int posted; /* Don't post text events for unprintable characters */ - if ((unsigned char)*text < ' ' || *text == 127) { + if (SDL_iscntrl((unsigned char)*text)) { return 0; } @@ -1071,19 +1071,18 @@ int SDL_SendKeyboardText(const char *text) posted = 0; if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { SDL_Event event; - size_t pos = 0, advance, length = SDL_strlen(text); - event.type = SDL_EVENT_TEXT_INPUT; event.common.timestamp = 0; event.text.windowID = keyboard->focus ? keyboard->focus->id : 0; - while (pos < length) { - advance = SDL_utf8strlcpy(event.text.text, text + pos, SDL_arraysize(event.text.text)); - if (!advance) { - break; - } - pos += advance; - posted |= (SDL_PushEvent(&event) > 0); + + size_t size = SDL_strlen(text) + 1; + event.text.text = (char *)SDL_AllocateEventMemory(size); + if (!event.text.text) { + return 0; } + SDL_memcpy(event.text.text, text, size); + + posted = (SDL_PushEvent(&event) > 0); } return posted; } @@ -1098,22 +1097,18 @@ int SDL_SendEditingText(const char *text, int start, int length) if (SDL_EventEnabled(SDL_EVENT_TEXT_EDITING)) { SDL_Event event; - if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE) && - SDL_strlen(text) >= SDL_arraysize(event.text.text)) { - event.type = SDL_EVENT_TEXT_EDITING_EXT; - event.common.timestamp = 0; - event.editExt.windowID = keyboard->focus ? keyboard->focus->id : 0; - event.editExt.text = text ? SDL_strdup(text) : NULL; - event.editExt.start = start; - event.editExt.length = length; - } else { - event.type = SDL_EVENT_TEXT_EDITING; - event.common.timestamp = 0; - event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0; - event.edit.start = start; - event.edit.length = length; - SDL_utf8strlcpy(event.edit.text, text, SDL_arraysize(event.edit.text)); + event.type = SDL_EVENT_TEXT_EDITING; + event.common.timestamp = 0; + event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0; + event.edit.start = start; + event.edit.length = length; + + size_t size = SDL_strlen(text) + 1; + event.edit.text = (char *)SDL_AllocateEventMemory(size); + if (!event.edit.text) { + return 0; } + SDL_memcpy(event.edit.text, text, size); posted = (SDL_PushEvent(&event) > 0); } @@ -1204,7 +1199,7 @@ const char *SDL_GetScancodeName(SDL_Scancode scancode) } name = SDL_scancode_names[scancode]; - if (name != NULL) { + if (name) { return name; } @@ -1215,7 +1210,7 @@ SDL_Scancode SDL_GetScancodeFromName(const char *name) { int i; - if (name == NULL || !*name) { + if (!name || !*name) { SDL_InvalidParamError("name"); return SDL_SCANCODE_UNKNOWN; } @@ -1275,7 +1270,7 @@ SDL_Keycode SDL_GetKeyFromName(const char *name) SDL_Keycode key; /* Check input */ - if (name == NULL) { + if (!name) { return SDLK_UNKNOWN; } diff --git a/src/events/SDL_keyboard_c.h b/src/events/SDL_keyboard_c.h index b8e76df6..98459b1d 100644 --- a/src/events/SDL_keyboard_c.h +++ b/src/events/SDL_keyboard_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_keysym_to_scancode.c b/src/events/SDL_keysym_to_scancode.c index a14cb60b..08e98094 100644 --- a/src/events/SDL_keysym_to_scancode.c +++ b/src/events/SDL_keysym_to_scancode.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_keysym_to_scancode_c.h b/src/events/SDL_keysym_to_scancode_c.h index 6e9e79e7..91611a5d 100644 --- a/src/events/SDL_keysym_to_scancode_c.h +++ b/src/events/SDL_keysym_to_scancode_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index ee9d4a25..2f14cacf 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,9 +22,11 @@ /* General mouse handling code for SDL */ -#include "SDL_events_c.h" #include "../SDL_hints_c.h" #include "../video/SDL_sysvideo.h" +#include "SDL_events_c.h" +#include "SDL_mouse_c.h" +#include "SDL_pen_c.h" #if defined(__WIN32__) || defined(__GDK__) #include "../core/windows/SDL_windows.h" // For GetDoubleClickTime() #endif @@ -221,6 +223,8 @@ void SDL_PostInitMouse(void) SDL_DestroySurface(surface); } } + + SDL_PenInit(); } void SDL_SetDefaultCursor(SDL_Cursor *cursor) @@ -351,17 +355,25 @@ void SDL_SetMouseFocus(SDL_Window *window) SDL_SetCursor(NULL); } +SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y) +{ + if (!window) { + return SDL_FALSE; + } + + if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + if (x < 0.0f || y < 0.0f || x >= (float)window->w || y >= (float)window->h) { + return SDL_FALSE; + } + } + return SDL_TRUE; +} + /* Check to see if we need to synthesize focus events */ static SDL_bool SDL_UpdateMouseFocus(SDL_Window *window, float x, float y, Uint32 buttonstate, SDL_bool send_mouse_motion) { SDL_Mouse *mouse = SDL_GetMouse(); - SDL_bool inWindow = SDL_TRUE; - - if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { - if (x < 0.0f || y < 0.0f || x >= (float)window->w || y >= (float)window->h) { - inWindow = SDL_FALSE; - } - } + SDL_bool inWindow = SDL_MousePositionInWindow(window, mouse->mouseID, x, y); if (!inWindow) { if (window == mouse->focus) { @@ -392,7 +404,7 @@ int SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI { if (window && !relative) { SDL_Mouse *mouse = SDL_GetMouse(); - if (!SDL_UpdateMouseFocus(window, x, y, GetButtonState(mouse, SDL_TRUE), (mouseID == SDL_TOUCH_MOUSEID) ? SDL_FALSE : SDL_TRUE)) { + if (!SDL_UpdateMouseFocus(window, x, y, GetButtonState(mouse, SDL_TRUE), (mouseID != SDL_TOUCH_MOUSEID))) { return 0; } } @@ -476,8 +488,8 @@ int SDL_SetMouseSystemScale(int num_values, const float *values) } v = (float *)SDL_realloc(mouse->system_scale_values, num_values * sizeof(*values)); - if (v == NULL) { - return SDL_OutOfMemory(); + if (!v) { + return -1; } SDL_memcpy(v, values, num_values * sizeof(*values)); @@ -656,7 +668,7 @@ static int SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_ event.motion.windowID = mouse->focus ? mouse->focus->id : 0; event.motion.which = mouseID; /* Set us pending (or clear during a normal mouse movement event) as having triggered */ - mouse->was_touch_mouse_events = (mouseID == SDL_TOUCH_MOUSEID) ? SDL_TRUE : SDL_FALSE; + mouse->was_touch_mouse_events = (mouseID == SDL_TOUCH_MOUSEID); event.motion.state = GetButtonState(mouse, SDL_TRUE); event.motion.x = mouse->x; event.motion.y = mouse->y; @@ -704,7 +716,7 @@ static SDL_MouseClickState *GetMouseClickState(SDL_Mouse *mouse, Uint8 button) if (button >= mouse->num_clickstates) { int i, count = button + 1; SDL_MouseClickState *clickstate = (SDL_MouseClickState *)SDL_realloc(mouse->clickstate, count * sizeof(*mouse->clickstate)); - if (clickstate == NULL) { + if (!clickstate) { return NULL; } mouse->clickstate = clickstate; @@ -726,7 +738,7 @@ static int SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_ SDL_MouseInputSource *source; source = GetMouseInputSource(mouse, mouseID); - if (source == NULL) { + if (!source) { return 0; } buttonstate = source->buttonstate; @@ -787,8 +799,8 @@ static int SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_ Uint64 now = SDL_GetTicks(); if (now >= (clickstate->last_timestamp + mouse->double_click_time) || - SDL_fabs(mouse->x - clickstate->last_x) > mouse->double_click_radius || - SDL_fabs(mouse->y - clickstate->last_y) > mouse->double_click_radius) { + SDL_fabs((double)mouse->x - clickstate->last_x) > mouse->double_click_radius || + SDL_fabs((double)mouse->y - clickstate->last_y) > mouse->double_click_radius) { clickstate->click_count = 0; } clickstate->last_timestamp = now; @@ -886,6 +898,7 @@ void SDL_QuitMouse(void) } SDL_SetRelativeMouseMode(SDL_FALSE); SDL_ShowCursor(); + SDL_PenQuit(); if (mouse->def_cursor) { SDL_SetDefaultCursor(NULL); @@ -982,10 +995,10 @@ Uint32 SDL_GetGlobalMouseState(float *x, float *y) float tmpx, tmpy; /* make sure these are never NULL for the backend implementations... */ - if (x == NULL) { + if (!x) { x = &tmpx; } - if (y == NULL) { + if (!y) { y = &tmpy; } @@ -1001,11 +1014,11 @@ void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, SDL_bool { SDL_Mouse *mouse = SDL_GetMouse(); - if (window == NULL) { + if (!window) { window = mouse->focus; } - if (window == NULL) { + if (!window) { return; } @@ -1225,13 +1238,19 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, const Uint32 black = 0xFF000000; const Uint32 white = 0xFFFFFFFF; const Uint32 transparent = 0x00000000; +#if defined(__WIN32__) + /* Only Windows backend supports inverted pixels in mono cursors. */ + const Uint32 inverted = 0x00FFFFFF; +#else + const Uint32 inverted = 0xFF000000; +#endif /* defined(__WIN32__) */ /* Make sure the width is a multiple of 8 */ w = ((w + 7) & ~7); /* Create the surface from a bitmap */ surface = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_ARGB8888); - if (surface == NULL) { + if (!surface) { return NULL; } for (y = 0; y < h; ++y) { @@ -1244,7 +1263,7 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, if (maskb & 0x80) { *pixel++ = (datab & 0x80) ? black : white; } else { - *pixel++ = (datab & 0x80) ? black : transparent; + *pixel++ = (datab & 0x80) ? inverted : transparent; } datab <<= 1; maskb <<= 1; @@ -1264,7 +1283,7 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) SDL_Surface *temp = NULL; SDL_Cursor *cursor; - if (surface == NULL) { + if (!surface) { SDL_InvalidParamError("surface"); return NULL; } @@ -1278,7 +1297,7 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) if (surface->format->format != SDL_PIXELFORMAT_ARGB8888) { temp = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888); - if (temp == NULL) { + if (!temp) { return NULL; } surface = temp; @@ -1288,9 +1307,6 @@ SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y) cursor = mouse->CreateCursor(surface, hot_x, hot_y); } else { cursor = SDL_calloc(1, sizeof(*cursor)); - if (!cursor) { - SDL_OutOfMemory(); - } } if (cursor) { cursor->next = mouse->cursors; @@ -1344,7 +1360,7 @@ int SDL_SetCursor(SDL_Cursor *cursor) break; } } - if (found == NULL) { + if (!found) { return SDL_SetError("Cursor not associated with the current mouse"); } } @@ -1373,7 +1389,7 @@ SDL_Cursor *SDL_GetCursor(void) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return NULL; } return mouse->cur_cursor; @@ -1383,7 +1399,7 @@ SDL_Cursor *SDL_GetDefaultCursor(void) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return NULL; } return mouse->def_cursor; @@ -1394,7 +1410,7 @@ void SDL_DestroyCursor(SDL_Cursor *cursor) SDL_Mouse *mouse = SDL_GetMouse(); SDL_Cursor *curr, *prev; - if (cursor == NULL) { + if (!cursor) { return; } diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index 0887b562..e024534f 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -163,6 +163,9 @@ extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, S extern void SDL_ResetMouse(void); #endif /* 0 */ +/* Check if mouse position is within window or captured by window */ +extern SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y); + /* Shutdown the mouse subsystem */ extern void SDL_QuitMouse(void); diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c new file mode 100644 index 00000000..5907eeaf --- /dev/null +++ b/src/events/SDL_pen.c @@ -0,0 +1,1099 @@ +/* + Simple DirectMedia Layer + 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 + 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" + +/* Pressure-sensitive pen handling code for SDL */ + +#include "../SDL_hints_c.h" +#include "SDL_events_c.h" +#include "SDL_pen_c.h" + +#define PEN_MOUSE_EMULATE 0 /* pen behaves like mouse */ +#define PEN_MOUSE_STATELESS 1 /* pen does not update mouse state */ +#define PEN_MOUSE_DISABLED 2 /* pen does not send mouse events */ + +/* flags that are not SDL_PEN_FLAG_ */ +#define PEN_FLAGS_CAPABILITIES (~(SDL_PEN_FLAG_NEW | SDL_PEN_FLAG_DETACHED | SDL_PEN_FLAG_STALE)) + +#define PEN_GET_PUBLIC_STATUS_MASK(pen) (((pen)->header.flags & (SDL_PEN_ERASER_MASK | SDL_PEN_DOWN_MASK))) + +static int pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* SDL_HINT_PEN_NOT_MOUSE */ + +static int pen_delay_mouse_button_mode = 1; /* SDL_HINT_PEN_DELAY_MOUSE_BUTTON */ + +#ifndef SDL_THREADS_DISABLED +static SDL_Mutex *SDL_pen_access_lock; +# define SDL_LOCK_PENS() SDL_LockMutex(SDL_pen_access_lock) +# define SDL_UNLOCK_PENS() SDL_UnlockMutex(SDL_pen_access_lock) +#else +# define SDL_LOCK_PENS() +# define SDL_UNLOCK_PENS() +#endif + +static struct +{ + SDL_Pen *pens; /* if "sorted" is SDL_TRUE: + sorted by: (is-attached, id): + - first all attached pens, in ascending ID order + - then all detached pens, in ascending ID order */ + size_t pens_allocated; /* # entries allocated to "pens" */ + size_t pens_known; /* <= pens_allocated; this includes detached pens */ + size_t pens_attached; /* <= pens_known; all attached pens are at the beginning of "pens" */ + SDL_bool sorted; /* This is SDL_FALSE in the period between SDL_PenGCMark() and SDL_PenGCSWeep() */ +} pen_handler; + +static SDL_PenID pen_invalid = { SDL_PEN_INVALID }; + +static SDL_GUID pen_guid_zero = { { 0 } }; + +#define SDL_LOAD_LOCK_PEN(penvar, instance_id, err_return) \ + SDL_Pen *penvar; \ + if (instance_id == SDL_PEN_INVALID) { \ + SDL_SetError("Invalid SDL_PenID"); \ + return (err_return); \ + } \ + SDL_LOCK_PENS();\ + penvar = SDL_GetPenPtr(instance_id); \ + if (!(penvar)) { \ + SDL_SetError("Stale SDL_PenID"); \ + return (err_return); \ + } + +static int SDL_GUIDCompare(SDL_GUID lhs, SDL_GUID rhs) +{ + return SDL_memcmp(lhs.data, rhs.data, sizeof(lhs.data)); +} + +static int SDLCALL pen_compare(const void *lhs, const void *rhs) +{ + int left_inactive = (((const SDL_Pen *)lhs)->header.flags & SDL_PEN_FLAG_DETACHED); + int right_inactive = (((const SDL_Pen *)rhs)->header.flags & SDL_PEN_FLAG_DETACHED); + if (left_inactive && !right_inactive) { + return 1; + } + if (!left_inactive && right_inactive) { + return -1; + } + return ((const SDL_Pen *)lhs)->header.id - ((const SDL_Pen *)rhs)->header.id; +} + +static int SDLCALL pen_header_compare(const void *lhs, const void *rhs) +{ + const struct SDL_Pen_header *l = lhs; + const struct SDL_Pen_header *r = rhs; + int l_detached = l->flags & SDL_PEN_FLAG_DETACHED; + int r_detached = r->flags & SDL_PEN_FLAG_DETACHED; + + if (l_detached != r_detached) { + if (l_detached) { + return -1; + } + return 1; + } + + return l->id - r->id; +} + +SDL_Pen *SDL_GetPenPtr(Uint32 instance_id) +{ + unsigned int i; + + if (!pen_handler.pens) { + return NULL; + } + + if (pen_handler.sorted) { + struct SDL_Pen_header key; + SDL_Pen *pen; + + SDL_zero(key); + key.id = instance_id; + + pen = SDL_bsearch(&key, pen_handler.pens, + pen_handler.pens_known, sizeof(SDL_Pen), + pen_header_compare); + if (pen) { + return pen; + } + /* If the pen is not active, fall through */ + } + + /* fall back to linear search */ + for (i = 0; i < pen_handler.pens_known; ++i) { + if (pen_handler.pens[i].header.id == instance_id) { + return &pen_handler.pens[i]; + } + } + return NULL; +} + +SDL_PenID *SDL_GetPens(int *count) +{ + int i; + int pens_nr = (int)pen_handler.pens_attached; + SDL_PenID *pens = SDL_calloc(pens_nr + 1, sizeof(SDL_PenID)); + if (!pens) { /* OOM */ + return pens; + } + + for (i = 0; i < pens_nr; ++i) { + pens[i] = pen_handler.pens[i].header.id; + } + + if (count) { + *count = pens_nr; + } + return pens; +} + +SDL_PenID SDL_GetPenFromGUID(SDL_GUID guid) +{ + unsigned int i; + /* Must do linear search */ + SDL_LOCK_PENS(); + for (i = 0; i < pen_handler.pens_known; ++i) { + SDL_Pen *pen = &pen_handler.pens[i]; + + if (0 == SDL_GUIDCompare(guid, pen->guid)) { + SDL_UNLOCK_PENS(); + return pen->header.id; + } + } + SDL_UNLOCK_PENS(); + return pen_invalid; +} + +SDL_bool SDL_PenConnected(SDL_PenID instance_id) +{ + SDL_Pen *pen; + SDL_bool result; + + if (instance_id == SDL_PEN_INVALID) { + return SDL_FALSE; + } + + SDL_LOCK_PENS(); + pen = SDL_GetPenPtr(instance_id); + if (!pen) { + SDL_UNLOCK_PENS(); + return SDL_FALSE; + } + result = (pen->header.flags & SDL_PEN_FLAG_DETACHED) ? SDL_FALSE : SDL_TRUE; + SDL_UNLOCK_PENS(); + return result; +} + +SDL_GUID SDL_GetPenGUID(SDL_PenID instance_id) +{ + SDL_GUID result; + SDL_LOAD_LOCK_PEN(pen, instance_id, pen_guid_zero); + result = pen->guid; + SDL_UNLOCK_PENS(); + return result; +} + +const char *SDL_GetPenName(SDL_PenID instance_id) +{ + const char *result; + SDL_LOAD_LOCK_PEN(pen, instance_id, NULL); + result = pen->name; /* Allocated separately from the pen table, so it is safe to hand to client code */ + SDL_UNLOCK_PENS(); + return result; +} + +SDL_PenSubtype SDL_GetPenType(SDL_PenID instance_id) +{ + SDL_PenSubtype result; + SDL_LOAD_LOCK_PEN(pen, instance_id, 0u); + result = pen->type; + SDL_UNLOCK_PENS(); + return result; +} + +Uint32 SDL_GetPenCapabilities(SDL_PenID instance_id, SDL_PenCapabilityInfo *info) +{ + Uint32 result; + SDL_LOAD_LOCK_PEN(pen, instance_id, 0u); + if (info) { + *info = pen->info; + } + result = pen->header.flags & PEN_FLAGS_CAPABILITIES; + SDL_UNLOCK_PENS(); + return result; +} + +Uint32 SDL_GetPenStatus(SDL_PenID instance_id, float *x, float *y, float *axes, size_t num_axes) +{ + Uint32 result; + SDL_LOAD_LOCK_PEN(pen, instance_id, 0u); + + if (x) { + *x = pen->last.x; + } + if (y) { + *y = pen->last.y; + } + if (axes && num_axes) { + size_t axes_to_copy = SDL_min(num_axes, SDL_PEN_NUM_AXES); + SDL_memcpy(axes, pen->last.axes, sizeof(float) * axes_to_copy); + } + result = pen->last.buttons | (pen->header.flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK | SDL_PEN_DOWN_MASK)); + SDL_UNLOCK_PENS(); + return result; +} + +/* Backend functionality */ + +/* Sort all pens. Only safe during SDL_LOCK_PENS. */ +static void pen_sort(void) +{ + SDL_qsort(pen_handler.pens, + pen_handler.pens_known, + sizeof(SDL_Pen), + pen_compare); + pen_handler.sorted = SDL_TRUE; +} + +SDL_Pen *SDL_PenModifyBegin(Uint32 instance_id) +{ + SDL_PenID id = { 0 }; + const size_t alloc_growth_constant = 1; /* Expect few pens */ + SDL_Pen *pen; + + id = instance_id; + + if (id == SDL_PEN_INVALID) { + SDL_SetError("Invalid SDL_PenID"); + return NULL; + } + + SDL_LOCK_PENS(); + pen = SDL_GetPenPtr(id); + + if (!pen) { + if (!pen_handler.pens || pen_handler.pens_known == pen_handler.pens_allocated) { + size_t pens_to_allocate = pen_handler.pens_allocated + alloc_growth_constant; + SDL_Pen *pens; + if (pen_handler.pens) { + pens = SDL_realloc(pen_handler.pens, sizeof(SDL_Pen) * pens_to_allocate); + SDL_memset(pens + pen_handler.pens_known, 0, + sizeof(SDL_Pen) * (pens_to_allocate - pen_handler.pens_allocated)); + } else { + pens = SDL_calloc(sizeof(SDL_Pen), pens_to_allocate); + } + pen_handler.pens = pens; + pen_handler.pens_allocated = pens_to_allocate; + } + pen = &pen_handler.pens[pen_handler.pens_known]; + pen_handler.pens_known += 1; + + /* Default pen initialisation */ + pen->header.id = id; + pen->header.flags = SDL_PEN_FLAG_NEW; + pen->info.num_buttons = SDL_PEN_INFO_UNKNOWN; + pen->info.max_tilt = SDL_PEN_INFO_UNKNOWN; + pen->type = SDL_PEN_TYPE_PEN; + pen->name = SDL_calloc(1, SDL_PEN_MAX_NAME); /* Never deallocated */ + } + return pen; +} + +void SDL_PenModifyAddCapabilities(SDL_Pen *pen, Uint32 capabilities) +{ + if (capabilities & SDL_PEN_ERASER_MASK) { + pen->header.flags &= ~SDL_PEN_INK_MASK; + } else if (capabilities & SDL_PEN_INK_MASK) { + pen->header.flags &= ~SDL_PEN_ERASER_MASK; + } + pen->header.flags |= (capabilities & PEN_FLAGS_CAPABILITIES); +} + +static void pen_hotplug_attach(SDL_Pen *pen) +{ + if (!pen->header.window) { + /* Attach to default window */ + const SDL_Mouse *mouse = SDL_GetMouse(); + SDL_SendPenWindowEvent(0, pen->header.id, mouse->focus); + } +} + +static void pen_hotplug_detach(SDL_Pen *pen) +{ + SDL_SendPenWindowEvent(0, pen->header.id, NULL); +} + +void SDL_PenModifyEnd(SDL_Pen *pen, SDL_bool attach) +{ + SDL_bool is_new = pen->header.flags & SDL_PEN_FLAG_NEW; + SDL_bool was_attached = !(pen->header.flags & (SDL_PEN_FLAG_DETACHED | SDL_PEN_FLAG_NEW)); + SDL_bool broke_sort_order = SDL_FALSE; + + if (pen->type == SDL_PEN_TYPE_NONE) { + /* remove pen */ + if (!is_new) { + if (!(pen->header.flags & SDL_PEN_FLAG_ERROR)) { + SDL_Log("Error: attempt to remove known pen %lu", (unsigned long) pen->header.id); + pen->header.flags |= SDL_PEN_FLAG_ERROR; + } + + /* Treat as detached pen of unknown type instead */ + pen->type = SDL_PEN_TYPE_PEN; + attach = SDL_FALSE; + } else { + pen_handler.pens_known -= 1; + SDL_memset(pen, 0, sizeof(SDL_Pen)); + SDL_UNLOCK_PENS(); + return; + } + } + + pen->header.flags &= ~(SDL_PEN_FLAG_NEW | SDL_PEN_FLAG_STALE | SDL_PEN_FLAG_DETACHED); + if (attach == SDL_FALSE) { + pen->header.flags |= SDL_PEN_FLAG_DETACHED; + if (was_attached) { + broke_sort_order = SDL_TRUE; + if (!is_new) { + pen_handler.pens_attached -= 1; + } + pen_hotplug_detach(pen); + } + } else if (!was_attached || is_new) { + broke_sort_order = SDL_TRUE; + pen_handler.pens_attached += 1; + pen_hotplug_attach(pen); + } + + if (is_new) { + /* default: name */ + if (!pen->name[0]) { + SDL_snprintf(pen->name, SDL_PEN_MAX_NAME, + "%s %lu", + pen->type == SDL_PEN_TYPE_ERASER ? "Eraser" : "Pen", + (unsigned long) pen->header.id); + } + + /* default: enabled axes */ + if (!(pen->header.flags & (SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK))) { + pen->info.max_tilt = 0; /* no tilt => no max_tilt */ + } + + /* sanity-check GUID */ + if (0 == SDL_GUIDCompare(pen->guid, pen_guid_zero)) { + SDL_Log("Error: pen %lu: has GUID 0", (unsigned long) pen->header.id); + } + + /* pen or eraser? */ + if (pen->type == SDL_PEN_TYPE_ERASER || pen->header.flags & SDL_PEN_ERASER_MASK) { + pen->header.flags = (pen->header.flags & ~SDL_PEN_INK_MASK) | SDL_PEN_ERASER_MASK; + pen->type = SDL_PEN_TYPE_ERASER; + } else { + pen->header.flags = (pen->header.flags & ~SDL_PEN_ERASER_MASK) | SDL_PEN_INK_MASK; + } + + broke_sort_order = SDL_TRUE; + } + if (broke_sort_order && pen_handler.sorted) { + /* Maintain sortedness invariant */ + pen_sort(); + } + SDL_UNLOCK_PENS(); +} + +void SDL_PenGCMark(void) +{ + unsigned int i; + SDL_LOCK_PENS(); + for (i = 0; i < pen_handler.pens_known; ++i) { + SDL_Pen *pen = &pen_handler.pens[i]; + pen->header.flags |= SDL_PEN_FLAG_STALE; + } + pen_handler.sorted = SDL_FALSE; + SDL_UNLOCK_PENS(); +} + +void SDL_PenGCSweep(void *context, void (*free_deviceinfo)(Uint32, void *, void *)) +{ + unsigned int i; + pen_handler.pens_attached = 0; + + SDL_LOCK_PENS(); + /* We don't actually free the SDL_Pen entries, so that we can still answer queries about + formerly active SDL_PenIDs later. */ + for (i = 0; i < pen_handler.pens_known; ++i) { + SDL_Pen *pen = &pen_handler.pens[i]; + + if (pen->header.flags & SDL_PEN_FLAG_STALE) { + pen->header.flags |= SDL_PEN_FLAG_DETACHED; + pen_hotplug_detach(pen); + if (pen->deviceinfo) { + free_deviceinfo(pen->header.id, pen->deviceinfo, context); + pen->deviceinfo = NULL; + } + } else { + pen_handler.pens_attached += 1; + } + + pen->header.flags &= ~SDL_PEN_FLAG_STALE; + } + pen_sort(); + /* We could test for changes in the above and send a hotplugging event here */ + SDL_UNLOCK_PENS(); +} + +static void pen_relative_coordinates(SDL_Window *window, SDL_bool window_relative, float *x, float *y) +{ + int win_x, win_y; + + if (window_relative) { + return; + } + SDL_GetWindowPosition(window, &win_x, &win_y); + *x -= win_x; + *y -= win_y; +} + +/* Initialises timestamp, windowID, which, pen_state, x, y, and axes */ +static void event_setup(const SDL_Pen *pen, const SDL_Window *window, Uint64 timestamp, const SDL_PenStatusInfo *status, SDL_Event *event) +{ + Uint16 last_buttons = pen->last.buttons; + + if (timestamp == 0) { + /* Generate timestamp ourselves, if needed, so that we report the same timestamp + for the pen event and for any emulated mouse event */ + timestamp = SDL_GetTicksNS(); + } + + /* This code assumes that all of the SDL_Pen*Event structures have the same layout for these fields. + * This is checked by testautomation_pen.c, pen_memoryLayout(). */ + event->pmotion.timestamp = timestamp; + event->pmotion.windowID = window ? window->id : 0; + event->pmotion.which = pen->header.id; + event->pmotion.pen_state = last_buttons | PEN_GET_PUBLIC_STATUS_MASK(pen); + event->pmotion.x = status->x; + event->pmotion.y = status->y; + SDL_memcpy(event->pmotion.axes, status->axes, SDL_PEN_NUM_AXES * sizeof(float)); +} + + +int SDL_SendPenMotion(Uint64 timestamp, + SDL_PenID instance_id, + SDL_bool window_relative, + const SDL_PenStatusInfo *status) +{ + const SDL_Mouse *mouse = SDL_GetMouse(); + int i; + SDL_Pen *pen = SDL_GetPenPtr(instance_id); + SDL_Event event; + SDL_bool posted = SDL_FALSE; + float x = status->x; + float y = status->y; + float last_x = pen->last.x; + float last_y = pen->last.y; + /* Suppress mouse updates for axis changes or sub-pixel movement: */ + SDL_bool send_mouse_update; + SDL_bool axes_changed = SDL_FALSE; + SDL_Window *window; + + if (!pen) { + return SDL_FALSE; + } + window = pen->header.window; + if (!window) { + return SDL_FALSE; + } + + pen_relative_coordinates(window, window_relative, &x, &y); + + /* Check if the event actually modifies any cached axis or location, update as neeed */ + if (x != last_x || y != last_y) { + axes_changed = SDL_TRUE; + pen->last.x = status->x; + pen->last.y = status->y; + } + for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { + if ((pen->header.flags & SDL_PEN_AXIS_CAPABILITY(i)) && (status->axes[i] != pen->last.axes[i])) { + axes_changed = SDL_TRUE; + pen->last.axes[i] = status->axes[i]; + } + } + if (!axes_changed) { + /* No-op event */ + return SDL_FALSE; + } + + send_mouse_update = (x != last_x) || (y != last_y); + + if (!(SDL_MousePositionInWindow(window, mouse->mouseID, x, y))) { + return SDL_FALSE; + } + + if (SDL_EventEnabled(SDL_EVENT_PEN_MOTION)) { + event_setup(pen, window, timestamp, status, &event); + event.pmotion.type = SDL_EVENT_PEN_MOTION; + + posted = SDL_PushEvent(&event) > 0; + + if (!posted) { + return SDL_FALSE; + } + } + + if (send_mouse_update) { + switch (pen_mouse_emulation_mode) { + case PEN_MOUSE_EMULATE: + return (SDL_SendMouseMotion(0, window, SDL_PEN_MOUSEID, 0, x, y)) || posted; + + case PEN_MOUSE_STATELESS: + /* Report mouse event but don't update mouse state */ + if (SDL_EventEnabled(SDL_EVENT_MOUSE_MOTION)) { + event.motion.windowID = window->id; + event.motion.timestamp = timestamp; + event.motion.which = SDL_PEN_MOUSEID; + event.motion.type = SDL_EVENT_MOUSE_MOTION; + event.motion.state = pen->last.buttons | PEN_GET_PUBLIC_STATUS_MASK(pen); + event.motion.x = x; + event.motion.y = y; + event.motion.xrel = last_x - x; + event.motion.yrel = last_y - y; + return (SDL_PushEvent(&event) > 0) || posted; + } + + default: + break; + } + } + return posted; +} + +int SDL_SendPenTipEvent(Uint64 timestamp, SDL_PenID instance_id, Uint8 state) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Pen *pen = SDL_GetPenPtr(instance_id); + SDL_Event event; + SDL_bool posted = SDL_FALSE; + SDL_PenStatusInfo *last = &pen->last; + Uint8 mouse_button = SDL_BUTTON_LEFT; + SDL_Window *window; + + if (!pen) { + return SDL_FALSE; + } + window = pen->header.window; + + if ((state == SDL_PRESSED) && !(window && SDL_MousePositionInWindow(window, mouse->mouseID, last->x, last->y))) { + return SDL_FALSE; + } + + if (state == SDL_PRESSED) { + event.pbutton.type = SDL_EVENT_PEN_DOWN; + pen->header.flags |= SDL_PEN_DOWN_MASK; + } else { + event.pbutton.type = SDL_EVENT_PEN_UP; + pen->header.flags &= ~SDL_PEN_DOWN_MASK; + } + + if (SDL_EventEnabled(event.ptip.type)) { + event_setup(pen, window, timestamp, &pen->last, &event); + + /* Used as eraser? Report eraser event, otherwise ink event */ + event.ptip.tip = (pen->header.flags & SDL_PEN_ERASER_MASK) ? SDL_PEN_TIP_ERASER : SDL_PEN_TIP_INK; + event.ptip.state = state == SDL_PRESSED ? SDL_PRESSED : SDL_RELEASED; + + posted = SDL_PushEvent(&event) > 0; + + if (!posted) { + return SDL_FALSE; + } + } + + /* Mouse emulation */ + if (pen_delay_mouse_button_mode) { + /* Send button events when pen touches / leaves surface */ + mouse_button = pen->last_mouse_button; + if (mouse_button == 0) { + mouse_button = SDL_BUTTON_LEFT; /* No current button? Instead report left mouse button */ + } + } + + switch (pen_mouse_emulation_mode) { + case PEN_MOUSE_EMULATE: + return (SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, state, mouse_button)) || posted; + + case PEN_MOUSE_STATELESS: + /* Report mouse event without updating mouse state */ + event.button.type = state == SDL_PRESSED ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; + if (SDL_EventEnabled(event.button.type)) { + event.button.windowID = window ? window->id : 0; + event.button.timestamp = timestamp; + event.button.which = SDL_PEN_MOUSEID; + + event.button.state = state; + event.button.button = mouse_button; + event.button.clicks = 1; + event.button.x = last->x; + event.button.y = last->y; + return (SDL_PushEvent(&event) > 0) || posted; + } + break; + + default: + break; + } + return posted; +} + +int SDL_SendPenButton(Uint64 timestamp, + SDL_PenID instance_id, + Uint8 state, Uint8 button) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Pen *pen = SDL_GetPenPtr(instance_id); + SDL_Event event; + SDL_bool posted = SDL_FALSE; + SDL_PenStatusInfo *last = &pen->last; + Uint8 mouse_button = button + 1; /* For mouse emulation, PEN_DOWN counts as button 1, so the first actual button is mouse button 2 */ + SDL_Window *window; + + if (!pen) { + return SDL_FALSE; + } + window = pen->header.window; + + if ((state == SDL_PRESSED) && !(window && SDL_MousePositionInWindow(window, mouse->mouseID, last->x, last->y))) { + return SDL_FALSE; + } + + if (state == SDL_PRESSED) { + event.pbutton.type = SDL_EVENT_PEN_BUTTON_DOWN; + pen->last.buttons |= (1 << (button - 1)); + } else { + event.pbutton.type = SDL_EVENT_PEN_BUTTON_UP; + pen->last.buttons &= ~(1 << (button - 1)); + } + + if (SDL_EventEnabled(event.pbutton.type)) { + event_setup(pen, window, timestamp, &pen->last, &event); + + event.pbutton.button = button; + event.pbutton.state = state == SDL_PRESSED ? SDL_PRESSED : SDL_RELEASED; + + posted = SDL_PushEvent(&event) > 0; + + if (!posted) { + return SDL_FALSE; + } + } + + /* Mouse emulation */ + if (pen_delay_mouse_button_mode) { + /* Can only change active mouse button while not touching the surface */ + if (!(pen->header.flags & SDL_PEN_DOWN_MASK)) { + if (state == SDL_RELEASED) { + pen->last_mouse_button = 0; + } else { + pen->last_mouse_button = mouse_button; + } + } + /* Defer emulation event */ + return SDL_TRUE; + } + + switch (pen_mouse_emulation_mode) { + case PEN_MOUSE_EMULATE: + return (SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, state, mouse_button)) || posted; + + case PEN_MOUSE_STATELESS: + /* Report mouse event without updating mouse state */ + event.button.type = state == SDL_PRESSED ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; + if (SDL_EventEnabled(event.button.type)) { + event.button.windowID = window ? window->id : 0; + event.button.timestamp = timestamp; + event.button.which = SDL_PEN_MOUSEID; + + event.button.state = state; + event.button.button = mouse_button; + event.button.clicks = 1; + event.button.x = last->x; + event.button.y = last->y; + return (SDL_PushEvent(&event) > 0) || posted; + } + break; + + default: + break; + } + return posted; +} + +int SDL_SendPenWindowEvent(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window) +{ + SDL_EventType event_id = window ? SDL_EVENT_WINDOW_PEN_ENTER : SDL_EVENT_WINDOW_PEN_LEAVE; + SDL_Event event = { 0 }; + SDL_Pen *pen = SDL_GetPenPtr(instance_id); + SDL_bool posted; + + if (!pen) { + return SDL_FALSE; + } + + if (pen->header.window == window) { /* (TRIVIAL-EVENT) Nothing new to report */ + return SDL_FALSE; + } + + if (timestamp == 0) { + /* Generate timestamp ourselves, if needed, so that we report the same timestamp + for the pen event and for any emulated mouse event */ + timestamp = SDL_GetTicksNS(); + } + + event.window.type = event_id; + /* If window == NULL and not (TRIVIAL-EVENT), then pen->header.window != NULL */ + event.window.timestamp = timestamp; + event.window.windowID = window ? window->id : pen->header.window->id; + event.window.data1 = instance_id; + posted = (SDL_PushEvent(&event) > 0); + + /* Update after assembling event */ + pen->header.window = window; + + switch (pen_mouse_emulation_mode) { + case PEN_MOUSE_EMULATE: + SDL_SetMouseFocus(event_id == SDL_EVENT_WINDOW_PEN_ENTER ? window : NULL); + break; + + case PEN_MOUSE_STATELESS: + /* Send event without going through mouse API */ + if (event_id == SDL_EVENT_WINDOW_PEN_ENTER) { + event.window.type = SDL_EVENT_WINDOW_MOUSE_ENTER; + } else { + event.window.type = SDL_EVENT_WINDOW_MOUSE_LEAVE; + } + posted = posted || (SDL_PushEvent(&event) > 0); + break; + + default: + break; + } + + return posted; +} + +static void SDLCALL SDL_PenUpdateHint(void *userdata, const char *name, const char *oldvalue, const char *newvalue) +{ + int *var = userdata; + if (newvalue == NULL) { + return; + } + + if (0 == SDL_strcmp("2", newvalue)) { + *var = 2; + } else if (0 == SDL_strcmp("1", newvalue)) { + *var = 1; + } else if (0 == SDL_strcmp("0", newvalue)) { + *var = 0; + } else { + SDL_Log("Unexpected value for pen hint: '%s'", newvalue); + } +} + +void SDL_PenInit(void) +{ +#if (SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM | SDL_PEN_DEBUG_UNKNOWN_WACOM) + printf("[pen] Debugging enabled: noid=%d nonwacom=%d unknown-wacom=%d\n", + SDL_PEN_DEBUG_NOID, SDL_PEN_DEBUG_NONWACOM, SDL_PEN_DEBUG_UNKNOWN_WACOM); +#endif + SDL_AddHintCallback(SDL_HINT_PEN_NOT_MOUSE, + SDL_PenUpdateHint, &pen_mouse_emulation_mode); + + SDL_AddHintCallback(SDL_HINT_PEN_DELAY_MOUSE_BUTTON, + SDL_PenUpdateHint, &pen_delay_mouse_button_mode); +#ifndef SDL_THREADS_DISABLED + SDL_pen_access_lock = SDL_CreateMutex(); +#endif +} + +void SDL_PenQuit(void) +{ + SDL_DelHintCallback(SDL_HINT_PEN_NOT_MOUSE, + SDL_PenUpdateHint, &pen_mouse_emulation_mode); + + SDL_DelHintCallback(SDL_HINT_PEN_DELAY_MOUSE_BUTTON, + SDL_PenUpdateHint, &pen_delay_mouse_button_mode); +#ifndef SDL_THREADS_DISABLED + SDL_DestroyMutex(SDL_pen_access_lock); + SDL_pen_access_lock = NULL; +#endif +} + +SDL_bool SDL_PenPerformHitTest(void) +{ + return pen_mouse_emulation_mode == PEN_MOUSE_EMULATE; +} + +/* Vendor-specific bits */ + +/* Default pen names */ +#define PEN_NAME_AES 0 +#define PEN_NAME_ART 1 +#define PEN_NAME_AIRBRUSH 2 +#define PEN_NAME_GENERAL 3 +#define PEN_NAME_GRIP 4 +#define PEN_NAME_INKING 5 +#define PEN_NAME_PRO 6 +#define PEN_NAME_PRO2 7 +#define PEN_NAME_PRO3 8 +#define PEN_NAME_PRO3D 9 +#define PEN_NAME_PRO_SLIM 10 +#define PEN_NAME_STROKE 11 + +#define PEN_NAME_LAST PEN_NAME_STROKE +#define PEN_NUM_NAMES (PEN_NAME_LAST + 1) + +static const char *default_pen_names[] = { + /* PEN_NAME_AES */ + "AES Pen", + /* PEN_NAME_ART */ + "Art Pen", + /* PEN_NAME_AIRBRUSH */ + "Airbrush Pen", + /* PEN_NAME_GENERAL */ + "Pen", + /* PEN_NAME_GRIP */ + "Grip Pen", + /* PEN_NAME_INKING */ + "Inking Pen", + /* PEN_NAME_PRO */ + "Pro Pen", + /* PEN_NAME_PRO2 */ + "Pro Pen 2", + /* PEN_NAME_PRO3 */ + "Pro Pen 3", + /* PEN_NAME_PRO3D */ + "Pro Pen 3D", + /* PEN_NAME_PRO_SLIM */ + "Pro Pen Slim", + /* PEN_NAME_STROKE */ + "Stroke Pen" +}; +SDL_COMPILE_TIME_ASSERT(default_pen_names, SDL_arraysize(default_pen_names) == PEN_NUM_NAMES); + +#define PEN_SPEC_TYPE_SHIFT 0 +#define PEN_SPEC_TYPE_MASK 0x0000000fu +#define PEN_SPEC_BUTTONS_SHIFT 4 +#define PEN_SPEC_BUTTONS_MASK 0x000000f0u +#define PEN_SPEC_NAME_SHIFT 8 +#define PEN_SPEC_NAME_MASK 0x00000f00u +#define PEN_SPEC_AXES_SHIFT 0 +#define PEN_SPEC_AXES_MASK 0xffff0000u + +#define PEN_WACOM_ID_INVALID 0xffffffffu /* Generic "invalid ID" marker */ + +#define PEN_SPEC(name, buttons, type, axes) (0 | (PEN_SPEC_NAME_MASK & ((name) << PEN_SPEC_NAME_SHIFT)) | (PEN_SPEC_BUTTONS_MASK & ((buttons) << PEN_SPEC_BUTTONS_SHIFT)) | (PEN_SPEC_TYPE_MASK & ((type) << PEN_SPEC_TYPE_SHIFT)) | (PEN_SPEC_AXES_MASK & ((axes) << PEN_SPEC_AXES_SHIFT))) + +/* Returns a suitable pen name string from default_pen_names on success, otherwise NULL. */ +static const char *pen_wacom_identify_tool(Uint32 requested_wacom_id, int *num_buttons, int *tool_type, int *axes) +{ + int i; + + /* List of known Wacom pens, extracted from libwacom.stylus and wacom_wac.c in the Linux kernel. + Could be complemented by dlopen()ing libwacom, in the future (if new pens get added). */ + struct + { + /* Compress properties to 8 bytes per device in order to keep memory cost well below 1k. + Could be compressed further with more complex code. */ + Uint32 wacom_id; /* Must be != PEN_WACOM_ID_INVALID */ + Uint32 properties; + } tools[] = { + { 0x0001, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0011, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0019, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0021, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0031, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0039, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0049, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0071, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0200, PEN_SPEC(PEN_NAME_PRO3, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0221, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0231, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0271, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0421, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0431, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0621, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0631, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, + { 0x0801, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0802, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0804, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, + { 0x080a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x080c, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0812, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0813, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x081b, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0822, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0823, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x082a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x082b, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0832, PEN_SPEC(PEN_NAME_STROKE, 0, SDL_PEN_TYPE_BRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0842, PEN_SPEC(PEN_NAME_PRO2, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x084a, PEN_SPEC(PEN_NAME_PRO2, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0852, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x085a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0862, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0885, PEN_SPEC(PEN_NAME_ART, 0, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, + { 0x08e2, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0902, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, + { 0x090a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0912, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, + { 0x0913, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x091a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x091b, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x0d12, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, + { 0x0d1a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x8051, PEN_SPEC(PEN_NAME_AES, 0, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x805b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x806b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x807b, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x826b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x846b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, + { 0x2802, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x4200, PEN_SPEC(PEN_NAME_PRO3, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x4802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x480a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x8842, PEN_SPEC(PEN_NAME_PRO3D, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x10802, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x10804, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, + { 0x1080a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x1080c, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x10842, PEN_SPEC(PEN_NAME_PRO_SLIM, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x1084a, PEN_SPEC(PEN_NAME_PRO_SLIM, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x10902, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, + { 0x1090a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x12802, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x14802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x1480a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x16802, PEN_SPEC(PEN_NAME_PRO, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x1680a, PEN_SPEC(PEN_NAME_PRO, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x18802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0x1880a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, + { 0, 0 } + }; + + /* The list of pens is sorted, so we could do binary search, but this call should be pretty rare. */ + for (i = 0; tools[i].wacom_id; ++i) { + if (tools[i].wacom_id == requested_wacom_id) { + Uint32 properties = tools[i].properties; + int name_index = (properties & PEN_SPEC_NAME_MASK) >> PEN_SPEC_NAME_SHIFT; + + *num_buttons = (properties & PEN_SPEC_BUTTONS_MASK) >> PEN_SPEC_BUTTONS_SHIFT; + *tool_type = (properties & PEN_SPEC_TYPE_MASK) >> PEN_SPEC_TYPE_SHIFT; + *axes = (properties & PEN_SPEC_AXES_MASK) >> PEN_SPEC_AXES_SHIFT; + + return default_pen_names[name_index]; + } + } + return NULL; +} + +void SDL_PenUpdateGUIDForGeneric(SDL_GUID *guid, Uint32 upper, Uint32 lower) +{ + int i; + + for (i = 0; i < 4; ++i) { + guid->data[8 + i] = (lower >> (i * 8)) & 0xff; + } + + for (i = 0; i < 4; ++i) { + guid->data[12 + i] = (upper >> (i * 8)) & 0xff; + } +} + +void SDL_PenUpdateGUIDForType(SDL_GUID *guid, SDL_PenSubtype pentype) +{ + guid->data[7] = pentype; +} + +void SDL_PenUpdateGUIDForWacom(SDL_GUID *guid, Uint32 wacom_devicetype_id, Uint32 wacom_serial_id) +{ + int i; + + for (i = 0; i < 4; ++i) { + guid->data[0 + i] = (wacom_serial_id >> (i * 8)) & 0xff; + } + + for (i = 0; i < 3; ++i) { /* 24 bit values */ + guid->data[4 + i] = (wacom_devicetype_id >> (i * 8)) & 0xff; + } +} + +int SDL_PenModifyForWacomID(SDL_Pen *pen, Uint32 wacom_devicetype_id, Uint32 *axis_flags) +{ + const char *name = NULL; + int num_buttons = 0; + int tool_type = 0; + int axes = 0; + +#if SDL_PEN_DEBUG_UNKNOWN_WACOM + wacom_devicetype_id = PEN_WACOM_ID_INVALID; /* force detection to fail */ +#endif + +#if defined(__LINUX__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + /* According to Ping Cheng, the curent Wacom for Linux maintainer, device IDs on Linux + squeeze a "0" nibble after the 3rd (least significant) nibble. + This may also affect the *BSDs, so they are heuristically included here. + On those platforms, we first try the "patched" ID: */ + if (0 == (wacom_devicetype_id & 0x0000f000u)) { + const Uint32 lower_mask = 0xfffu; + int wacom_devicetype_alt_id = ((wacom_devicetype_id & ~lower_mask) >> 4) | (wacom_devicetype_id & lower_mask); + + name = pen_wacom_identify_tool(wacom_devicetype_alt_id, &num_buttons, &tool_type, &axes); + if (name) { + wacom_devicetype_id = wacom_devicetype_alt_id; + } + } +#endif + if (name == NULL) { + name = pen_wacom_identify_tool(wacom_devicetype_id, &num_buttons, &tool_type, &axes); + } + + if (!name) { + *axis_flags = 0; + return SDL_FALSE; + } + + *axis_flags = axes; + + /* Override defaults */ + if (pen->info.num_buttons == SDL_PEN_INFO_UNKNOWN) { + pen->info.num_buttons = (Sint8)SDL_min(num_buttons, SDL_MAX_SINT8); + } + if (pen->type == SDL_PEN_TYPE_PEN) { + pen->type = (SDL_PenSubtype)tool_type; + } + if (pen->info.max_tilt == SDL_PEN_INFO_UNKNOWN) { + /* supposedly: 64 degrees left, 63 right, as reported by the Wacom X11 driver */ + pen->info.max_tilt = 64.0f; + } + pen->info.wacom_id = wacom_devicetype_id; + if (0 == pen->name[0]) { + SDL_snprintf(pen->name, SDL_PEN_MAX_NAME, + "Wacom %s%s", name, (tool_type == SDL_PEN_TYPE_ERASER) ? " Eraser" : ""); + } + return SDL_TRUE; +} diff --git a/src/events/SDL_pen_c.h b/src/events/SDL_pen_c.h new file mode 100644 index 00000000..fef9c529 --- /dev/null +++ b/src/events/SDL_pen_c.h @@ -0,0 +1,341 @@ +/* + Simple DirectMedia Layer + 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 + 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_pen_c_h_ +#define SDL_pen_c_h_ + +#include "../../include/SDL3/SDL_pen.h" +#include "SDL_mouse_c.h" + +/* For testing alternate code paths: */ +#define SDL_PEN_DEBUG_NOID 0 /* Pretend that pen device does not supply ID / ID is some default value \ + affects: SDL_x11pen.c \ + SDL_waylandevents.c */ +#define SDL_PEN_DEBUG_NONWACOM 0 /* Pretend that no attached device is a Wacom device \ + affects: SDL_x11pen.c \ + SDL_waylandevents.c */ +#define SDL_PEN_DEBUG_UNKNOWN_WACOM 0 /* Pretend that any attached Wacom device is of an unknown make \ + affects: SDL_PenModifyFromWacomID() */ +#define SDL_PEN_DEBUG_NOSERIAL_WACOM 0 /* Pretend that any attached Wacom device has serial number 0 \ + affects: SDL_x11pen.c \ + SDL_waylandevents.c */ + +#define SDL_PEN_TYPE_NONE 0 /**< Pen type for non-pens (use to cancel pen registration) */ + +#define SDL_PEN_MAX_NAME 64 + +#define SDL_PEN_FLAG_ERROR (1ul << 28) /* Printed an internal API usage error about this pen (used to prevent spamming) */ +#define SDL_PEN_FLAG_NEW (1ul << 29) /* Pen was registered in most recent call to SDL_PenRegisterBegin() */ +#define SDL_PEN_FLAG_DETACHED (1ul << 30) /* Detached (not re-registered before last SDL_PenGCSweep()) */ +#define SDL_PEN_FLAG_STALE (1ul << 31) /* Not re-registered since last SDL_PenGCMark() */ + +typedef struct SDL_PenStatusInfo +{ + float x, y; + float axes[SDL_PEN_NUM_AXES]; + Uint16 buttons; /* SDL_BUTTON(1) | SDL_BUTTON(2) | ... | SDL_PEN_DOWN_MASK */ +} SDL_PenStatusInfo; + +/** + * Internal (backend driver-independent) pen representation + * + * Implementation-specific backend drivers may read and write most of this structure, and + * are expected to initialise parts of it when registering a new pen. They must not write + * to the "header" section. + */ +typedef struct SDL_Pen +{ + /* Backend driver MUST NOT not write to: */ + struct SDL_Pen_header + { + SDL_PenID id; /* id determines sort order unless SDL_PEN_FLAG_DETACHED is set */ + Uint32 flags; /* SDL_PEN_FLAG_* | SDK_PEN_DOWN_MASK | SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_* */ + SDL_Window *window; /* Current SDL window for this pen, or NULL */ + } header; + + SDL_PenStatusInfo last; /* Last reported status, normally read-only for backend */ + + /* Backend: MUST initialise this block when pen is first registered: */ + SDL_GUID guid; /* GUID, MUST be set by backend. + MUST be unique (no other pen ID with same GUID). + SHOULD be persistent across sessions. */ + + /* Backend: SHOULD initialise this block when pen is first registered if it can + Otherwise: Set to sane default values during SDL_PenModifyEnd() */ + SDL_PenCapabilityInfo info; /* Detail information about the pen (buttons, tilt) */ + SDL_PenSubtype type; + Uint8 last_mouse_button; /* For mouse button emulation: last emulated button */ + char *name; /* Preallocated; set via SDL_strlcpy(pen->name, src, SDL_PEN_MAX_NAME) */ + /* We hand this exact pointer to client code, so it must not be modified after + creation. */ + + void *deviceinfo; /* implementation-specific information */ +} SDL_Pen; + +/* ---- API for backend driver only ---- */ + +/** + * (Only for backend driver) Look up a pen by pen ID + * + * \param instance_id A Uint32 pen identifier (driver-dependent meaning). Must not be 0 = SDL_PEN_INVALID. + * The same ID is exposed to clients as SDL_PenID. + * + * The pen pointer is only valid until the next call to SDL_PenModifyEnd() or SDL_PenGCSweep() + * + * \return pen, if it exists, or NULL + */ +extern SDL_Pen *SDL_GetPenPtr(Uint32 instance_id); + +/** + * (Only for backend driver) Start registering a new pen or updating an existing pen. + * + * Acquires the pen mutex, which is held until the next call to SDL_PenModifyEnd() . + * + * If the PenID already exists, returns the existing entry. Otherwise initialise fresh SDL_Pen. + * For new pens, sets SDL_PEN_FLAG_NEW. + * + * Usage: + * - SDL_PenModifyStart() + * - update pen object, in any order: + * - SDL_PenModifyAddCapabilities() + * - pen->guid (MUST be set for new pens, e.g. via ::SDL_PenUpdateGUIDForGeneric and related operations) + * - pen->info.num_buttons + * - pen->info.max_tilt + * - pen->type + * - pen->name + * - pen->deviceinfo (backend-specific) + * - SDL_PenModifyEnd() + * + * For new pens, sets defaults for: + * - num_buttons (SDL_PEN_INFO_UNKNOWN) + * - max_tilt (SDL_PEN_INFO_UNKNOWN) + * - pen_type (SDL_PEN_TYPE_PEN) + * - Zeroes all other (non-header) fields + * + * \param instance_id Pen ID to allocate (must not be 0 = SDL_PEN_ID_INVALID) + * \returns SDL_Pen pointer; only valid until the call to SDL_PenModifyEnd() + */ +extern SDL_Pen *SDL_PenModifyBegin(Uint32 instance_id); + +/** + * (Only for backend driver) Add capabilities to a pen (cf. SDL_PenModifyBegin()). + * + * Adds capabilities to a pen obtained via SDL_PenModifyBegin(). Can be called more than once. + * + * \param pen The pen to update + * \param capabilities Capabilities flags, out of: SDL_PEN_AXIS_*, SDL_PEN_ERASER_MASK, SDL_PEN_INK_MASK + * Setting SDL_PEN_ERASER_MASK will clear SDL_PEN_INK_MASK, and vice versa. + */ +extern void SDL_PenModifyAddCapabilities(SDL_Pen *pen, Uint32 capabilities); + +/** + * Set up a pen structure for a Wacom device. + * + * Some backends (e.g., XInput2, Wayland) can only partially identify the capabilities of a given + * pen but can identify Wacom pens and obtain their Wacom-specific device type identifiers. + * This function partly automates device setup in those cases. + * + * This function does NOT set up the pen's GUID. Use ::SD_PenModifyGUIDForWacom instead. + * + * This function does NOT call SDL_PenModifyAddCapabilities() ifself, since some backends may + * not have access to all pen axes (e.g., Xinput2). + * + * \param pen The pen to initialise + * \param wacom_devicetype_id The Wacom-specific device type identifier + * \param[out] axis_flags The set of physically supported axes for this pen, suitable for passing to + * SDL_PenModifyAddCapabilities() + * + * \returns SDL_TRUE if the device ID could be identified, otherwise SDL_FALSE + */ +extern int SDL_PenModifyForWacomID(SDL_Pen *pen, Uint32 wacom_devicetype_id, Uint32 *axis_flags); + +/** + * Updates a GUID for a generic pen device. + * + * Assumes that the GUID has been pre-initialised (typically to 0). + * Idempotent, and commutative with ::SDL_PenUpdateGUIDForWacom and ::SDL_PenUpdateGUIDForType + * + * \param[out] guid The GUID to update + * \param upper Upper half of the device ID (assume lower entropy than "lower"; pass 0 if not available) + * \param lower Lower half of the device ID (assume higher entropy than "upper") + */ +extern void SDL_PenUpdateGUIDForGeneric(SDL_GUID *guid, Uint32 upper, Uint32 lower); + +/** + * Updates a GUID based on a pen type + * + * Assumes that the GUID has been pre-initialised (typically to 0). + * Idempotent, and commutative with ::SDL_PenUpdateGUIDForWacom and ::SDL_PenUpdateGUIDForGeneric + * + * \param[out] guid The GUID to update + * \param pentype The pen type to insert + */ +extern void SDL_PenUpdateGUIDForType(SDL_GUID *guid, SDL_PenSubtype pentype); + +/** + * Updates a GUID for a Wacom pen device. + * + * Assumes that the GUID has been pre-initialised (typically to 0). + * Idempotent, and commutative with ::SDL_PenUpdateGUIDForType and ::SDL_PenUpdateGUIDForGeneric + * + * This update is identical to the one written by ::SDL_PenModifyFromWacomID . + * + * \param[out] guid The GUID to update + * \param wacom_devicetype_id The Wacom-specific device type identifier + * \param wacom_serial_id The Wacom-specific serial number + */ +extern void SDL_PenUpdateGUIDForWacom(SDL_GUID *guid, Uint32 wacom_devicetype_id, Uint32 wacom_serial_id); + +/** + * (Only for backend driver) Finish updating a pen. + * + * Releases the pen mutex acquired by SDL_PenModifyBegin() . + * + * If pen->type == SDL_PEN_TYPE_NONE, removes the pen entirely (only + * for new pens). This allows backends to start registering a + * potential pen device and to abort if the device turns out to not be + * a pen. + * + * For new pens, this call will also set the following: + * - name (default name, if not yet set) + * + * \param pen The pen to register. That pointer is no longer valid after this call. + * \param attach Whether the pen should be attached (SDL_TRUE) or detached (SDL_FALSE). + * + * If the pen is detached or removed, it is the caller's responsibility to free + * and null "deviceinfo". + */ +extern void SDL_PenModifyEnd(SDL_Pen *pen, SDL_bool attach); + +/** + * (Only for backend driver) Mark all current pens for garbage collection. + * + * Must not be called while the pen mutex is held (by SDL_PenModifyBegin() ). + * + * SDL_PenGCMark() / SDL_PenGCSweep() provide a simple mechanism for + * detaching all known pens that are not discoverable. This allows + * backends to use the same code for pen discovery and for + * hotplugging: + * + * - SDL_PenGCMark() and start backend-specific discovery + * - for each discovered pen: SDL_PenModifyBegin() + SDL_PenModifyEnd() (this will retain existing state) + * - SDL_PenGCSweep() (will now detach all pens that were not re-registered). + */ +extern void SDL_PenGCMark(void); + +/** + * (Only for backend driver) Detach pens that haven't been reported attached since the last call to SDL_PenGCMark(). + * + * Must not be called while the pen mutex is held (by SDL_PenModifyBegin() ). + * + * See SDL_PenGCMark() for details. + * + * \param context Extra parameter to pass through to "free_deviceinfo" + * \param free_deviceinfo Operation to call on any non-NULL "backend.deviceinfo". + * + * \sa SDL_PenGCMark() + */ +extern void SDL_PenGCSweep(void *context, void (*free_deviceinfo)(Uint32 instance_id, void *deviceinfo, void *context)); + +/** + * (Only for backend driver) Send a pen motion event. + * + * Suppresses pen motion events that do not change the current pen state. + * May also send a mouse motion event, if mouse emulation is enabled and the pen position has + * changed sufficiently for the motion to be visible to mouse event listeners. + * + * \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() . + * While 0 is safe to report, your backends may be able to report more precise + * timing information. + * Keep in mind that you should never report timestamps that are greater than + * SDL_GetTicksNS() . In particular, SDL_GetTicksNS() reports nanoseconds since the start + * of the SDL session, and your backend may use a different starting point as "timestamp zero". + * \param instance_id Pen + * \param window_relative Coordinates are already window-relative + * \param status Coordinates and axes (buttons are ignored) + * + * \returns SDL_TRUE if at least one event was sent + */ +extern int SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_bool window_relative, const SDL_PenStatusInfo *status); + +/** + * (Only for backend driver) Send a pen button event + * + * \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() . + * See SDL_SendPenMotion() for a discussion about how to handle timestamps. + * \param instance_id Pen + * \param state SDL_PRESSED or SDL_RELEASED + * \param button Button number: 1 (first physical button) etc. + * + * \returns SDL_TRUE if at least one event was sent + */ +extern int SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, Uint8 state, Uint8 button); + +/** + * (Only for backend driver) Send a pen tip event (touching or no longer touching the surface) + * + * Note: the backend should perform hit testing on window decoration elements to allow the pen + * to e.g. resize/move the window, just as for mouse events, unless ::SDL_SendPenTipEvent is false. + * + * \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() . + * See SDL_SendPenMotion() for a discussion about how to handle timestamps. + * \param instance_id Pen + * \param state SDL_PRESSED (for PEN_DOWN) or SDL_RELEASED (for PEN_UP) + * + * \returns SDL_TRUE if at least one event was sent + */ +extern int SDL_SendPenTipEvent(Uint64 timestamp, SDL_PenID instance_id, Uint8 state); + +/** + * (Only for backend driver) Check if a PEN_DOWN event should perform hit box testing. + * + * \returns SDL_TRUE if and only if the backend should perform hit testing + */ +extern SDL_bool SDL_PenPerformHitTest(void); + +/** + * (Only for backend driver) Send a pen window event. + * + * Tracks when a pen has entered/left a window. + * Don't call this when reporting new pens or removing known pens; those cases are handled automatically. + * + * \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() . + * See SDL_SendPenMotion() for a discussion about how to handle timestamps. + * \param instance_id Pen + * \param window Window to enter, or NULL to exit + */ +extern int SDL_SendPenWindowEvent(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window); + +/** + * Initialises the pen subsystem. + */ +extern void SDL_PenInit(void); + +/** + * De-initialises the pen subsystem. + */ +extern void SDL_PenQuit(void); + +#endif /* SDL_pen_c_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/events/SDL_quit.c b/src/events/SDL_quit.c index d103c313..e4928d37 100644 --- a/src/events/SDL_quit.c +++ b/src/events/SDL_quit.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_scancode_tables.c b/src/events/SDL_scancode_tables.c index 78c6304c..c09d9797 100644 --- a/src/events/SDL_scancode_tables.c +++ b/src/events/SDL_scancode_tables.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_scancode_tables_c.h b/src/events/SDL_scancode_tables_c.h index 7a39e8bc..655f627c 100644 --- a/src/events/SDL_scancode_tables_c.h +++ b/src/events/SDL_scancode_tables_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/SDL_touch.c b/src/events/SDL_touch.c index d989a25e..071fdff4 100644 --- a/src/events/SDL_touch.c +++ b/src/events/SDL_touch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -44,27 +44,30 @@ int SDL_InitTouch(void) return 0; } -int SDL_GetNumTouchDevices(void) +SDL_bool SDL_TouchDevicesAvailable(void) { - return SDL_num_touch; + return SDL_num_touch > 0; } -SDL_TouchID SDL_GetTouchDevice(int index) +SDL_TouchID *SDL_GetTouchDevices(int *count) { - if (index < 0 || index >= SDL_num_touch) { - SDL_SetError("Unknown touch device index %d", index); - return 0; + if (count) { + *count = 0; } - return SDL_touchDevices[index]->id; -} -const char *SDL_GetTouchName(int index) -{ - if (index < 0 || index >= SDL_num_touch) { - SDL_SetError("Unknown touch device"); - return NULL; + const int total = SDL_num_touch; + SDL_TouchID *retval = (SDL_TouchID *) SDL_malloc(sizeof (SDL_TouchID) * (total + 1)); + if (retval) { + for (int i = 0; i < total; i++) { + retval[i] = SDL_touchDevices[i]->id; + } + retval[total] = 0; + if (count) { + *count = SDL_num_touch; + } } - return SDL_touchDevices[index]->name; + + return retval; } static int SDL_GetTouchIndex(SDL_TouchID id) @@ -96,13 +99,16 @@ SDL_Touch *SDL_GetTouch(SDL_TouchID id) return SDL_touchDevices[index]; } +const char *SDL_GetTouchDeviceName(SDL_TouchID id) +{ + SDL_Touch *touch = SDL_GetTouch(id); + return touch ? touch->name : NULL; +} + SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID id) { SDL_Touch *touch = SDL_GetTouch(id); - if (touch) { - return touch->type; - } - return SDL_TOUCH_DEVICE_INVALID; + return touch ? touch->type : SDL_TOUCH_DEVICE_INVALID; } static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid) @@ -137,7 +143,7 @@ int SDL_GetNumTouchFingers(SDL_TouchID touchID) SDL_Finger *SDL_GetTouchFinger(SDL_TouchID touchID, int index) { SDL_Touch *touch = SDL_GetTouch(touchID); - if (touch == NULL) { + if (!touch) { return NULL; } if (index < 0 || index >= touch->num_fingers) { @@ -160,8 +166,8 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name /* Add the touch to the list of touch */ touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices, (SDL_num_touch + 1) * sizeof(*touchDevices)); - if (touchDevices == NULL) { - return SDL_OutOfMemory(); + if (!touchDevices) { + return -1; } SDL_touchDevices = touchDevices; @@ -169,7 +175,7 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index])); if (!SDL_touchDevices[index]) { - return SDL_OutOfMemory(); + return -1; } /* Added touch to list */ @@ -193,13 +199,13 @@ static int SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float if (touch->num_fingers == touch->max_fingers) { SDL_Finger **new_fingers; new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers + 1) * sizeof(*touch->fingers)); - if (new_fingers == NULL) { - return SDL_OutOfMemory(); + if (!new_fingers) { + return -1; } touch->fingers = new_fingers; touch->fingers[touch->max_fingers] = (SDL_Finger *)SDL_malloc(sizeof(*finger)); if (!touch->fingers[touch->max_fingers]) { - return SDL_OutOfMemory(); + return -1; } touch->max_fingers++; } @@ -235,7 +241,7 @@ int SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_W SDL_Mouse *mouse; SDL_Touch *touch = SDL_GetTouch(id); - if (touch == NULL) { + if (!touch) { return -1; } @@ -329,7 +335,7 @@ int SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_W posted = (SDL_PushEvent(&event) > 0); } } else { - if (finger == NULL) { + if (!finger) { /* This finger is already up */ return 0; } @@ -366,7 +372,7 @@ int SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, float xrel, yrel, prel; touch = SDL_GetTouch(id); - if (touch == NULL) { + if (!touch) { return -1; } @@ -409,7 +415,7 @@ int SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, } finger = SDL_GetFinger(touch, fingerid); - if (finger == NULL) { + if (!finger) { return SDL_SendTouch(timestamp, id, fingerid, window, SDL_TRUE, x, y, pressure); } @@ -461,7 +467,7 @@ void SDL_DelTouch(SDL_TouchID id) index = SDL_GetTouchIndex(id); touch = SDL_GetTouch(id); - if (touch == NULL) { + if (!touch) { return; } diff --git a/src/events/SDL_touch_c.h b/src/events/SDL_touch_c.h index c768de8a..dda6adc1 100644 --- a/src/events/SDL_touch_c.h +++ b/src/events/SDL_touch_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,6 +36,9 @@ typedef struct SDL_Touch /* Initialize the touch subsystem */ extern int SDL_InitTouch(void); +/* Returns SDL_TRUE if _any_ connected touch devices are known to SDL */ +extern SDL_bool SDL_TouchDevicesAvailable(void); + /* Add a touch, returning the index of the touch, or -1 if there was an error. */ extern int SDL_AddTouch(SDL_TouchID id, SDL_TouchDeviceType type, const char *name); diff --git a/src/events/SDL_windowevents.c b/src/events/SDL_windowevents.c index 8273f82d..9a184bc8 100644 --- a/src/events/SDL_windowevents.c +++ b/src/events/SDL_windowevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,7 +43,7 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, { int posted; - if (window == NULL) { + if (!window) { return 0; } if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) { @@ -71,6 +71,11 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { window->windowed.x = data1; window->windowed.y = data2; + + if (!(window->flags & SDL_WINDOW_MAXIMIZED) && !window->state_not_floating) { + window->floating.x = data1; + window->floating.y = data2; + } } if (data1 == window->x && data2 == window->y) { return 0; @@ -82,6 +87,11 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { window->windowed.w = data1; window->windowed.h = data2; + + if (!(window->flags & SDL_WINDOW_MAXIMIZED) && !window->state_not_floating) { + window->floating.w = data1; + window->floating.h = data2; + } } if (data1 == window->w && data2 == window->h) { SDL_CheckWindowPixelSizeChanged(window); @@ -153,6 +163,18 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, } window->flags |= SDL_WINDOW_OCCLUDED; break; + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + if (window->flags & SDL_WINDOW_FULLSCREEN) { + return 0; + } + window->flags |= SDL_WINDOW_FULLSCREEN; + break; + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + return 0; + } + window->flags &= ~SDL_WINDOW_FULLSCREEN; + break; default: break; } @@ -222,11 +244,11 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, break; } - if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && window->parent == NULL) { + if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && !window->parent) { int toplevel_count = 0; SDL_Window *n; - for (n = SDL_GetVideoDevice()->windows; n != NULL; n = n->next) { - if (n->parent == NULL) { + for (n = SDL_GetVideoDevice()->windows; n; n = n->next) { + if (!n->parent) { ++toplevel_count; } } diff --git a/src/events/SDL_windowevents_c.h b/src/events/SDL_windowevents_c.h index 4c81281e..93e95093 100644 --- a/src/events/SDL_windowevents_c.h +++ b/src/events/SDL_windowevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/blank_cursor.h b/src/events/blank_cursor.h index 95899f71..fd84744c 100644 --- a/src/events/blank_cursor.h +++ b/src/events/blank_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/default_cursor.h b/src/events/default_cursor.h index 10692b72..4e30aff2 100644 --- a/src/events/default_cursor.h +++ b/src/events/default_cursor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/scancodes_ascii.h b/src/events/scancodes_ascii.h index 9a88e8f2..ee49ab88 100644 --- a/src/events/scancodes_ascii.h +++ b/src/events/scancodes_ascii.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/scancodes_darwin.h b/src/events/scancodes_darwin.h index c6ae005b..c38ed60e 100644 --- a/src/events/scancodes_darwin.h +++ b/src/events/scancodes_darwin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/scancodes_linux.h b/src/events/scancodes_linux.h index d9c478c9..a9869a8d 100644 --- a/src/events/scancodes_linux.h +++ b/src/events/scancodes_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/events/scancodes_windows.h b/src/events/scancodes_windows.h index 91a3dc38..75635e6d 100644 --- a/src/events/scancodes_windows.h +++ b/src/events/scancodes_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,36 +20,267 @@ */ #include "SDL_internal.h" -/* Windows scancode to SDL scancode mapping table */ -/* derived from Microsoft scan code document, http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc */ +/* + * Windows scancode to SDL scancode mapping table + * https://learn.microsoft.com/windows/win32/inputdev/about-keyboard-input#scan-codes */ /* *INDENT-OFF* */ /* clang-format off */ -static const SDL_Scancode windows_scancode_table[] = -{ - /* 0 1 2 3 4 5 6 7 */ - /* 8 9 A B C D E F */ - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, /* 0 */ - SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, /* 0 */ - - SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, /* 1 */ - SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, /* 1 */ - - SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, /* 2 */ - SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, /* 2 */ - - SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_PRINTSCREEN,/* 3 */ - SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, /* 3 */ - - SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_HOME, /* 4 */ - SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_LEFT, SDL_SCANCODE_KP_5, SDL_SCANCODE_RIGHT, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_END, /* 4 */ - - SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_NONUSBACKSLASH,SDL_SCANCODE_F11, /* 5 */ - SDL_SCANCODE_F12, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_APPLICATION, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 5 */ - - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, /* 6 */ - SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 6 */ - - SDL_SCANCODE_INTERNATIONAL2, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL1, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */ - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL4, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL5, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL3, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN /* 7 */ +static const SDL_Scancode windows_scancode_table[] = { + /*0x00*/ SDL_SCANCODE_UNKNOWN, + /*0x01*/ SDL_SCANCODE_ESCAPE, + /*0x02*/ SDL_SCANCODE_1, + /*0x03*/ SDL_SCANCODE_2, + /*0x04*/ SDL_SCANCODE_3, + /*0x05*/ SDL_SCANCODE_4, + /*0x06*/ SDL_SCANCODE_5, + /*0x07*/ SDL_SCANCODE_6, + /*0x08*/ SDL_SCANCODE_7, + /*0x09*/ SDL_SCANCODE_8, + /*0x0a*/ SDL_SCANCODE_9, + /*0x0b*/ SDL_SCANCODE_0, + /*0x0c*/ SDL_SCANCODE_MINUS, + /*0x0d*/ SDL_SCANCODE_EQUALS, + /*0x0e*/ SDL_SCANCODE_BACKSPACE, + /*0x0f*/ SDL_SCANCODE_TAB, + /*0x10*/ SDL_SCANCODE_Q, + /*0x11*/ SDL_SCANCODE_W, + /*0x12*/ SDL_SCANCODE_E, + /*0x13*/ SDL_SCANCODE_R, + /*0x14*/ SDL_SCANCODE_T, + /*0x15*/ SDL_SCANCODE_Y, + /*0x16*/ SDL_SCANCODE_U, + /*0x17*/ SDL_SCANCODE_I, + /*0x18*/ SDL_SCANCODE_O, + /*0x19*/ SDL_SCANCODE_P, + /*0x1a*/ SDL_SCANCODE_LEFTBRACKET, + /*0x1b*/ SDL_SCANCODE_RIGHTBRACKET, + /*0x1c*/ SDL_SCANCODE_RETURN, + /*0x1d*/ SDL_SCANCODE_LCTRL, + /*0x1e*/ SDL_SCANCODE_A, + /*0x1f*/ SDL_SCANCODE_S, + /*0x20*/ SDL_SCANCODE_D, + /*0x21*/ SDL_SCANCODE_F, + /*0x22*/ SDL_SCANCODE_G, + /*0x23*/ SDL_SCANCODE_H, + /*0x24*/ SDL_SCANCODE_J, + /*0x25*/ SDL_SCANCODE_K, + /*0x26*/ SDL_SCANCODE_L, + /*0x27*/ SDL_SCANCODE_SEMICOLON, + /*0x28*/ SDL_SCANCODE_APOSTROPHE, + /*0x29*/ SDL_SCANCODE_GRAVE, + /*0x2a*/ SDL_SCANCODE_LSHIFT, + /*0x2b*/ SDL_SCANCODE_BACKSLASH, + /*0x2c*/ SDL_SCANCODE_Z, + /*0x2d*/ SDL_SCANCODE_X, + /*0x2e*/ SDL_SCANCODE_C, + /*0x2f*/ SDL_SCANCODE_V, + /*0x30*/ SDL_SCANCODE_B, + /*0x31*/ SDL_SCANCODE_N, + /*0x32*/ SDL_SCANCODE_M, + /*0x33*/ SDL_SCANCODE_COMMA, + /*0x34*/ SDL_SCANCODE_PERIOD, + /*0x35*/ SDL_SCANCODE_SLASH, + /*0x36*/ SDL_SCANCODE_RSHIFT, + /*0x37*/ SDL_SCANCODE_KP_MULTIPLY, + /*0x38*/ SDL_SCANCODE_LALT, + /*0x39*/ SDL_SCANCODE_SPACE, + /*0x3a*/ SDL_SCANCODE_CAPSLOCK, + /*0x3b*/ SDL_SCANCODE_F1, + /*0x3c*/ SDL_SCANCODE_F2, + /*0x3d*/ SDL_SCANCODE_F3, + /*0x3e*/ SDL_SCANCODE_F4, + /*0x3f*/ SDL_SCANCODE_F5, + /*0x40*/ SDL_SCANCODE_F6, + /*0x41*/ SDL_SCANCODE_F7, + /*0x42*/ SDL_SCANCODE_F8, + /*0x43*/ SDL_SCANCODE_F9, + /*0x44*/ SDL_SCANCODE_F10, + /*0x45*/ SDL_SCANCODE_NUMLOCKCLEAR, + /*0x46*/ SDL_SCANCODE_SCROLLLOCK, + /*0x47*/ SDL_SCANCODE_KP_7, + /*0x48*/ SDL_SCANCODE_KP_8, + /*0x49*/ SDL_SCANCODE_KP_9, + /*0x4a*/ SDL_SCANCODE_KP_MINUS, + /*0x4b*/ SDL_SCANCODE_KP_4, + /*0x4c*/ SDL_SCANCODE_KP_5, + /*0x4d*/ SDL_SCANCODE_KP_6, + /*0x4e*/ SDL_SCANCODE_KP_PLUS, + /*0x4f*/ SDL_SCANCODE_KP_1, + /*0x50*/ SDL_SCANCODE_KP_2, + /*0x51*/ SDL_SCANCODE_KP_3, + /*0x52*/ SDL_SCANCODE_KP_0, + /*0x53*/ SDL_SCANCODE_KP_PERIOD, + /*0x54*/ SDL_SCANCODE_UNKNOWN, + /*0x55*/ SDL_SCANCODE_UNKNOWN, + /*0x56*/ SDL_SCANCODE_NONUSBACKSLASH, + /*0x57*/ SDL_SCANCODE_F11, + /*0x58*/ SDL_SCANCODE_F12, + /*0x59*/ SDL_SCANCODE_KP_EQUALS, + /*0x5a*/ SDL_SCANCODE_UNKNOWN, + /*0x5b*/ SDL_SCANCODE_UNKNOWN, + /*0x5c*/ SDL_SCANCODE_INTERNATIONAL6, + /*0x5d*/ SDL_SCANCODE_UNKNOWN, + /*0x5e*/ SDL_SCANCODE_UNKNOWN, + /*0x5f*/ SDL_SCANCODE_UNKNOWN, + /*0x60*/ SDL_SCANCODE_UNKNOWN, + /*0x61*/ SDL_SCANCODE_UNKNOWN, + /*0x62*/ SDL_SCANCODE_UNKNOWN, + /*0x63*/ SDL_SCANCODE_UNKNOWN, + /*0x64*/ SDL_SCANCODE_F13, + /*0x65*/ SDL_SCANCODE_F14, + /*0x66*/ SDL_SCANCODE_F15, + /*0x67*/ SDL_SCANCODE_F16, + /*0x68*/ SDL_SCANCODE_F17, + /*0x69*/ SDL_SCANCODE_F18, + /*0x6a*/ SDL_SCANCODE_F19, + /*0x6b*/ SDL_SCANCODE_F20, + /*0x6c*/ SDL_SCANCODE_F21, + /*0x6d*/ SDL_SCANCODE_F22, + /*0x6e*/ SDL_SCANCODE_F23, + /*0x6f*/ SDL_SCANCODE_UNKNOWN, + /*0x70*/ SDL_SCANCODE_INTERNATIONAL2, + /*0x71*/ SDL_SCANCODE_LANG2, + /*0x72*/ SDL_SCANCODE_LANG1, + /*0x73*/ SDL_SCANCODE_INTERNATIONAL1, + /*0x74*/ SDL_SCANCODE_UNKNOWN, + /*0x75*/ SDL_SCANCODE_UNKNOWN, + /*0x76*/ SDL_SCANCODE_F24, + /*0x77*/ SDL_SCANCODE_LANG4, + /*0x78*/ SDL_SCANCODE_LANG3, + /*0x79*/ SDL_SCANCODE_INTERNATIONAL4, + /*0x7a*/ SDL_SCANCODE_UNKNOWN, + /*0x7b*/ SDL_SCANCODE_INTERNATIONAL5, + /*0x7c*/ SDL_SCANCODE_UNKNOWN, + /*0x7d*/ SDL_SCANCODE_INTERNATIONAL3, + /*0x7e*/ SDL_SCANCODE_KP_COMMA, + /*0x7f*/ SDL_SCANCODE_UNKNOWN, + /*0xe000*/ SDL_SCANCODE_UNKNOWN, + /*0xe001*/ SDL_SCANCODE_UNKNOWN, + /*0xe002*/ SDL_SCANCODE_UNKNOWN, + /*0xe003*/ SDL_SCANCODE_UNKNOWN, + /*0xe004*/ SDL_SCANCODE_UNKNOWN, + /*0xe005*/ SDL_SCANCODE_UNKNOWN, + /*0xe006*/ SDL_SCANCODE_UNKNOWN, + /*0xe007*/ SDL_SCANCODE_UNKNOWN, + /*0xe008*/ SDL_SCANCODE_UNKNOWN, + /*0xe009*/ SDL_SCANCODE_UNKNOWN, + /*0xe00a*/ SDL_SCANCODE_UNKNOWN, + /*0xe00b*/ SDL_SCANCODE_UNKNOWN, + /*0xe00c*/ SDL_SCANCODE_UNKNOWN, + /*0xe00d*/ SDL_SCANCODE_UNKNOWN, + /*0xe00e*/ SDL_SCANCODE_UNKNOWN, + /*0xe00f*/ SDL_SCANCODE_UNKNOWN, + /*0xe010*/ SDL_SCANCODE_AUDIOPREV, + /*0xe011*/ SDL_SCANCODE_UNKNOWN, + /*0xe012*/ SDL_SCANCODE_UNKNOWN, + /*0xe013*/ SDL_SCANCODE_UNKNOWN, + /*0xe014*/ SDL_SCANCODE_UNKNOWN, + /*0xe015*/ SDL_SCANCODE_UNKNOWN, + /*0xe016*/ SDL_SCANCODE_UNKNOWN, + /*0xe017*/ SDL_SCANCODE_UNKNOWN, + /*0xe018*/ SDL_SCANCODE_UNKNOWN, + /*0xe019*/ SDL_SCANCODE_AUDIONEXT, + /*0xe01a*/ SDL_SCANCODE_UNKNOWN, + /*0xe01b*/ SDL_SCANCODE_UNKNOWN, + /*0xe01c*/ SDL_SCANCODE_KP_ENTER, + /*0xe01d*/ SDL_SCANCODE_RCTRL, + /*0xe01e*/ SDL_SCANCODE_UNKNOWN, + /*0xe01f*/ SDL_SCANCODE_UNKNOWN, + /*0xe020*/ SDL_SCANCODE_MUTE, + /*0xe021*/ SDL_SCANCODE_CALCULATOR, + /*0xe022*/ SDL_SCANCODE_AUDIOPLAY, + /*0xe023*/ SDL_SCANCODE_UNKNOWN, + /*0xe024*/ SDL_SCANCODE_AUDIOSTOP, + /*0xe025*/ SDL_SCANCODE_UNKNOWN, + /*0xe026*/ SDL_SCANCODE_UNKNOWN, + /*0xe027*/ SDL_SCANCODE_UNKNOWN, + /*0xe028*/ SDL_SCANCODE_UNKNOWN, + /*0xe029*/ SDL_SCANCODE_UNKNOWN, + /*0xe02a*/ SDL_SCANCODE_UNKNOWN, + /*0xe02b*/ SDL_SCANCODE_UNKNOWN, + /*0xe02c*/ SDL_SCANCODE_UNKNOWN, + /*0xe02d*/ SDL_SCANCODE_UNKNOWN, + /*0xe02e*/ SDL_SCANCODE_VOLUMEDOWN, + /*0xe02f*/ SDL_SCANCODE_UNKNOWN, + /*0xe030*/ SDL_SCANCODE_VOLUMEUP, + /*0xe031*/ SDL_SCANCODE_UNKNOWN, + /*0xe032*/ SDL_SCANCODE_AC_HOME, + /*0xe033*/ SDL_SCANCODE_UNKNOWN, + /*0xe034*/ SDL_SCANCODE_UNKNOWN, + /*0xe035*/ SDL_SCANCODE_KP_DIVIDE, + /*0xe036*/ SDL_SCANCODE_UNKNOWN, + /*0xe037*/ SDL_SCANCODE_PRINTSCREEN, + /*0xe038*/ SDL_SCANCODE_RALT, + /*0xe039*/ SDL_SCANCODE_UNKNOWN, + /*0xe03a*/ SDL_SCANCODE_UNKNOWN, + /*0xe03b*/ SDL_SCANCODE_UNKNOWN, + /*0xe03c*/ SDL_SCANCODE_UNKNOWN, + /*0xe03d*/ SDL_SCANCODE_UNKNOWN, + /*0xe03e*/ SDL_SCANCODE_UNKNOWN, + /*0xe03f*/ SDL_SCANCODE_UNKNOWN, + /*0xe040*/ SDL_SCANCODE_UNKNOWN, + /*0xe041*/ SDL_SCANCODE_UNKNOWN, + /*0xe042*/ SDL_SCANCODE_UNKNOWN, + /*0xe043*/ SDL_SCANCODE_UNKNOWN, + /*0xe044*/ SDL_SCANCODE_UNKNOWN, + /*0xe045*/ SDL_SCANCODE_NUMLOCKCLEAR, + /*0xe046*/ SDL_SCANCODE_PAUSE, + /*0xe047*/ SDL_SCANCODE_HOME, + /*0xe048*/ SDL_SCANCODE_UP, + /*0xe049*/ SDL_SCANCODE_PAGEUP, + /*0xe04a*/ SDL_SCANCODE_UNKNOWN, + /*0xe04b*/ SDL_SCANCODE_LEFT, + /*0xe04c*/ SDL_SCANCODE_UNKNOWN, + /*0xe04d*/ SDL_SCANCODE_RIGHT, + /*0xe04e*/ SDL_SCANCODE_UNKNOWN, + /*0xe04f*/ SDL_SCANCODE_END, + /*0xe050*/ SDL_SCANCODE_DOWN, + /*0xe051*/ SDL_SCANCODE_PAGEDOWN, + /*0xe052*/ SDL_SCANCODE_INSERT, + /*0xe053*/ SDL_SCANCODE_DELETE, + /*0xe054*/ SDL_SCANCODE_UNKNOWN, + /*0xe055*/ SDL_SCANCODE_UNKNOWN, + /*0xe056*/ SDL_SCANCODE_UNKNOWN, + /*0xe057*/ SDL_SCANCODE_UNKNOWN, + /*0xe058*/ SDL_SCANCODE_UNKNOWN, + /*0xe059*/ SDL_SCANCODE_UNKNOWN, + /*0xe05a*/ SDL_SCANCODE_UNKNOWN, + /*0xe05b*/ SDL_SCANCODE_LGUI, + /*0xe05c*/ SDL_SCANCODE_RGUI, + /*0xe05d*/ SDL_SCANCODE_APPLICATION, + /*0xe05e*/ SDL_SCANCODE_POWER, + /*0xe05f*/ SDL_SCANCODE_SLEEP, + /*0xe060*/ SDL_SCANCODE_UNKNOWN, + /*0xe061*/ SDL_SCANCODE_UNKNOWN, + /*0xe062*/ SDL_SCANCODE_UNKNOWN, + /*0xe063*/ SDL_SCANCODE_UNKNOWN, + /*0xe064*/ SDL_SCANCODE_UNKNOWN, + /*0xe065*/ SDL_SCANCODE_AC_SEARCH, + /*0xe066*/ SDL_SCANCODE_AC_BOOKMARKS, + /*0xe067*/ SDL_SCANCODE_AC_REFRESH, + /*0xe068*/ SDL_SCANCODE_AC_STOP, + /*0xe069*/ SDL_SCANCODE_AC_FORWARD, + /*0xe06a*/ SDL_SCANCODE_AC_BACK, + /*0xe06b*/ SDL_SCANCODE_COMPUTER, + /*0xe06c*/ SDL_SCANCODE_MAIL, + /*0xe06d*/ SDL_SCANCODE_MEDIASELECT, + /*0xe06e*/ SDL_SCANCODE_UNKNOWN, + /*0xe06f*/ SDL_SCANCODE_UNKNOWN, + /*0xe070*/ SDL_SCANCODE_UNKNOWN, + /*0xe071*/ SDL_SCANCODE_UNKNOWN, + /*0xe072*/ SDL_SCANCODE_UNKNOWN, + /*0xe073*/ SDL_SCANCODE_UNKNOWN, + /*0xe074*/ SDL_SCANCODE_UNKNOWN, + /*0xe075*/ SDL_SCANCODE_UNKNOWN, + /*0xe076*/ SDL_SCANCODE_UNKNOWN, + /*0xe077*/ SDL_SCANCODE_UNKNOWN, + /*0xe078*/ SDL_SCANCODE_UNKNOWN, + /*0xe079*/ SDL_SCANCODE_UNKNOWN, + /*0xe07a*/ SDL_SCANCODE_UNKNOWN, + /*0xe07b*/ SDL_SCANCODE_UNKNOWN, + /*0xe07c*/ SDL_SCANCODE_UNKNOWN, + /*0xe07d*/ SDL_SCANCODE_UNKNOWN, + /*0xe07e*/ SDL_SCANCODE_UNKNOWN, + /*0xe07f*/ SDL_SCANCODE_UNKNOWN }; /* *INDENT-ON* */ /* clang-format on */ diff --git a/src/events/scancodes_xfree86.h b/src/events/scancodes_xfree86.h index e297eb53..f3001d83 100644 --- a/src/events/scancodes_xfree86.h +++ b/src/events/scancodes_xfree86.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/file/SDL_rwops.c b/src/file/SDL_rwops.c index cdd5b361..dde12ded 100644 --- a/src/file/SDL_rwops.c +++ b/src/file/SDL_rwops.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -92,7 +92,7 @@ static int SDLCALL windows_file_open(SDL_RWops *context, const char *filename, c context->hidden.windowsio.buffer.data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE); if (!context->hidden.windowsio.buffer.data) { - return SDL_OutOfMemory(); + return -1; } #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) && !defined(__WINRT__) /* Do not open a dialog box if failure */ @@ -389,7 +389,7 @@ static SDL_RWops *SDL_RWFromFP(void *fp, SDL_bool autoclose) SDL_RWops *rwops = NULL; rwops = SDL_CreateRW(); - if (rwops != NULL) { + if (rwops) { rwops->seek = stdio_seek; rwops->read = stdio_read; rwops->write = stdio_write; @@ -462,7 +462,7 @@ static size_t SDLCALL mem_write(SDL_RWops *context, const void *ptr, size_t size SDL_RWops *SDL_RWFromFile(const char *file, const char *mode) { SDL_RWops *rwops = NULL; - if (file == NULL || !*file || mode == NULL || !*mode) { + if (!file || !*file || !mode || !*mode) { SDL_SetError("SDL_RWFromFile(): No file or no mode specified"); return NULL; } @@ -495,7 +495,7 @@ SDL_RWops *SDL_RWFromFile(const char *file, const char *mode) /* Try to open the file from the asset system */ rwops = SDL_CreateRW(); - if (rwops == NULL) { + if (!rwops) { return NULL; /* SDL_SetError already setup by SDL_CreateRW() */ } @@ -512,7 +512,7 @@ SDL_RWops *SDL_RWFromFile(const char *file, const char *mode) #elif defined(__WIN32__) || defined(__GDK__) || defined(__WINRT__) rwops = SDL_CreateRW(); - if (rwops == NULL) { + if (!rwops) { return NULL; /* SDL_SetError already setup by SDL_CreateRW() */ } @@ -538,7 +538,7 @@ SDL_RWops *SDL_RWFromFile(const char *file, const char *mode) #else FILE *fp = fopen(file, mode); #endif - if (fp == NULL) { + if (!fp) { SDL_SetError("Couldn't open %s", file); } else { rwops = SDL_RWFromFP(fp, SDL_TRUE); @@ -555,7 +555,7 @@ SDL_RWops *SDL_RWFromMem(void *mem, size_t size) { SDL_RWops *rwops = NULL; - if (mem == NULL) { + if (!mem) { SDL_InvalidParamError("mem"); return NULL; } @@ -565,7 +565,7 @@ SDL_RWops *SDL_RWFromMem(void *mem, size_t size) } rwops = SDL_CreateRW(); - if (rwops != NULL) { + if (rwops) { rwops->size = mem_size; rwops->seek = mem_seek; rwops->read = mem_read; @@ -582,7 +582,7 @@ SDL_RWops *SDL_RWFromConstMem(const void *mem, size_t size) { SDL_RWops *rwops = NULL; - if (mem == NULL) { + if (!mem) { SDL_InvalidParamError("mem"); return NULL; } @@ -592,7 +592,7 @@ SDL_RWops *SDL_RWFromConstMem(const void *mem, size_t size) } rwops = SDL_CreateRW(); - if (rwops != NULL) { + if (rwops) { rwops->size = mem_size; rwops->seek = mem_seek; rwops->read = mem_read; @@ -609,9 +609,7 @@ SDL_RWops *SDL_CreateRW(void) SDL_RWops *context; context = (SDL_RWops *)SDL_calloc(1, sizeof(*context)); - if (context == NULL) { - SDL_OutOfMemory(); - } else { + if (context) { context->type = SDL_RWOPS_UNKNOWN; } return context; @@ -619,6 +617,7 @@ SDL_RWops *SDL_CreateRW(void) void SDL_DestroyRW(SDL_RWops *context) { + SDL_DestroyProperties(context->props); SDL_free(context); } @@ -631,7 +630,7 @@ void *SDL_LoadFile_RW(SDL_RWops *src, size_t *datasize, SDL_bool freesrc) char *data = NULL, *newdata; SDL_bool loading_chunks = SDL_FALSE; - if (src == NULL) { + if (!src) { SDL_InvalidParamError("src"); return NULL; } @@ -642,12 +641,10 @@ void *SDL_LoadFile_RW(SDL_RWops *src, size_t *datasize, SDL_bool freesrc) loading_chunks = SDL_TRUE; } if (size >= SDL_SIZE_MAX) { - SDL_OutOfMemory(); goto done; } data = (char *)SDL_malloc((size_t)(size + 1)); if (!data) { - SDL_OutOfMemory(); goto done; } @@ -661,10 +658,9 @@ void *SDL_LoadFile_RW(SDL_RWops *src, size_t *datasize, SDL_bool freesrc) } else { newdata = SDL_realloc(data, (size_t)(size + 1)); } - if (newdata == NULL) { + if (!newdata) { SDL_free(data); data = NULL; - SDL_OutOfMemory(); goto done; } data = newdata; @@ -698,6 +694,19 @@ void *SDL_LoadFile(const char *file, size_t *datasize) return SDL_LoadFile_RW(SDL_RWFromFile(file, "rb"), datasize, SDL_TRUE); } +SDL_PropertiesID SDL_GetRWProperties(SDL_RWops *context) +{ + if (!context) { + SDL_InvalidParamError("context"); + return 0; + } + + if (context->props == 0) { + context->props = SDL_CreateProperties(); + } + return context->props; +} + Sint64 SDL_RWsize(SDL_RWops *context) { if (!context) { @@ -794,6 +803,41 @@ size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size) return bytes; } +size_t SDL_RWprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + va_list ap; + int size; + char *string; + size_t bytes; + + va_start(ap, fmt); + size = SDL_vasprintf(&string, fmt, ap); + va_end(ap); + if (size < 0) { + return 0; + } + + bytes = SDL_RWwrite(context, string, (size_t)size); + SDL_free(string); + return bytes; +} + +size_t SDL_RWvprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) +{ + int size; + char *string; + size_t bytes; + + size = SDL_vasprintf(&string, fmt, ap); + if (size < 0) { + return 0; + } + + bytes = SDL_RWwrite(context, string, (size_t)size); + SDL_free(string); + return bytes; +} + int SDL_RWclose(SDL_RWops *context) { if (!context) { @@ -938,13 +982,13 @@ SDL_bool SDL_ReadS64BE(SDL_RWops *src, Sint64 *value) SDL_bool SDL_WriteU8(SDL_RWops *dst, Uint8 value) { - return (SDL_RWwrite(dst, &value, sizeof(value)) == sizeof(value)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &value, sizeof(value)) == sizeof(value)); } SDL_bool SDL_WriteU16LE(SDL_RWops *dst, Uint16 value) { const Uint16 swapped = SDL_SwapLE16(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS16LE(SDL_RWops *dst, Sint16 value) @@ -955,7 +999,7 @@ SDL_bool SDL_WriteS16LE(SDL_RWops *dst, Sint16 value) SDL_bool SDL_WriteU16BE(SDL_RWops *dst, Uint16 value) { const Uint16 swapped = SDL_SwapBE16(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS16BE(SDL_RWops *dst, Sint16 value) @@ -966,7 +1010,7 @@ SDL_bool SDL_WriteS16BE(SDL_RWops *dst, Sint16 value) SDL_bool SDL_WriteU32LE(SDL_RWops *dst, Uint32 value) { const Uint32 swapped = SDL_SwapLE32(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS32LE(SDL_RWops *dst, Sint32 value) @@ -977,7 +1021,7 @@ SDL_bool SDL_WriteS32LE(SDL_RWops *dst, Sint32 value) SDL_bool SDL_WriteU32BE(SDL_RWops *dst, Uint32 value) { const Uint32 swapped = SDL_SwapBE32(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS32BE(SDL_RWops *dst, Sint32 value) @@ -988,7 +1032,7 @@ SDL_bool SDL_WriteS32BE(SDL_RWops *dst, Sint32 value) SDL_bool SDL_WriteU64LE(SDL_RWops *dst, Uint64 value) { const Uint64 swapped = SDL_SwapLE64(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS64LE(SDL_RWops *dst, Sint64 value) @@ -999,7 +1043,7 @@ SDL_bool SDL_WriteS64LE(SDL_RWops *dst, Sint64 value) SDL_bool SDL_WriteU64BE(SDL_RWops *dst, Uint64 value) { const Uint64 swapped = SDL_SwapBE64(value); - return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)) ? SDL_TRUE : SDL_FALSE; + return (SDL_RWwrite(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); } SDL_bool SDL_WriteS64BE(SDL_RWops *dst, Sint64 value) diff --git a/src/file/cocoa/SDL_rwopsbundlesupport.h b/src/file/cocoa/SDL_rwopsbundlesupport.h index 17bc86b4..c57ce452 100644 --- a/src/file/cocoa/SDL_rwopsbundlesupport.h +++ b/src/file/cocoa/SDL_rwopsbundlesupport.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/file/cocoa/SDL_rwopsbundlesupport.m b/src/file/cocoa/SDL_rwopsbundlesupport.m index 9ce5f0e6..a6f763b4 100644 --- a/src/file/cocoa/SDL_rwopsbundlesupport.m +++ b/src/file/cocoa/SDL_rwopsbundlesupport.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/file/n3ds/SDL_rwopsromfs.c b/src/file/n3ds/SDL_rwopsromfs.c index acb8701a..afa9e53c 100644 --- a/src/file/n3ds/SDL_rwopsromfs.c +++ b/src/file/n3ds/SDL_rwopsromfs.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,7 +65,7 @@ static FILE *TryOpenFile(const char *file, const char *mode) FILE *fp = NULL; fp = TryOpenInRomfs(file, mode); - if (fp == NULL) { + if (!fp) { fp = fopen(file, mode); } @@ -78,7 +78,6 @@ FILE *TryOpenInRomfs(const char *file, const char *mode) char *prefixed_filepath = NULL; if (SDL_asprintf(&prefixed_filepath, "romfs:/%s", file) < 0) { - SDL_OutOfMemory(); return NULL; } diff --git a/src/file/n3ds/SDL_rwopsromfs.h b/src/file/n3ds/SDL_rwopsromfs.h index 99bc0b44..572a7144 100644 --- a/src/file/n3ds/SDL_rwopsromfs.h +++ b/src/file/n3ds/SDL_rwopsromfs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/filesystem/android/SDL_sysfilesystem.c b/src/filesystem/android/SDL_sysfilesystem.c index d14f7892..4d5fb5de 100644 --- a/src/filesystem/android/SDL_sysfilesystem.c +++ b/src/filesystem/android/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,8 +40,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) if (path) { size_t pathlen = SDL_strlen(path) + 2; char *fullpath = (char *)SDL_malloc(pathlen); - if (fullpath == NULL) { - SDL_OutOfMemory(); + if (!fullpath) { return NULL; } SDL_snprintf(fullpath, pathlen, "%s/", path); diff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m index 0a78a2d9..71f674fa 100644 --- a/src/filesystem/cocoa/SDL_sysfilesystem.m +++ b/src/filesystem/cocoa/SDL_sysfilesystem.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -52,9 +52,7 @@ char *SDL_GetBasePath(void) if (base) { const size_t len = SDL_strlen(base) + 2; retval = (char *)SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); - } else { + if (retval != NULL) { SDL_snprintf(retval, len, "%s/", base); } } @@ -105,9 +103,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) if (base) { const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4; retval = (char *)SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); - } else { + if (retval != NULL) { char *ptr; if (*org) { SDL_snprintf(retval, len, "%s/%s/%s/", base, org, app); @@ -152,13 +148,7 @@ char *SDL_GetUserFolder(SDL_Folder folder) SDL_SetError("No $HOME environment variable available"); } - retval = SDL_strdup(base); - - if (!retval) { - SDL_OutOfMemory(); - } - - return retval; + return SDL_strdup(base); case SDL_FOLDER_DESKTOP: dir = NSDesktopDirectory; @@ -221,7 +211,6 @@ char *SDL_GetUserFolder(SDL_Folder folder) retval = SDL_strdup(base); if (retval == NULL) { - SDL_OutOfMemory(); return NULL; } diff --git a/src/filesystem/dummy/SDL_sysfilesystem.c b/src/filesystem/dummy/SDL_sysfilesystem.c index 197284cf..44272822 100644 --- a/src/filesystem/dummy/SDL_sysfilesystem.c +++ b/src/filesystem/dummy/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/filesystem/emscripten/SDL_sysfilesystem.c b/src/filesystem/emscripten/SDL_sysfilesystem.c index 389eff08..69069e93 100644 --- a/src/filesystem/emscripten/SDL_sysfilesystem.c +++ b/src/filesystem/emscripten/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,18 +42,17 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *ptr = NULL; size_t len = 0; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; retval = (char *)SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } @@ -86,7 +85,6 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *SDL_GetUserFolder(SDL_Folder folder) { const char *home = NULL; - char *retval; if (folder != SDL_FOLDER_HOME) { SDL_SetError("Emscripten only supports the home folder"); @@ -99,13 +97,7 @@ char *SDL_GetUserFolder(SDL_Folder folder) return NULL; } - retval = SDL_strdup(home); - - if (!retval) { - SDL_OutOfMemory(); - } - - return retval; + return SDL_strdup(home); } #endif /* SDL_FILESYSTEM_EMSCRIPTEN */ diff --git a/src/filesystem/gdk/SDL_sysfilesystem.cpp b/src/filesystem/gdk/SDL_sysfilesystem.cpp index 517524bd..26f51574 100644 --- a/src/filesystem/gdk/SDL_sysfilesystem.cpp +++ b/src/filesystem/gdk/SDL_sysfilesystem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,7 +18,7 @@ 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_internal.h" #ifdef SDL_FILESYSTEM_XBOX @@ -26,9 +26,9 @@ /* System dependent filesystem routines */ #include "../../core/windows/SDL_windows.h" -#include "SDL_hints.h" -#include "SDL_system.h" -#include "SDL_filesystem.h" +#include +#include +#include #include char * @@ -44,9 +44,8 @@ SDL_GetBasePath(void) while (SDL_TRUE) { void *ptr = SDL_realloc(path, buflen * sizeof(CHAR)); - if (ptr == NULL) { + if (!ptr) { SDL_free(path); - SDL_OutOfMemory(); return NULL; } @@ -90,13 +89,13 @@ SDL_GetPrefPath(const char *org, const char *app) HRESULT result; const char *csid = SDL_GetHint("SDL_GDK_SERVICE_CONFIGURATION_ID"); - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } /* This should be set before calling SDL_GetPrefPath! */ - if (csid == NULL) { + if (!csid) { SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM, "Set SDL_GDK_SERVICE_CONFIGURATION_ID before calling SDL_GetPrefPath!"); return SDL_strdup("T:\\"); } diff --git a/src/filesystem/haiku/SDL_sysfilesystem.cc b/src/filesystem/haiku/SDL_sysfilesystem.cc index 466a2576..893e24a6 100644 --- a/src/filesystem/haiku/SDL_sysfilesystem.cc +++ b/src/filesystem/haiku/SDL_sysfilesystem.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,8 +51,7 @@ char *SDL_GetBasePath(void) const size_t len = SDL_strlen(str); char *retval = (char *) SDL_malloc(len + 2); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } @@ -70,11 +69,11 @@ char *SDL_GetPrefPath(const char *org, const char *app) const char *append = "/config/settings/"; size_t len = SDL_strlen(home); - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } @@ -83,9 +82,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) } len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; char *retval = (char *) SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); - } else { + if (retval) { if (*org) { SDL_snprintf(retval, len, "%s%s%s/%s/", home, append, org, app); } else { @@ -110,25 +107,17 @@ char *SDL_GetUserFolder(SDL_Folder folder) switch (folder) { case SDL_FOLDER_HOME: - retval = SDL_strdup(home); - - if (!retval) { - SDL_OutOfMemory(); - } - - return retval; + return SDL_strdup(home); /* TODO: Is Haiku's desktop folder always ~/Desktop/ ? */ case SDL_FOLDER_DESKTOP: retval = (char *) SDL_malloc(SDL_strlen(home) + 10); - if (!retval) { - SDL_OutOfMemory(); + if (retval) { + SDL_strlcpy(retval, home, SDL_strlen(home) + 10); + SDL_strlcat(retval, "/Desktop/", SDL_strlen(home) + 10); } - SDL_strlcpy(retval, home, SDL_strlen(home) + 10); - SDL_strlcat(retval, "/Desktop/", SDL_strlen(home) + 10); - return retval; case SDL_FOLDER_DOCUMENTS: diff --git a/src/filesystem/n3ds/SDL_sysfilesystem.c b/src/filesystem/n3ds/SDL_sysfilesystem.c index 7bde8310..689a35bb 100644 --- a/src/filesystem/n3ds/SDL_sysfilesystem.c +++ b/src/filesystem/n3ds/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,13 +41,13 @@ char *SDL_GetBasePath(void) char *SDL_GetPrefPath(const char *org, const char *app) { char *pref_path = NULL; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } pref_path = MakePrefPath(app); - if (pref_path == NULL) { + if (!pref_path) { return NULL; } @@ -70,7 +70,6 @@ static char *MakePrefPath(const char *app) { char *pref_path; if (SDL_asprintf(&pref_path, "sdmc:/3ds/%s/", app) < 0) { - SDL_OutOfMemory(); return NULL; } return pref_path; diff --git a/src/filesystem/ps2/SDL_sysfilesystem.c b/src/filesystem/ps2/SDL_sysfilesystem.c index b52fdea3..d5b2632b 100644 --- a/src/filesystem/ps2/SDL_sysfilesystem.c +++ b/src/filesystem/ps2/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -79,11 +79,11 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *retval = NULL; size_t len; char *base = SDL_GetBasePath(); - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } diff --git a/src/filesystem/psp/SDL_sysfilesystem.c b/src/filesystem/psp/SDL_sysfilesystem.c index 239e6743..5386cb53 100644 --- a/src/filesystem/psp/SDL_sysfilesystem.c +++ b/src/filesystem/psp/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -47,11 +47,11 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *retval = NULL; size_t len; char *base = SDL_GetBasePath(); - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } diff --git a/src/filesystem/riscos/SDL_sysfilesystem.c b/src/filesystem/riscos/SDL_sysfilesystem.c index 17d6625b..bbbc144b 100644 --- a/src/filesystem/riscos/SDL_sysfilesystem.c +++ b/src/filesystem/riscos/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,21 +34,20 @@ static char *SDL_unixify_std(const char *ro_path, char *buffer, size_t buf_len, { const char *const in_buf = buffer; /* = NULL if we allocate the buffer. */ - if (buffer == NULL) { + if (!buffer) { /* This matches the logic in __unixify, with an additional byte for the * extra path separator. */ buf_len = SDL_strlen(ro_path) + 14 + 1; buffer = SDL_malloc(buf_len); - if (buffer == NULL) { - SDL_OutOfMemory(); + if (!buffer) { return NULL; } } if (!__unixify_std(ro_path, buffer, buf_len, filetype)) { - if (in_buf == NULL) { + if (!in_buf) { SDL_free(buffer); } @@ -88,8 +87,7 @@ static char *canonicalisePath(const char *path, const char *pathVar) regs.r[5] = 1 - regs.r[5]; buf = SDL_malloc(regs.r[5]); - if (buf == NULL) { - SDL_OutOfMemory(); + if (!buf) { return NULL; } regs.r[2] = (int)buf; @@ -117,7 +115,7 @@ static _kernel_oserror *createDirectoryRecursive(char *path) *ptr = '\0'; error = _kernel_swi(OS_File, ®s, ®s); *ptr = '.'; - if (error != NULL) { + if (error) { return error; } } @@ -137,13 +135,13 @@ char *SDL_GetBasePath(void) } canon = canonicalisePath((const char *)regs.r[0], "Run$Path"); - if (canon == NULL) { + if (!canon) { return NULL; } /* chop off filename. */ ptr = SDL_strrchr(canon, '.'); - if (ptr != NULL) { + if (ptr) { *ptr = '\0'; } @@ -158,23 +156,22 @@ char *SDL_GetPrefPath(const char *org, const char *app) size_t len; _kernel_oserror *error; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } canon = canonicalisePath("", "Run$Path"); - if (canon == NULL) { + if (!canon) { return NULL; } len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4; dir = (char *)SDL_malloc(len); - if (dir == NULL) { - SDL_OutOfMemory(); + if (!dir) { SDL_free(canon); return NULL; } @@ -188,7 +185,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) SDL_free(canon); error = createDirectoryRecursive(dir); - if (error != NULL) { + if (error) { SDL_SetError("Couldn't create directory: %s", error->errmess); SDL_free(dir); return NULL; diff --git a/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c index 88a3a353..91baf437 100644 --- a/src/filesystem/unix/SDL_sysfilesystem.c +++ b/src/filesystem/unix/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -47,8 +47,7 @@ static char *readSymLink(const char *path) while (1) { char *ptr = (char *)SDL_realloc(retval, (size_t)len); - if (ptr == NULL) { - SDL_OutOfMemory(); + if (!ptr) { break; } @@ -78,14 +77,13 @@ static char *search_path_for_binary(const char *bin) char *start = envr; char *ptr; - if (envr == NULL) { + if (!envr) { SDL_SetError("No $PATH set"); return NULL; } envr = SDL_strdup(envr); - if (envr == NULL) { - SDL_OutOfMemory(); + if (!envr) { return NULL; } @@ -110,7 +108,7 @@ static char *search_path_for_binary(const char *bin) } } start = ptr + 1; /* start points to beginning of next element. */ - } while (ptr != NULL); + } while (ptr); SDL_free(envr); SDL_free(exe); @@ -130,8 +128,7 @@ char *SDL_GetBasePath(void) const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; if (sysctl(mib, SDL_arraysize(mib), fullpath, &buflen, NULL, 0) != -1) { retval = SDL_strdup(fullpath); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } } @@ -144,15 +141,13 @@ char *SDL_GetBasePath(void) if (sysctl(mib, 4, NULL, &len, NULL, 0) != -1) { char *exe, *pwddst; char *realpathbuf = (char *)SDL_malloc(PATH_MAX + 1); - if (realpathbuf == NULL) { - SDL_OutOfMemory(); + if (!realpathbuf) { return NULL; } cmdline = SDL_malloc(len); - if (cmdline == NULL) { + if (!cmdline) { SDL_free(realpathbuf); - SDL_OutOfMemory(); return NULL; } @@ -172,7 +167,7 @@ char *SDL_GetBasePath(void) } if (exe) { - if (pwddst == NULL) { + if (!pwddst) { if (realpath(exe, realpathbuf) != NULL) { retval = realpathbuf; } @@ -188,7 +183,7 @@ char *SDL_GetBasePath(void) } } - if (retval == NULL) { + if (!retval) { SDL_free(realpathbuf); } @@ -197,7 +192,7 @@ char *SDL_GetBasePath(void) #endif /* is a Linux-style /proc filesystem available? */ - if (retval == NULL && (access("/proc", F_OK) == 0)) { + if (!retval && (access("/proc", F_OK) == 0)) { /* !!! FIXME: after 2.0.6 ships, let's delete this code and just use the /proc/%llu version. There's no reason to have two copies of this plus all the #ifdefs. --ryan. */ @@ -209,7 +204,7 @@ char *SDL_GetBasePath(void) retval = readSymLink("/proc/self/path/a.out"); #else retval = readSymLink("/proc/self/exe"); /* linux. */ - if (retval == NULL) { + if (!retval) { /* older kernels don't have /proc/self ... try PID version... */ char path[64]; const int rc = SDL_snprintf(path, sizeof(path), @@ -225,10 +220,9 @@ char *SDL_GetBasePath(void) #ifdef __SOLARIS__ /* try this as a fallback if /proc didn't pan out */ if (!retval) { const char *path = getexecname(); - if ((path != NULL) && (path[0] == '/')) { /* must be absolute path... */ + if ((path) && (path[0] == '/')) { /* must be absolute path... */ retval = SDL_strdup(path); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } } @@ -237,9 +231,9 @@ char *SDL_GetBasePath(void) /* If we had access to argv[0] here, we could check it for a path, or troll through $PATH looking for it, too. */ - if (retval != NULL) { /* chop off filename. */ + if (retval) { /* chop off filename. */ char *ptr = SDL_strrchr(retval, '/'); - if (ptr != NULL) { + if (ptr) { *(ptr + 1) = '\0'; } else { /* shouldn't happen, but just in case... */ SDL_free(retval); @@ -247,10 +241,10 @@ char *SDL_GetBasePath(void) } } - if (retval != NULL) { + if (retval) { /* try to shrink buffer... */ char *ptr = (char *)SDL_realloc(retval, SDL_strlen(retval) + 1); - if (ptr != NULL) { + if (ptr) { retval = ptr; /* oh well if it failed. */ } } @@ -273,18 +267,18 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *ptr = NULL; size_t len = 0; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } - if (envr == NULL) { + if (!envr) { /* You end up with "$HOME/.local/share/Game Name 2" */ envr = SDL_getenv("HOME"); - if (envr == NULL) { + if (!envr) { /* we could take heroic measures with /etc/passwd, but oh well. */ SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set"); return NULL; @@ -301,8 +295,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3; retval = (char *)SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } @@ -372,15 +365,15 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa home_dir = SDL_getenv ("HOME"); - if (home_dir == NULL) + if (!home_dir) goto error; config_home = SDL_getenv ("XDG_CONFIG_HOME"); - if (config_home == NULL || config_home[0] == 0) + if (!config_home || config_home[0] == 0) { l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1; config_file = (char*) SDL_malloc (l); - if (config_file == NULL) + if (!config_file) goto error; SDL_strlcpy (config_file, home_dir, l); @@ -390,7 +383,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa { l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1; config_file = (char*) SDL_malloc (l); - if (config_file == NULL) + if (!config_file) goto error; SDL_strlcpy (config_file, config_home, l); @@ -399,7 +392,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa file = fopen (config_file, "r"); SDL_free (config_file); - if (file == NULL) + if (!file) goto error; user_dir = NULL; @@ -452,7 +445,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa { l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1; user_dir = (char*) SDL_malloc (l); - if (user_dir == NULL) + if (!user_dir) goto error2; SDL_strlcpy (user_dir, home_dir, l); @@ -461,7 +454,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa else { user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1); - if (user_dir == NULL) + if (!user_dir) goto error2; *user_dir = 0; @@ -493,12 +486,12 @@ static char *xdg_user_dir_lookup (const char *type) char *dir, *home_dir, *user_dir; dir = xdg_user_dir_lookup_with_fallback(type, NULL); - if (dir != NULL) + if (dir) return dir; home_dir = SDL_getenv("HOME"); - if (home_dir == NULL) + if (!home_dir) return NULL; /* Special case desktop for historical compatibility */ @@ -506,7 +499,7 @@ static char *xdg_user_dir_lookup (const char *type) { user_dir = (char*) SDL_malloc(SDL_strlen(home_dir) + SDL_strlen("/Desktop") + 1); - if (user_dir == NULL) + if (!user_dir) return NULL; strcpy(user_dir, home_dir); @@ -541,13 +534,7 @@ char *SDL_GetUserFolder(SDL_Folder folder) return NULL; } - retval = SDL_strdup(param); - - if (!retval) { - SDL_OutOfMemory(); - } - - return retval; + return SDL_strdup(param); case SDL_FOLDER_DESKTOP: param = "DESKTOP"; diff --git a/src/filesystem/vita/SDL_sysfilesystem.c b/src/filesystem/vita/SDL_sysfilesystem.c index 836416ab..11d320ee 100644 --- a/src/filesystem/vita/SDL_sysfilesystem.c +++ b/src/filesystem/vita/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -48,11 +48,11 @@ char *SDL_GetPrefPath(const char *org, const char *app) char *ptr = NULL; size_t len = 0; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } @@ -60,8 +60,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) len += SDL_strlen(org) + SDL_strlen(app) + 3; retval = (char *)SDL_malloc(len); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } diff --git a/src/filesystem/windows/SDL_sysfilesystem.c b/src/filesystem/windows/SDL_sysfilesystem.c index 3027fb00..f9e23616 100644 --- a/src/filesystem/windows/SDL_sysfilesystem.c +++ b/src/filesystem/windows/SDL_sysfilesystem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,9 +51,8 @@ char *SDL_GetBasePath(void) while (SDL_TRUE) { void *ptr = SDL_realloc(path, buflen * sizeof(WCHAR)); - if (ptr == NULL) { + if (!ptr) { SDL_free(path); - SDL_OutOfMemory(); return NULL; } @@ -108,11 +107,11 @@ char *SDL_GetPrefPath(const char *org, const char *app) size_t new_wpath_len = 0; BOOL api_result = FALSE; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } @@ -122,15 +121,13 @@ char *SDL_GetPrefPath(const char *org, const char *app) } worg = WIN_UTF8ToStringW(org); - if (worg == NULL) { - SDL_OutOfMemory(); + if (!worg) { return NULL; } wapp = WIN_UTF8ToStringW(app); - if (wapp == NULL) { + if (!wapp) { SDL_free(worg); - SDL_OutOfMemory(); return NULL; } diff --git a/src/filesystem/winrt/SDL_sysfilesystem.cpp b/src/filesystem/winrt/SDL_sysfilesystem.cpp index a5e85b4a..d5b7c020 100644 --- a/src/filesystem/winrt/SDL_sysfilesystem.cpp +++ b/src/filesystem/winrt/SDL_sysfilesystem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -106,7 +106,7 @@ SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType) } const wchar_t *ucs2Path = SDL_WinRTGetFSPathUNICODE(pathType); - if (ucs2Path == NULL) { + if (!ucs2Path) { return NULL; } @@ -123,15 +123,14 @@ SDL_GetBasePath(void) size_t destPathLen; char *destPath = NULL; - if (srcPath == NULL) { + if (!srcPath) { SDL_SetError("Couldn't locate our basepath: %s", SDL_GetError()); return NULL; } destPathLen = SDL_strlen(srcPath) + 2; destPath = (char *)SDL_malloc(destPathLen); - if (destPath == NULL) { - SDL_OutOfMemory(); + if (!destPath) { return NULL; } @@ -156,16 +155,16 @@ SDL_GetPrefPath(const char *org, const char *app) size_t new_wpath_len = 0; BOOL api_result = FALSE; - if (app == NULL) { + if (!app) { SDL_InvalidParamError("app"); return NULL; } - if (org == NULL) { + if (!org) { org = ""; } srcPath = SDL_WinRTGetFSPathUNICODE(SDL_WINRT_PATH_LOCAL_FOLDER); - if (srcPath == NULL) { + if (!srcPath) { SDL_SetError("Unable to find a source path"); return NULL; } @@ -177,15 +176,13 @@ SDL_GetPrefPath(const char *org, const char *app) SDL_wcslcpy(path, srcPath, SDL_arraysize(path)); worg = WIN_UTF8ToString(org); - if (worg == NULL) { - SDL_OutOfMemory(); + if (!worg) { return NULL; } wapp = WIN_UTF8ToString(app); - if (wapp == NULL) { + if (!wapp) { SDL_free(worg); - SDL_OutOfMemory(); return NULL; } diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index fbd77628..3fbac01e 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -55,7 +55,7 @@ static int ValidHaptic(SDL_Haptic *haptic) SDL_Haptic *hapticlist; valid = 0; - if (haptic != NULL) { + if (haptic) { hapticlist = SDL_haptics; while (hapticlist) { if (hapticlist == haptic) { @@ -124,8 +124,7 @@ SDL_Haptic *SDL_HapticOpen(int device_index) /* Create the haptic device */ haptic = (SDL_Haptic *)SDL_malloc(sizeof(*haptic)); - if (haptic == NULL) { - SDL_OutOfMemory(); + if (!haptic) { return NULL; } @@ -296,8 +295,7 @@ SDL_Haptic *SDL_HapticOpenFromJoystick(SDL_Joystick *joystick) /* Create the haptic device */ haptic = (SDL_Haptic *)SDL_malloc(sizeof(*haptic)); - if (haptic == NULL) { - SDL_OutOfMemory(); + if (!haptic) { SDL_UnlockJoysticks(); return NULL; } @@ -609,7 +607,7 @@ int SDL_HapticSetGain(SDL_Haptic *haptic, int gain) /* We use the envvar to get the maximum gain. */ env = SDL_getenv("SDL_HAPTIC_GAIN_MAX"); - if (env != NULL) { + if (env) { max_gain = SDL_atoi(env); /* Check for sanity. */ diff --git a/src/haptic/SDL_haptic_c.h b/src/haptic/SDL_haptic_c.h index 744dfb7e..06ef1be0 100644 --- a/src/haptic/SDL_haptic_c.h +++ b/src/haptic/SDL_haptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/SDL_syshaptic.h b/src/haptic/SDL_syshaptic.h index 58fc55b9..71f7ed55 100644 --- a/src/haptic/SDL_syshaptic.h +++ b/src/haptic/SDL_syshaptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/android/SDL_syshaptic.c b/src/haptic/android/SDL_syshaptic.c index f65a26ef..bf8404c9 100644 --- a/src/haptic/android/SDL_syshaptic.c +++ b/src/haptic/android/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -69,7 +69,7 @@ static SDL_hapticlist_item *HapticByOrder(int index) static SDL_hapticlist_item *HapticByDevId(int device_id) { SDL_hapticlist_item *item; - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (device_id == item->device_id) { /*SDL_Log("=+=+=+=+=+= HapticByDevId id [%d]", device_id);*/ return item; @@ -81,7 +81,7 @@ static SDL_hapticlist_item *HapticByDevId(int device_id) const char *SDL_SYS_HapticName(int index) { SDL_hapticlist_item *item = HapticByOrder(index); - if (item == NULL) { + if (!item) { SDL_SetError("No such device"); return NULL; } @@ -90,11 +90,11 @@ const char *SDL_SYS_HapticName(int index) static SDL_hapticlist_item *OpenHaptic(SDL_Haptic *haptic, SDL_hapticlist_item *item) { - if (item == NULL) { + if (!item) { SDL_SetError("No such device"); return NULL; } - if (item->haptic != NULL) { + if (item->haptic) { SDL_SetError("Haptic already opened"); return NULL; } @@ -106,8 +106,7 @@ static SDL_hapticlist_item *OpenHaptic(SDL_Haptic *haptic, SDL_hapticlist_item * haptic->neffects = 1; haptic->nplaying = haptic->neffects; haptic->effects = (struct haptic_effect *)SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); - if (haptic->effects == NULL) { - SDL_OutOfMemory(); + if (!haptic->effects) { return NULL; } SDL_memset(haptic->effects, 0, sizeof(struct haptic_effect) * haptic->neffects); @@ -138,7 +137,7 @@ int SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick) { SDL_hapticlist_item *item; item = HapticByDevId(((joystick_hwdata *)joystick->hwdata)->device_id); - return (item != NULL) ? 1 : 0; + return (item) ? 1 : 0; } int SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) @@ -246,18 +245,18 @@ int Android_AddHaptic(int device_id, const char *name) { SDL_hapticlist_item *item; item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); - if (item == NULL) { + if (!item) { return -1; } item->device_id = device_id; item->name = SDL_strdup(name); - if (item->name == NULL) { + if (!item->name) { SDL_free(item); return -1; } - if (SDL_hapticlist_tail == NULL) { + if (!SDL_hapticlist_tail) { SDL_hapticlist = SDL_hapticlist_tail = item; } else { SDL_hapticlist_tail->next = item; @@ -273,12 +272,12 @@ int Android_RemoveHaptic(int device_id) SDL_hapticlist_item *item; SDL_hapticlist_item *prev = NULL; - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { /* found it, remove it. */ if (device_id == item->device_id) { const int retval = item->haptic ? item->haptic->index : -1; - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_hapticlist == item); diff --git a/src/haptic/android/SDL_syshaptic_c.h b/src/haptic/android/SDL_syshaptic_c.h index 001c0da0..f07f4e1d 100644 --- a/src/haptic/android/SDL_syshaptic_c.h +++ b/src/haptic/android/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/darwin/SDL_syshaptic.c b/src/haptic/darwin/SDL_syshaptic.c index d9e59048..a00cb9d6 100644 --- a/src/haptic/darwin/SDL_syshaptic.c +++ b/src/haptic/darwin/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -154,7 +154,7 @@ int SDL_SYS_HapticInit(void) /* Get HID devices. */ match = IOServiceMatching(kIOHIDDeviceKey); - if (match == NULL) { + if (!match) { return SDL_SetError("Haptic: Failed to get IOServiceMatching."); } @@ -226,7 +226,7 @@ int MacHaptic_MaybeAddDevice(io_object_t device) } item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); - if (item == NULL) { + if (!item) { return SDL_SetError("Could not allocate haptic storage"); } @@ -262,7 +262,7 @@ int MacHaptic_MaybeAddDevice(io_object_t device) CFRelease(hidProperties); } - if (SDL_hapticlist_tail == NULL) { + if (!SDL_hapticlist_tail) { SDL_hapticlist = SDL_hapticlist_tail = item; } else { SDL_hapticlist_tail->next = item; @@ -284,12 +284,12 @@ int MacHaptic_MaybeRemoveDevice(io_object_t device) return -1; /* not initialized. ignore this. */ } - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { /* found it, remove it. */ if (IOObjectIsEqualTo((io_object_t)item->dev, device)) { const int retval = item->haptic ? item->haptic->index : -1; - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_hapticlist == item); @@ -473,13 +473,10 @@ static int SDL_SYS_HapticOpenFromService(SDL_Haptic *haptic, io_service_t servic int ret2; /* Allocate the hwdata */ - haptic->hwdata = (struct haptic_hwdata *) - SDL_malloc(sizeof(*haptic->hwdata)); - if (haptic->hwdata == NULL) { - SDL_OutOfMemory(); + haptic->hwdata = (struct haptic_hwdata *) SDL_calloc(1, sizeof(*haptic->hwdata)); + if (!haptic->hwdata) { goto creat_err; } - SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); /* Open the device */ ret = FFCreateDevice(service, &haptic->hwdata->device); @@ -512,8 +509,7 @@ static int SDL_SYS_HapticOpenFromService(SDL_Haptic *haptic, io_service_t servic /* Allocate effects memory. */ haptic->effects = (struct haptic_effect *) SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); - if (haptic->effects == NULL) { - SDL_OutOfMemory(); + if (!haptic->effects) { goto open_err; } /* Clear the memory */ @@ -526,7 +522,7 @@ static int SDL_SYS_HapticOpenFromService(SDL_Haptic *haptic, io_service_t servic open_err: FFReleaseDevice(haptic->hwdata->device); creat_err: - if (haptic->hwdata != NULL) { + if (haptic->hwdata) { SDL_free(haptic->hwdata); haptic->hwdata = NULL; } @@ -699,8 +695,8 @@ static int SDL_SYS_SetDirection(FFEFFECT *effect, SDL_HapticDirection *dir, int /* Has axes. */ rglDir = SDL_malloc(sizeof(LONG) * naxes); - if (rglDir == NULL) { - return SDL_OutOfMemory(); + if (!rglDir) { + return -1; } SDL_memset(rglDir, 0, sizeof(LONG) * naxes); effect->rglDirection = rglDir; @@ -771,11 +767,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe dest->dwFlags = FFEFF_OBJECTOFFSETS; /* Seems obligatory. */ /* Envelope. */ - envelope = SDL_malloc(sizeof(FFENVELOPE)); - if (envelope == NULL) { - return SDL_OutOfMemory(); + envelope = SDL_calloc(1, sizeof(FFENVELOPE)); + if (!envelope) { + return -1; } - SDL_memset(envelope, 0, sizeof(FFENVELOPE)); dest->lpEnvelope = envelope; envelope->dwSize = sizeof(FFENVELOPE); /* Always should be this. */ @@ -787,8 +782,8 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe } if (dest->cAxes > 0) { axes = SDL_malloc(sizeof(DWORD) * dest->cAxes); - if (axes == NULL) { - return SDL_OutOfMemory(); + if (!axes) { + return -1; } axes[0] = haptic->hwdata->axes[0]; /* Always at least one axis. */ if (dest->cAxes > 1) { @@ -804,11 +799,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe switch (src->type) { case SDL_HAPTIC_CONSTANT: hap_constant = &src->constant; - constant = SDL_malloc(sizeof(FFCONSTANTFORCE)); - if (constant == NULL) { - return SDL_OutOfMemory(); + constant = SDL_calloc(1, sizeof(FFCONSTANTFORCE)); + if (!constant) { + return -1; } - SDL_memset(constant, 0, sizeof(FFCONSTANTFORCE)); /* Specifics */ constant->lMagnitude = CONVERT(hap_constant->level); @@ -846,11 +840,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe case SDL_HAPTIC_SAWTOOTHUP: case SDL_HAPTIC_SAWTOOTHDOWN: hap_periodic = &src->periodic; - periodic = SDL_malloc(sizeof(FFPERIODIC)); - if (periodic == NULL) { - return SDL_OutOfMemory(); + periodic = SDL_calloc(1, sizeof(FFPERIODIC)); + if (!periodic) { + return -1; } - SDL_memset(periodic, 0, sizeof(FFPERIODIC)); /* Specifics */ periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude)); @@ -891,11 +884,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe case SDL_HAPTIC_FRICTION: hap_condition = &src->condition; if (dest->cAxes > 0) { - condition = SDL_malloc(sizeof(FFCONDITION) * dest->cAxes); - if (condition == NULL) { - return SDL_OutOfMemory(); + condition = SDL_calloc(dest->cAxes, sizeof(FFCONDITION)); + if (!condition) { + return -1; } - SDL_memset(condition, 0, sizeof(FFCONDITION)); /* Specifics */ for (i = 0; i < dest->cAxes; i++) { @@ -934,11 +926,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe case SDL_HAPTIC_RAMP: hap_ramp = &src->ramp; - ramp = SDL_malloc(sizeof(FFRAMPFORCE)); - if (ramp == NULL) { - return SDL_OutOfMemory(); + ramp = SDL_calloc(1, sizeof(FFRAMPFORCE)); + if (!ramp) { + return -1; } - SDL_memset(ramp, 0, sizeof(FFRAMPFORCE)); /* Specifics */ ramp->lStart = CONVERT(hap_ramp->start); @@ -972,11 +963,10 @@ static int SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, SDL_HapticEffe case SDL_HAPTIC_CUSTOM: hap_custom = &src->custom; - custom = SDL_malloc(sizeof(FFCUSTOMFORCE)); - if (custom == NULL) { - return SDL_OutOfMemory(); + custom = SDL_calloc(1, sizeof(FFCUSTOMFORCE)); + if (!custom) { + return -1; } - SDL_memset(custom, 0, sizeof(FFCUSTOMFORCE)); /* Specifics */ custom->cChannels = hap_custom->channels; @@ -1033,7 +1023,7 @@ static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type) effect->lpEnvelope = NULL; SDL_free(effect->rgdwAxes); effect->rgdwAxes = NULL; - if (effect->lpvTypeSpecificParams != NULL) { + if (effect->lpvTypeSpecificParams) { if (type == SDL_HAPTIC_CUSTOM) { /* Must free the custom data. */ custom = (FFCUSTOMFORCE *)effect->lpvTypeSpecificParams; SDL_free(custom->rglForceData); @@ -1107,15 +1097,14 @@ int SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, /* Alloc the effect. */ effect->hweffect = (struct haptic_hweffect *) - SDL_malloc(sizeof(struct haptic_hweffect)); - if (effect->hweffect == NULL) { - SDL_OutOfMemory(); + SDL_calloc(1, sizeof(struct haptic_hweffect)); + if (!effect->hweffect) { goto err_hweffect; } /* Get the type. */ type = SDL_SYS_HapticEffectType(base->type); - if (type == NULL) { + if (!type) { goto err_hweffect; } diff --git a/src/haptic/darwin/SDL_syshaptic_c.h b/src/haptic/darwin/SDL_syshaptic_c.h index 896a71b2..67bc8635 100644 --- a/src/haptic/darwin/SDL_syshaptic_c.h +++ b/src/haptic/darwin/SDL_syshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/dummy/SDL_syshaptic.c b/src/haptic/dummy/SDL_syshaptic.c index 480195fc..419f7bad 100644 --- a/src/haptic/dummy/SDL_syshaptic.c +++ b/src/haptic/dummy/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/linux/SDL_syshaptic.c b/src/haptic/linux/SDL_syshaptic.c index 549354e3..c4e6c183 100644 --- a/src/haptic/linux/SDL_syshaptic.c +++ b/src/haptic/linux/SDL_syshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -199,7 +199,7 @@ static SDL_hapticlist_item *HapticByDevIndex(int device_index) #ifdef SDL_USE_LIBUDEV static void haptic_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) { - if (devpath == NULL || !(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) { + if (!devpath || !(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) { return; } @@ -225,7 +225,7 @@ static int MaybeAddDevice(const char *path) int success; SDL_hapticlist_item *item; - if (path == NULL) { + if (!path) { return -1; } @@ -235,7 +235,7 @@ static int MaybeAddDevice(const char *path) } /* check for duplicates */ - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (item->dev_num == sb.st_rdev) { return -1; /* duplicate. */ } @@ -259,12 +259,12 @@ static int MaybeAddDevice(const char *path) } item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); - if (item == NULL) { + if (!item) { return -1; } item->fname = SDL_strdup(path); - if (item->fname == NULL) { + if (!item->fname) { SDL_free(item); return -1; } @@ -272,7 +272,7 @@ static int MaybeAddDevice(const char *path) item->dev_num = sb.st_rdev; /* TODO: should we add instance IDs? */ - if (SDL_hapticlist_tail == NULL) { + if (!SDL_hapticlist_tail) { SDL_hapticlist = SDL_hapticlist_tail = item; } else { SDL_hapticlist_tail->next = item; @@ -292,16 +292,16 @@ static int MaybeRemoveDevice(const char *path) SDL_hapticlist_item *item; SDL_hapticlist_item *prev = NULL; - if (path == NULL) { + if (!path) { return -1; } - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { /* found it, remove it. */ if (SDL_strcmp(path, item->fname) == 0) { const int retval = item->haptic ? item->haptic->index : -1; - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_hapticlist == item); @@ -358,7 +358,7 @@ const char *SDL_SYS_HapticName(int index) if (fd >= 0) { name = SDL_SYS_HapticNameFromFD(fd); - if (name == NULL) { + if (!name) { /* No name found, return device character device */ name = item->fname; } @@ -375,12 +375,10 @@ static int SDL_SYS_HapticOpenFromFD(SDL_Haptic *haptic, int fd) { /* Allocate the hwdata */ haptic->hwdata = (struct haptic_hwdata *) - SDL_malloc(sizeof(*haptic->hwdata)); - if (haptic->hwdata == NULL) { - SDL_OutOfMemory(); + SDL_calloc(1, sizeof(*haptic->hwdata)); + if (!haptic->hwdata) { goto open_err; } - SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); /* Set the data. */ haptic->hwdata->fd = fd; @@ -396,8 +394,7 @@ static int SDL_SYS_HapticOpenFromFD(SDL_Haptic *haptic, int fd) haptic->nplaying = haptic->neffects; /* Linux makes no distinction. */ haptic->effects = (struct haptic_effect *) SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); - if (haptic->effects == NULL) { - SDL_OutOfMemory(); + if (!haptic->effects) { goto open_err; } /* Clear the memory */ @@ -409,7 +406,7 @@ static int SDL_SYS_HapticOpenFromFD(SDL_Haptic *haptic, int fd) /* Error handling */ open_err: close(fd); - if (haptic->hwdata != NULL) { + if (haptic->hwdata) { SDL_free(haptic->hwdata); haptic->hwdata = NULL; } @@ -640,17 +637,6 @@ static int SDL_SYS_ToDirection(Uint16 *dest, SDL_HapticDirection *src) switch (src->type) { case SDL_HAPTIC_POLAR: - /* Linux directions start from south. - (and range from 0 to 0xFFFF) - Quoting include/linux/input.h, line 926: - Direction of the effect is encoded as follows: - 0 deg -> 0x0000 (down) - 90 deg -> 0x4000 (left) - 180 deg -> 0x8000 (up) - 270 deg -> 0xC000 (right) - The force pulls into the direction specified by Linux directions, - i.e. the opposite convention of SDL directions. - */ tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ *dest = (Uint16)tmp; break; @@ -914,9 +900,9 @@ int SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, /* Allocate the hardware effect */ effect->hweffect = (struct haptic_hweffect *) - SDL_malloc(sizeof(struct haptic_hweffect)); - if (effect->hweffect == NULL) { - return SDL_OutOfMemory(); + SDL_calloc(1, sizeof(struct haptic_hweffect)); + if (!effect->hweffect) { + return -1; } /* Prepare the ff_effect */ diff --git a/src/haptic/windows/SDL_dinputhaptic.c b/src/haptic/windows/SDL_dinputhaptic.c index 20901a16..04f4a48c 100644 --- a/src/haptic/windows/SDL_dinputhaptic.c +++ b/src/haptic/windows/SDL_dinputhaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -92,7 +92,7 @@ int SDL_DINPUT_HapticInit(void) /* Because we used CoCreateInstance, we need to Initialize it, first. */ instance = GetModuleHandle(NULL); - if (instance == NULL) { + if (!instance) { SDL_SYS_HapticQuit(); return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError()); @@ -133,7 +133,7 @@ int SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance) DIDEVCAPS capabilities; SDL_hapticlist_item *item = NULL; - if (dinput == NULL) { + if (!dinput) { return -1; /* not initialized. We'll pick these up on enumeration if we init later. */ } @@ -166,8 +166,8 @@ int SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance) } item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); - if (item == NULL) { - return SDL_OutOfMemory(); + if (!item) { + return -1; } item->name = WIN_StringToUTF8(pdidInstance->tszProductName); @@ -188,11 +188,11 @@ int SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance) SDL_hapticlist_item *item; SDL_hapticlist_item *prev = NULL; - if (dinput == NULL) { + if (!dinput) { return -1; /* not initialized, ignore this. */ } - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (!item->bXInputHaptic && SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) { /* found it, remove it. */ return SDL_SYS_RemoveHapticDevice(prev, item); @@ -286,11 +286,10 @@ static int SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVI DIPROPDWORD dipdw; /* Allocate the hwdata */ - haptic->hwdata = (struct haptic_hwdata *)SDL_malloc(sizeof(*haptic->hwdata)); - if (haptic->hwdata == NULL) { - return SDL_OutOfMemory(); + haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata)); + if (!haptic->hwdata) { + return -1; } - SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); /* We'll use the device8 from now on. */ haptic->hwdata->device = device8; @@ -401,8 +400,7 @@ static int SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVI /* Prepare effects memory. */ haptic->effects = (struct haptic_effect *) SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); - if (haptic->effects == NULL) { - SDL_OutOfMemory(); + if (!haptic->effects) { goto acquire_err; } /* Clear the memory */ @@ -474,7 +472,7 @@ int SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick } /* Since it comes from a joystick we have to try to match it with a haptic device on our haptic list. */ - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (!item->bXInputHaptic && WIN_IsEqualGUID(&item->instance.guidInstance, &joy_instance.guidInstance)) { haptic->index = index; return SDL_DINPUT_HapticOpenFromDevice(haptic, joystick->hwdata->InputDevice, SDL_TRUE); @@ -540,8 +538,8 @@ static int SDL_SYS_SetDirection(DIEFFECT *effect, SDL_HapticDirection *dir, int /* Has axes. */ rglDir = SDL_malloc(sizeof(LONG) * naxes); - if (rglDir == NULL) { - return SDL_OutOfMemory(); + if (!rglDir) { + return -1; } SDL_memset(rglDir, 0, sizeof(LONG) * naxes); effect->rglDirection = rglDir; @@ -613,11 +611,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, dest->dwFlags = DIEFF_OBJECTOFFSETS; /* Seems obligatory. */ /* Envelope. */ - envelope = SDL_malloc(sizeof(DIENVELOPE)); - if (envelope == NULL) { - return SDL_OutOfMemory(); + envelope = SDL_calloc(1, sizeof(DIENVELOPE)); + if (!envelope) { + return -1; } - SDL_memset(envelope, 0, sizeof(DIENVELOPE)); dest->lpEnvelope = envelope; envelope->dwSize = sizeof(DIENVELOPE); /* Always should be this. */ @@ -629,8 +626,8 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, } if (dest->cAxes > 0) { axes = SDL_malloc(sizeof(DWORD) * dest->cAxes); - if (axes == NULL) { - return SDL_OutOfMemory(); + if (!axes) { + return -1; } axes[0] = haptic->hwdata->axes[0]; /* Always at least one axis. */ if (dest->cAxes > 1) { @@ -646,11 +643,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, switch (src->type) { case SDL_HAPTIC_CONSTANT: hap_constant = &src->constant; - constant = SDL_malloc(sizeof(DICONSTANTFORCE)); - if (constant == NULL) { - return SDL_OutOfMemory(); + constant = SDL_calloc(1, sizeof(DICONSTANTFORCE)); + if (!constant) { + return -1; } - SDL_memset(constant, 0, sizeof(DICONSTANTFORCE)); /* Specifics */ constant->lMagnitude = CONVERT(hap_constant->level); @@ -688,11 +684,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, case SDL_HAPTIC_SAWTOOTHUP: case SDL_HAPTIC_SAWTOOTHDOWN: hap_periodic = &src->periodic; - periodic = SDL_malloc(sizeof(DIPERIODIC)); - if (periodic == NULL) { - return SDL_OutOfMemory(); + periodic = SDL_calloc(1, sizeof(DIPERIODIC)); + if (!periodic) { + return -1; } - SDL_memset(periodic, 0, sizeof(DIPERIODIC)); /* Specifics */ periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude)); @@ -732,11 +727,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, case SDL_HAPTIC_INERTIA: case SDL_HAPTIC_FRICTION: hap_condition = &src->condition; - condition = SDL_malloc(sizeof(DICONDITION) * dest->cAxes); - if (condition == NULL) { - return SDL_OutOfMemory(); + condition = SDL_calloc(dest->cAxes, sizeof(DICONDITION)); + if (!condition) { + return -1; } - SDL_memset(condition, 0, sizeof(DICONDITION)); /* Specifics */ for (i = 0; i < (int)dest->cAxes; i++) { @@ -773,11 +767,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, case SDL_HAPTIC_RAMP: hap_ramp = &src->ramp; - ramp = SDL_malloc(sizeof(DIRAMPFORCE)); - if (ramp == NULL) { - return SDL_OutOfMemory(); + ramp = SDL_calloc(1, sizeof(DIRAMPFORCE)); + if (!ramp) { + return -1; } - SDL_memset(ramp, 0, sizeof(DIRAMPFORCE)); /* Specifics */ ramp->lStart = CONVERT(hap_ramp->start); @@ -811,11 +804,10 @@ static int SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest, case SDL_HAPTIC_CUSTOM: hap_custom = &src->custom; - custom = SDL_malloc(sizeof(DICUSTOMFORCE)); - if (custom == NULL) { - return SDL_OutOfMemory(); + custom = SDL_calloc(1, sizeof(DICUSTOMFORCE)); + if (!custom) { + return -1; } - SDL_memset(custom, 0, sizeof(DICUSTOMFORCE)); /* Specifics */ custom->cChannels = hap_custom->channels; @@ -871,7 +863,7 @@ static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT *effect, int type) effect->lpEnvelope = NULL; SDL_free(effect->rgdwAxes); effect->rgdwAxes = NULL; - if (effect->lpvTypeSpecificParams != NULL) { + if (effect->lpvTypeSpecificParams) { if (type == SDL_HAPTIC_CUSTOM) { /* Must free the custom data. */ custom = (DICUSTOMFORCE *)effect->lpvTypeSpecificParams; SDL_free(custom->rglForceData); @@ -937,7 +929,7 @@ int SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, HRESULT ret; REFGUID type = SDL_SYS_HapticEffectType(base); - if (type == NULL) { + if (!type) { return SDL_SetError("Haptic: Unknown effect type."); } diff --git a/src/haptic/windows/SDL_dinputhaptic_c.h b/src/haptic/windows/SDL_dinputhaptic_c.h index 89dc4c11..0f87afbc 100644 --- a/src/haptic/windows/SDL_dinputhaptic_c.h +++ b/src/haptic/windows/SDL_dinputhaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/windows/SDL_windowshaptic.c b/src/haptic/windows/SDL_windowshaptic.c index 1a2d5d63..b7dbae3f 100644 --- a/src/haptic/windows/SDL_windowshaptic.c +++ b/src/haptic/windows/SDL_windowshaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -75,7 +75,7 @@ int SDL_SYS_HapticInit(void) int SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item) { - if (SDL_hapticlist_tail == NULL) { + if (!SDL_hapticlist_tail) { SDL_hapticlist = SDL_hapticlist_tail = item; } else { SDL_hapticlist_tail->next = item; @@ -91,7 +91,7 @@ int SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item) int SDL_SYS_RemoveHapticDevice(SDL_hapticlist_item *prev, SDL_hapticlist_item *item) { const int retval = item->haptic ? item->haptic->index : -1; - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_hapticlist == item); @@ -159,7 +159,7 @@ int SDL_SYS_HapticMouse(void) int index = 0; /* Grab the first mouse haptic device we find. */ - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER) { return index; } @@ -291,13 +291,10 @@ int SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, int result; /* Alloc the effect. */ - effect->hweffect = (struct haptic_hweffect *) - SDL_malloc(sizeof(struct haptic_hweffect)); - if (effect->hweffect == NULL) { - SDL_OutOfMemory(); + effect->hweffect = (struct haptic_hweffect *) SDL_calloc(1, sizeof(struct haptic_hweffect)); + if (!effect->hweffect) { return -1; } - SDL_zerop(effect->hweffect); if (haptic->hwdata->bXInputHaptic) { result = SDL_XINPUT_HapticNewEffect(haptic, effect, base); diff --git a/src/haptic/windows/SDL_windowshaptic_c.h b/src/haptic/windows/SDL_windowshaptic_c.h index 9219dcf0..a84430c3 100644 --- a/src/haptic/windows/SDL_windowshaptic_c.h +++ b/src/haptic/windows/SDL_windowshaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/haptic/windows/SDL_xinputhaptic.c b/src/haptic/windows/SDL_xinputhaptic.c index 681b4571..6e8de2a0 100644 --- a/src/haptic/windows/SDL_xinputhaptic.c +++ b/src/haptic/windows/SDL_xinputhaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,7 +43,7 @@ static SDL_bool loaded_xinput = SDL_FALSE; int SDL_XINPUT_HapticInit(void) { if (SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE)) { - loaded_xinput = (WIN_LoadXInputDLL() == 0) ? SDL_TRUE : SDL_FALSE; + loaded_xinput = (WIN_LoadXInputDLL() == 0); } /* If the joystick subsystem is active, it will manage adding XInput haptic devices */ @@ -78,17 +78,15 @@ int SDL_XINPUT_HapticMaybeAddDevice(const DWORD dwUserid) return -1; /* no force feedback on this device. */ } - item = (SDL_hapticlist_item *)SDL_malloc(sizeof(SDL_hapticlist_item)); - if (item == NULL) { - return SDL_OutOfMemory(); + item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item)); + if (!item) { + return -1; } - SDL_zerop(item); - /* !!! FIXME: I'm not bothering to query for a real name right now (can we even?) */ { char buf[64]; - (void)SDL_snprintf(buf, sizeof(buf), "XInput Controller #%u", userid + 1); + (void)SDL_snprintf(buf, sizeof(buf), "XInput Controller #%d", 1 + userid); item->name = SDL_strdup(buf); } @@ -114,7 +112,7 @@ int SDL_XINPUT_HapticMaybeRemoveDevice(const DWORD dwUserid) return -1; } - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (item->bXInputHaptic && item->userid == userid) { /* found it, remove it. */ return SDL_SYS_RemoveHapticDevice(prev, item); @@ -173,36 +171,35 @@ static int SDL_XINPUT_HapticOpenFromUserIndex(SDL_Haptic *haptic, const Uint8 us /* Prepare effects memory. */ haptic->effects = (struct haptic_effect *) SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); - if (haptic->effects == NULL) { - return SDL_OutOfMemory(); + if (!haptic->effects) { + return -1; } /* Clear the memory */ SDL_memset(haptic->effects, 0, sizeof(struct haptic_effect) * haptic->neffects); - haptic->hwdata = (struct haptic_hwdata *)SDL_malloc(sizeof(*haptic->hwdata)); - if (haptic->hwdata == NULL) { + haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata)); + if (!haptic->hwdata) { SDL_free(haptic->effects); haptic->effects = NULL; - return SDL_OutOfMemory(); + return -1; } - SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); haptic->hwdata->bXInputHaptic = 1; haptic->hwdata->userid = userid; haptic->hwdata->mutex = SDL_CreateMutex(); - if (haptic->hwdata->mutex == NULL) { + if (!haptic->hwdata->mutex) { SDL_free(haptic->effects); SDL_free(haptic->hwdata); haptic->effects = NULL; return SDL_SetError("Couldn't create XInput haptic mutex"); } - (void)SDL_snprintf(threadName, sizeof(threadName), "SDLXInputDev%d", userid); + (void)SDL_snprintf(threadName, sizeof(threadName), "SDLXInputDev%u", userid); haptic->hwdata->thread = SDL_CreateThreadInternal(SDL_RunXInputHaptic, threadName, 64 * 1024, haptic->hwdata); - if (haptic->hwdata->thread == NULL) { + if (!haptic->hwdata->thread) { SDL_DestroyMutex(haptic->hwdata->mutex); SDL_free(haptic->effects); SDL_free(haptic->hwdata); @@ -229,7 +226,7 @@ int SDL_XINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick Uint8 index = 0; /* Since it comes from a joystick we have to try to match it with a haptic device on our haptic list. */ - for (item = SDL_hapticlist; item != NULL; item = item->next) { + for (item = SDL_hapticlist; item; item = item->next) { if (item->bXInputHaptic && item->userid == joystick->hwdata->userid) { haptic->index = index; return SDL_XINPUT_HapticOpenFromUserIndex(haptic, joystick->hwdata->userid); @@ -286,7 +283,7 @@ int SDL_XINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, } else if ((!effect->effect.leftright.length) || (!iterations)) { /* do nothing. Effect runs for zero milliseconds. */ } else { - haptic->hwdata->stopTicks = SDL_GetTicks() + (effect->effect.leftright.length * iterations); + haptic->hwdata->stopTicks = SDL_GetTicks() + ((Uint64)effect->effect.leftright.length * iterations); } SDL_UnlockMutex(haptic->hwdata->mutex); return (XINPUTSETSTATE(haptic->hwdata->userid, vib) == ERROR_SUCCESS) ? 0 : -1; diff --git a/src/haptic/windows/SDL_xinputhaptic_c.h b/src/haptic/windows/SDL_xinputhaptic_c.h index 47a64807..b61fd5c9 100644 --- a/src/haptic/windows/SDL_xinputhaptic_c.h +++ b/src/haptic/windows/SDL_xinputhaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/BUILD.cmake.md b/src/hidapi/BUILD.cmake.md index aa5ba740..5555fd2e 100644 --- a/src/hidapi/BUILD.cmake.md +++ b/src/hidapi/BUILD.cmake.md @@ -214,7 +214,7 @@ This is done to let the host project's developer decide what is important (what In a _subdirectory build_, even if not set, those variables remain unchanged, so a host project's developer has a full control over the HIDAPI build configuration. Available CMake targets after `add_subdirectory(hidapi)` _are the same as in case of [standalone build](#standalone-package-build)_, and a few additional ones: -- `hidapi_include` - the interface library; `hidapi::hidapi` is an alias of it; +- `hidapi_include` - the interface library; `hidapi::include` is an alias of it; - `hidapi_winapi` - library target on Windows; `hidapi::winapi` is an alias of it; - `hidapi_darwin` - library target on macOS; `hidapi::darwin` is an alias of it; - `hidapi_libusb` - library target for libusb backend; `hidapi::libusb` is an alias of it; diff --git a/src/hidapi/CMakeLists.txt b/src/hidapi/CMakeLists.txt index e18ee23b..b4c99be5 100644 --- a/src/hidapi/CMakeLists.txt +++ b/src/hidapi/CMakeLists.txt @@ -42,6 +42,9 @@ elseif(NOT WIN32) option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON) option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON) endif() + if(CMAKE_SYSTEM_NAME MATCHES "NetBSD") + option(HIDAPI_WITH_NETBSD "Build NetBSD/UHID implementation of HIDAPI" ON) + endif() endif() option(BUILD_SHARED_LIBS "Build shared version of the libraries, otherwise build statically" ON) diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index b31683b3..97a60c42 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -573,6 +573,8 @@ typedef struct PLATFORM_hid_device_ PLATFORM_hid_device; #ifdef __LINUX__ #include "SDL_hidapi_linux.h" +#elif defined(__NETBSD__) +#include "SDL_hidapi_netbsd.h" #elif defined(__MACOS__) #include "SDL_hidapi_mac.h" #elif defined(__WINDOWS__) || defined(__WINGDK__) diff --git a/src/hidapi/SDL_hidapi_android.h b/src/hidapi/SDL_hidapi_android.h index 4fe554c7..58d80c30 100644 --- a/src/hidapi/SDL_hidapi_android.h +++ b/src/hidapi/SDL_hidapi_android.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_c.h b/src/hidapi/SDL_hidapi_c.h index 59e4f3e8..62148cdc 100644 --- a/src/hidapi/SDL_hidapi_c.h +++ b/src/hidapi/SDL_hidapi_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_ios.h b/src/hidapi/SDL_hidapi_ios.h index c921fde3..4b61150a 100644 --- a/src/hidapi/SDL_hidapi_ios.h +++ b/src/hidapi/SDL_hidapi_ios.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_libusb.h b/src/hidapi/SDL_hidapi_libusb.h index 456a4bf1..9f62be52 100644 --- a/src/hidapi/SDL_hidapi_libusb.h +++ b/src/hidapi/SDL_hidapi_libusb.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_linux.h b/src/hidapi/SDL_hidapi_linux.h index 7face241..388af323 100644 --- a/src/hidapi/SDL_hidapi_linux.h +++ b/src/hidapi/SDL_hidapi_linux.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_mac.h b/src/hidapi/SDL_hidapi_mac.h index d7c0f500..423a9469 100644 --- a/src/hidapi/SDL_hidapi_mac.h +++ b/src/hidapi/SDL_hidapi_mac.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_netbsd.h b/src/hidapi/SDL_hidapi_netbsd.h new file mode 100644 index 00000000..fa5cf972 --- /dev/null +++ b/src/hidapi/SDL_hidapi_netbsd.h @@ -0,0 +1,25 @@ +/* + Simple DirectMedia Layer + 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 + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#undef HIDAPI_H__ +#include "netbsd/hid.c" +#define HAVE_PLATFORM_BACKEND 1 +#define udev_ctx 1 diff --git a/src/hidapi/SDL_hidapi_steamxbox.h b/src/hidapi/SDL_hidapi_steamxbox.h index 0570acb3..cab4ef58 100644 --- a/src/hidapi/SDL_hidapi_steamxbox.h +++ b/src/hidapi/SDL_hidapi_steamxbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/SDL_hidapi_windows.h b/src/hidapi/SDL_hidapi_windows.h index 09604c67..f96bbd53 100644 --- a/src/hidapi/SDL_hidapi_windows.h +++ b/src/hidapi/SDL_hidapi_windows.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/doxygen/Doxyfile b/src/hidapi/doxygen/Doxyfile index 4e01360e..b0bc233d 100644 --- a/src/hidapi/doxygen/Doxyfile +++ b/src/hidapi/doxygen/Doxyfile @@ -214,7 +214,7 @@ JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# requiring an explicit command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO diff --git a/src/hidapi/hidtest/test.c b/src/hidapi/hidtest/test.c index 94bbf37a..1eb65822 100644 --- a/src/hidapi/hidtest/test.c +++ b/src/hidapi/hidtest/test.c @@ -77,7 +77,7 @@ void print_device(struct hid_device_info *cur_dev) { printf(" Release: %hx\n", cur_dev->release_number); printf(" Interface: %d\n", cur_dev->interface_number); printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page); - printf(" Bus type: %d (%s)\n", cur_dev->bus_type, hid_bus_name(cur_dev->bus_type)); + printf(" Bus type: %u (%s)\n", (unsigned)cur_dev->bus_type, hid_bus_name(cur_dev->bus_type)); printf("\n"); } diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index 880f8d61..f0ec00ca 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -123,6 +123,7 @@ struct hid_device_ { /* Quirks */ int skip_output_report_id; + int no_skip_output_report_id; int no_output_reports_on_intr_ep; /* List of received input reports. */ @@ -867,6 +868,7 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de static const int xb1_iface_subclass = 71; static const int xb1_iface_protocol = 208; static const int supported_vendors[] = { + 0x03f0, /* HP */ 0x044f, /* Thrustmaster */ 0x045e, /* Microsoft */ 0x0738, /* Mad Catz */ @@ -1346,6 +1348,7 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa /* Initialize XBox 360 controllers */ if (is_xbox360(desc.idVendor, intf_desc)) { + dev->no_skip_output_report_id = 1; init_xbox360(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc); } @@ -1571,7 +1574,7 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t report_number = data[0]; - if (report_number == 0x0 || dev->skip_output_report_id) { + if ((!dev->no_skip_output_report_id && report_number == 0x0) || dev->skip_output_report_id) { data++; length--; skipped_report_id = 1; @@ -2085,7 +2088,7 @@ uint16_t get_usb_code_for_current_locale(void) return 0x0; /* Make a copy of the current locale string. */ - strncpy(search_string, locale, sizeof(search_string)); + strncpy(search_string, locale, sizeof(search_string)-1); search_string[sizeof(search_string)-1] = '\0'; /* Chop off the encoding part, and make it lower case. */ diff --git a/src/hidapi/libusb/hidapi_thread_sdl.h b/src/hidapi/libusb/hidapi_thread_sdl.h index f96eacf8..78b7e09e 100644 --- a/src/hidapi/libusb/hidapi_thread_sdl.h +++ b/src/hidapi/libusb/hidapi_thread_sdl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c index 20c66450..ee6666b6 100644 --- a/src/hidapi/mac/hid.c +++ b/src/hidapi/mac/hid.c @@ -54,15 +54,15 @@ static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrie { (void) attr; - if(count == 0) { + if (count == 0) { errno = EINVAL; return -1; } - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + if (pthread_mutex_init(&barrier->mutex, 0) < 0) { return -1; } - if(pthread_cond_init(&barrier->cond, 0) < 0) { + if (pthread_cond_init(&barrier->cond, 0) < 0) { pthread_mutex_destroy(&barrier->mutex); return -1; } @@ -83,16 +83,18 @@ static int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&barrier->mutex); ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { + if (barrier->count >= barrier->trip_count) { barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); pthread_mutex_unlock(&barrier->mutex); + pthread_cond_broadcast(&barrier->cond); return 1; } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + else { + do { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + } + while (barrier->count != 0); + pthread_mutex_unlock(&barrier->mutex); return 0; } @@ -1230,7 +1232,9 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) return buffer (data), and delete the liked list item. */ struct input_report *rpt = dev->input_reports; size_t len = (length < rpt->len)? length: rpt->len; - memcpy(data, rpt->data, len); + if (data != NULL) { + memcpy(data, rpt->data, len); + } dev->input_reports = rpt->next; free(rpt->data); free(rpt); diff --git a/src/hidapi/netbsd/CMakeLists.txt b/src/hidapi/netbsd/CMakeLists.txt new file mode 100644 index 00000000..86067f89 --- /dev/null +++ b/src/hidapi/netbsd/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.6.3 FATAL_ERROR) + +add_library(hidapi_netbsd + ${HIDAPI_PUBLIC_HEADERS} + hid.c +) +target_link_libraries(hidapi_netbsd PUBLIC hidapi_include) + +find_package(Threads REQUIRED) + +target_link_libraries(hidapi_netbsd PRIVATE Threads::Threads) + +set_target_properties(hidapi_netbsd + PROPERTIES + EXPORT_NAME "netbsd" + OUTPUT_NAME "hidapi-netbsd" + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}" +) + +# compatibility with find_package() +add_library(hidapi::netbsd ALIAS hidapi_netbsd) +# compatibility with raw library link +add_library(hidapi-netbsd ALIAS hidapi_netbsd) + +if(HIDAPI_INSTALL_TARGETS) + install(TARGETS hidapi_netbsd EXPORT hidapi + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/hidapi" + ) +endif() + +hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi-netbsd.pc.in") diff --git a/src/hidapi/netbsd/README.md b/src/hidapi/netbsd/README.md new file mode 100644 index 00000000..f1b12a0a --- /dev/null +++ b/src/hidapi/netbsd/README.md @@ -0,0 +1,29 @@ +Implementation Notes +-------------------- +NetBSD maps every `uhidev` device to one or more `uhid` +devices. Each `uhid` device only supports one report ID. +The parent device `uhidev` creates one `uhid` device per +report ID found in the hardware's report descriptor. + +In the event there are no report ID(s) found within the +report descriptor, only one `uhid` device with a report ID +of `0` is created. + +In order to remain compatible with existing `hidapi` APIs, +all the `uhid` devices created by the parent `uhidev` device +must be opened under the same `hid_device` instance to ensure +that we can route reports to their appropriate `uhid` device. + +Internally the `uhid` driver will insert the report ID as +needed so we must also omit the report ID in any situation +where the `hidapi` API expects it to be included in the +report data stream. + +Given the design of `uhid`, it must be augmented with extra +platform specific APIs to ensure that the exact relationship +between `uhidev` devices and `uhid` devices can be determined. + +The NetBSD implementation does this via the `drvctl` kernel +driver. At present there is no known way to do this on OpenBSD +for a `uhid` implementation to be at the same level as the +NetBSD one. diff --git a/src/hidapi/netbsd/hid.c b/src/hidapi/netbsd/hid.c new file mode 100644 index 00000000..4c4d0a1a --- /dev/null +++ b/src/hidapi/netbsd/hid.c @@ -0,0 +1,1173 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + James Buren + libusb/hidapi Team + + Copyright 2023, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/* C */ +#include +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include + +/* NetBSD */ +#include +#include +#include + +#include "../hidapi/hidapi.h" + +#define HIDAPI_MAX_CHILD_DEVICES 256 + +struct hid_device_ { + int device_handle; + int blocking; + wchar_t *last_error_str; + struct hid_device_info *device_info; + size_t poll_handles_length; + struct pollfd poll_handles[256]; + int report_handles[256]; + char path[USB_MAX_DEVNAMELEN]; +}; + +struct hid_enumerate_data { + struct hid_device_info *root; + struct hid_device_info *end; + int drvctl; + uint16_t vendor_id; + uint16_t product_id; +}; + +typedef void (*enumerate_devices_callback) (const struct usb_device_info *, void *); + +static wchar_t *last_global_error_str = NULL; + +/* The caller must free the returned string with free(). */ +static wchar_t *utf8_to_wchar_t(const char *utf8) +{ + wchar_t *ret = NULL; + + if (utf8) { + size_t wlen = mbstowcs(NULL, utf8, 0); + if ((size_t) -1 == wlen) { + return wcsdup(L""); + } + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } + mbstowcs(ret, utf8, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; +} + +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[256]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&last_global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&last_global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->last_error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->last_error_str, format, args); + va_end(args); +} + + +/* + * Gets the size of the HID item at the given position + * Returns 1 if successful, 0 if an invalid key + * Sets data_len and key_size when successful + */ +static int get_hid_item_size(const uint8_t *report_descriptor, uint32_t size, unsigned int pos, int *data_len, int *key_size) +{ + int key = report_descriptor[pos]; + int size_code; + + /* + * This is a Long Item. The next byte contains the + * length of the data section (value) for this key. + * See the HID specification, version 1.11, section + * 6.2.2.3, titled "Long Items." + */ + if ((key & 0xf0) == 0xf0) { + if (pos + 1 < size) + { + *data_len = report_descriptor[pos + 1]; + *key_size = 3; + return 1; + } + *data_len = 0; /* malformed report */ + *key_size = 0; + } + + /* + * This is a Short Item. The bottom two bits of the + * key contain the size code for the data section + * (value) for this key. Refer to the HID + * specification, version 1.11, section 6.2.2.2, + * titled "Short Items." + */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + *data_len = size_code; + *key_size = 1; + return 1; + case 3: + *data_len = 4; + *key_size = 1; + return 1; + default: + /* Can't ever happen since size_code is & 0x3 */ + *data_len = 0; + *key_size = 0; + break; + }; + + /* malformed report */ + return 0; +} + +/* + * Get bytes from a HID Report Descriptor. + * Only call with a num_bytes of 0, 1, 2, or 4. + */ +static uint32_t get_hid_report_bytes(const uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) +{ + /* Return if there aren't enough bytes. */ + if (cur + num_bytes >= len) + return 0; + + if (num_bytes == 0) + return 0; + else if (num_bytes == 1) + return rpt[cur + 1]; + else if (num_bytes == 2) + return (rpt[cur + 2] * 256 + rpt[cur + 1]); + else if (num_bytes == 4) + return ( + rpt[cur + 4] * 0x01000000 + + rpt[cur + 3] * 0x00010000 + + rpt[cur + 2] * 0x00000100 + + rpt[cur + 1] * 0x00000001 + ); + else + return 0; +} + +/* + * Iterates until the end of a Collection. + * Assumes that *pos is exactly at the beginning of a Collection. + * Skips all nested Collection, i.e. iterates until the end of current level Collection. + * + * The return value is non-0 when an end of current Collection is found, + * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin, + * or no Collection is found at all). + */ +static int hid_iterate_over_collection(const uint8_t *report_descriptor, uint32_t size, unsigned int *pos, int *data_len, int *key_size) +{ + int collection_level = 0; + + while (*pos < size) { + int key = report_descriptor[*pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size)) + return 0; /* malformed report */ + + switch (key_cmd) { + case 0xa0: /* Collection 6.2.2.4 (Main) */ + collection_level++; + break; + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + collection_level--; + break; + } + + if (collection_level < 0) { + /* Broken descriptor or someone is using this function wrong, + * i.e. should be called exactly at the collection start */ + return 0; + } + + if (collection_level == 0) { + /* Found it! + * Also possible when called not at the collection start, but should not happen if used correctly */ + return 1; + } + + *pos += *data_len + *key_size; + } + + return 0; /* Did not find the end of a Collection */ +} + +struct hid_usage_iterator { + unsigned int pos; + int usage_page_found; + unsigned short usage_page; +}; + +/* + * Retrieves the device's Usage Page and Usage from the report descriptor. + * The algorithm returns the current Usage Page/Usage pair whenever a new + * Collection is found and a Usage Local Item is currently in scope. + * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). + * The algorithm should give similar results as Apple's: + * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc + * Physical Collections are also matched (macOS does the same). + * + * This function can be called repeatedly until it returns non-0 + * Usage is found. pos is the starting point (initially 0) and will be updated + * to the next search position. + * + * The return value is 0 when a pair is found. + * 1 when finished processing descriptor. + * -1 on a malformed report. + */ +static int get_next_hid_usage(const uint8_t *report_descriptor, uint32_t size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage) +{ + int data_len, key_size; + int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */ + + int usage_found = 0; + + while (ctx->pos < size) { + int key = report_descriptor[ctx->pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size)) + return -1; /* malformed report */ + + switch (key_cmd) { + case 0x4: /* Usage Page 6.2.2.7 (Global) */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + ctx->usage_page_found = 1; + break; + + case 0x8: /* Usage 6.2.2.8 (Local) */ + if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2); + ctx->usage_page_found = 1; + *usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos); + usage_found = 1; + } + else { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + usage_found = 1; + } + break; + + case 0xa0: /* Collection 6.2.2.4 (Main) */ + if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) { + return -1; + } + + /* A pair is valid - to be reported when Collection is found */ + if (usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; + } + + break; + } + + /* Skip over this key and its associated data */ + ctx->pos += data_len + key_size; + } + + /* If no top-level application collection is found and usage page/usage pair is found, pair is valid + https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ + if (initial && usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; /* success */ + } + + return 1; /* finished processing */ +} + +static struct hid_device_info *create_device_info(const struct usb_device_info *udi, const char *path, const struct usb_ctl_report_desc *ucrd) +{ + struct hid_device_info *root; + struct hid_device_info *end; + + root = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info)); + if (!root) + return NULL; + + end = root; + + /* Path */ + end->path = (path) ? strdup(path) : NULL; + + /* Vendor Id */ + end->vendor_id = udi->udi_vendorNo; + + /* Product Id */ + end->product_id = udi->udi_productNo; + + /* Serial Number */ + end->serial_number = utf8_to_wchar_t(udi->udi_serial); + + /* Release Number */ + end->release_number = udi->udi_releaseNo; + + /* Manufacturer String */ + end->manufacturer_string = utf8_to_wchar_t(udi->udi_vendor); + + /* Product String */ + end->product_string = utf8_to_wchar_t(udi->udi_product); + + /* Usage Page */ + end->usage_page = 0; + + /* Usage */ + end->usage = 0; + + /* Interface Number */ + end->interface_number = -1; + + /* Next Device Info */ + end->next = NULL; + + /* Bus Type */ + end->bus_type = HID_API_BUS_USB; + + if (ucrd) { + uint16_t page; + uint16_t usage; + struct hid_usage_iterator usage_iterator; + + page = usage = 0; + memset(&usage_iterator, 0, sizeof(usage_iterator)); + + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (get_next_hid_usage(ucrd->ucrd_data, ucrd->ucrd_size, &usage_iterator, &page, &usage) == 0) { + end->usage_page = page; + end->usage = usage; + } + + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (get_next_hid_usage(ucrd->ucrd_data, ucrd->ucrd_size, &usage_iterator, &page, &usage) == 0) { + /* Create new record for additional usage pairs */ + struct hid_device_info *node = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info)); + + if (!node) + continue; + + /* Update fields */ + node->path = (end->path) ? strdup(end->path) : NULL; + node->vendor_id = end->vendor_id; + node->product_id = end->product_id; + node->serial_number = (end->serial_number) ? wcsdup(end->serial_number) : NULL; + node->release_number = end->release_number; + node->manufacturer_string = (end->manufacturer_string) ? wcsdup(end->manufacturer_string) : NULL; + node->product_string = (end->product_string) ? wcsdup(end->product_string) : NULL; + node->usage_page = page; + node->usage = usage; + node->interface_number = end->interface_number; + node->next = NULL; + node->bus_type = end->bus_type; + + /* Insert node */ + end->next = node; + end = node; + } + } + + return root; +} + +static int is_usb_controller(const char *s) +{ + return (!strncmp(s, "usb", 3) && isdigit((int) s[3])); +} + +static int is_uhid_parent_device(const char *s) +{ + return (!strncmp(s, "uhidev", 6) && isdigit((int) s[6])); +} + +static int is_uhid_device(const char *s) +{ + return (!strncmp(s, "uhid", 4) && isdigit((int) s[4])); +} + +static void walk_device_tree(int drvctl, const char *dev, int depth, char arr[static HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN], size_t *len, int (*cmp) (const char *)) +{ + int res; + char childname[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN]; + struct devlistargs dla; + + if (depth && (!dev || !*dev)) + return; + + if (cmp(dev) && *len < HIDAPI_MAX_CHILD_DEVICES) + strlcpy(arr[(*len)++], dev, sizeof(*arr)); + + strlcpy(dla.l_devname, dev, sizeof(dla.l_devname)); + dla.l_childname = childname; + dla.l_children = HIDAPI_MAX_CHILD_DEVICES; + + res = ioctl(drvctl, DRVLISTDEV, &dla); + if (res == -1) + return; + + /* + * DO NOT CHANGE THIS. This is a fail-safe check + * for the unlikely event that a parent device has + * more than HIDAPI_MAX_CHILD_DEVICES child devices + * to prevent iterating over uninitialized data. + */ + if (dla.l_children > HIDAPI_MAX_CHILD_DEVICES) + return; + + for (size_t i = 0; i < dla.l_children; i++) + walk_device_tree(drvctl, dla.l_childname[i], depth + 1, arr, len, cmp); +} + +static void enumerate_usb_devices(int bus, uint8_t addr, enumerate_devices_callback func, void *data) +{ + int res; + struct usb_device_info udi; + + udi.udi_addr = addr; + + res = ioctl(bus, USB_DEVICEINFO, &udi); + if (res == -1) + return; + + for (int port = 0; port < udi.udi_nports; port++) { + addr = udi.udi_ports[port]; + if (addr >= USB_MAX_DEVICES) + continue; + + enumerate_usb_devices(bus, addr, func, data); + } + + func(&udi, data); +} + +static void hid_enumerate_callback(const struct usb_device_info *udi, void *data) +{ + struct hid_enumerate_data *hed; + + hed = (struct hid_enumerate_data *) data; + + if (hed->vendor_id != 0 && hed->vendor_id != udi->udi_vendorNo) + return; + + if (hed->product_id != 0 && hed->product_id != udi->udi_productNo) + return; + + for (size_t i = 0; i < USB_MAX_DEVNAMES; i++) { + const char *parent_dev; + char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN]; + size_t len; + const char *child_dev; + char devpath[USB_MAX_DEVNAMELEN]; + int uhid; + struct usb_ctl_report_desc ucrd; + int use_ucrd; + struct hid_device_info *node; + + parent_dev = udi->udi_devnames[i]; + if (!is_uhid_parent_device(parent_dev)) + continue; + + len = 0; + walk_device_tree(hed->drvctl, parent_dev, 0, arr, &len, is_uhid_device); + + if (len == 0) + continue; + + child_dev = arr[0]; + strlcpy(devpath, "/dev/", sizeof(devpath)); + strlcat(devpath, child_dev, sizeof(devpath)); + + uhid = open(devpath, O_RDONLY | O_CLOEXEC); + if (uhid >= 0) { + use_ucrd = (ioctl(uhid, USB_GET_REPORT_DESC, &ucrd) != -1); + close(uhid); + } else { + use_ucrd = 0; + } + + node = create_device_info(udi, parent_dev, (use_ucrd) ? &ucrd : NULL); + if (!node) + continue; + + if (!hed->root) { + hed->root = node; + hed->end = node; + } else { + hed->end->next = node; + hed->end = node; + } + + while (hed->end->next) + hed->end = hed->end->next; + } +} + +static int set_report(hid_device *dev, const uint8_t *data, size_t length, int report) +{ + int res; + int device_handle; + struct usb_ctl_report ucr; + + if (length < 1) { + register_device_error(dev, "report must be greater than 1 byte"); + return -1; + } + + device_handle = dev->report_handles[*data]; + if (device_handle < 0) { + register_device_error_format(dev, "unsupported report id: %hhu", *data); + return -1; + } + + length--; + data++; + + if (length > sizeof(ucr.ucr_data)) { + register_device_error_format(dev, "report must be less than or equal to %zu bytes", sizeof(ucr.ucr_data)); + return -1; + } + + ucr.ucr_report = report; + memcpy(ucr.ucr_data, data, length); + + res = ioctl(device_handle, USB_SET_REPORT, &ucr); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_SET_REPORT): %s", strerror(errno)); + return -1; + } + + return (int) (length + 1); +} + +static int get_report(hid_device *dev, uint8_t *data, size_t length, int report) +{ + int res; + int device_handle; + struct usb_ctl_report ucr; + + if (length < 1) { + register_device_error(dev, "report must be greater than 1 byte"); + return -1; + } + + device_handle = dev->report_handles[*data]; + if (device_handle < 0) { + register_device_error_format(dev, "unsupported report id: %hhu", *data); + return -1; + } + + length--; + data++; + + if (length > sizeof(ucr.ucr_data)) { + length = sizeof(ucr.ucr_data); + } + + ucr.ucr_report = report; + + res = ioctl(device_handle, USB_GET_REPORT, &ucr); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_GET_REPORT): %s", strerror(errno)); + return -1; + } + + memcpy(data, ucr.ucr_data, length); + + return (int) (length + 1); +} + +int HID_API_EXPORT HID_API_CALL hid_init(void) +{ + /* indicate no error */ + register_global_error(NULL); + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_exit(void) +{ + /* Free global error message */ + register_global_error(NULL); + + return 0; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + int res; + int drvctl; + char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN]; + size_t len; + struct hid_enumerate_data hed; + + res = hid_init(); + if (res == -1) + return NULL; + + drvctl = open(DRVCTLDEV, O_RDONLY | O_CLOEXEC); + if (drvctl == -1) { + register_global_error_format("failed to open drvctl: %s", strerror(errno)); + return NULL; + } + + len = 0; + walk_device_tree(drvctl, "", 0, arr, &len, is_usb_controller); + + hed.root = NULL; + hed.end = NULL; + hed.drvctl = drvctl; + hed.vendor_id = vendor_id; + hed.product_id = product_id; + + for (size_t i = 0; i < len; i++) { + char devpath[USB_MAX_DEVNAMELEN]; + int bus; + + strlcpy(devpath, "/dev/", sizeof(devpath)); + strlcat(devpath, arr[i], sizeof(devpath)); + + bus = open(devpath, O_RDONLY | O_CLOEXEC); + if (bus == -1) + continue; + + enumerate_usb_devices(bus, 0, hid_enumerate_callback, &hed); + + close(bus); + } + + close(drvctl); + + return hed.root; +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + while (devs) { + struct hid_device_info *next = devs->next; + free(devs->path); + free(devs->serial_number); + free(devs->manufacturer_string); + free(devs->product_string); + free(devs); + devs = next; + } +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + struct hid_device_info *devs; + struct hid_device_info *dev; + char path[USB_MAX_DEVNAMELEN]; + + devs = hid_enumerate(vendor_id, product_id); + if (!devs) + return NULL; + + *path = '\0'; + + for (dev = devs; dev; dev = dev->next) { + if (dev->vendor_id != vendor_id) + continue; + + if (dev->product_id != product_id) + continue; + + if (serial_number && wcscmp(dev->serial_number, serial_number)) + continue; + + strlcpy(path, dev->path, sizeof(path)); + + break; + } + + hid_free_enumeration(devs); + + if (*path == '\0') { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); + return NULL; + } + + return hid_open_path(path); +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) +{ + int res; + hid_device *dev; + int drvctl; + char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN]; + size_t len; + + res = hid_init(); + if (res == -1) + goto err_0; + + dev = (hid_device *) calloc(1, sizeof(hid_device)); + if (!dev) { + register_global_error("could not allocate hid_device"); + goto err_0; + } + + drvctl = open(DRVCTLDEV, O_RDONLY | O_CLOEXEC); + if (drvctl == -1) { + register_global_error_format("failed to open drvctl: %s", strerror(errno)); + goto err_1; + } + + if (!is_uhid_parent_device(path)) { + register_global_error("not a uhidev device"); + goto err_2; + } + + len = 0; + walk_device_tree(drvctl, path, 0, arr, &len, is_uhid_device); + + dev->poll_handles_length = 0; + memset(dev->poll_handles, 0x00, sizeof(dev->poll_handles)); + memset(dev->report_handles, 0xff, sizeof(dev->report_handles)); + + for (size_t i = 0; i < len; i++) { + const char *child_dev; + char devpath[USB_MAX_DEVNAMELEN]; + int uhid; + int rep_id; + struct pollfd *ph; + + child_dev = arr[i]; + strlcpy(devpath, "/dev/", sizeof(devpath)); + strlcat(devpath, child_dev, sizeof(devpath)); + + uhid = open(devpath, O_RDWR | O_CLOEXEC); + if (uhid == -1) { + register_global_error_format("failed to open %s: %s", child_dev, strerror(errno)); + goto err_3; + } + + res = ioctl(uhid, USB_GET_REPORT_ID, &rep_id); + if (res == -1) { + close(uhid); + register_global_error_format("failed to get report id %s: %s", child_dev, strerror(errno)); + goto err_3; + } + + ph = &dev->poll_handles[dev->poll_handles_length++]; + ph->fd = uhid; + ph->events = POLLIN; + ph->revents = 0; + dev->report_handles[rep_id] = uhid; + dev->device_handle = uhid; + } + + dev->blocking = 1; + dev->last_error_str = NULL; + dev->device_info = NULL; + strlcpy(dev->path, path, sizeof(dev->path)); + + register_global_error(NULL); + return dev; + +err_3: + for (size_t i = 0; i < dev->poll_handles_length; i++) + close(dev->poll_handles[i].fd); +err_2: + close(drvctl); +err_1: + free(dev); +err_0: + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, data, length, UHID_OUTPUT_REPORT); +} + +int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + int res; + size_t i; + struct pollfd *ph; + ssize_t n; + + res = poll(dev->poll_handles, dev->poll_handles_length, milliseconds); + if (res == -1) { + register_device_error_format(dev, "error while polling: %s", strerror(errno)); + return -1; + } + + if (res == 0) + return 0; + + for (i = 0; i < dev->poll_handles_length; i++) { + ph = &dev->poll_handles[i]; + + if (ph->revents & (POLLERR | POLLHUP | POLLNVAL)) { + register_device_error(dev, "device IO error while polling"); + return -1; + } + + if (ph->revents & POLLIN) + break; + } + + if (i == dev->poll_handles_length) + return 0; + + n = read(ph->fd, data, length); + if (n == -1) { + if (errno == EAGAIN || errno == EINPROGRESS) + n = 0; + else + register_device_error_format(dev, "error while reading: %s", strerror(errno)); + } + + return n; +} + +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking) ? -1 : 0); +} + +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, data, length, UHID_FEATURE_REPORT); +} + +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + return get_report(dev, data, length, UHID_FEATURE_REPORT); +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + return get_report(dev, data, length, UHID_INPUT_REPORT); +} + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Free the device error message */ + register_device_error(dev, NULL); + + hid_free_enumeration(dev->device_info); + + for (size_t i = 0; i < dev->poll_handles_length; i++) + close(dev->poll_handles[i].fd); + + free(dev); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + struct hid_device_info *hdi; + + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + hdi = hid_get_device_info(dev); + if (!dev) + return -1; + + if (hdi->manufacturer_string) { + wcsncpy(string, hdi->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + } else { + string[0] = L'\0'; + } + + return 0; +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + struct hid_device_info *hdi; + + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + hdi = hid_get_device_info(dev); + if (!dev) + return -1; + + if (hdi->product_string) { + wcsncpy(string, hdi->product_string, maxlen); + string[maxlen - 1] = L'\0'; + } else { + string[0] = L'\0'; + } + + return 0; +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + struct hid_device_info *hdi; + + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + hdi = hid_get_device_info(dev); + if (!dev) + return -1; + + if (hdi->serial_number) { + wcsncpy(string, hdi->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + } else { + string[0] = L'\0'; + } + + return 0; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev) +{ + int res; + struct usb_device_info udi; + struct usb_ctl_report_desc ucrd; + int use_ucrd; + struct hid_device_info *hdi; + + if (dev->device_info) + return dev->device_info; + + res = ioctl(dev->device_handle, USB_GET_DEVICEINFO, &udi); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_GET_DEVICEINFO): %s", strerror(errno)); + return NULL; + } + + use_ucrd = (ioctl(dev->device_handle, USB_GET_REPORT_DESC, &ucrd) != -1); + + hdi = create_device_info(&udi, dev->path, (use_ucrd) ? &ucrd : NULL); + if (!hdi) { + register_device_error(dev, "failed to create device info"); + return NULL; + } + + dev->device_info = hdi; + + return hdi; +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + int res; + struct usb_string_desc usd; + usb_string_descriptor_t *str; + iconv_t ic; + const char *src; + size_t srcleft; + char *dst; + size_t dstleft; + size_t ic_res; + + /* First let us get the supported language IDs. */ + usd.usd_string_index = 0; + usd.usd_language_id = 0; + + res = ioctl(dev->device_handle, USB_GET_STRING_DESC, &usd); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_GET_STRING_DESC): %s", strerror(errno)); + return -1; + } + + str = &usd.usd_desc; + + if (str->bLength < 4) { + register_device_error(dev, "failed to get supported language IDs"); + return -1; + } + + /* Now we can get the requested string. */ + usd.usd_string_index = string_index; + usd.usd_language_id = UGETW(str->bString[0]); + + res = ioctl(dev->device_handle, USB_GET_STRING_DESC, &usd); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_GET_STRING_DESC): %s", strerror(errno)); + return -1; + } + + /* Now we need to convert it, using iconv. */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + ic = iconv_open("utf-32le", "utf-16le"); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + ic = iconv_open("utf-32be", "utf-16le"); +#endif + if (ic == (iconv_t) -1) { + register_device_error_format(dev, "iconv_open failed: %s", strerror(errno)); + return -1; + } + + src = (const char *) str->bString; + srcleft = str->bLength - 2; + dst = (char *) string; + dstleft = sizeof(wchar_t[maxlen]); + + ic_res = iconv(ic, &src, &srcleft, &dst, &dstleft); + iconv_close(ic); + if (ic_res == (size_t) -1) { + register_device_error_format(dev, "iconv failed: %s", strerror(errno)); + return -1; + } + + /* Write the terminating NULL. */ + string[maxlen - 1] = L'\0'; + if (dstleft >= sizeof(wchar_t)) + *((wchar_t *) dst) = L'\0'; + + return 0; +} + +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + int res; + struct usb_ctl_report_desc ucrd; + + res = ioctl(dev->device_handle, USB_GET_REPORT_DESC, &ucrd); + if (res == -1) { + register_device_error_format(dev, "ioctl (USB_GET_REPORT_DESC): %s", strerror(errno)); + return -1; + } + + if ((size_t) ucrd.ucrd_size < buf_size) + buf_size = (size_t) ucrd.ucrd_size; + + memcpy(buf, ucrd.ucrd_data, buf_size); + + return (int) buf_size; +} + +HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev) +{ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; + } + + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; +} + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) +{ + static const struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH + }; + + return &api_version; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) +{ + return HID_API_VERSION_STR; +} diff --git a/src/hidapi/pc/hidapi-netbsd.pc.in b/src/hidapi/pc/hidapi-netbsd.pc.in new file mode 100644 index 00000000..fcb5ca21 --- /dev/null +++ b/src/hidapi/pc/hidapi-netbsd.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: hidapi-netbsd +Description: C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the netbsd implementation. +URL: https://github.com/libusb/hidapi +Version: @VERSION@ +Libs: -L${libdir} -lhidapi-netbsd +Cflags: -I${includedir}/hidapi diff --git a/src/hidapi/src/CMakeLists.txt b/src/hidapi/src/CMakeLists.txt index d08224be..e663c3fd 100644 --- a/src/hidapi/src/CMakeLists.txt +++ b/src/hidapi/src/CMakeLists.txt @@ -68,8 +68,9 @@ function(hidapi_configure_pc PC_IN_FILE) get_filename_component(PC_IN_FILENAME "${PC_IN_FILE}" NAME_WE) set(PC_FILE "${CMAKE_CURRENT_BINARY_DIR}/pc/${PC_IN_FILENAME}.pc") configure_file("${PC_IN_FILE}" "${PC_FILE}" @ONLY) - - install(FILES "${PC_FILE}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") + if(HIDAPI_INSTALL_TARGETS) + install(FILES "${PC_FILE}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") + endif() endfunction() # The library @@ -140,6 +141,18 @@ else() set(HIDAPI_NEED_EXPORT_LIBUDEV TRUE) endif() endif() + elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD") + if(NOT DEFINED HIDAPI_WITH_NETBSD) + set(HIDAPI_WITH_NETBSD ON) + endif() + if(HIDAPI_WITH_NETBSD) + add_subdirectory("${PROJECT_ROOT}/netbsd" netbsd) + list(APPEND EXPORT_COMPONENTS netbsd) + set(EXPORT_ALIAS netbsd) + if(NOT BUILD_SHARED_LIBS) + set(HIDAPI_NEED_EXPORT_THREADS TRUE) + endif() + endif() else() set(HIDAPI_WITH_LIBUSB ON) endif() diff --git a/src/hidapi/windows/hid.c b/src/hidapi/windows/hid.c index e8b6ee98..3a90a3db 100644 --- a/src/hidapi/windows/hid.c +++ b/src/hidapi/windows/hid.c @@ -120,7 +120,7 @@ static void free_library_handles() cfgmgr32_lib_handle = NULL; } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wcast-function-type" #endif @@ -170,7 +170,7 @@ err: return -1; } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic pop #endif @@ -325,7 +325,7 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR #endif } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #endif @@ -355,7 +355,7 @@ static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR #endif /* HIDAPI_USING_SDL_RUNTIME */ } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic pop #endif @@ -1462,7 +1462,7 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length); } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wcast-function-type" #endif @@ -1486,7 +1486,7 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) } free_hid_device(dev); } -#if defined(__GNUC__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA # pragma GCC diagnostic pop #endif diff --git a/src/hidapi/windows/hidapi_descriptor_reconstruct.h b/src/hidapi/windows/hidapi_descriptor_reconstruct.h index 5f0b54aa..a6223b6a 100644 --- a/src/hidapi/windows/hidapi_descriptor_reconstruct.h +++ b/src/hidapi/windows/hidapi_descriptor_reconstruct.h @@ -37,6 +37,7 @@ #include #include "hidapi_hidsdi.h" +/*#include */ #define NUM_OF_HIDP_REPORT_TYPES 3 @@ -125,6 +126,14 @@ typedef struct hid_pp_link_collection_node_ { // Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end } hid_pp_link_collection_node, *phid_pp_link_collection_node; +// Note: This is risk-reduction-measure for this specific struct, as it has ULONG bit-field. +// Although very unlikely, it might still be possible that the compiler creates a memory layout that is +// not binary compatile. +// Other structs are not checked at the time of writing. +//static_assert(sizeof(struct hid_pp_link_collection_node_) == 16, +// "Size of struct hid_pp_link_collection_node_ not as expected. This might break binary compatibility"); +SDL_COMPILE_TIME_ASSERT(hid_pp_link_collection_node_, sizeof(struct hid_pp_link_collection_node_) == 16); + typedef struct hidp_unknown_token_ { UCHAR Token; /* Specifies the one-byte prefix of a global item. */ UCHAR Reserved[3]; @@ -213,15 +222,19 @@ typedef struct hidp_preparsed_data_ { USHORT FirstByteOfLinkCollectionArray; USHORT NumberLinkCollectionNodes; -#if defined(__MINGW32__) || defined(__CYGWIN__) +#ifndef _MSC_VER // MINGW fails with: Flexible array member in union not supported // Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html union { +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" +#endif hid_pp_cap caps[0]; hid_pp_link_collection_node LinkCollectionArray[0]; +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop +#endif }; #else union { diff --git a/src/hidapi/windows/pp_data_dump/pp_data_dump.c b/src/hidapi/windows/pp_data_dump/pp_data_dump.c index 561dc96a..d5df68b5 100644 --- a/src/hidapi/windows/pp_data_dump/pp_data_dump.c +++ b/src/hidapi/windows/pp_data_dump/pp_data_dump.c @@ -1,95 +1,95 @@ - +#if defined(__MINGW32__) + // Needed for %hh + #define __USE_MINGW_ANSI_STDIO 1 +#endif #include #include <../windows/hidapi_descriptor_reconstruct.h> #include -#if defined(__MINGW32__) -#pragma GCC diagnostic ignored "-Wformat" -#pragma GCC diagnostic ignored "-Wformat-extra-args" -#endif - void dump_hid_pp_cap(FILE* file, phid_pp_cap pp_cap, unsigned int cap_idx) { - fprintf(file, "pp_data->cap[%d]->UsagePage = 0x%04hX\n", cap_idx, pp_cap->UsagePage); - fprintf(file, "pp_data->cap[%d]->ReportID = 0x%02hhX\n", cap_idx, pp_cap->ReportID); - fprintf(file, "pp_data->cap[%d]->BitPosition = %hhu\n", cap_idx, pp_cap->BitPosition); - fprintf(file, "pp_data->cap[%d]->BitSize = %hu\n", cap_idx, pp_cap->ReportSize); - fprintf(file, "pp_data->cap[%d]->ReportCount = %hu\n", cap_idx, pp_cap->ReportCount); - fprintf(file, "pp_data->cap[%d]->BytePosition = 0x%04hX\n", cap_idx, pp_cap->BytePosition); - fprintf(file, "pp_data->cap[%d]->BitCount = %hu\n", cap_idx, pp_cap->BitCount); - fprintf(file, "pp_data->cap[%d]->BitField = 0x%02lX\n", cap_idx, pp_cap->BitField); - fprintf(file, "pp_data->cap[%d]->NextBytePosition = 0x%04hX\n", cap_idx, pp_cap->NextBytePosition); - fprintf(file, "pp_data->cap[%d]->LinkCollection = 0x%04hX\n", cap_idx, pp_cap->LinkCollection); - fprintf(file, "pp_data->cap[%d]->LinkUsagePage = 0x%04hX\n", cap_idx, pp_cap->LinkUsagePage); - fprintf(file, "pp_data->cap[%d]->LinkUsage = 0x%04hX\n", cap_idx, pp_cap->LinkUsage); + fprintf(file, "pp_data->cap[%u]->UsagePage = 0x%04hX\n", cap_idx, pp_cap->UsagePage); + fprintf(file, "pp_data->cap[%u]->ReportID = 0x%02hhX\n", cap_idx, pp_cap->ReportID); + fprintf(file, "pp_data->cap[%u]->BitPosition = %hhu\n", cap_idx, pp_cap->BitPosition); + fprintf(file, "pp_data->cap[%u]->BitSize = %hu\n", cap_idx, pp_cap->ReportSize); + fprintf(file, "pp_data->cap[%u]->ReportCount = %hu\n", cap_idx, pp_cap->ReportCount); + fprintf(file, "pp_data->cap[%u]->BytePosition = 0x%04hX\n", cap_idx, pp_cap->BytePosition); + fprintf(file, "pp_data->cap[%u]->BitCount = %hu\n", cap_idx, pp_cap->BitCount); + fprintf(file, "pp_data->cap[%u]->BitField = 0x%02lX\n", cap_idx, pp_cap->BitField); + fprintf(file, "pp_data->cap[%u]->NextBytePosition = 0x%04hX\n", cap_idx, pp_cap->NextBytePosition); + fprintf(file, "pp_data->cap[%u]->LinkCollection = 0x%04hX\n", cap_idx, pp_cap->LinkCollection); + fprintf(file, "pp_data->cap[%u]->LinkUsagePage = 0x%04hX\n", cap_idx, pp_cap->LinkUsagePage); + fprintf(file, "pp_data->cap[%u]->LinkUsage = 0x%04hX\n", cap_idx, pp_cap->LinkUsage); // 8 Flags in one byte - fprintf(file, "pp_data->cap[%d]->IsMultipleItemsForArray = %hhu\n", cap_idx, pp_cap->IsMultipleItemsForArray); - fprintf(file, "pp_data->cap[%d]->IsButtonCap = %hhu\n", cap_idx, pp_cap->IsButtonCap); - fprintf(file, "pp_data->cap[%d]->IsPadding = %hhu\n", cap_idx, pp_cap->IsPadding); - fprintf(file, "pp_data->cap[%d]->IsAbsolute = %hhu\n", cap_idx, pp_cap->IsAbsolute); - fprintf(file, "pp_data->cap[%d]->IsRange = %hhu\n", cap_idx, pp_cap->IsRange); - fprintf(file, "pp_data->cap[%d]->IsAlias = %hhu\n", cap_idx, pp_cap->IsAlias); - fprintf(file, "pp_data->cap[%d]->IsStringRange = %hhu\n", cap_idx, pp_cap->IsStringRange); - fprintf(file, "pp_data->cap[%d]->IsDesignatorRange = %hhu\n", cap_idx, pp_cap->IsDesignatorRange); + fprintf(file, "pp_data->cap[%u]->IsMultipleItemsForArray = %hhu\n", cap_idx, pp_cap->IsMultipleItemsForArray); + fprintf(file, "pp_data->cap[%u]->IsButtonCap = %hhu\n", cap_idx, pp_cap->IsButtonCap); + fprintf(file, "pp_data->cap[%u]->IsPadding = %hhu\n", cap_idx, pp_cap->IsPadding); + fprintf(file, "pp_data->cap[%u]->IsAbsolute = %hhu\n", cap_idx, pp_cap->IsAbsolute); + fprintf(file, "pp_data->cap[%u]->IsRange = %hhu\n", cap_idx, pp_cap->IsRange); + fprintf(file, "pp_data->cap[%u]->IsAlias = %hhu\n", cap_idx, pp_cap->IsAlias); + fprintf(file, "pp_data->cap[%u]->IsStringRange = %hhu\n", cap_idx, pp_cap->IsStringRange); + fprintf(file, "pp_data->cap[%u]->IsDesignatorRange = %hhu\n", cap_idx, pp_cap->IsDesignatorRange); - fprintf(file, "pp_data->cap[%d]->Reserved1 = 0x%02hhX%02hhX%02hhX\n", cap_idx, pp_cap->Reserved1[0], pp_cap->Reserved1[1], pp_cap->Reserved1[2]); + fprintf(file, "pp_data->cap[%u]->Reserved1 = 0x%02hhX%02hhX%02hhX\n", cap_idx, pp_cap->Reserved1[0], pp_cap->Reserved1[1], pp_cap->Reserved1[2]); for (int token_idx = 0; token_idx < 4; token_idx++) { - fprintf(file, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].Token = 0x%02hhX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].Token); - fprintf(file, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].Reserved = 0x%02hhX%02hhX%02hhX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].Reserved[0], pp_cap->UnknownTokens[token_idx].Reserved[1], pp_cap->UnknownTokens[token_idx].Reserved[2]); - fprintf(file, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].BitField = 0x%08lX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].BitField); + fprintf(file, "pp_data->cap[%u]->pp_cap->UnknownTokens[%d].Token = 0x%02hhX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].Token); + fprintf(file, "pp_data->cap[%u]->pp_cap->UnknownTokens[%d].Reserved = 0x%02hhX%02hhX%02hhX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].Reserved[0], pp_cap->UnknownTokens[token_idx].Reserved[1], pp_cap->UnknownTokens[token_idx].Reserved[2]); + fprintf(file, "pp_data->cap[%u]->pp_cap->UnknownTokens[%d].BitField = 0x%08lX\n", cap_idx, token_idx, pp_cap->UnknownTokens[token_idx].BitField); } if (pp_cap->IsRange) { - fprintf(file, "pp_data->cap[%d]->Range.UsageMin = 0x%04hX\n", cap_idx, pp_cap->Range.UsageMin); - fprintf(file, "pp_data->cap[%d]->Range.UsageMax = 0x%04hX\n", cap_idx, pp_cap->Range.UsageMax); - fprintf(file, "pp_data->cap[%d]->Range.StringMin = %hu\n", cap_idx, pp_cap->Range.StringMin); - fprintf(file, "pp_data->cap[%d]->Range.StringMax = %hu\n", cap_idx, pp_cap->Range.StringMax); - fprintf(file, "pp_data->cap[%d]->Range.DesignatorMin = %hu\n", cap_idx, pp_cap->Range.DesignatorMin); - fprintf(file, "pp_data->cap[%d]->Range.DesignatorMax = %hu\n", cap_idx, pp_cap->Range.DesignatorMax); - fprintf(file, "pp_data->cap[%d]->Range.DataIndexMin = %hu\n", cap_idx, pp_cap->Range.DataIndexMin); - fprintf(file, "pp_data->cap[%d]->Range.DataIndexMax = %hu\n", cap_idx, pp_cap->Range.DataIndexMax); + fprintf(file, "pp_data->cap[%u]->Range.UsageMin = 0x%04hX\n", cap_idx, pp_cap->Range.UsageMin); + fprintf(file, "pp_data->cap[%u]->Range.UsageMax = 0x%04hX\n", cap_idx, pp_cap->Range.UsageMax); + fprintf(file, "pp_data->cap[%u]->Range.StringMin = %hu\n", cap_idx, pp_cap->Range.StringMin); + fprintf(file, "pp_data->cap[%u]->Range.StringMax = %hu\n", cap_idx, pp_cap->Range.StringMax); + fprintf(file, "pp_data->cap[%u]->Range.DesignatorMin = %hu\n", cap_idx, pp_cap->Range.DesignatorMin); + fprintf(file, "pp_data->cap[%u]->Range.DesignatorMax = %hu\n", cap_idx, pp_cap->Range.DesignatorMax); + fprintf(file, "pp_data->cap[%u]->Range.DataIndexMin = %hu\n", cap_idx, pp_cap->Range.DataIndexMin); + fprintf(file, "pp_data->cap[%u]->Range.DataIndexMax = %hu\n", cap_idx, pp_cap->Range.DataIndexMax); } else { - fprintf(file, "pp_data->cap[%d]->NotRange.Usage = 0x%04hX\n", cap_idx, pp_cap->NotRange.Usage); - fprintf(file, "pp_data->cap[%d]->NotRange.Reserved1 = 0x%04hX\n", cap_idx, pp_cap->NotRange.Reserved1); - fprintf(file, "pp_data->cap[%d]->NotRange.StringIndex = %hu\n", cap_idx, pp_cap->NotRange.StringIndex); - fprintf(file, "pp_data->cap[%d]->NotRange.Reserved2 = %hu\n", cap_idx, pp_cap->NotRange.Reserved2); - fprintf(file, "pp_data->cap[%d]->NotRange.DesignatorIndex = %hu\n", cap_idx, pp_cap->NotRange.DesignatorIndex); - fprintf(file, "pp_data->cap[%d]->NotRange.Reserved3 = %hu\n", cap_idx, pp_cap->NotRange.Reserved3); - fprintf(file, "pp_data->cap[%d]->NotRange.DataIndex = %hu\n", cap_idx, pp_cap->NotRange.DataIndex); - fprintf(file, "pp_data->cap[%d]->NotRange.Reserved4 = %hu\n", cap_idx, pp_cap->NotRange.Reserved4); + fprintf(file, "pp_data->cap[%u]->NotRange.Usage = 0x%04hX\n", cap_idx, pp_cap->NotRange.Usage); + fprintf(file, "pp_data->cap[%u]->NotRange.Reserved1 = 0x%04hX\n", cap_idx, pp_cap->NotRange.Reserved1); + fprintf(file, "pp_data->cap[%u]->NotRange.StringIndex = %hu\n", cap_idx, pp_cap->NotRange.StringIndex); + fprintf(file, "pp_data->cap[%u]->NotRange.Reserved2 = %hu\n", cap_idx, pp_cap->NotRange.Reserved2); + fprintf(file, "pp_data->cap[%u]->NotRange.DesignatorIndex = %hu\n", cap_idx, pp_cap->NotRange.DesignatorIndex); + fprintf(file, "pp_data->cap[%u]->NotRange.Reserved3 = %hu\n", cap_idx, pp_cap->NotRange.Reserved3); + fprintf(file, "pp_data->cap[%u]->NotRange.DataIndex = %hu\n", cap_idx, pp_cap->NotRange.DataIndex); + fprintf(file, "pp_data->cap[%u]->NotRange.Reserved4 = %hu\n", cap_idx, pp_cap->NotRange.Reserved4); } if (pp_cap->IsButtonCap) { - fprintf(file, "pp_data->cap[%d]->Button.LogicalMin = %ld\n", cap_idx, pp_cap->Button.LogicalMin); - fprintf(file, "pp_data->cap[%d]->Button.LogicalMax = %ld\n", cap_idx, pp_cap->Button.LogicalMax); + fprintf(file, "pp_data->cap[%u]->Button.LogicalMin = %ld\n", cap_idx, pp_cap->Button.LogicalMin); + fprintf(file, "pp_data->cap[%u]->Button.LogicalMax = %ld\n", cap_idx, pp_cap->Button.LogicalMax); } else { - fprintf(file, "pp_data->cap[%d]->NotButton.HasNull = %hhu\n", cap_idx, pp_cap->NotButton.HasNull); - fprintf(file, "pp_data->cap[%d]->NotButton.Reserved4 = 0x%02hhX%02hhX%02hhX\n", cap_idx, pp_cap->NotButton.Reserved4[0], pp_cap->NotButton.Reserved4[1], pp_cap->NotButton.Reserved4[2]); - fprintf(file, "pp_data->cap[%d]->NotButton.LogicalMin = %ld\n", cap_idx, pp_cap->NotButton.LogicalMin); - fprintf(file, "pp_data->cap[%d]->NotButton.LogicalMax = %ld\n", cap_idx, pp_cap->NotButton.LogicalMax); - fprintf(file, "pp_data->cap[%d]->NotButton.PhysicalMin = %ld\n", cap_idx, pp_cap->NotButton.PhysicalMin); - fprintf(file, "pp_data->cap[%d]->NotButton.PhysicalMax = %ld\n", cap_idx, pp_cap->NotButton.PhysicalMax); + fprintf(file, "pp_data->cap[%u]->NotButton.HasNull = %hhu\n", cap_idx, pp_cap->NotButton.HasNull); + fprintf(file, "pp_data->cap[%u]->NotButton.Reserved4 = 0x%02hhX%02hhX%02hhX\n", cap_idx, pp_cap->NotButton.Reserved4[0], pp_cap->NotButton.Reserved4[1], pp_cap->NotButton.Reserved4[2]); + fprintf(file, "pp_data->cap[%u]->NotButton.LogicalMin = %ld\n", cap_idx, pp_cap->NotButton.LogicalMin); + fprintf(file, "pp_data->cap[%u]->NotButton.LogicalMax = %ld\n", cap_idx, pp_cap->NotButton.LogicalMax); + fprintf(file, "pp_data->cap[%u]->NotButton.PhysicalMin = %ld\n", cap_idx, pp_cap->NotButton.PhysicalMin); + fprintf(file, "pp_data->cap[%u]->NotButton.PhysicalMax = %ld\n", cap_idx, pp_cap->NotButton.PhysicalMax); }; - fprintf(file, "pp_data->cap[%d]->Units = %lu\n", cap_idx, pp_cap->Units); - fprintf(file, "pp_data->cap[%d]->UnitsExp = %lu\n", cap_idx, pp_cap->UnitsExp); + fprintf(file, "pp_data->cap[%u]->Units = %lu\n", cap_idx, pp_cap->Units); + fprintf(file, "pp_data->cap[%u]->UnitsExp = %lu\n", cap_idx, pp_cap->UnitsExp); } void dump_hidp_link_collection_node(FILE* file, phid_pp_link_collection_node pcoll, unsigned int coll_idx) { - fprintf(file, "pp_data->LinkCollectionArray[%d]->LinkUsage = 0x%04hX\n", coll_idx, pcoll->LinkUsage); - fprintf(file, "pp_data->LinkCollectionArray[%d]->LinkUsagePage = 0x%04hX\n", coll_idx, pcoll->LinkUsagePage); - fprintf(file, "pp_data->LinkCollectionArray[%d]->Parent = %hu\n", coll_idx, pcoll->Parent); - fprintf(file, "pp_data->LinkCollectionArray[%d]->NumberOfChildren = %hu\n", coll_idx, pcoll->NumberOfChildren); - fprintf(file, "pp_data->LinkCollectionArray[%d]->NextSibling = %hu\n", coll_idx, pcoll->NextSibling); - fprintf(file, "pp_data->LinkCollectionArray[%d]->FirstChild = %hu\n", coll_idx, pcoll->FirstChild); - fprintf(file, "pp_data->LinkCollectionArray[%d]->CollectionType = %d\n", coll_idx, pcoll->CollectionType); - fprintf(file, "pp_data->LinkCollectionArray[%d]->IsAlias = %d\n", coll_idx, pcoll->IsAlias); - fprintf(file, "pp_data->LinkCollectionArray[%d]->Reserved = 0x%08X\n", coll_idx, pcoll->Reserved); + fprintf(file, "pp_data->LinkCollectionArray[%u]->LinkUsage = 0x%04hX\n", coll_idx, pcoll->LinkUsage); + fprintf(file, "pp_data->LinkCollectionArray[%u]->LinkUsagePage = 0x%04hX\n", coll_idx, pcoll->LinkUsagePage); + fprintf(file, "pp_data->LinkCollectionArray[%u]->Parent = %hu\n", coll_idx, pcoll->Parent); + fprintf(file, "pp_data->LinkCollectionArray[%u]->NumberOfChildren = %hu\n", coll_idx, pcoll->NumberOfChildren); + fprintf(file, "pp_data->LinkCollectionArray[%u]->NextSibling = %hu\n", coll_idx, pcoll->NextSibling); + fprintf(file, "pp_data->LinkCollectionArray[%u]->FirstChild = %hu\n", coll_idx, pcoll->FirstChild); + // The compilers are not consistent on ULONG-bit-fields: They lose the unsinged or define them as int. + // Thus just always cast them to unsinged int, which should be fine, as the biggest bit-field is 28 bit + fprintf(file, "pp_data->LinkCollectionArray[%u]->CollectionType = %u\n", coll_idx, (unsigned int)(pcoll->CollectionType)); + fprintf(file, "pp_data->LinkCollectionArray[%u]->IsAlias = %u\n", coll_idx, (unsigned int)(pcoll->IsAlias)); + fprintf(file, "pp_data->LinkCollectionArray[%u]->Reserved = 0x%08X\n", coll_idx, (unsigned int)(pcoll->Reserved)); } int dump_pp_data(FILE* file, hid_device* dev) diff --git a/src/hidapi/windows/test/hid_report_reconstructor_test.c b/src/hidapi/windows/test/hid_report_reconstructor_test.c index 4c94fd97..a7adeb54 100644 --- a/src/hidapi/windows/test/hid_report_reconstructor_test.c +++ b/src/hidapi/windows/test/hid_report_reconstructor_test.c @@ -1,12 +1,14 @@ +#if defined(__MINGW32__) + // Needed for %zu + #define __USE_MINGW_ANSI_STDIO 1 +#endif + #include "../hidapi_descriptor_reconstruct.h" #include #include #include -#if defined(__MINGW32__) -#pragma GCC diagnostic ignored "-Wformat" -#endif static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) { FILE* file; @@ -107,25 +109,25 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) if (sscanf(line, "pp_data->UsagePage = 0x%04hX\n", &pp_data->UsagePage)) continue; if (sscanf(line, "pp_data->Reserved = 0x%04hX%04hX\n", &pp_data->Reserved[0], &pp_data->Reserved[1])) continue; - if (sscanf(line, "pp_data->caps_info[%d]", &rt_idx) == 1) { + if (sscanf(line, "pp_data->caps_info[%u]", &rt_idx) == 1) { const size_t caps_info_count = sizeof(pp_data->caps_info) / sizeof(pp_data->caps_info[0]); if (rt_idx >= caps_info_count) { fprintf(stderr, "Broken pp_data file, pp_data->caps_info[] can have at most %zu elements, accessing %ud, (%s)", caps_info_count, rt_idx, line); continue; } - if (sscanf(line, "pp_data->caps_info[%d]->FirstCap = %hu\n", &rt_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->caps_info[%u]->FirstCap = %hu\n", &rt_idx, &temp_ushort) == 2) { pp_data->caps_info[rt_idx].FirstCap = temp_ushort; continue; } - if (sscanf(line, "pp_data->caps_info[%d]->LastCap = %hu\n", &rt_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->caps_info[%u]->LastCap = %hu\n", &rt_idx, &temp_ushort) == 2) { pp_data->caps_info[rt_idx].LastCap = temp_ushort; continue; } - if (sscanf(line, "pp_data->caps_info[%d]->NumberOfCaps = %hu\n", &rt_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->caps_info[%u]->NumberOfCaps = %hu\n", &rt_idx, &temp_ushort) == 2) { pp_data->caps_info[rt_idx].NumberOfCaps = temp_ushort; continue; } - if (sscanf(line, "pp_data->caps_info[%d]->ReportByteLength = %hu\n", &rt_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->caps_info[%u]->ReportByteLength = %hu\n", &rt_idx, &temp_ushort) == 2) { pp_data->caps_info[rt_idx].ReportByteLength = temp_ushort; continue; } @@ -140,7 +142,7 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) continue; } - if (sscanf(line, "pp_data->cap[%d]", &caps_idx) == 1) { + if (sscanf(line, "pp_data->cap[%u]", &caps_idx) == 1) { if (pp_data->FirstByteOfLinkCollectionArray == 0) { fprintf(stderr, "Error reading pp_data file (%s): FirstByteOfLinkCollectionArray is 0 or not reported yet\n", line); continue; @@ -149,113 +151,113 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) fprintf(stderr, "Error reading pp_data file (%s): the caps index (%u) is out of pp_data bytes boundary (%hu vs %hu)\n", line, caps_idx, (unsigned short) ((caps_idx + 1) * sizeof(hid_pp_cap)), pp_data->FirstByteOfLinkCollectionArray); continue; } - if (sscanf(line, "pp_data->cap[%d]->UsagePage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->UsagePage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].UsagePage = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->ReportID = 0x%02hhX\n", &caps_idx, &temp_uchar[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->ReportID = 0x%02hhX\n", &caps_idx, &temp_uchar[0]) == 2) { pp_data->caps[caps_idx].ReportID = temp_uchar[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->BitPosition = %hhu\n", &caps_idx, &temp_uchar[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->BitPosition = %hhu\n", &caps_idx, &temp_uchar[0]) == 2) { pp_data->caps[caps_idx].BitPosition = temp_uchar[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->BitSize = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->BitSize = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].ReportSize = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->ReportCount = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->ReportCount = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].ReportCount = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->BytePosition = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->BytePosition = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].BytePosition = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->BitCount = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->BitCount = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].BitCount = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->BitField = 0x%02lX\n", &caps_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->cap[%u]->BitField = 0x%02lX\n", &caps_idx, &temp_ulong) == 2) { pp_data->caps[caps_idx].BitField = temp_ulong; continue; } - if (sscanf(line, "pp_data->cap[%d]->NextBytePosition = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NextBytePosition = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NextBytePosition = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->LinkCollection = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->LinkCollection = 0x%04hX\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].LinkCollection = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->LinkUsagePage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->LinkUsagePage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].LinkUsagePage = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->LinkUsage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->LinkUsage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].LinkUsage = temp_usage; continue; } // 8 Flags in one byte - if (sscanf(line, "pp_data->cap[%d]->IsMultipleItemsForArray = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsMultipleItemsForArray = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsMultipleItemsForArray = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsButtonCap = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsButtonCap = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsButtonCap = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsPadding = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsPadding = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsPadding = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsAbsolute = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsAbsolute = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsAbsolute = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsRange = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsAlias = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsAlias = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsAlias = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsStringRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsStringRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsStringRange = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->IsDesignatorRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->IsDesignatorRange = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].IsDesignatorRange = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->Reserved1 = 0x%hhu%hhu%hhu\n", &caps_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { + if (sscanf(line, "pp_data->cap[%u]->Reserved1 = 0x%hhu%hhu%hhu\n", &caps_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { pp_data->caps[caps_idx].Reserved1[0] = temp_uchar[0]; pp_data->caps[caps_idx].Reserved1[1] = temp_uchar[1]; pp_data->caps[caps_idx].Reserved1[2] = temp_uchar[2]; continue; } - if (sscanf(line, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d]", &caps_idx, &token_idx) == 2) { + if (sscanf(line, "pp_data->cap[%u]->pp_cap->UnknownTokens[%u]", &caps_idx, &token_idx) == 2) { const size_t unknown_tokens_count = sizeof(pp_data->caps[0].UnknownTokens) / sizeof(pp_data->caps[0].UnknownTokens[0]); if (token_idx >= unknown_tokens_count) { fprintf(stderr, "Broken pp_data file, pp_data->caps[].UnknownTokens[] can have at most %zu elements, accessing %ud, (%s)", unknown_tokens_count, token_idx, line); continue; } - if (sscanf(line, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].Token = 0x%02hhX\n", &caps_idx, &token_idx, &temp_uchar[0]) == 3) { + if (sscanf(line, "pp_data->cap[%u]->pp_cap->UnknownTokens[%u].Token = 0x%02hhX\n", &caps_idx, &token_idx, &temp_uchar[0]) == 3) { pp_data->caps[caps_idx].UnknownTokens[token_idx].Token = temp_uchar[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].Reserved = 0x%02hhX%02hhX%02hhX\n", &caps_idx, &token_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 5) { + if (sscanf(line, "pp_data->cap[%u]->pp_cap->UnknownTokens[%u].Reserved = 0x%02hhX%02hhX%02hhX\n", &caps_idx, &token_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 5) { pp_data->caps[caps_idx].UnknownTokens[token_idx].Reserved[0] = temp_uchar[0]; pp_data->caps[caps_idx].UnknownTokens[token_idx].Reserved[1] = temp_uchar[1]; pp_data->caps[caps_idx].UnknownTokens[token_idx].Reserved[2] = temp_uchar[2]; continue; } - if (sscanf(line, "pp_data->cap[%d]->pp_cap->UnknownTokens[%d].BitField = 0x%08lX\n", &caps_idx, &token_idx, &temp_ulong) == 3) { + if (sscanf(line, "pp_data->cap[%u]->pp_cap->UnknownTokens[%u].BitField = 0x%08lX\n", &caps_idx, &token_idx, &temp_ulong) == 3) { pp_data->caps[caps_idx].UnknownTokens[token_idx].BitField = temp_ulong; continue; } @@ -264,120 +266,120 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) } // Range - if (sscanf(line, "pp_data->cap[%d]->Range.UsageMin = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.UsageMin = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].Range.UsageMin = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.UsageMax = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.UsageMax = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].Range.UsageMax = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.StringMin = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.StringMin = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.StringMin = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.StringMax = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.StringMax = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.StringMax = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.DesignatorMin = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.DesignatorMin = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.DesignatorMin = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.DesignatorMax = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.DesignatorMax = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.DesignatorMax = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.DataIndexMin = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.DataIndexMin = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.DataIndexMin = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->Range.DataIndexMax = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Range.DataIndexMax = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].Range.DataIndexMax = temp_ushort; continue; } // NotRange - if (sscanf(line, "pp_data->cap[%d]->NotRange.Usage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.Usage = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].NotRange.Usage = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.Reserved1 = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.Reserved1 = 0x%04hX\n", &caps_idx, &temp_usage) == 2) { pp_data->caps[caps_idx].NotRange.Reserved1 = temp_usage; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.StringIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.StringIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.StringIndex = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.Reserved2 = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.Reserved2 = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.Reserved2 = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.DesignatorIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.DesignatorIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.DesignatorIndex = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.Reserved3 = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.Reserved3 = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.Reserved3 = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.DataIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.DataIndex = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.DataIndex = temp_ushort; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotRange.Reserved4 = %hu\n", &caps_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotRange.Reserved4 = %hu\n", &caps_idx, &temp_ushort) == 2) { pp_data->caps[caps_idx].NotRange.Reserved4 = temp_ushort; continue; } // Button - if (sscanf(line, "pp_data->cap[%d]->Button.LogicalMin = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Button.LogicalMin = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].Button.LogicalMin = temp_long; continue; } - if (sscanf(line, "pp_data->cap[%d]->Button.LogicalMax = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Button.LogicalMax = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].Button.LogicalMax = temp_long; continue; } // NotButton - if (sscanf(line, "pp_data->cap[%d]->NotButton.HasNull = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.HasNull = %hhu\n", &caps_idx, &temp_boolean[0]) == 2) { pp_data->caps[caps_idx].NotButton.HasNull = temp_boolean[0]; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotButton.Reserved4 = 0x%02hhX%02hhX%02hhX\n", &caps_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.Reserved4 = 0x%02hhX%02hhX%02hhX\n", &caps_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { pp_data->caps[caps_idx].NotButton.Reserved4[0] = temp_uchar[0]; pp_data->caps[caps_idx].NotButton.Reserved4[1] = temp_uchar[1]; pp_data->caps[caps_idx].NotButton.Reserved4[2] = temp_uchar[2]; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotButton.LogicalMin = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.LogicalMin = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].NotButton.LogicalMin = temp_long; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotButton.LogicalMax = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.LogicalMax = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].NotButton.LogicalMax = temp_long; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotButton.PhysicalMin = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.PhysicalMin = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].NotButton.PhysicalMin = temp_long; continue; } - if (sscanf(line, "pp_data->cap[%d]->NotButton.PhysicalMax = %ld\n", &caps_idx, &temp_long) == 2) { + if (sscanf(line, "pp_data->cap[%u]->NotButton.PhysicalMax = %ld\n", &caps_idx, &temp_long) == 2) { pp_data->caps[caps_idx].NotButton.PhysicalMax = temp_long; continue; } - if (sscanf(line, "pp_data->cap[%d]->Units = %lu\n", &caps_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->cap[%u]->Units = %lu\n", &caps_idx, &temp_ulong) == 2) { pp_data->caps[caps_idx].Units = temp_ulong; continue; } - if (sscanf(line, "pp_data->cap[%d]->UnitsExp = %lu\n", &caps_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->cap[%u]->UnitsExp = %lu\n", &caps_idx, &temp_ulong) == 2) { pp_data->caps[caps_idx].UnitsExp = temp_ulong; continue; } - if (sscanf(line, "pp_data->cap[%d]->Reserved1 = 0x%02hhu%02hhu%02hhu\n", &coll_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { + if (sscanf(line, "pp_data->cap[%u]->Reserved1 = 0x%02hhu%02hhu%02hhu\n", &coll_idx, &temp_uchar[0], &temp_uchar[1], &temp_uchar[2]) == 4) { pp_data->caps[caps_idx].Reserved1[0] = temp_uchar[0]; pp_data->caps[caps_idx].Reserved1[1] = temp_uchar[1]; pp_data->caps[caps_idx].Reserved1[2] = temp_uchar[2]; @@ -387,7 +389,7 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]", &coll_idx) == 1) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]", &coll_idx) == 1) { if (pp_data->FirstByteOfLinkCollectionArray == 0 || pp_data->NumberLinkCollectionNodes == 0) { fprintf(stderr, "Error reading pp_data file (%s): FirstByteOfLinkCollectionArray or NumberLinkCollectionNodes is 0 or not reported yet\n", line); continue; @@ -397,39 +399,39 @@ static hidp_preparsed_data * alloc_preparsed_data_from_file(char* filename) continue; } phid_pp_link_collection_node pcoll = (phid_pp_link_collection_node)(((unsigned char*)&pp_data->caps[0]) + pp_data->FirstByteOfLinkCollectionArray); - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->LinkUsage = 0x%04hX\n", &coll_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->LinkUsage = 0x%04hX\n", &coll_idx, &temp_usage) == 2) { pcoll[coll_idx].LinkUsage = temp_usage; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->LinkUsagePage = 0x%04hX\n", &coll_idx, &temp_usage) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->LinkUsagePage = 0x%04hX\n", &coll_idx, &temp_usage) == 2) { pcoll[coll_idx].LinkUsagePage = temp_usage; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->Parent = %hu\n", &coll_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->Parent = %hu\n", &coll_idx, &temp_ushort) == 2) { pcoll[coll_idx].Parent = temp_ushort; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->NumberOfChildren = %hu\n", &coll_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->NumberOfChildren = %hu\n", &coll_idx, &temp_ushort) == 2) { pcoll[coll_idx].NumberOfChildren = temp_ushort; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->NextSibling = %hu\n", &coll_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->NextSibling = %hu\n", &coll_idx, &temp_ushort) == 2) { pcoll[coll_idx].NextSibling = temp_ushort; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->FirstChild = %hu\n", &coll_idx, &temp_ushort) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->FirstChild = %hu\n", &coll_idx, &temp_ushort) == 2) { pcoll[coll_idx].FirstChild = temp_ushort; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->CollectionType = %ld\n", &coll_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->CollectionType = %lu\n", &coll_idx, &temp_ulong) == 2) { pcoll[coll_idx].CollectionType = temp_ulong; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->IsAlias = %ld\n", &coll_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->IsAlias = %lu\n", &coll_idx, &temp_ulong) == 2) { pcoll[coll_idx].IsAlias = temp_ulong; continue; } - if (sscanf(line, "pp_data->LinkCollectionArray[%d]->Reserved = %ld\n", &coll_idx, &temp_ulong) == 2) { + if (sscanf(line, "pp_data->LinkCollectionArray[%u]->Reserved = %lu\n", &coll_idx, &temp_ulong) == 2) { pcoll[coll_idx].Reserved = temp_ulong; continue; } diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 4cb4a148..77852ee1 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,6 +25,7 @@ #include "../SDL_utils_c.h" #include "SDL_sysjoystick.h" #include "SDL_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" #include "SDL_gamepad_c.h" #include "SDL_gamepad_db.h" #include "controller_type.h" @@ -45,6 +46,8 @@ #define SDL_GAMEPAD_CRC_FIELD_SIZE 4 /* hard-coded for speed */ #define SDL_GAMEPAD_TYPE_FIELD "type:" #define SDL_GAMEPAD_TYPE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_TYPE_FIELD) +#define SDL_GAMEPAD_FACE_FIELD "face:" +#define SDL_GAMEPAD_FACE_FIELD_SIZE 5 /* hard-coded for speed */ #define SDL_GAMEPAD_PLATFORM_FIELD "platform:" #define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD) #define SDL_GAMEPAD_HINT_FIELD "hint:" @@ -57,6 +60,15 @@ static SDL_bool SDL_gamepads_initialized; static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +/* The face button style of a gamepad */ +typedef enum +{ + SDL_GAMEPAD_FACE_STYLE_UNKNOWN, + SDL_GAMEPAD_FACE_STYLE_ABXY, + SDL_GAMEPAD_FACE_STYLE_BAYX, + SDL_GAMEPAD_FACE_STYLE_SONY, +} SDL_GamepadFaceStyle; + /* our hard coded list of mapping support */ typedef enum { @@ -107,6 +119,8 @@ struct SDL_Gamepad int ref_count _guarded; const char *name _guarded; + SDL_GamepadType type _guarded; + SDL_GamepadFaceStyle face_style _guarded; GamepadMapping_t *mapping _guarded; int num_bindings _guarded; SDL_GamepadBinding *bindings _guarded; @@ -127,22 +141,22 @@ struct SDL_Gamepad return retval; \ } -static SDL_vidpid_list SDL_allowed_gamepads; -static SDL_vidpid_list SDL_ignored_gamepads; - -static void SDLCALL SDL_GamepadIgnoreDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_LoadVIDPIDListFromHint(hint, &SDL_ignored_gamepads); -} - -static void SDLCALL SDL_GamepadIgnoreDevicesExceptChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_LoadVIDPIDListFromHint(hint, &SDL_allowed_gamepads); -} +static SDL_vidpid_list SDL_allowed_gamepads = { + SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, 0, 0, NULL, + NULL, 0, 0, NULL, + 0, NULL, + SDL_FALSE +}; +static SDL_vidpid_list SDL_ignored_gamepads = { + SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, 0, 0, NULL, + NULL, 0, 0, NULL, + 0, NULL, + SDL_FALSE +}; static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_JoystickGUID jGUID, const char *mappingString, SDL_bool *existing, SDL_GamepadMappingPriority priority); static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping); -static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id); +static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, SDL_bool create_mapping); static int SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value); static int SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, Uint8 state); @@ -197,7 +211,7 @@ static void HandleJoystickAxis(Uint64 timestamp, SDL_Gamepad *gamepad, int axis, } } - if (last_match && (match == NULL || !HasSameOutput(last_match, match))) { + if (last_match && (!match || !HasSameOutput(last_match, match))) { /* Clear the last input that this axis generated */ ResetOutput(timestamp, gamepad, last_match); } @@ -490,7 +504,7 @@ static void PushMappingChangeTracking(void) return; } for (i = 0; i < num_joysticks; ++i) { - tracker->joystick_mappings[i] = SDL_PrivateGetGamepadMapping(tracker->joysticks[i]); + tracker->joystick_mappings[i] = SDL_PrivateGetGamepadMapping(tracker->joysticks[i], SDL_FALSE); } } @@ -548,7 +562,7 @@ static void PopMappingChangeTracking(void) /* Looking up the new mapping might create one and associate it with the gamepad (and generate events) */ SDL_JoystickID joystick = tracker->joysticks[i]; SDL_Gamepad *gamepad = SDL_GetGamepadFromInstanceID(joystick); - GamepadMapping_t *new_mapping = SDL_PrivateGetGamepadMapping(joystick); + GamepadMapping_t *new_mapping = SDL_PrivateGetGamepadMapping(joystick, SDL_FALSE); GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i]; if (new_mapping && !old_mapping) { @@ -576,10 +590,10 @@ static void PopMappingChangeTracking(void) */ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_JoystickGUID guid) { - const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_A) | - (1 << SDL_GAMEPAD_BUTTON_B) | - (1 << SDL_GAMEPAD_BUTTON_X) | - (1 << SDL_GAMEPAD_BUTTON_Y)); + const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_SOUTH) | + (1 << SDL_GAMEPAD_BUTTON_EAST) | + (1 << SDL_GAMEPAD_BUTTON_WEST) | + (1 << SDL_GAMEPAD_BUTTON_NORTH)); SDL_bool existing; char mapping_string[1024]; int button_mask; @@ -598,20 +612,20 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_JoystickGUID gui SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_A)) { + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) { SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_B)) { + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) { SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { /* Use the back button as "B" for easy UI navigation with TV remotes */ SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string)); button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK); } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_X)) { + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) { SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); } - if (button_mask & (1 << SDL_GAMEPAD_BUTTON_Y)) { + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) { SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); } if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { @@ -688,9 +702,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) || - (vendor == USB_VENDOR_DRAGONRISE && product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER)) { + (vendor == USB_VENDOR_DRAGONRISE && + (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) { /* GameCube driver has 12 buttons and 6 axes */ - SDL_strlcat(mapping_string, "a:b0,b:b1,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:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,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:b2,y:b3,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -705,26 +721,26 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConRight)) { switch (guid.data[15]) { case k_eSwitchDeviceInfoControllerType_HVCLeft: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_HVCRight: - SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_NESLeft: case k_eSwitchDeviceInfoControllerType_NESRight: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_SNES: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_N64: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string)); + 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,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); break; case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: - SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b11,", sizeof(mapping_string)); break; case k_eWiiExtensionControllerType_None: - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); break; case k_eWiiExtensionControllerType_Nunchuk: { @@ -741,75 +757,67 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, SDL_FALSE)) { /* Vertical mode */ if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - SDL_strlcat(mapping_string, "back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b15,paddle2:b17,paddle4:b19,", sizeof(mapping_string)); + 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:b13,paddle4:b15,", sizeof(mapping_string)); } else { - 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,paddle1:b16,paddle3:b18,", sizeof(mapping_string)); + 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,paddle1:b12,paddle3:b14,", sizeof(mapping_string)); } } else { /* Mini gamepad mode */ if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - 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:b17,paddle4:b19,", sizeof(mapping_string)); + 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:b13,paddle4:b15,", sizeof(mapping_string)); } else { - 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,paddle1:b16,paddle3:b18,", sizeof(mapping_string)); + 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,paddle1:b12,paddle3:b14,", sizeof(mapping_string)); } } break; } } else { /* All other gamepads have the standard set of 19 buttons and 6 axes */ - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", sizeof(mapping_string)); + 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,", sizeof(mapping_string)); if (SDL_IsJoystickXboxSeriesX(vendor, product)) { /* XBox Series X Controllers have a share button under the guide button */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickXboxOneElite(vendor, product)) { /* XBox One Elite Controllers have 4 back paddle buttons */ - SDL_strlcat(mapping_string, "paddle1:b15,paddle2:b17,paddle3:b16,paddle4:b18,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); } else if (SDL_IsJoystickSteamController(vendor, product)) { /* Steam controllers have 2 back paddle buttons */ - SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) { /* The Nintendo Switch Joy-Con combined controllers has a share button and paddles */ - SDL_strlcat(mapping_string, "misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string)); } else if (SDL_IsJoystickAmazonLunaController(vendor, product)) { /* Amazon Luna Controller has a mic button under the guide button */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { /* The Google Stadia controller has a share button and a Google Assistant button */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { /* The NVIDIA SHIELD controller has a share button between back and start buttons */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { /* The original SHIELD controller has a touchpad as well */ - SDL_strlcat(mapping_string, "touchpad:b16,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "touchpad:b12,", sizeof(mapping_string)); } - } else { - switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { - case SDL_GAMEPAD_TYPE_PS4: - /* PS4 controllers have an additional touchpad button */ - SDL_strlcat(mapping_string, "touchpad:b15,", sizeof(mapping_string)); - break; - case SDL_GAMEPAD_TYPE_PS5: - /* PS5 controllers have a microphone button and an additional touchpad button */ - SDL_strlcat(mapping_string, "touchpad:b15,misc1:b16,", sizeof(mapping_string)); - /* DualSense Edge controllers have paddles */ - if (SDL_IsJoystickDualSenseEdge(vendor, product)) { - SDL_strlcat(mapping_string, "paddle1:b20,paddle2:b19,paddle3:b18,paddle4:b17,", sizeof(mapping_string)); - } - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: - /* Nintendo Switch Pro controllers have a screenshot button */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); - break; - default: - if (vendor == 0 && product == 0) { - /* This is a Bluetooth Nintendo Switch Pro controller */ - SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); - } - break; + } else if (SDL_IsJoystickPS4(vendor, product)) { + /* PS4 controllers have an additional touchpad button */ + SDL_strlcat(mapping_string, "touchpad:b11,", sizeof(mapping_string)); + } else if (SDL_IsJoystickPS5(vendor, product)) { + /* PS5 controllers have a microphone button and an additional touchpad button */ + SDL_strlcat(mapping_string, "touchpad:b11,misc1:b12,", sizeof(mapping_string)); + /* DualSense Edge controllers have paddles */ + if (SDL_IsJoystickDualSenseEdge(vendor, product)) { + SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } + } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || + SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) { + /* Nintendo Switch Pro controllers have a screenshot button */ + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } else if (vendor == 0 && product == 0) { + /* This is a Bluetooth Nintendo Switch Pro controller */ + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } } @@ -928,7 +936,7 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_JoystickGUID gu /* Try harder to get the best match, or create a mapping */ - if (vendor && product) { + if (SDL_JoystickGUIDUsesVersion(guid)) { /* Try again, ignoring the version */ if (crc) { mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, SDL_TRUE, SDL_FALSE); @@ -987,7 +995,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str) { int i; - if (str == NULL || str[0] == '\0') { + if (!str || str[0] == '\0') { return SDL_GAMEPAD_TYPE_UNKNOWN; } @@ -1031,7 +1039,7 @@ SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str) { int i; - if (str == NULL || str[0] == '\0') { + if (!str || str[0] == '\0') { return SDL_GAMEPAD_AXIS_INVALID; } @@ -1086,21 +1094,40 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForG /* * convert a string to its enum equivalent */ -SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) +static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, SDL_bool baxy) { int i; - if (str == NULL || str[0] == '\0') { + if (!str || str[0] == '\0') { return SDL_GAMEPAD_BUTTON_INVALID; } for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) { if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) { + if (baxy) { + /* Need to swap face buttons */ + switch (i) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_GAMEPAD_BUTTON_EAST; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_SOUTH; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_NORTH; + case SDL_GAMEPAD_BUTTON_NORTH: + return SDL_GAMEPAD_BUTTON_WEST; + default: + break; + } + } return (SDL_GamepadButton)i; } } return SDL_GAMEPAD_BUTTON_INVALID; } +SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) +{ + return SDL_PrivateGetGamepadButtonFromString(str, SDL_FALSE); +} /* * convert an enum to its string equivalent @@ -1116,7 +1143,7 @@ const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button) /* * given a gamepad button name and a joystick name update our mapping structure with it */ -static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton) +static SDL_bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton) { SDL_GamepadBinding bind; SDL_GamepadButton button; @@ -1124,15 +1151,24 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa SDL_bool invert_input = SDL_FALSE; char half_axis_input = 0; char half_axis_output = 0; + int i; + SDL_GamepadBinding *new_bindings; + SDL_bool baxy_mapping = SDL_FALSE; SDL_AssertJoysticksLocked(); + SDL_zero(bind); + if (*szGameButton == '+' || *szGameButton == '-') { half_axis_output = *szGameButton++; } + if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = SDL_TRUE; + } + axis = SDL_GetGamepadAxisFromString(szGameButton); - button = SDL_GetGamepadButtonFromString(szGameButton); + button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping); if (axis != SDL_GAMEPAD_AXIS_INVALID) { bind.outputType = SDL_GAMEPAD_BINDTYPE_AXIS; bind.output.axis.axis = axis; @@ -1155,7 +1191,7 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa bind.outputType = SDL_GAMEPAD_BINDTYPE_BUTTON; bind.output.button = button; } else { - return SDL_SetError("Unexpected gamepad element %s", szGameButton); + return SDL_FALSE; } if (*szJoystickButton == '+' || *szJoystickButton == '-') { @@ -1194,17 +1230,27 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa bind.input.hat.hat = hat; bind.input.hat.hat_mask = mask; } else { - return SDL_SetError("Unexpected joystick element: %s", szJoystickButton); + return SDL_FALSE; + } + + for (i = 0; i < gamepad->num_bindings; ++i) { + if (SDL_memcmp(&gamepad->bindings[i], &bind, sizeof(bind)) == 0) { + /* We already have this binding, could be different face button names? */ + return SDL_TRUE; + } } ++gamepad->num_bindings; - gamepad->bindings = (SDL_GamepadBinding *)SDL_realloc(gamepad->bindings, gamepad->num_bindings * sizeof(*gamepad->bindings)); - if (!gamepad->bindings) { + new_bindings = (SDL_GamepadBinding *)SDL_realloc(gamepad->bindings, gamepad->num_bindings * sizeof(*gamepad->bindings)); + if (!new_bindings) { + SDL_free(gamepad->bindings); gamepad->num_bindings = 0; - return SDL_OutOfMemory(); + gamepad->bindings = NULL; + return SDL_FALSE; } + gamepad->bindings = new_bindings; gamepad->bindings[gamepad->num_bindings - 1] = bind; - return 0; + return SDL_TRUE; } /* @@ -1236,12 +1282,14 @@ static int SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char } else if (bGameButton) { if (i >= sizeof(szGameButton)) { + szGameButton[sizeof(szGameButton) - 1] = '\0'; return SDL_SetError("Button name too large: %s", szGameButton); } szGameButton[i] = *pchPos; i++; } else { if (i >= sizeof(szJoystickButton)) { + szJoystickButton[sizeof(szJoystickButton) - 1] = '\0'; return SDL_SetError("Joystick button name too large: %s", szJoystickButton); } szJoystickButton[i] = *pchPos; @@ -1257,6 +1305,93 @@ static int SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char return 0; } +static void SDL_UpdateGamepadType(SDL_Gamepad *gamepad) +{ + char *type_string, *comma; + + SDL_AssertJoysticksLocked(); + + gamepad->type = SDL_GAMEPAD_TYPE_UNKNOWN; + + type_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_TYPE_FIELD); + if (type_string) { + type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; + comma = SDL_strchr(type_string, ','); + if (comma) { + *comma = '\0'; + gamepad->type = SDL_GetGamepadTypeFromString(type_string); + *comma = ','; + } else { + gamepad->type = SDL_GetGamepadTypeFromString(type_string); + } + } + if (gamepad->type == SDL_GAMEPAD_TYPE_UNKNOWN) { + gamepad->type = SDL_GetRealGamepadInstanceType(gamepad->joystick->instance_id); + } +} + +static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string) +{ + if (SDL_strcmp(string, "abxy") == 0) { + return SDL_GAMEPAD_FACE_STYLE_ABXY; + } else if (SDL_strcmp(string, "bayx") == 0) { + return SDL_GAMEPAD_FACE_STYLE_BAYX; + } else if (SDL_strcmp(string, "sony") == 0) { + return SDL_GAMEPAD_FACE_STYLE_SONY; + } else { + return SDL_GAMEPAD_FACE_STYLE_UNKNOWN; + } +} + +static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadType type) +{ + switch (type) { + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + return SDL_GAMEPAD_FACE_STYLE_SONY; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return SDL_GAMEPAD_FACE_STYLE_BAYX; + default: + return SDL_GAMEPAD_FACE_STYLE_ABXY; + } +} + +static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) +{ + char *face_string, *comma; + + SDL_AssertJoysticksLocked(); + + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_UNKNOWN; + + face_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD); + if (face_string) { + face_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; + comma = SDL_strchr(face_string, ','); + if (comma) { + *comma = '\0'; + gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string); + *comma = ','; + } else { + gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string); + } + } + + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && + SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) { + /* This controller uses Nintendo button style */ + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_BAYX; + } + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN) { + gamepad->face_style = SDL_GetGamepadFaceStyleForGamepadType(gamepad->type); + } +} + + /* * Make a new button mapping struct */ @@ -1269,10 +1404,13 @@ static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t gamepad->name = pGamepadMapping->name; gamepad->num_bindings = 0; gamepad->mapping = pGamepadMapping; - if (gamepad->joystick->naxes != 0 && gamepad->last_match_axis != NULL) { + if (gamepad->joystick->naxes != 0 && gamepad->last_match_axis) { SDL_memset(gamepad->last_match_axis, 0, gamepad->joystick->naxes * sizeof(*gamepad->last_match_axis)); } + SDL_UpdateGamepadType(gamepad); + SDL_UpdateGamepadFaceStyle(gamepad); + SDL_PrivateParseGamepadConfigString(gamepad, pGamepadMapping->mapping); /* Set the zero point for triggers */ @@ -1298,8 +1436,7 @@ static char *SDL_PrivateGetGamepadGUIDFromMappingString(const char *pMapping) const char *pFirstComma = SDL_strchr(pMapping, ','); if (pFirstComma) { char *pchGUID = SDL_malloc(pFirstComma - pMapping + 1); - if (pchGUID == NULL) { - SDL_OutOfMemory(); + if (!pchGUID) { return NULL; } SDL_memcpy(pchGUID, pMapping, pFirstComma - pMapping); @@ -1337,18 +1474,17 @@ static char *SDL_PrivateGetGamepadNameFromMappingString(const char *pMapping) char *pchName; pFirstComma = SDL_strchr(pMapping, ','); - if (pFirstComma == NULL) { + if (!pFirstComma) { return NULL; } pSecondComma = SDL_strchr(pFirstComma + 1, ','); - if (pSecondComma == NULL) { + if (!pSecondComma) { return NULL; } pchName = SDL_malloc(pSecondComma - pFirstComma); - if (pchName == NULL) { - SDL_OutOfMemory(); + if (!pchName) { return NULL; } SDL_memcpy(pchName, pFirstComma + 1, pSecondComma - pFirstComma); @@ -1366,12 +1502,12 @@ static char *SDL_PrivateGetGamepadMappingFromMappingString(const char *pMapping) size_t length; pFirstComma = SDL_strchr(pMapping, ','); - if (pFirstComma == NULL) { + if (!pFirstComma) { return NULL; } pSecondComma = SDL_strchr(pFirstComma + 1, ','); - if (pSecondComma == NULL) { + if (!pSecondComma) { return NULL; } @@ -1405,13 +1541,13 @@ static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_JoystickGUID jGUID, co SDL_AssertJoysticksLocked(); pchName = SDL_PrivateGetGamepadNameFromMappingString(mappingString); - if (pchName == NULL) { + if (!pchName) { SDL_SetError("Couldn't parse name from %s", mappingString); return NULL; } pchMapping = SDL_PrivateGetGamepadMappingFromMappingString(mappingString); - if (pchMapping == NULL) { + if (!pchMapping) { SDL_free(pchName); SDL_SetError("Couldn't parse %s", mappingString); return NULL; @@ -1481,11 +1617,10 @@ static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_JoystickGUID jGUID, co AddMappingChangeTracking(pGamepadMapping); } else { pGamepadMapping = SDL_malloc(sizeof(*pGamepadMapping)); - if (pGamepadMapping == NULL) { + if (!pGamepadMapping) { PopMappingChangeTracking(); SDL_free(pchName); SDL_free(pchMapping); - SDL_OutOfMemory(); return NULL; } /* Clear the CRC, we've already added it to the mapping */ @@ -1532,7 +1667,7 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char * mapping = SDL_PrivateGetGamepadMappingForGUID(guid, SDL_FALSE); #ifdef __LINUX__ - if (mapping == NULL && name) { + if (!mapping && name) { if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { /* The Linux driver xpad.c maps the wireless dpad to buttons */ SDL_bool existing; @@ -1543,7 +1678,7 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char * } #endif /* __LINUX__ */ - if (mapping == NULL) { + if (!mapping) { mapping = s_pDefaultMapping; } return mapping; @@ -1563,10 +1698,14 @@ static void SDL_PrivateAppendToMappingString(char *mapping_string, SDL_strlcat(mapping_string, ":", mapping_string_len); switch (mapping->kind) { case EMappingKind_Button: - (void)SDL_snprintf(buffer, sizeof(buffer), "b%i", mapping->target); + (void)SDL_snprintf(buffer, sizeof(buffer), "b%u", mapping->target); break; case EMappingKind_Axis: - (void)SDL_snprintf(buffer, sizeof(buffer), "a%i", mapping->target); + (void)SDL_snprintf(buffer, sizeof(buffer), "%sa%u%s", + mapping->half_axis_positive ? "+" : + mapping->half_axis_negative ? "-" : "", + mapping->target, + mapping->axis_reversed ? "~" : ""); break; case EMappingKind_Hat: (void)SDL_snprintf(buffer, sizeof(buffer), "h%i.%i", mapping->target >> 4, mapping->target & 0x0F); @@ -1626,11 +1765,12 @@ static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char * SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righty", &raw_map->righty); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefttrigger", &raw_map->lefttrigger); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righttrigger", &raw_map->righttrigger); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "touchpad", &raw_map->touchpad); return SDL_PrivateAddMappingForGUID(guid, mapping, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); } -static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id) +static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, SDL_bool create_mapping) { const char *name; SDL_JoystickGUID guid; @@ -1641,7 +1781,7 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id name = SDL_GetJoystickInstanceName(instance_id); guid = SDL_GetJoystickInstanceGUID(instance_id); mapping = SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid); - if (mapping == NULL) { + if (!mapping && create_mapping) { SDL_GamepadMapping raw_map; SDL_zero(raw_map); @@ -1665,16 +1805,18 @@ int SDL_AddGamepadMappingsFromRW(SDL_RWops *src, SDL_bool freesrc) size_t platform_len; buf = (char *)SDL_LoadFile_RW(src, &db_size, freesrc); - if (buf == NULL) { + if (!buf) { return SDL_SetError("Could not allocate space to read DB into memory"); } line = buf; + SDL_LockJoysticks(); + PushMappingChangeTracking(); while (line < buf + db_size) { line_end = SDL_strchr(line, '\n'); - if (line_end != NULL) { + if (line_end) { *line_end = '\0'; } else { line_end = buf + db_size; @@ -1682,10 +1824,10 @@ int SDL_AddGamepadMappingsFromRW(SDL_RWops *src, SDL_bool freesrc) /* Extract and verify the platform */ tmp = SDL_strstr(line, SDL_GAMEPAD_PLATFORM_FIELD); - if (tmp != NULL) { + if (tmp) { tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE; comma = SDL_strchr(tmp, ','); - if (comma != NULL) { + if (comma) { platform_len = comma - tmp + 1; if (platform_len + 1 < SDL_arraysize(line_platform)) { SDL_strlcpy(line_platform, tmp, platform_len); @@ -1702,6 +1844,8 @@ int SDL_AddGamepadMappingsFromRW(SDL_RWops *src, SDL_bool freesrc) PopMappingChangeTracking(); + SDL_UnlockJoysticks(); + SDL_free(buf); return gamepads; } @@ -1747,7 +1891,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa SDL_AssertJoysticksLocked(); - if (mappingString == NULL) { + if (!mappingString) { return SDL_InvalidParamError("mappingString"); } @@ -1755,7 +1899,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa const char *tmp; tmp = SDL_strstr(mappingString, SDL_GAMEPAD_HINT_FIELD); - if (tmp != NULL) { + if (tmp) { SDL_bool default_value, value, negate; int len; char hint[128]; @@ -1797,14 +1941,14 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa const char *tmp; tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKGE_FIELD); - if (tmp != NULL) { + if (tmp) { tmp += SDL_GAMEPAD_SDKGE_FIELD_SIZE; if (!(SDL_GetAndroidSDKVersion() >= SDL_atoi(tmp))) { return SDL_SetError("SDK version %d < minimum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp)); } } tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKLE_FIELD); - if (tmp != NULL) { + if (tmp) { tmp += SDL_GAMEPAD_SDKLE_FIELD_SIZE; if (!(SDL_GetAndroidSDKVersion() <= SDL_atoi(tmp))) { return SDL_SetError("SDK version %d > maximum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp)); @@ -1814,7 +1958,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa #endif pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); - if (pchGUID == NULL) { + if (!pchGUID) { return SDL_SetError("Couldn't parse GUID from %s", mappingString); } if (!SDL_strcasecmp(pchGUID, "default")) { @@ -1826,7 +1970,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa SDL_free(pchGUID); pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); - if (pGamepadMapping == NULL) { + if (!pGamepadMapping) { return -1; } @@ -1858,29 +2002,6 @@ int SDL_AddGamepadMapping(const char *mapping) return retval; } -/* - * Get the number of mappings installed - */ -int SDL_GetNumGamepadMappings(void) -{ - int num_mappings = 0; - - SDL_LockJoysticks(); - { - GamepadMapping_t *mapping; - - for (mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { - if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { - continue; - } - ++num_mappings; - } - } - SDL_UnlockJoysticks(); - - return num_mappings; -} - /* * Create a mapping string for a mapping */ @@ -1910,8 +2031,7 @@ static char *CreateMappingString(GamepadMapping_t *mapping, SDL_JoystickGUID gui } pMappingString = SDL_malloc(needed); - if (pMappingString == NULL) { - SDL_OutOfMemory(); + if (!pMappingString) { return NULL; } @@ -1937,33 +2057,79 @@ static char *CreateMappingString(GamepadMapping_t *mapping, SDL_JoystickGUID gui return pMappingString; } -/* - * Get the mapping at a particular index. - */ -char *SDL_GetGamepadMappingForIndex(int mapping_index) +char **SDL_GetGamepadMappings(int *count) { - char *retval = NULL; + int num_mappings = 0; + char **retval = NULL; + char **mappings = NULL; + + if (count) { + *count = 0; + } SDL_LockJoysticks(); - { - GamepadMapping_t *mapping; - for (mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { + for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + num_mappings++; + } + + size_t final_allocation = sizeof (char *); // for the NULL terminator element. + SDL_bool failed = SDL_FALSE; + mappings = (char **) SDL_calloc(num_mappings + 1, sizeof (char *)); + if (!mappings) { + failed = SDL_TRUE; + } else { + int i = 0; + for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { continue; } - if (mapping_index == 0) { - retval = CreateMappingString(mapping, mapping->guid); - break; + + char *mappingstr = CreateMappingString(mapping, mapping->guid); + if (!mappingstr) { + failed = SDL_TRUE; + break; // error string is already set. } - --mapping_index; + + SDL_assert(i < num_mappings); + mappings[i++] = mappingstr; + + final_allocation += SDL_strlen(mappingstr) + 1 + sizeof (char *); } } + SDL_UnlockJoysticks(); - if (retval == NULL) { - SDL_SetError("Mapping not available"); + if (!failed) { + retval = (char **) SDL_malloc(final_allocation); + if (retval) { + final_allocation -= (sizeof (char *) * num_mappings + 1); + char *strptr = (char *) (retval + (num_mappings + 1)); + for (int i = 0; i < num_mappings; i++) { + retval[i] = strptr; + const size_t slen = SDL_strlcpy(strptr, mappings[i], final_allocation) + 1; + SDL_assert(final_allocation >= slen); + final_allocation -= slen; + strptr += slen; + } + retval[num_mappings] = NULL; + + if (count) { + *count = num_mappings; + } + } } + + if (mappings) { + for (int i = 0; i < num_mappings; i++) { + SDL_free(mappings[i]); + } + SDL_free(mappings); + } + return retval; } @@ -2113,10 +2279,8 @@ int SDL_InitGamepadMappings(void) /* load in any user supplied config */ SDL_LoadGamepadHints(); - SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, - SDL_GamepadIgnoreDevicesChanged, NULL); - SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, - SDL_GamepadIgnoreDevicesExceptChanged, NULL); + SDL_LoadVIDPIDList(&SDL_allowed_gamepads); + SDL_LoadVIDPIDList(&SDL_ignored_gamepads); PopMappingChangeTracking(); @@ -2174,8 +2338,8 @@ const char *SDL_GetGamepadInstanceName(SDL_JoystickID instance_id) SDL_LockJoysticks(); { - GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id); - if (mapping != NULL) { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE); + if (mapping) { if (SDL_strcmp(mapping->name, "*") == 0) { retval = SDL_GetJoystickInstanceName(instance_id); } else { @@ -2224,21 +2388,20 @@ SDL_GamepadType SDL_GetGamepadInstanceType(SDL_JoystickID instance_id) SDL_LockJoysticks(); { - GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id); - if (mapping != NULL) { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE); + if (mapping) { char *type_string, *comma; type_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_TYPE_FIELD); - if (type_string != NULL) { + if (type_string) { type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; comma = SDL_strchr(type_string, ','); - if (comma != NULL) { + if (comma) { *comma = '\0'; type = SDL_GetGamepadTypeFromString(type_string); *comma = ','; } } - } } SDL_UnlockJoysticks(); @@ -2251,7 +2414,21 @@ SDL_GamepadType SDL_GetGamepadInstanceType(SDL_JoystickID instance_id) SDL_GamepadType SDL_GetRealGamepadInstanceType(SDL_JoystickID instance_id) { - return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickInstanceGUID(instance_id), SDL_GetJoystickInstanceName(instance_id)); + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id); + if (info) { + type = info->type; + } else { + type = SDL_GetGamepadTypeFromGUID(SDL_GetJoystickInstanceGUID(instance_id), SDL_GetJoystickInstanceName(instance_id)); + } + } + SDL_UnlockJoysticks(); + + return type; } char *SDL_GetGamepadInstanceMapping(SDL_JoystickID instance_id) @@ -2260,21 +2437,12 @@ char *SDL_GetGamepadInstanceMapping(SDL_JoystickID instance_id) SDL_LockJoysticks(); { - GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id); - if (mapping != NULL) { - SDL_JoystickGUID guid; + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE); + if (mapping) { char pchGUID[33]; - size_t needed; - guid = SDL_GetJoystickInstanceGUID(instance_id); + const SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id); SDL_GetJoystickGUIDString(guid, pchGUID, sizeof(pchGUID)); - /* allocate enough memory for GUID + ',' + name + ',' + mapping + \0 */ - needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1; - retval = (char *)SDL_malloc(needed); - if (retval != NULL) { - (void)SDL_snprintf(retval, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); - } else { - SDL_OutOfMemory(); - } + SDL_asprintf(&retval, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); } } SDL_UnlockJoysticks(); @@ -2310,7 +2478,7 @@ SDL_bool SDL_IsGamepad(SDL_JoystickID instance_id) SDL_LockJoysticks(); { - if (SDL_PrivateGetGamepadMapping(instance_id) != NULL) { + if (SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE) != NULL) { retval = SDL_TRUE; } else { retval = SDL_FALSE; @@ -2353,8 +2521,8 @@ SDL_bool SDL_ShouldIgnoreGamepad(const char *name, SDL_JoystickGUID guid) return SDL_TRUE; } - if (SDL_allowed_gamepads.num_entries == 0 && - SDL_ignored_gamepads.num_entries == 0) { + if (SDL_allowed_gamepads.num_included_entries == 0 && + SDL_ignored_gamepads.num_included_entries == 0) { return SDL_FALSE; } @@ -2367,7 +2535,7 @@ SDL_bool SDL_ShouldIgnoreGamepad(const char *name, SDL_JoystickGUID guid) #ifdef __LINUX__ bSteamVirtualGamepad = (vendor == USB_VENDOR_VALVE && product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD); #elif defined(__MACOS__) - bSteamVirtualGamepad = (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 1); + bSteamVirtualGamepad = (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0); #elif defined(__WIN32__) /* We can't tell on Windows, but Steam will block others in input hooks */ bSteamVirtualGamepad = SDL_TRUE; @@ -2377,7 +2545,7 @@ SDL_bool SDL_ShouldIgnoreGamepad(const char *name, SDL_JoystickGUID guid) } } - if (SDL_allowed_gamepads.num_entries > 0) { + if (SDL_allowed_gamepads.num_included_entries > 0) { if (SDL_VIDPIDInList(vendor, product, &SDL_allowed_gamepads)) { return SDL_FALSE; } @@ -2405,7 +2573,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) gamepadlist = SDL_gamepads; /* If the gamepad is already open, return it */ - while (gamepadlist != NULL) { + while (gamepadlist) { if (instance_id == gamepadlist->joystick->instance_id) { gamepad = gamepadlist; ++gamepad->ref_count; @@ -2416,8 +2584,8 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) } /* Find a gamepad mapping */ - pSupportedGamepad = SDL_PrivateGetGamepadMapping(instance_id); - if (pSupportedGamepad == NULL) { + pSupportedGamepad = SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE); + if (!pSupportedGamepad) { SDL_SetError("Couldn't find mapping for device (%" SDL_PRIu32 ")", instance_id); SDL_UnlockJoysticks(); return NULL; @@ -2425,15 +2593,14 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) /* Create and initialize the gamepad */ gamepad = (SDL_Gamepad *)SDL_calloc(1, sizeof(*gamepad)); - if (gamepad == NULL) { - SDL_OutOfMemory(); + if (!gamepad) { SDL_UnlockJoysticks(); return NULL; } gamepad->magic = &gamepad_magic; gamepad->joystick = SDL_OpenJoystick(instance_id); - if (gamepad->joystick == NULL) { + if (!gamepad->joystick) { SDL_free(gamepad); SDL_UnlockJoysticks(); return NULL; @@ -2442,7 +2609,6 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->naxes) { gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis)); if (!gamepad->last_match_axis) { - SDL_OutOfMemory(); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad); SDL_UnlockJoysticks(); @@ -2452,7 +2618,6 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->nhats) { gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask)); if (!gamepad->last_hat_mask) { - SDL_OutOfMemory(); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad->last_match_axis); SDL_free(gamepad); @@ -2650,6 +2815,100 @@ Uint8 SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) return retval; } +/** + * Get the label of a button on a gamepad. + */ +static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadFaceStyle face_style, SDL_GamepadButton button) +{ + SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN; + + switch (face_style) { + case SDL_GAMEPAD_FACE_STYLE_ABXY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + default: + break; + } + break; + case SDL_GAMEPAD_FACE_STYLE_BAYX: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + default: + break; + } + break; + case SDL_GAMEPAD_FACE_STYLE_SONY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_CROSS; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_CIRCLE; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_SQUARE; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE; + break; + default: + break; + } + break; + default: + break; + } + return label; +} + +/** + * Get the label of a button on a gamepad. + */ +SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button) +{ + return SDL_GetGamepadButtonLabelForFaceStyle(SDL_GetGamepadFaceStyleForGamepadType(type), button); +} + +/** + * Get the label of a button on a gamepad. + */ +SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + SDL_GamepadFaceStyle face_style; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN); + + face_style = gamepad->face_style; + } + SDL_UnlockJoysticks(); + + return SDL_GetGamepadButtonLabelForFaceStyle(face_style, button); +} + /** * Get the number of touchpads on a gamepad. */ @@ -2919,12 +3178,27 @@ SDL_JoystickID SDL_GetGamepadInstanceID(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return 0; } return SDL_GetJoystickInstanceID(joystick); } +SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad) +{ + SDL_PropertiesID retval = 0; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, 0); + + retval = SDL_GetJoystickProperties(gamepad->joystick); + } + SDL_UnlockJoysticks(); + + return retval; +} + const char *SDL_GetGamepadName(SDL_Gamepad *gamepad) { const char *retval = NULL; @@ -2933,7 +3207,8 @@ const char *SDL_GetGamepadName(SDL_Gamepad *gamepad) { CHECK_GAMEPAD_MAGIC(gamepad, NULL); - if (SDL_strcmp(gamepad->name, "*") == 0) { + if (SDL_strcmp(gamepad->name, "*") == 0 || + gamepad->joystick->steam_handle != 0) { retval = SDL_GetJoystickName(gamepad->joystick); } else { retval = gamepad->name; @@ -2948,7 +3223,7 @@ const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return NULL; } return SDL_GetJoystickPath(joystick); @@ -2956,24 +3231,30 @@ const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad) SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad) { - SDL_JoystickID instance_id = 0; + SDL_GamepadType type; + const SDL_SteamVirtualGamepadInfo *info; SDL_LockJoysticks(); { CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN); - instance_id = gamepad->joystick->instance_id; + info = SDL_GetJoystickInstanceVirtualGamepadInfo(gamepad->joystick->instance_id); + if (info) { + type = info->type; + } else { + type = gamepad->type; + } } SDL_UnlockJoysticks(); - return SDL_GetGamepadInstanceType(instance_id); + return type; } SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_GAMEPAD_TYPE_UNKNOWN; } return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUID(joystick), SDL_GetJoystickName(joystick)); @@ -2983,7 +3264,7 @@ int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return -1; } return SDL_GetJoystickPlayerIndex(joystick); @@ -2996,7 +3277,7 @@ int SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { /* SDL_SetError() will have been called already by SDL_GetGamepadJoystick() */ return -1; } @@ -3007,7 +3288,7 @@ Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return 0; } return SDL_GetJoystickVendor(joystick); @@ -3017,7 +3298,7 @@ Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return 0; } return SDL_GetJoystickProduct(joystick); @@ -3027,7 +3308,7 @@ Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return 0; } return SDL_GetJoystickProductVersion(joystick); @@ -3037,7 +3318,7 @@ Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return 0; } return SDL_GetJoystickFirmwareVersion(joystick); @@ -3047,17 +3328,32 @@ const char * SDL_GetGamepadSerial(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return NULL; } return SDL_GetJoystickSerial(joystick); } +Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad) +{ + Uint64 handle = 0; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, 0); + + handle = gamepad->joystick->steam_handle; + } + SDL_UnlockJoysticks(); + + return handle; +} + SDL_JoystickPowerLevel SDL_GetGamepadPowerLevel(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_JOYSTICK_POWER_UNKNOWN; } return SDL_GetJoystickPowerLevel(joystick); @@ -3071,7 +3367,7 @@ SDL_bool SDL_GamepadConnected(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_FALSE; } return SDL_JoystickConnected(joystick); @@ -3164,8 +3460,6 @@ SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count) if (count) { *count = gamepad->num_bindings; } - } else { - SDL_OutOfMemory(); } } SDL_UnlockJoysticks(); @@ -3177,7 +3471,7 @@ int SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return -1; } return SDL_RumbleJoystick(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms); @@ -3187,7 +3481,7 @@ int SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 r { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return -1; } return SDL_RumbleJoystickTriggers(joystick, left_rumble, right_rumble, duration_ms); @@ -3197,7 +3491,7 @@ SDL_bool SDL_GamepadHasLED(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_FALSE; } return SDL_JoystickHasLED(joystick); @@ -3207,7 +3501,7 @@ SDL_bool SDL_GamepadHasRumble(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_FALSE; } return SDL_JoystickHasRumble(joystick); @@ -3217,7 +3511,7 @@ SDL_bool SDL_GamepadHasRumbleTriggers(SDL_Gamepad *gamepad) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return SDL_FALSE; } return SDL_JoystickHasRumbleTriggers(joystick); @@ -3227,7 +3521,7 @@ int SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return -1; } return SDL_SetJoystickLED(joystick, red, green, blue); @@ -3237,7 +3531,7 @@ int SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size) { SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); - if (joystick == NULL) { + if (!joystick) { return -1; } return SDL_SendJoystickEffect(joystick, data, size); @@ -3249,7 +3543,7 @@ void SDL_CloseGamepad(SDL_Gamepad *gamepad) SDL_LockJoysticks(); - if (gamepad == NULL || gamepad->magic != &gamepad_magic) { + if (!gamepad || gamepad->magic != &gamepad_magic) { SDL_UnlockJoysticks(); return; } @@ -3326,11 +3620,6 @@ void SDL_QuitGamepadMappings(void) SDL_free(pGamepadMap); } - SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, - SDL_GamepadIgnoreDevicesChanged, NULL); - SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, - SDL_GamepadIgnoreDevicesExceptChanged, NULL); - SDL_FreeVIDPIDList(&SDL_allowed_gamepads); SDL_FreeVIDPIDList(&SDL_ignored_gamepads); } diff --git a/src/joystick/SDL_gamepad_c.h b/src/joystick/SDL_gamepad_c.h index 113be066..0c061409 100644 --- a/src/joystick/SDL_gamepad_c.h +++ b/src/joystick/SDL_gamepad_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 00e6a244..971cc0e9 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,12 +30,11 @@ */ static const char *s_GamepadMappings[] = { #ifdef SDL_JOYSTICK_XINPUT - "xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "xinput,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", #endif #ifdef SDL_JOYSTICK_WGI "03000000491900001904000000007700,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", "03000000d11800000094000000007700,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", - "030000007e0500000920000000007701,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000007e0500000920000000007701,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000004c050000c405000000007701,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000004c050000e60c000000007700,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -44,53 +43,30 @@ static const char *s_GamepadMappings[] = { #endif #ifdef SDL_JOYSTICK_DINPUT "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,", - "03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00005106000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00005106000000000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000003512000012ab000000000000,8BitDo NES30 Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000000000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000022000000090000000000000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000203800000900000000000000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000660000000000000,8BitDo Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001290000000000000,8BitDo SN30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001290000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00006228000000000000,8BitDo SN30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00006228000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000003512000020ab000000000000,8BitDo SNES30 Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000020ab000000000000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001130000000000000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000c82d00001330000000000000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", - "03000000c82d00001890000000000000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00003032000000000000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000a00500003232000000000000,8BitDo Zero Gamepad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000a00500003232000000000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000050b00000579000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000050b00000679000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", @@ -224,10 +200,8 @@ static const char *s_GamepadMappings[] = { "03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,", "030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000004b120000014d000000000000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", - "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,", - "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", "03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -278,6 +252,7 @@ static const char *s_GamepadMappings[] = { "03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", "03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", "03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,", "03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -293,7 +268,6 @@ static const char *s_GamepadMappings[] = { "030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", "0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", - "03000000790000001100000000000000,Retrolink SNES Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b0,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -356,51 +330,31 @@ static const char *s_GamepadMappings[] = { "03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", - "03000000830500006020000000000000,iBuffalo SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", #endif #ifdef __MACOS__ - "03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000022000000090000001000000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000203800000900000000010000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000660000000020000,8BitDo Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001130000000020000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000c82d00001330000000020000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "03000000c82d00001890000001000000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00003032000000010000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000a00500003232000008010000,8BitDo Zero Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b2,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000a00500003232000009010000,8BitDo Zero Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000a00500003232000009010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000050b00000579000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,", "03000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,", @@ -452,7 +406,6 @@ static const char *s_GamepadMappings[] = { "03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", - "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", @@ -473,7 +426,6 @@ static const char *s_GamepadMappings[] = { "03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", "03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,", - "03000000790000001100000006010000,Retrolink SNES Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b0,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -515,67 +467,40 @@ static const char *s_GamepadMappings[] = { "03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */ "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "03000000830500006020000000010000,iBuffalo SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,", #endif #if defined(SDL_JOYSTICK_LINUX) || defined(__OpenBSD__) - "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000650000011010000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000003512000012ab000010010000,8BitDo NES30 Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000012ab000010010000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000022000000090000011010000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000022000000090000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000203800000900000000010000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", - "03000000c82d00000660000011010000,8BitDo Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00000660000000010000,8BitDo Pro 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b0,b:b1,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001130000011010000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000c82d00001330000011010000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", - "03000000c82d00001890000011010000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00003032000000010000,8BitDo Zero 2,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000a00500003232000001000000,8BitDo Zero Gamepad,a:b0,b:b1,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000a00500003232000001000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000a00500003232000008010000,8BitDo Zero Gamepad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00000031000011010000,8Bitdo Receiver,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,", - "03000000c82d00001290000011010000,8Bitdo SN30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000c82d00001290000011010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d00006228000000010000,8Bitdo SN30 Gamepad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d00006228000000010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", "05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", @@ -593,6 +518,7 @@ static const char *s_GamepadMappings[] = { "05000000503200000110000000000000,Atari Classic Controller,a:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,x:b1,", "03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", "05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", + "05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", "030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -632,7 +558,7 @@ static const char *s_GamepadMappings[] = { "030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000000d0f00008800000011010000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", "030000000d0f00008700000011010000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,x:b0,y:b3,", - "030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", + "030000000d0f0000d800000072056800,HORI Real Arcade Pro S,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,", "030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -696,32 +622,18 @@ static const char *s_GamepadMappings[] = { "03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000007e0500003703000000016800,Nintendo GameCube Controller,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,", - "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b15,b:b16,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b17,y:b14,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b16,b:b15,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b2,b:b1,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b3,y:b0,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e0500000603000000060000,Nintendo Wii Remote Classic Controller,crc:0d8a,a:b0,b:b1,back:b10,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000603000000060000,Nintendo Wii Remote Classic Controller,crc:0d8a,a:b1,b:b0,back:b10,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b1,b:b0,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000603000000060000,Nintendo Wii Remote,crc:60be,a:b1,b:b0,back:b4,dpdown:b8,dpleft:b6,dpright:b7,dpup:b5,guide:b2,start:b3,x:b9,y:b10,", "05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", @@ -784,7 +696,6 @@ static const char *s_GamepadMappings[] = { "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", - "03000000790000001100000010010000,Retrolink SNES Controller,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b0,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -816,6 +727,7 @@ static const char *s_GamepadMappings[] = { "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "03000000de2800000512000000016800,Steam Deck,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,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,", "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -857,7 +769,6 @@ static const char *s_GamepadMappings[] = { "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", "030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,", - "03000000830500006020000010010000,iBuffalo SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", @@ -870,37 +781,21 @@ static const char *s_GamepadMappings[] = { "030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4~,start:b7,x:b2,y:b3,", #endif #ifdef __ANDROID__ - "05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b0,b:b1,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b0,b:b1,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,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,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b0,b:b1,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b0,b:b1,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b20,b:b21,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b23,y:b24,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b0,b:b1,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b0,b:b1,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000d6020000e5890000dfff3f80,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", "0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", @@ -908,12 +803,9 @@ static const char *s_GamepadMappings[] = { "050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000005509000014720000df7f3f80,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", - "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,sdk>=:29,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,sdk>=:29,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,sdk<=:28,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Extremely slow in Bluetooth mode on Android */ "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b2,y:b17,sdk<=:28,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Extremely slow in Bluetooth mode on Android */ "050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", - "030000004c050000cc09000000006800,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000004c050000c405000000783f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000004c050000c4050000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,", "050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", @@ -938,7 +830,6 @@ static const char *s_GamepadMappings[] = { "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,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,", "050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,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,", /* The DPAD doesn't seem to work on this controller on Android TV? */ "050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", - "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", #endif #ifdef SDL_JOYSTICK_MFI @@ -949,19 +840,12 @@ static const char *s_GamepadMappings[] = { "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", - "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b2,b:b0,leftshoulder:b4,rightshoulder:b5,x:b3,y:b1,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b2,b:b0,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b3,y:b1,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e050000072000000f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b2,b:b0,leftshoulder:b4,rightshoulder:b5,x:b3,y:b1,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e050000072000000f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b2,b:b0,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b3,y:b1,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", "050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 48385b4b..e39fdc5e 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,6 +26,7 @@ #include "../SDL_hints_c.h" #include "SDL_gamepad_c.h" #include "SDL_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" #ifndef SDL_EVENTS_DISABLED #include "../events/SDL_events_c.h" @@ -116,12 +117,289 @@ static SDL_bool SDL_joysticks_initialized; static SDL_bool SDL_joysticks_quitting; static SDL_bool SDL_joystick_being_added; static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL; -static SDL_AtomicInt SDL_last_joystick_instance_id SDL_GUARDED_BY(SDL_joystick_lock); static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static SDL_bool SDL_joystick_allows_background_events = SDL_FALSE; char SDL_joystick_magic; +static Uint32 initial_arcadestick_devices[] = { + MAKE_VIDPID(0x0079, 0x181a), /* Venom Arcade Stick */ + MAKE_VIDPID(0x0079, 0x181b), /* Venom Arcade Stick */ + MAKE_VIDPID(0x0c12, 0x0ef6), /* Hitbox Arcade Stick */ + MAKE_VIDPID(0x0e6f, 0x0109), /* PDP Versus Fighting Pad */ + MAKE_VIDPID(0x0f0d, 0x0016), /* Hori Real Arcade Pro.EX */ + MAKE_VIDPID(0x0f0d, 0x001b), /* Hori Real Arcade Pro VX */ + MAKE_VIDPID(0x0f0d, 0x0063), /* Hori Real Arcade Pro Hayabusa (USA) Xbox One */ + MAKE_VIDPID(0x0f0d, 0x006a), /* Real Arcade Pro 4 */ + MAKE_VIDPID(0x0f0d, 0x0078), /* Hori Real Arcade Pro V Kai Xbox One */ + MAKE_VIDPID(0x0f0d, 0x008a), /* HORI Real Arcade Pro 4 */ + MAKE_VIDPID(0x0f0d, 0x008c), /* Hori Real Arcade Pro 4 */ + MAKE_VIDPID(0x0f0d, 0x00aa), /* HORI Real Arcade Pro V Hayabusa in Switch Mode */ + MAKE_VIDPID(0x0f0d, 0x00ed), /* Hori Fighting Stick mini 4 kai */ + MAKE_VIDPID(0x0f0d, 0x011c), /* Hori Fighting Stick α in PS4 Mode */ + MAKE_VIDPID(0x0f0d, 0x011e), /* Hori Fighting Stick α in PC Mode */ + MAKE_VIDPID(0x0f0d, 0x0184), /* Hori Fighting Stick α in PS5 Mode */ + MAKE_VIDPID(0x146b, 0x0604), /* NACON Daija Arcade Stick */ + MAKE_VIDPID(0x1532, 0x0a00), /* Razer Atrox Arcade Stick */ + MAKE_VIDPID(0x1bad, 0xf03d), /* Street Fighter IV Arcade Stick TE - Chun Li */ + MAKE_VIDPID(0x1bad, 0xf502), /* Hori Real Arcade Pro.VX SA */ + MAKE_VIDPID(0x1bad, 0xf504), /* Hori Real Arcade Pro. EX */ + MAKE_VIDPID(0x1bad, 0xf506), /* Hori Real Arcade Pro.EX Premium VLX */ + MAKE_VIDPID(0x20d6, 0xa715), /* PowerA Nintendo Switch Fusion Arcade Stick */ + MAKE_VIDPID(0x24c6, 0x5000), /* Razer Atrox Arcade Stick */ + MAKE_VIDPID(0x24c6, 0x5501), /* Hori Real Arcade Pro VX-SA */ + MAKE_VIDPID(0x24c6, 0x550e), /* Hori Real Arcade Pro V Kai 360 */ + MAKE_VIDPID(0x2c22, 0x2300), /* Qanba Obsidian Arcade Joystick in PS4 Mode */ + MAKE_VIDPID(0x2c22, 0x2302), /* Qanba Obsidian Arcade Joystick in PS3 Mode */ + MAKE_VIDPID(0x2c22, 0x2303), /* Qanba Obsidian Arcade Joystick in PC Mode */ + MAKE_VIDPID(0x2c22, 0x2500), /* Qanba Dragon Arcade Joystick in PS4 Mode */ + MAKE_VIDPID(0x2c22, 0x2502), /* Qanba Dragon Arcade Joystick in PS3 Mode */ + MAKE_VIDPID(0x2c22, 0x2503), /* Qanba Dragon Arcade Joystick in PC Mode */ +}; +static SDL_vidpid_list arcadestick_devices = { + SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_arcadestick_devices), initial_arcadestick_devices, + SDL_FALSE +}; + +/* This list is taken from: + https://raw.githubusercontent.com/denilsonsa/udev-joystick-blacklist/master/generate_rules.py + */ +static Uint32 initial_blacklist_devices[] = { + /* Microsoft Microsoft Wireless Optical Desktop 2.10 */ + /* Microsoft Wireless Desktop - Comfort Edition */ + MAKE_VIDPID(0x045e, 0x009d), + + /* Microsoft Microsoft Digital Media Pro Keyboard */ + /* Microsoft Corp. Digital Media Pro Keyboard */ + MAKE_VIDPID(0x045e, 0x00b0), + + /* Microsoft Microsoft Digital Media Keyboard */ + /* Microsoft Corp. Digital Media Keyboard 1.0A */ + MAKE_VIDPID(0x045e, 0x00b4), + + /* Microsoft Microsoft Digital Media Keyboard 3000 */ + MAKE_VIDPID(0x045e, 0x0730), + + /* Microsoft Microsoft 2.4GHz Transceiver v6.0 */ + /* Microsoft Microsoft 2.4GHz Transceiver v8.0 */ + /* Microsoft Corp. Nano Transceiver v1.0 for Bluetooth */ + /* Microsoft Wireless Mobile Mouse 1000 */ + /* Microsoft Wireless Desktop 3000 */ + MAKE_VIDPID(0x045e, 0x0745), + + /* Microsoft SideWinder(TM) 2.4GHz Transceiver */ + MAKE_VIDPID(0x045e, 0x0748), + + /* Microsoft Corp. Wired Keyboard 600 */ + MAKE_VIDPID(0x045e, 0x0750), + + /* Microsoft Corp. Sidewinder X4 keyboard */ + MAKE_VIDPID(0x045e, 0x0768), + + /* Microsoft Corp. Arc Touch Mouse Transceiver */ + MAKE_VIDPID(0x045e, 0x0773), + + /* Microsoft 2.4GHz Transceiver v9.0 */ + /* Microsoft Nano Transceiver v2.1 */ + /* Microsoft Sculpt Ergonomic Keyboard (5KV-00001) */ + MAKE_VIDPID(0x045e, 0x07a5), + + /* Microsoft Nano Transceiver v1.0 */ + /* Microsoft Wireless Keyboard 800 */ + MAKE_VIDPID(0x045e, 0x07b2), + + /* Microsoft Nano Transceiver v2.0 */ + MAKE_VIDPID(0x045e, 0x0800), + + MAKE_VIDPID(0x046d, 0xc30a), /* Logitech, Inc. iTouch Composite keboard */ + + MAKE_VIDPID(0x04d9, 0xa0df), /* Tek Syndicate Mouse (E-Signal USB Gaming Mouse) */ + + /* List of Wacom devices at: http://linuxwacom.sourceforge.net/wiki/index.php/Device_IDs */ + MAKE_VIDPID(0x056a, 0x0010), /* Wacom ET-0405 Graphire */ + MAKE_VIDPID(0x056a, 0x0011), /* Wacom ET-0405A Graphire2 (4x5) */ + MAKE_VIDPID(0x056a, 0x0012), /* Wacom ET-0507A Graphire2 (5x7) */ + MAKE_VIDPID(0x056a, 0x0013), /* Wacom CTE-430 Graphire3 (4x5) */ + MAKE_VIDPID(0x056a, 0x0014), /* Wacom CTE-630 Graphire3 (6x8) */ + MAKE_VIDPID(0x056a, 0x0015), /* Wacom CTE-440 Graphire4 (4x5) */ + MAKE_VIDPID(0x056a, 0x0016), /* Wacom CTE-640 Graphire4 (6x8) */ + MAKE_VIDPID(0x056a, 0x0017), /* Wacom CTE-450 Bamboo Fun (4x5) */ + MAKE_VIDPID(0x056a, 0x0018), /* Wacom CTE-650 Bamboo Fun 6x8 */ + MAKE_VIDPID(0x056a, 0x0019), /* Wacom CTE-631 Bamboo One */ + MAKE_VIDPID(0x056a, 0x00d1), /* Wacom Bamboo Pen and Touch CTH-460 */ + MAKE_VIDPID(0x056a, 0x030e), /* Wacom Intuos Pen (S) CTL-480 */ + + MAKE_VIDPID(0x09da, 0x054f), /* A4 Tech Co., G7 750 mouse */ + MAKE_VIDPID(0x09da, 0x1410), /* A4 Tech Co., Ltd Bloody AL9 mouse */ + MAKE_VIDPID(0x09da, 0x3043), /* A4 Tech Co., Ltd Bloody R8A Gaming Mouse */ + MAKE_VIDPID(0x09da, 0x31b5), /* A4 Tech Co., Ltd Bloody TL80 Terminator Laser Gaming Mouse */ + MAKE_VIDPID(0x09da, 0x3997), /* A4 Tech Co., Ltd Bloody RT7 Terminator Wireless */ + MAKE_VIDPID(0x09da, 0x3f8b), /* A4 Tech Co., Ltd Bloody V8 mouse */ + MAKE_VIDPID(0x09da, 0x51f4), /* Modecom MC-5006 Keyboard */ + MAKE_VIDPID(0x09da, 0x5589), /* A4 Tech Co., Ltd Terminator TL9 Laser Gaming Mouse */ + MAKE_VIDPID(0x09da, 0x7b22), /* A4 Tech Co., Ltd Bloody V5 */ + MAKE_VIDPID(0x09da, 0x7f2d), /* A4 Tech Co., Ltd Bloody R3 mouse */ + MAKE_VIDPID(0x09da, 0x8090), /* A4 Tech Co., Ltd X-718BK Oscar Optical Gaming Mouse */ + MAKE_VIDPID(0x09da, 0x9033), /* A4 Tech Co., X7 X-705K */ + MAKE_VIDPID(0x09da, 0x9066), /* A4 Tech Co., Sharkoon Fireglider Optical */ + MAKE_VIDPID(0x09da, 0x9090), /* A4 Tech Co., Ltd XL-730K / XL-750BK / XL-755BK Laser Mouse */ + MAKE_VIDPID(0x09da, 0x90c0), /* A4 Tech Co., Ltd X7 G800V keyboard */ + MAKE_VIDPID(0x09da, 0xf012), /* A4 Tech Co., Ltd Bloody V7 mouse */ + MAKE_VIDPID(0x09da, 0xf32a), /* A4 Tech Co., Ltd Bloody B540 keyboard */ + MAKE_VIDPID(0x09da, 0xf613), /* A4 Tech Co., Ltd Bloody V2 mouse */ + MAKE_VIDPID(0x09da, 0xf624), /* A4 Tech Co., Ltd Bloody B120 Keyboard */ + + MAKE_VIDPID(0x1b1c, 0x1b3c), /* Corsair Harpoon RGB gaming mouse */ + + MAKE_VIDPID(0x1d57, 0xad03), /* [T3] 2.4GHz and IR Air Mouse Remote Control */ + + MAKE_VIDPID(0x1e7d, 0x2e4a), /* Roccat Tyon Mouse */ + + MAKE_VIDPID(0x20a0, 0x422d), /* Winkeyless.kr Keyboards */ + + MAKE_VIDPID(0x2516, 0x001f), /* Cooler Master Storm Mizar Mouse */ + MAKE_VIDPID(0x2516, 0x0028), /* Cooler Master Storm Alcor Mouse */ + + /*****************************************************************/ + /* Additional entries */ + /*****************************************************************/ + + MAKE_VIDPID(0x04d9, 0x8008), /* OBINLB USB-HID Keyboard (Anne Pro II) */ + MAKE_VIDPID(0x04d9, 0x8009), /* OBINLB USB-HID Keyboard (Anne Pro II) */ + MAKE_VIDPID(0x04d9, 0xa292), /* OBINLB USB-HID Keyboard (Anne Pro II) */ + MAKE_VIDPID(0x04d9, 0xa293), /* OBINLB USB-HID Keyboard (Anne Pro II) */ + MAKE_VIDPID(0x1532, 0x0266), /* Razer Huntsman V2 Analog, non-functional DInput device */ + MAKE_VIDPID(0x1532, 0x0282), /* Razer Huntsman Mini Analog, non-functional DInput device */ + MAKE_VIDPID(0x26ce, 0x01a2), /* ASRock LED Controller */ + MAKE_VIDPID(0x20d6, 0x0002), /* PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) */ +}; +static SDL_vidpid_list blacklist_devices = { + SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_blacklist_devices), initial_blacklist_devices, + SDL_FALSE +}; + +static Uint32 initial_flightstick_devices[] = { + MAKE_VIDPID(0x044f, 0x0402), /* HOTAS Warthog Joystick */ + MAKE_VIDPID(0x0738, 0x2221), /* Saitek Pro Flight X-56 Rhino Stick */ + MAKE_VIDPID(0x044f, 0xb10a), /* ThrustMaster, Inc. T.16000M Joystick */ + MAKE_VIDPID(0x046d, 0xc215), /* Logitech Extreme 3D */ + MAKE_VIDPID(0x231d, 0x0126), /* Gunfighter Mk.III ‘Space Combat Edition’ (right) */ + MAKE_VIDPID(0x231d, 0x0127), /* Gunfighter Mk.III ‘Space Combat Edition’ (left) */ +}; +static SDL_vidpid_list flightstick_devices = { + SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_flightstick_devices), initial_flightstick_devices, + SDL_FALSE +}; + +static Uint32 initial_gamecube_devices[] = { + MAKE_VIDPID(0x0e6f, 0x0185), /* PDP Wired Fight Pad Pro for Nintendo Switch */ + MAKE_VIDPID(0x20d6, 0xa711), /* PowerA Wired Controller Nintendo GameCube Style */ +}; +static SDL_vidpid_list gamecube_devices = { + SDL_HINT_JOYSTICK_GAMECUBE_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_gamecube_devices), initial_gamecube_devices, + SDL_FALSE +}; + +static Uint32 initial_rog_gamepad_mice[] = { + MAKE_VIDPID(0x0b05, 0x1906), /* ROG Pugio II */ + MAKE_VIDPID(0x0b05, 0x1958), /* ROG Chakram Core Mouse */ + MAKE_VIDPID(0x0b05, 0x18e3), /* ROG Chakram (wired) Mouse */ + MAKE_VIDPID(0x0b05, 0x18e5), /* ROG Chakram (wireless) Mouse */ + MAKE_VIDPID(0x0b05, 0x1a18), /* ROG Chakram X (wired) Mouse */ + MAKE_VIDPID(0x0b05, 0x1a1a), /* ROG Chakram X (wireless) Mouse */ + MAKE_VIDPID(0x0b05, 0x1a1c), /* ROG Chakram X (Bluetooth) Mouse */ +}; +static SDL_vidpid_list rog_gamepad_mice = { + SDL_HINT_ROG_GAMEPAD_MICE, 0, 0, NULL, + SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_rog_gamepad_mice), initial_rog_gamepad_mice, + SDL_FALSE +}; + +static Uint32 initial_throttle_devices[] = { + MAKE_VIDPID(0x044f, 0x0404), /* HOTAS Warthog Throttle */ + MAKE_VIDPID(0x0738, 0xa221), /* Saitek Pro Flight X-56 Rhino Throttle */ +}; +static SDL_vidpid_list throttle_devices = { + SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_throttle_devices), initial_throttle_devices, + SDL_FALSE +}; + +static Uint32 initial_wheel_devices[] = { + MAKE_VIDPID(0x0079, 0x1864), /* DragonRise Inc. Wired Wheel (active mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) */ + MAKE_VIDPID(0x046d, 0xc294), /* Logitech generic wheel */ + MAKE_VIDPID(0x046d, 0xc295), /* Logitech Momo Force */ + MAKE_VIDPID(0x046d, 0xc298), /* Logitech Driving Force Pro */ + MAKE_VIDPID(0x046d, 0xc299), /* Logitech G25 */ + MAKE_VIDPID(0x046d, 0xc29a), /* Logitech Driving Force GT */ + MAKE_VIDPID(0x046d, 0xc29b), /* Logitech G27 */ + MAKE_VIDPID(0x046d, 0xc24f), /* Logitech G29 (PS3) */ + MAKE_VIDPID(0x046d, 0xc260), /* Logitech G29 (PS4) */ + MAKE_VIDPID(0x046d, 0xc261), /* Logitech G920 (initial mode) */ + MAKE_VIDPID(0x046d, 0xc262), /* Logitech G920 (active mode) */ + MAKE_VIDPID(0x046d, 0xc268), /* Logitech PRO Racing Wheel (PC mode) */ + MAKE_VIDPID(0x046d, 0xc269), /* Logitech PRO Racing Wheel (PS4/PS5 mode) */ + MAKE_VIDPID(0x046d, 0xc272), /* Logitech PRO Racing Wheel for Xbox (PC mode) */ + MAKE_VIDPID(0x046d, 0xc26d), /* Logitech G923 (Xbox) */ + MAKE_VIDPID(0x046d, 0xc26e), /* Logitech G923 */ + MAKE_VIDPID(0x046d, 0xc266), /* Logitech G923 for Playstation 4 and PC (PC mode) */ + MAKE_VIDPID(0x046d, 0xc267), /* Logitech G923 for Playstation 4 and PC (PS4 mode)*/ + MAKE_VIDPID(0x046d, 0xca03), /* Logitech Momo Racing */ + MAKE_VIDPID(0x044f, 0xb65d), /* Thrustmaster Wheel FFB */ + MAKE_VIDPID(0x044f, 0xb66d), /* Thrustmaster Wheel FFB */ + MAKE_VIDPID(0x044f, 0xb677), /* Thrustmaster T150 */ + MAKE_VIDPID(0x044f, 0xb696), /* Thrustmaster T248 */ + MAKE_VIDPID(0x044f, 0xb66e), /* Thrustmaster T300RS (normal mode) */ + MAKE_VIDPID(0x044f, 0xb66f), /* Thrustmaster T300RS (advanced mode) */ + MAKE_VIDPID(0x044f, 0xb66d), /* Thrustmaster T300RS (PS4 mode) */ + MAKE_VIDPID(0x044f, 0xb65e), /* Thrustmaster T500RS */ + MAKE_VIDPID(0x044f, 0xb664), /* Thrustmaster TX (initial mode) */ + MAKE_VIDPID(0x044f, 0xb669), /* Thrustmaster TX (active mode) */ + MAKE_VIDPID(0x0483, 0x0522), /* Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) */ + MAKE_VIDPID(0x0eb7, 0x0001), /* Fanatec ClubSport Wheel Base V2 */ + MAKE_VIDPID(0x0eb7, 0x0004), /* Fanatec ClubSport Wheel Base V2.5 */ + MAKE_VIDPID(0x0eb7, 0x0005), /* Fanatec CSL Elite Wheel Base+ (PS4) */ + MAKE_VIDPID(0x0eb7, 0x0006), /* Fanatec Podium Wheel Base DD1 */ + MAKE_VIDPID(0x0eb7, 0x0007), /* Fanatec Podium Wheel Base DD2 */ + MAKE_VIDPID(0x0eb7, 0x0011), /* Fanatec Forza Motorsport (CSR Wheel / CSR Elite Wheel) */ + MAKE_VIDPID(0x0eb7, 0x0020), /* Fanatec generic wheel / CSL DD / GT DD Pro */ + MAKE_VIDPID(0x0eb7, 0x0197), /* Fanatec Porsche Wheel (Turbo / GT3 RS / Turbo S / GT3 V2 / GT2) */ + MAKE_VIDPID(0x0eb7, 0x038e), /* Fanatec ClubSport Wheel Base V1 */ + MAKE_VIDPID(0x0eb7, 0x0e03), /* Fanatec CSL Elite Wheel Base */ + MAKE_VIDPID(0x11ff, 0x0511), /* DragonRise Inc. Wired Wheel (initial mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) */ + MAKE_VIDPID(0x2433, 0xf300), /* Asetek SimSports Invicta Wheelbase */ + MAKE_VIDPID(0x2433, 0xf301), /* Asetek SimSports Forte Wheelbase */ + MAKE_VIDPID(0x2433, 0xf303), /* Asetek SimSports La Prima Wheelbase */ + MAKE_VIDPID(0x2433, 0xf306), /* Asetek SimSports Tony Kannan Wheelbase */ +}; +static SDL_vidpid_list wheel_devices = { + SDL_HINT_JOYSTICK_WHEEL_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_wheel_devices), initial_wheel_devices, + SDL_FALSE +}; + +static Uint32 initial_zero_centered_devices[] = { + MAKE_VIDPID(0x0e8f, 0x3013), /* HuiJia SNES USB adapter */ + MAKE_VIDPID(0x05a0, 0x3232), /* 8Bitdo Zero Gamepad */ +}; +static SDL_vidpid_list zero_centered_devices = { + SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES, 0, 0, NULL, + NULL, 0, 0, NULL, + SDL_arraysize(initial_zero_centered_devices), initial_zero_centered_devices, + SDL_FALSE +}; + #define CHECK_JOYSTICK_MAGIC(joystick, retval) \ if (!joystick || joystick->magic != &SDL_joystick_magic) { \ SDL_InvalidParamError("joystick"); \ @@ -182,7 +460,7 @@ void SDL_UnlockJoysticks(void) SDL_bool SDL_JoysticksLocked(void) { - return (SDL_joysticks_locked > 0) ? SDL_TRUE : SDL_FALSE; + return (SDL_joysticks_locked > 0); } void SDL_AssertJoysticksLocked(void) @@ -270,8 +548,7 @@ static SDL_bool SDL_SetJoystickIDForPlayerIndex(int player_index, SDL_JoystickID if (player_index >= SDL_joystick_player_count) { SDL_JoystickID *new_players = (SDL_JoystickID *)SDL_realloc(SDL_joystick_players, (player_index + 1) * sizeof(*SDL_joystick_players)); - if (new_players == NULL) { - SDL_OutOfMemory(); + if (!new_players) { return SDL_FALSE; } @@ -335,10 +612,21 @@ int SDL_InitJoysticks(void) SDL_InitGamepadMappings(); + SDL_LoadVIDPIDList(&arcadestick_devices); + SDL_LoadVIDPIDList(&blacklist_devices); + SDL_LoadVIDPIDList(&flightstick_devices); + SDL_LoadVIDPIDList(&gamecube_devices); + SDL_LoadVIDPIDList(&rog_gamepad_mice); + SDL_LoadVIDPIDList(&throttle_devices); + SDL_LoadVIDPIDList(&wheel_devices); + SDL_LoadVIDPIDList(&zero_centered_devices); + /* See if we should allow joystick events while in the background */ SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); + SDL_InitSteamVirtualGamepadInfo(); + status = -1; for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { if (SDL_joystick_drivers[i]->Init() >= 0) { @@ -404,8 +692,6 @@ SDL_JoystickID *SDL_GetJoysticks(int *count) if (count) { *count = 0; } - - SDL_OutOfMemory(); } } SDL_UnlockJoysticks(); @@ -413,13 +699,17 @@ SDL_JoystickID *SDL_GetJoysticks(int *count) return joysticks; } -/* - * Return the next available joystick instance ID - * This may be called by drivers from multiple threads, unprotected by any locks - */ -SDL_JoystickID SDL_GetNextJoystickInstanceID(void) +const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickID instance_id) { - return SDL_AtomicIncRef(&SDL_last_joystick_instance_id) + 1; + SDL_JoystickDriver *driver; + int device_index; + const SDL_SteamVirtualGamepadInfo *info = NULL; + + if (SDL_SteamVirtualGamepadEnabled() && + SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + info = SDL_GetSteamVirtualGamepadInfo(driver->GetDeviceSteamVirtualGamepadSlot(device_index)); + } + return info; } /* @@ -430,9 +720,13 @@ const char *SDL_GetJoystickInstanceName(SDL_JoystickID instance_id) SDL_JoystickDriver *driver; int device_index; const char *name = NULL; + const SDL_SteamVirtualGamepadInfo *info; SDL_LockJoysticks(); - if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id); + if (info) { + name = info->name; + } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { name = driver->GetDeviceName(device_index); } SDL_UnlockJoysticks(); @@ -457,7 +751,7 @@ const char *SDL_GetJoystickInstancePath(SDL_JoystickID instance_id) SDL_UnlockJoysticks(); /* FIXME: Really we should reference count this path so it doesn't go away after unlock */ - if (path == NULL) { + if (!path) { SDL_Unsupported(); } return path; @@ -487,35 +781,14 @@ static SDL_bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick) #ifdef __WINRT__ return SDL_TRUE; #else - static Uint32 zero_centered_joysticks[] = { - MAKE_VIDPID(0x0e8f, 0x3013), /* HuiJia SNES USB adapter */ - MAKE_VIDPID(0x05a0, 0x3232), /* 8Bitdo Zero Gamepad */ - }; - - SDL_bool retval = SDL_FALSE; - int i; - Uint32 id = MAKE_VIDPID(SDL_GetJoystickVendor(joystick), - SDL_GetJoystickProduct(joystick)); - /*printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes);*/ - SDL_LockJoysticks(); - { - if (joystick->naxes == 2) { - /* Assume D-pad or thumbstick style axes are centered at 0 */ - retval = SDL_TRUE; - } - - for (i = 0; i < SDL_arraysize(zero_centered_joysticks); ++i) { - if (id == zero_centered_joysticks[i]) { - retval = SDL_TRUE; - break; - } - } + if (joystick->naxes == 2) { + /* Assume D-pad or thumbstick style axes are centered at 0 */ + return SDL_TRUE; } - SDL_UnlockJoysticks(); - return retval; + return SDL_VIDPIDInList(SDL_GetJoystickVendor(joystick), SDL_GetJoystickProduct(joystick), &zero_centered_devices); #endif /* __WINRT__ */ } @@ -600,7 +873,7 @@ static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick, SDL_bool *inve /* See if the gamepad is in our list of devices to enable */ guid = SDL_GetJoystickGUID(joystick); SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); - SDL_LoadVIDPIDListFromHint(hint, &gamepads); + SDL_LoadVIDPIDListFromHints(&gamepads, hint, NULL); enabled = SDL_VIDPIDInList(vendor, product, &gamepads); SDL_FreeVIDPIDList(&gamepads); if (enabled) { @@ -740,6 +1013,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) const char *joystickpath = NULL; SDL_JoystickPowerLevel initial_power_level; SDL_bool invert_sensors = SDL_FALSE; + const SDL_SteamVirtualGamepadInfo *info; SDL_LockJoysticks(); @@ -764,8 +1038,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) /* Create and initialize the joystick */ joystick = (SDL_Joystick *)SDL_calloc(sizeof(*joystick), 1); - if (joystick == NULL) { - SDL_OutOfMemory(); + if (!joystick) { SDL_UnlockJoysticks(); return NULL; } @@ -808,7 +1081,6 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) joystick->buttons = (Uint8 *)SDL_calloc(joystick->nbuttons, sizeof(Uint8)); } if (((joystick->naxes > 0) && !joystick->axes) || ((joystick->nhats > 0) && !joystick->hats) || ((joystick->nbuttons > 0) && !joystick->buttons)) { - SDL_OutOfMemory(); SDL_CloseJoystick(joystick); SDL_UnlockJoysticks(); return NULL; @@ -825,6 +1097,12 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) joystick->is_gamepad = SDL_IsGamepad(instance_id); + /* Get the Steam Input API handle */ + info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id); + if (info) { + joystick->steam_handle = info->handle; + } + /* Use system gyro and accelerometer if the gamepad doesn't have built-in sensors */ if (ShouldAttemptSensorFusion(joystick, &invert_sensors)) { AttemptSensorFusion(joystick, invert_sensors); @@ -1213,21 +1491,51 @@ SDL_Joystick *SDL_GetJoystickFromPlayerIndex(int player_index) return joystick; } +/* + * Get the properties associated with a joystick + */ +SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick) +{ + SDL_PropertiesID retval; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + if (joystick->props == 0) { + joystick->props = SDL_CreateProperties(); + } + retval = joystick->props; + } + SDL_UnlockJoysticks(); + + return retval; +} + /* * Get the friendly name of this joystick */ const char *SDL_GetJoystickName(SDL_Joystick *joystick) { const char *retval; + const SDL_SteamVirtualGamepadInfo *info; SDL_LockJoysticks(); { CHECK_JOYSTICK_MAGIC(joystick, NULL); - retval = joystick->name; + info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id); + if (info) { + retval = info->name; + } else { + CHECK_JOYSTICK_MAGIC(joystick, NULL); + + retval = joystick->name; + } } SDL_UnlockJoysticks(); + /* FIXME: Really we should reference count this name so it doesn't go away after unlock */ return retval; } @@ -1465,6 +1773,8 @@ void SDL_CloseJoystick(SDL_Joystick *joystick) return; } + SDL_DestroyProperties(joystick->props); + if (joystick->rumble_expiration) { SDL_RumbleJoystick(joystick, 0, 0, 0); } @@ -1549,9 +1859,20 @@ void SDL_QuitJoysticks(void) SDL_QuitSubSystem(SDL_INIT_EVENTS); #endif + SDL_QuitSteamVirtualGamepadInfo(); + SDL_DelHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); + SDL_FreeVIDPIDList(&arcadestick_devices); + SDL_FreeVIDPIDList(&blacklist_devices); + SDL_FreeVIDPIDList(&flightstick_devices); + SDL_FreeVIDPIDList(&gamecube_devices); + SDL_FreeVIDPIDList(&rog_gamepad_mice); + SDL_FreeVIDPIDList(&throttle_devices); + SDL_FreeVIDPIDList(&wheel_devices); + SDL_FreeVIDPIDList(&zero_centered_devices); + SDL_QuitGamepadMappings(); SDL_joysticks_quitting = SDL_FALSE; @@ -1638,7 +1959,10 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) SDL_joystick_being_added = SDL_TRUE; if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { - player_index = driver->GetDevicePlayerIndex(device_index); + player_index = driver->GetDeviceSteamVirtualGamepadSlot(device_index); + if (player_index < 0) { + player_index = driver->GetDevicePlayerIndex(device_index); + } } if (player_index < 0 && SDL_IsGamepad(instance_id)) { player_index = SDL_FindFreePlayerIndex(); @@ -1917,6 +2241,43 @@ int SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 butto return posted; } +static void SendSteamHandleUpdateEvents(void) +{ + SDL_Joystick *joystick; + const SDL_SteamVirtualGamepadInfo *info; + + /* Check to see if any Steam handles changed */ + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + SDL_bool changed = SDL_FALSE; + + if (!joystick->is_gamepad) { + continue; + } + + info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id); + if (info) { + if (joystick->steam_handle != info->handle) { + joystick->steam_handle = info->handle; + changed = SDL_TRUE; + } + } else { + if (joystick->steam_handle != 0) { + joystick->steam_handle = 0; + changed = SDL_TRUE; + } + } + if (changed) { + SDL_Event event; + + SDL_zero(event); + event.type = SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED; + event.common.timestamp = 0; + event.gdevice.which = joystick->instance_id; + SDL_PushEvent(&event); + } + } +} + void SDL_UpdateJoysticks(void) { int i; @@ -1929,6 +2290,10 @@ void SDL_UpdateJoysticks(void) SDL_LockJoysticks(); + if (SDL_UpdateSteamVirtualGamepadInfo()) { + SendSteamHandleUpdateEvents(); + } + #ifdef SDL_JOYSTICK_HIDAPI /* Special function for HIDAPI devices, as a single device can provide multiple SDL_Joysticks */ HIDAPI_UpdateDevices(); @@ -2117,6 +2482,7 @@ char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_n { "HORI CO.,LTD", "HORI" }, { "HORI CO.,LTD.", "HORI" }, { "Mad Catz Inc.", "Mad Catz" }, + { "Nintendo Co., Ltd.", "Nintendo" }, { "NVIDIA Corporation ", "" }, { "Performance Designed Products", "PDP" }, { "QANBA USA, LLC", "Qanba" }, @@ -2132,10 +2498,10 @@ char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_n return SDL_strdup(custom_name); } - if (vendor_name == NULL) { + if (!vendor_name) { vendor_name = ""; } - if (product_name == NULL) { + if (!product_name) { product_name = ""; } @@ -2178,7 +2544,7 @@ char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_n default: len = (6 + 1 + 6 + 1); name = (char *)SDL_malloc(len); - if (name != NULL) { + if (name) { (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); } break; @@ -2187,7 +2553,7 @@ char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_n name = SDL_strdup("Controller"); } - if (name == NULL) { + if (!name) { return NULL; } @@ -2244,21 +2610,26 @@ char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_n return name; } -SDL_JoystickGUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *name, Uint8 driver_signature, Uint8 driver_data) +SDL_JoystickGUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data) { SDL_JoystickGUID guid; Uint16 *guid16 = (Uint16 *)guid.data; + Uint16 crc = 0; SDL_zero(guid); - if (name == NULL) { - name = ""; + if (vendor_name && *vendor_name && product_name && *product_name) { + crc = SDL_crc16(crc, vendor_name, SDL_strlen(vendor_name)); + crc = SDL_crc16(crc, " ", 1); + crc = SDL_crc16(crc, product_name, SDL_strlen(product_name)); + } else if (product_name) { + crc = SDL_crc16(crc, product_name, SDL_strlen(product_name)); } /* We only need 16 bits for each of these; space them out to fill 128. */ /* Byteswap so devices get same GUID on little/big endian platforms. */ *guid16++ = SDL_SwapLE16(bus); - *guid16++ = SDL_SwapLE16(SDL_crc16(0, name, SDL_strlen(name))); + *guid16++ = SDL_SwapLE16(crc); if (vendor && product) { *guid16++ = SDL_SwapLE16(vendor); @@ -2276,14 +2647,14 @@ SDL_JoystickGUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 produc guid.data[14] = driver_signature; guid.data[15] = driver_data; } - SDL_strlcpy((char *)guid16, name, available_space); + SDL_strlcpy((char *)guid16, product_name, available_space); } return guid; } SDL_JoystickGUID SDL_CreateJoystickGUIDForName(const char *name) { - return SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, 0, 0, 0, name, 0, 0); + return SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, 0, 0, 0, NULL, name, 0, 0); } void SDL_SetJoystickGUIDVendor(SDL_JoystickGUID *guid, Uint16 vendor) @@ -2331,9 +2702,6 @@ 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_MICROSOFT && product == USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER) { - type = SDL_GAMEPAD_TYPE_XBOXONE; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; @@ -2355,6 +2723,10 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; + } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { + /* We don't have a type for the Nintendo GameCube controller */ + type = SDL_GAMEPAD_TYPE_STANDARD; + } else { switch (GuessControllerType(vendor, product)) { case k_eControllerType_XBox360Controller: @@ -2418,6 +2790,22 @@ SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_JoystickGUID guid, const char *na return type; } +SDL_bool SDL_JoystickGUIDUsesVersion(SDL_JoystickGUID guid) +{ + Uint16 vendor, product; + + if (SDL_IsJoystickMFI(guid)) { + /* The version bits are used as button capability mask */ + return SDL_FALSE; + } + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (vendor && product) { + return SDL_TRUE; + } + return SDL_FALSE; +} + SDL_bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id) { EControllerType eType = GuessControllerType(vendor_id, product_id); @@ -2466,6 +2854,12 @@ SDL_bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) return SDL_TRUE; } } + if (vendor_id == USB_VENDOR_HP) { + if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX || + product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) { + return SDL_TRUE; + } + } if (vendor_id == USB_VENDOR_RAZER) { if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 || product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA) { @@ -2484,7 +2878,8 @@ SDL_bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) } } if (vendor_id == USB_VENDOR_8BITDO) { - if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER) { + if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 || + product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) { return SDL_TRUE; } } @@ -2575,6 +2970,11 @@ SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; } +SDL_bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &gamecube_devices); +} + SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id) { return ((vendor_id == USB_VENDOR_AMAZON && product_id == USB_PRODUCT_AMAZON_LUNA_CONTROLLER) || @@ -2599,6 +2999,12 @@ SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id) return eType == k_eControllerType_SteamController || eType == k_eControllerType_SteamControllerV2; } +SDL_bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SteamControllerNeptune; +} + SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid) { return (guid.data[14] == 'x') ? SDL_TRUE : SDL_FALSE; @@ -2614,6 +3020,11 @@ SDL_bool SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid) return (guid.data[14] == 'h') ? SDL_TRUE : SDL_FALSE; } +SDL_bool SDL_IsJoystickMFI(SDL_JoystickGUID guid) +{ + return (guid.data[14] == 'm') ? SDL_TRUE : SDL_FALSE; +} + SDL_bool SDL_IsJoystickRAWINPUT(SDL_JoystickGUID guid) { return (guid.data[14] == 'r') ? SDL_TRUE : SDL_FALSE; @@ -2624,148 +3035,48 @@ SDL_bool SDL_IsJoystickVIRTUAL(SDL_JoystickGUID guid) return (guid.data[14] == 'v') ? SDL_TRUE : SDL_FALSE; } -static SDL_bool SDL_IsJoystickProductWheel(Uint32 vidpid) +static SDL_bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) { - static Uint32 wheel_joysticks[] = { - MAKE_VIDPID(0x0079, 0x1864), /* DragonRise Inc. Wired Wheel (active mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) */ - MAKE_VIDPID(0x046d, 0xc294), /* Logitech generic wheel */ - MAKE_VIDPID(0x046d, 0xc295), /* Logitech Momo Force */ - MAKE_VIDPID(0x046d, 0xc298), /* Logitech Driving Force Pro */ - MAKE_VIDPID(0x046d, 0xc299), /* Logitech G25 */ - MAKE_VIDPID(0x046d, 0xc29a), /* Logitech Driving Force GT */ - MAKE_VIDPID(0x046d, 0xc29b), /* Logitech G27 */ - MAKE_VIDPID(0x046d, 0xc24f), /* Logitech G29 (PS3) */ - MAKE_VIDPID(0x046d, 0xc260), /* Logitech G29 (PS4) */ - MAKE_VIDPID(0x046d, 0xc261), /* Logitech G920 (initial mode) */ - MAKE_VIDPID(0x046d, 0xc262), /* Logitech G920 (active mode) */ - MAKE_VIDPID(0x046d, 0xc268), /* Logitech PRO Racing Wheel (PC mode) */ - MAKE_VIDPID(0x046d, 0xc269), /* Logitech PRO Racing Wheel (PS4/PS5 mode) */ - MAKE_VIDPID(0x046d, 0xc272), /* Logitech PRO Racing Wheel for Xbox (PC mode) */ - MAKE_VIDPID(0x046d, 0xc26d), /* Logitech G923 (Xbox) */ - MAKE_VIDPID(0x046d, 0xc26e), /* Logitech G923 */ - MAKE_VIDPID(0x046d, 0xc266), /* Logitech G923 for Playstation 4 and PC (PC mode) */ - MAKE_VIDPID(0x046d, 0xc267), /* Logitech G923 for Playstation 4 and PC (PS4 mode)*/ - MAKE_VIDPID(0x046d, 0xca03), /* Logitech Momo Racing */ - MAKE_VIDPID(0x044f, 0xb65d), /* Thrustmaster Wheel FFB */ - MAKE_VIDPID(0x044f, 0xb66d), /* Thrustmaster Wheel FFB */ - MAKE_VIDPID(0x044f, 0xb677), /* Thrustmaster T150 */ - MAKE_VIDPID(0x044f, 0xb696), /* Thrustmaster T248 */ - MAKE_VIDPID(0x044f, 0xb66e), /* Thrustmaster T300RS (normal mode) */ - MAKE_VIDPID(0x044f, 0xb66f), /* Thrustmaster T300RS (advanced mode) */ - MAKE_VIDPID(0x044f, 0xb66d), /* Thrustmaster T300RS (PS4 mode) */ - MAKE_VIDPID(0x044f, 0xb65e), /* Thrustmaster T500RS */ - MAKE_VIDPID(0x044f, 0xb664), /* Thrustmaster TX (initial mode) */ - MAKE_VIDPID(0x044f, 0xb669), /* Thrustmaster TX (active mode) */ - MAKE_VIDPID(0x0483, 0x0522), /* Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) */ - MAKE_VIDPID(0x0eb7, 0x0001), /* Fanatec ClubSport Wheel Base V2 */ - MAKE_VIDPID(0x0eb7, 0x0004), /* Fanatec ClubSport Wheel Base V2.5 */ - MAKE_VIDPID(0x0eb7, 0x0005), /* Fanatec CSL Elite Wheel Base+ (PS4) */ - MAKE_VIDPID(0x0eb7, 0x0006), /* Fanatec Podium Wheel Base DD1 */ - MAKE_VIDPID(0x0eb7, 0x0007), /* Fanatec Podium Wheel Base DD2 */ - MAKE_VIDPID(0x0eb7, 0x0011), /* Fanatec Forza Motorsport (CSR Wheel / CSR Elite Wheel) */ - MAKE_VIDPID(0x0eb7, 0x0020), /* Fanatec generic wheel / CSL DD / GT DD Pro */ - MAKE_VIDPID(0x0eb7, 0x0197), /* Fanatec Porsche Wheel (Turbo / GT3 RS / Turbo S / GT3 V2 / GT2) */ - MAKE_VIDPID(0x0eb7, 0x038e), /* Fanatec ClubSport Wheel Base V1 */ - MAKE_VIDPID(0x0eb7, 0x0e03), /* Fanatec CSL Elite Wheel Base */ - MAKE_VIDPID(0x11ff, 0x0511), /* DragonRise Inc. Wired Wheel (initial mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) */ - }; - int i; - - for (i = 0; i < SDL_arraysize(wheel_joysticks); ++i) { - if (vidpid == wheel_joysticks[i]) { - return SDL_TRUE; - } - } - return SDL_FALSE; + return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices); } -static SDL_bool SDL_IsJoystickProductArcadeStick(Uint32 vidpid) +static SDL_bool SDL_IsJoystickArcadeStick(Uint16 vendor_id, Uint16 product_id) { - static Uint32 arcadestick_joysticks[] = { - MAKE_VIDPID(0x0079, 0x181a), /* Venom Arcade Stick */ - MAKE_VIDPID(0x0079, 0x181b), /* Venom Arcade Stick */ - MAKE_VIDPID(0x0c12, 0x0ef6), /* Hitbox Arcade Stick */ - MAKE_VIDPID(0x0e6f, 0x0109), /* PDP Versus Fighting Pad */ - MAKE_VIDPID(0x0f0d, 0x0016), /* Hori Real Arcade Pro.EX */ - MAKE_VIDPID(0x0f0d, 0x001b), /* Hori Real Arcade Pro VX */ - MAKE_VIDPID(0x0f0d, 0x0063), /* Hori Real Arcade Pro Hayabusa (USA) Xbox One */ - MAKE_VIDPID(0x0f0d, 0x006a), /* Real Arcade Pro 4 */ - MAKE_VIDPID(0x0f0d, 0x0078), /* Hori Real Arcade Pro V Kai Xbox One */ - MAKE_VIDPID(0x0f0d, 0x008a), /* HORI Real Arcade Pro 4 */ - MAKE_VIDPID(0x0f0d, 0x008c), /* Hori Real Arcade Pro 4 */ - MAKE_VIDPID(0x0f0d, 0x00aa), /* HORI Real Arcade Pro V Hayabusa in Switch Mode */ - MAKE_VIDPID(0x0f0d, 0x00ed), /* Hori Fighting Stick mini 4 kai */ - MAKE_VIDPID(0x0f0d, 0x011c), /* Hori Fighting Stick α in PS4 Mode */ - MAKE_VIDPID(0x0f0d, 0x011e), /* Hori Fighting Stick α in PC Mode */ - MAKE_VIDPID(0x0f0d, 0x0184), /* Hori Fighting Stick α in PS5 Mode */ - MAKE_VIDPID(0x146b, 0x0604), /* NACON Daija Arcade Stick */ - MAKE_VIDPID(0x1532, 0x0a00), /* Razer Atrox Arcade Stick */ - MAKE_VIDPID(0x1bad, 0xf03d), /* Street Fighter IV Arcade Stick TE - Chun Li */ - MAKE_VIDPID(0x1bad, 0xf502), /* Hori Real Arcade Pro.VX SA */ - MAKE_VIDPID(0x1bad, 0xf504), /* Hori Real Arcade Pro. EX */ - MAKE_VIDPID(0x1bad, 0xf506), /* Hori Real Arcade Pro.EX Premium VLX */ - MAKE_VIDPID(0x20d6, 0xa715), /* PowerA Nintendo Switch Fusion Arcade Stick */ - MAKE_VIDPID(0x24c6, 0x5000), /* Razer Atrox Arcade Stick */ - MAKE_VIDPID(0x24c6, 0x5501), /* Hori Real Arcade Pro VX-SA */ - MAKE_VIDPID(0x24c6, 0x550e), /* Hori Real Arcade Pro V Kai 360 */ - MAKE_VIDPID(0x2c22, 0x2300), /* Qanba Obsidian Arcade Joystick in PS4 Mode */ - MAKE_VIDPID(0x2c22, 0x2302), /* Qanba Obsidian Arcade Joystick in PS3 Mode */ - MAKE_VIDPID(0x2c22, 0x2303), /* Qanba Obsidian Arcade Joystick in PC Mode */ - MAKE_VIDPID(0x2c22, 0x2500), /* Qanba Dragon Arcade Joystick in PS4 Mode */ - MAKE_VIDPID(0x2c22, 0x2502), /* Qanba Dragon Arcade Joystick in PS3 Mode */ - MAKE_VIDPID(0x2c22, 0x2503), /* Qanba Dragon Arcade Joystick in PC Mode */ - }; - int i; - - for (i = 0; i < SDL_arraysize(arcadestick_joysticks); ++i) { - if (vidpid == arcadestick_joysticks[i]) { - return SDL_TRUE; - } - } - return SDL_FALSE; + return SDL_VIDPIDInList(vendor_id, product_id, &arcadestick_devices); } -static SDL_bool SDL_IsJoystickProductFlightStick(Uint32 vidpid) +static SDL_bool SDL_IsJoystickFlightStick(Uint16 vendor_id, Uint16 product_id) { - static Uint32 flightstick_joysticks[] = { - MAKE_VIDPID(0x044f, 0x0402), /* HOTAS Warthog Joystick */ - MAKE_VIDPID(0x0738, 0x2221), /* Saitek Pro Flight X-56 Rhino Stick */ - MAKE_VIDPID(0x044f, 0xb10a), /* ThrustMaster, Inc. T.16000M Joystick */ - MAKE_VIDPID(0x046d, 0xc215), /* Logitech Extreme 3D */ - MAKE_VIDPID(0x231d, 0x0126), /* Gunfighter Mk.III ‘Space Combat Edition’ (right) */ - MAKE_VIDPID(0x231d, 0x0127), /* Gunfighter Mk.III ‘Space Combat Edition’ (left) */ - }; - int i; - - for (i = 0; i < SDL_arraysize(flightstick_joysticks); ++i) { - if (vidpid == flightstick_joysticks[i]) { - return SDL_TRUE; - } - } - return SDL_FALSE; + return SDL_VIDPIDInList(vendor_id, product_id, &flightstick_devices); } -static SDL_bool SDL_IsJoystickProductThrottle(Uint32 vidpid) +static SDL_bool SDL_IsJoystickThrottle(Uint16 vendor_id, Uint16 product_id) { - static Uint32 throttle_joysticks[] = { - MAKE_VIDPID(0x044f, 0x0404), /* HOTAS Warthog Throttle */ - MAKE_VIDPID(0x0738, 0xa221), /* Saitek Pro Flight X-56 Rhino Throttle */ - }; - int i; - - for (i = 0; i < SDL_arraysize(throttle_joysticks); ++i) { - if (vidpid == throttle_joysticks[i]) { - return SDL_TRUE; - } - } - return SDL_FALSE; + return SDL_VIDPIDInList(vendor_id, product_id, &throttle_devices); } static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_JoystickGUID guid) { Uint16 vendor; Uint16 product; - Uint32 vidpid; + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + + if (SDL_IsJoystickWheel(vendor, product)) { + return SDL_JOYSTICK_TYPE_WHEEL; + } + + if (SDL_IsJoystickArcadeStick(vendor, product)) { + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + + if (SDL_IsJoystickFlightStick(vendor, product)) { + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + + if (SDL_IsJoystickThrottle(vendor, product)) { + return SDL_JOYSTICK_TYPE_THROTTLE; + } if (SDL_IsJoystickXInput(guid)) { /* XInput GUID, get the type based on the XInput device subtype */ @@ -2801,25 +3112,6 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_JoystickGUID guid) return (SDL_JoystickType)guid.data[15]; } - SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); - vidpid = MAKE_VIDPID(vendor, product); - - if (SDL_IsJoystickProductWheel(vidpid)) { - return SDL_JOYSTICK_TYPE_WHEEL; - } - - if (SDL_IsJoystickProductArcadeStick(vidpid)) { - return SDL_JOYSTICK_TYPE_ARCADE_STICK; - } - - if (SDL_IsJoystickProductFlightStick(vidpid)) { - return SDL_JOYSTICK_TYPE_FLIGHT_STICK; - } - - if (SDL_IsJoystickProductThrottle(vidpid)) { - return SDL_JOYSTICK_TYPE_THROTTLE; - } - #ifdef SDL_JOYSTICK_HIDAPI if (SDL_IsJoystickHIDAPI(guid)) { return HIDAPI_GetJoystickTypeFromGUID(guid); @@ -2835,147 +3127,18 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_JoystickGUID guid) SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid) { - /* This list is taken from: - https://raw.githubusercontent.com/denilsonsa/udev-joystick-blacklist/master/generate_rules.py - */ - static Uint32 joystick_blacklist[] = { - /* Microsoft Microsoft Wireless Optical Desktop 2.10 */ - /* Microsoft Wireless Desktop - Comfort Edition */ - MAKE_VIDPID(0x045e, 0x009d), - - /* Microsoft Microsoft Digital Media Pro Keyboard */ - /* Microsoft Corp. Digital Media Pro Keyboard */ - MAKE_VIDPID(0x045e, 0x00b0), - - /* Microsoft Microsoft Digital Media Keyboard */ - /* Microsoft Corp. Digital Media Keyboard 1.0A */ - MAKE_VIDPID(0x045e, 0x00b4), - - /* Microsoft Microsoft Digital Media Keyboard 3000 */ - MAKE_VIDPID(0x045e, 0x0730), - - /* Microsoft Microsoft 2.4GHz Transceiver v6.0 */ - /* Microsoft Microsoft 2.4GHz Transceiver v8.0 */ - /* Microsoft Corp. Nano Transceiver v1.0 for Bluetooth */ - /* Microsoft Wireless Mobile Mouse 1000 */ - /* Microsoft Wireless Desktop 3000 */ - MAKE_VIDPID(0x045e, 0x0745), - - /* Microsoft SideWinder(TM) 2.4GHz Transceiver */ - MAKE_VIDPID(0x045e, 0x0748), - - /* Microsoft Corp. Wired Keyboard 600 */ - MAKE_VIDPID(0x045e, 0x0750), - - /* Microsoft Corp. Sidewinder X4 keyboard */ - MAKE_VIDPID(0x045e, 0x0768), - - /* Microsoft Corp. Arc Touch Mouse Transceiver */ - MAKE_VIDPID(0x045e, 0x0773), - - /* Microsoft 2.4GHz Transceiver v9.0 */ - /* Microsoft Nano Transceiver v2.1 */ - /* Microsoft Sculpt Ergonomic Keyboard (5KV-00001) */ - MAKE_VIDPID(0x045e, 0x07a5), - - /* Microsoft Nano Transceiver v1.0 */ - /* Microsoft Wireless Keyboard 800 */ - MAKE_VIDPID(0x045e, 0x07b2), - - /* Microsoft Nano Transceiver v2.0 */ - MAKE_VIDPID(0x045e, 0x0800), - - MAKE_VIDPID(0x046d, 0xc30a), /* Logitech, Inc. iTouch Composite keboard */ - - MAKE_VIDPID(0x04d9, 0xa0df), /* Tek Syndicate Mouse (E-Signal USB Gaming Mouse) */ - - /* List of Wacom devices at: http://linuxwacom.sourceforge.net/wiki/index.php/Device_IDs */ - MAKE_VIDPID(0x056a, 0x0010), /* Wacom ET-0405 Graphire */ - MAKE_VIDPID(0x056a, 0x0011), /* Wacom ET-0405A Graphire2 (4x5) */ - MAKE_VIDPID(0x056a, 0x0012), /* Wacom ET-0507A Graphire2 (5x7) */ - MAKE_VIDPID(0x056a, 0x0013), /* Wacom CTE-430 Graphire3 (4x5) */ - MAKE_VIDPID(0x056a, 0x0014), /* Wacom CTE-630 Graphire3 (6x8) */ - MAKE_VIDPID(0x056a, 0x0015), /* Wacom CTE-440 Graphire4 (4x5) */ - MAKE_VIDPID(0x056a, 0x0016), /* Wacom CTE-640 Graphire4 (6x8) */ - MAKE_VIDPID(0x056a, 0x0017), /* Wacom CTE-450 Bamboo Fun (4x5) */ - MAKE_VIDPID(0x056a, 0x0018), /* Wacom CTE-650 Bamboo Fun 6x8 */ - MAKE_VIDPID(0x056a, 0x0019), /* Wacom CTE-631 Bamboo One */ - MAKE_VIDPID(0x056a, 0x00d1), /* Wacom Bamboo Pen and Touch CTH-460 */ - MAKE_VIDPID(0x056a, 0x030e), /* Wacom Intuos Pen (S) CTL-480 */ - - MAKE_VIDPID(0x09da, 0x054f), /* A4 Tech Co., G7 750 mouse */ - MAKE_VIDPID(0x09da, 0x1410), /* A4 Tech Co., Ltd Bloody AL9 mouse */ - MAKE_VIDPID(0x09da, 0x3043), /* A4 Tech Co., Ltd Bloody R8A Gaming Mouse */ - MAKE_VIDPID(0x09da, 0x31b5), /* A4 Tech Co., Ltd Bloody TL80 Terminator Laser Gaming Mouse */ - MAKE_VIDPID(0x09da, 0x3997), /* A4 Tech Co., Ltd Bloody RT7 Terminator Wireless */ - MAKE_VIDPID(0x09da, 0x3f8b), /* A4 Tech Co., Ltd Bloody V8 mouse */ - MAKE_VIDPID(0x09da, 0x51f4), /* Modecom MC-5006 Keyboard */ - MAKE_VIDPID(0x09da, 0x5589), /* A4 Tech Co., Ltd Terminator TL9 Laser Gaming Mouse */ - MAKE_VIDPID(0x09da, 0x7b22), /* A4 Tech Co., Ltd Bloody V5 */ - MAKE_VIDPID(0x09da, 0x7f2d), /* A4 Tech Co., Ltd Bloody R3 mouse */ - MAKE_VIDPID(0x09da, 0x8090), /* A4 Tech Co., Ltd X-718BK Oscar Optical Gaming Mouse */ - MAKE_VIDPID(0x09da, 0x9033), /* A4 Tech Co., X7 X-705K */ - MAKE_VIDPID(0x09da, 0x9066), /* A4 Tech Co., Sharkoon Fireglider Optical */ - MAKE_VIDPID(0x09da, 0x9090), /* A4 Tech Co., Ltd XL-730K / XL-750BK / XL-755BK Laser Mouse */ - MAKE_VIDPID(0x09da, 0x90c0), /* A4 Tech Co., Ltd X7 G800V keyboard */ - MAKE_VIDPID(0x09da, 0xf012), /* A4 Tech Co., Ltd Bloody V7 mouse */ - MAKE_VIDPID(0x09da, 0xf32a), /* A4 Tech Co., Ltd Bloody B540 keyboard */ - MAKE_VIDPID(0x09da, 0xf613), /* A4 Tech Co., Ltd Bloody V2 mouse */ - MAKE_VIDPID(0x09da, 0xf624), /* A4 Tech Co., Ltd Bloody B120 Keyboard */ - - MAKE_VIDPID(0x1b1c, 0x1b3c), /* Corsair Harpoon RGB gaming mouse */ - - MAKE_VIDPID(0x1d57, 0xad03), /* [T3] 2.4GHz and IR Air Mouse Remote Control */ - - MAKE_VIDPID(0x1e7d, 0x2e4a), /* Roccat Tyon Mouse */ - - MAKE_VIDPID(0x20a0, 0x422d), /* Winkeyless.kr Keyboards */ - - MAKE_VIDPID(0x2516, 0x001f), /* Cooler Master Storm Mizar Mouse */ - MAKE_VIDPID(0x2516, 0x0028), /* Cooler Master Storm Alcor Mouse */ - - /*****************************************************************/ - /* Additional entries */ - /*****************************************************************/ - - MAKE_VIDPID(0x04d9, 0x8008), /* OBINLB USB-HID Keyboard (Anne Pro II) */ - MAKE_VIDPID(0x04d9, 0x8009), /* OBINLB USB-HID Keyboard (Anne Pro II) */ - MAKE_VIDPID(0x04d9, 0xa292), /* OBINLB USB-HID Keyboard (Anne Pro II) */ - MAKE_VIDPID(0x04d9, 0xa293), /* OBINLB USB-HID Keyboard (Anne Pro II) */ - MAKE_VIDPID(0x1532, 0x0266), /* Razer Huntsman V2 Analog, non-functional DInput device */ - MAKE_VIDPID(0x1532, 0x0282), /* Razer Huntsman Mini Analog, non-functional DInput device */ - MAKE_VIDPID(0x26ce, 0x01a2), /* ASRock LED Controller */ - MAKE_VIDPID(0x20d6, 0x0002), /* PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) */ - }; - - static Uint32 rog_chakram_list[] = { - MAKE_VIDPID(0x0b05, 0x1958), /* ROG Chakram Core Mouse */ - MAKE_VIDPID(0x0b05, 0x18e3), /* ROG Chakram (wired) Mouse */ - MAKE_VIDPID(0x0b05, 0x18e5), /* ROG Chakram (wireless) Mouse */ - MAKE_VIDPID(0x0b05, 0x1a18), /* ROG Chakram X (wired) Mouse */ - MAKE_VIDPID(0x0b05, 0x1a1a), /* ROG Chakram X (wireless) Mouse */ - MAKE_VIDPID(0x0b05, 0x1a1c), /* ROG Chakram X (Bluetooth) Mouse */ - }; - - unsigned int i; - Uint32 id; Uint16 vendor; Uint16 product; SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); /* Check the joystick blacklist */ - id = MAKE_VIDPID(vendor, product); - for (i = 0; i < SDL_arraysize(joystick_blacklist); ++i) { - if (id == joystick_blacklist[i]) { - return SDL_TRUE; - } + if (SDL_VIDPIDInList(vendor, product, &blacklist_devices)) { + return SDL_TRUE; } if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_ROG_CHAKRAM, SDL_FALSE)) { - for (i = 0; i < SDL_arraysize(rog_chakram_list); ++i) { - if (id == rog_chakram_list[i]) { - return SDL_TRUE; - } + if (SDL_VIDPIDInList(vendor, product, &rog_gamepad_mice)) { + return SDL_TRUE; } } @@ -3007,18 +3170,38 @@ SDL_JoystickGUID SDL_GetJoystickInstanceGUID(SDL_JoystickID instance_id) Uint16 SDL_GetJoystickInstanceVendor(SDL_JoystickID instance_id) { Uint16 vendor; - SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id); + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id); + if (info) { + vendor = info->vendor_id; + } else { + SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id); + + SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); + } + SDL_UnlockJoysticks(); - SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); return vendor; } Uint16 SDL_GetJoystickInstanceProduct(SDL_JoystickID instance_id) { Uint16 product; - SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id); + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id); + if (info) { + product = info->product_id; + } else { + SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id); + + SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); + } + SDL_UnlockJoysticks(); - SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); return product; } @@ -3065,18 +3248,46 @@ SDL_JoystickGUID SDL_GetJoystickGUID(SDL_Joystick *joystick) Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick) { Uint16 vendor; - SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id); + if (info) { + vendor = info->vendor_id; + } else { + SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + + SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); + } + } + SDL_UnlockJoysticks(); - SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); return vendor; } Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick) { Uint16 product; - SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id); + if (info) { + product = info->product_id; + } else { + SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + + SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); + } + } + SDL_UnlockJoysticks(); - SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); return product; } @@ -3329,21 +3540,19 @@ int SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorT return posted; } -void SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list) +static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *max_entries, Uint32 **entries) { Uint32 entry; char *spot; char *file = NULL; - list->num_entries = 0; - if (hint && *hint == '@') { spot = file = (char *)SDL_LoadFile(hint + 1, NULL); } else { spot = (char *)hint; } - if (spot == NULL) { + if (!spot) { return; } @@ -3351,22 +3560,22 @@ void SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list) entry = (Uint16)SDL_strtol(spot, &spot, 0); entry <<= 16; spot = SDL_strstr(spot, "0x"); - if (spot == NULL) { + if (!spot) { break; } entry |= (Uint16)SDL_strtol(spot, &spot, 0); - if (list->num_entries == list->max_entries) { - int max_entries = list->max_entries + 16; - Uint32 *entries = (Uint32 *)SDL_realloc(list->entries, max_entries * sizeof(*list->entries)); - if (entries == NULL) { + if (*num_entries == *max_entries) { + int new_max_entries = *max_entries + 16; + Uint32 *new_entries = (Uint32 *)SDL_realloc(*entries, new_max_entries * sizeof(**entries)); + if (!new_entries) { /* Out of memory, go with what we have already */ break; } - list->entries = entries; - list->max_entries = max_entries; + *entries = new_entries; + *max_entries = new_max_entries; } - list->entries[list->num_entries++] = entry; + (*entries)[(*num_entries)++] = entry; } if (file) { @@ -3374,13 +3583,100 @@ void SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list) } } +void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list) +{ + /* Empty the list */ + list->num_included_entries = 0; + list->num_excluded_entries = 0; + + /* Add the initial entries */ + if (list->num_initial_entries > 0) { + if (list->num_included_entries < list->num_initial_entries) { + Uint32 *entries = (Uint32 *)SDL_malloc(list->num_initial_entries * sizeof(*entries)); + if (entries) { + SDL_memcpy(entries, list->initial_entries, list->num_initial_entries * sizeof(*entries)); + list->included_entries = entries; + list->num_included_entries = list->num_initial_entries; + list->max_included_entries = list->num_initial_entries; + } + } + } + + /* Add the included entries from the hint */ + SDL_LoadVIDPIDListFromHint(included_list, &list->num_included_entries, &list->max_included_entries, &list->included_entries); + + /* Add the excluded entries from the hint */ + SDL_LoadVIDPIDListFromHint(excluded_list, &list->num_excluded_entries, &list->max_excluded_entries, &list->excluded_entries); +} + +static void SDLCALL SDL_VIDPIDIncludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_vidpid_list *list = (SDL_vidpid_list *)userdata; + const char *included_list = hint; + const char *excluded_list = NULL; + + if (!list->initialized) { + return; + } + + if (list->excluded_hint_name) { + excluded_list = SDL_GetHint(list->excluded_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + +static void SDLCALL SDL_VIDPIDExcludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_vidpid_list *list = (SDL_vidpid_list *)userdata; + const char *included_list = NULL; + const char *excluded_list = hint; + + if (!list->initialized) { + return; + } + + if (list->included_hint_name) { + included_list = SDL_GetHint(list->included_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + +void SDL_LoadVIDPIDList(SDL_vidpid_list *list) +{ + const char *included_list = NULL; + const char *excluded_list = NULL; + + if (list->included_hint_name) { + SDL_AddHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list); + } + + if (list->excluded_hint_name) { + SDL_AddHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list); + } + + list->initialized = SDL_TRUE; + + if (list->included_hint_name) { + included_list = SDL_GetHint(list->included_hint_name); + } + if (list->excluded_hint_name) { + excluded_list = SDL_GetHint(list->excluded_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + SDL_bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list) { int i; Uint32 vidpid = MAKE_VIDPID(vendor_id, product_id); - for (i = 0; i < list->num_entries; ++i) { - if (vidpid == list->entries[i]) { + for (i = 0; i < list->num_excluded_entries; ++i) { + if (vidpid == list->excluded_entries[i]) { + return SDL_FALSE; + } + } + for (i = 0; i < list->num_included_entries; ++i) { + if (vidpid == list->included_entries[i]) { return SDL_TRUE; } } @@ -3389,8 +3685,27 @@ SDL_bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_ void SDL_FreeVIDPIDList(SDL_vidpid_list *list) { - if (list->entries) { - SDL_free(list->entries); - SDL_zerop(list); + if (list->included_hint_name) { + SDL_DelHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list); } + + if (list->excluded_hint_name) { + SDL_DelHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list); + } + + if (list->included_entries) { + SDL_free(list->included_entries); + list->included_entries = NULL; + list->num_included_entries = 0; + list->max_included_entries = 0; + } + + if (list->excluded_entries) { + SDL_free(list->excluded_entries); + list->excluded_entries = NULL; + list->num_excluded_entries = 0; + list->max_excluded_entries = 0; + } + + list->initialized = SDL_FALSE; } diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 685fbebe..95ab4f71 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,7 @@ extern "C" { #endif struct SDL_JoystickDriver; +struct SDL_SteamVirtualGamepadInfo; extern char SDL_joystick_magic; /* Initialization and shutdown functions */ @@ -53,16 +54,13 @@ extern void SDL_AssertJoysticksLocked(void) SDL_ASSERT_CAPABILITY(SDL_joystick_l /* Function to return whether there are any joysticks opened by the application */ extern SDL_bool SDL_JoysticksOpened(void); -/* Function to get the next available joystick instance ID */ -extern SDL_JoystickID SDL_GetNextJoystickInstanceID(void); - /* Function to standardize the name for a controller This should be freed with SDL_free() when no longer needed */ extern char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name); /* Function to create a GUID for a joystick based on the VID/PID and name */ -extern SDL_JoystickGUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *name, Uint8 driver_signature, Uint8 driver_data); +extern SDL_JoystickGUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data); /* Function to create a GUID for a joystick based on the name, with no VID/PID information */ extern SDL_JoystickGUID SDL_CreateJoystickGUIDForName(const char *name); @@ -83,6 +81,9 @@ extern void SDL_SetJoystickGUIDCRC(SDL_JoystickGUID *guid, Uint16 crc); extern SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, SDL_bool forUI); extern SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_JoystickGUID guid, const char *name); +/* Function to return whether a joystick GUID uses the version field */ +extern SDL_bool SDL_JoystickGUIDUsesVersion(SDL_JoystickGUID guid); + /* Function to return whether a joystick is an Xbox One controller */ extern SDL_bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id); @@ -111,6 +112,9 @@ extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id); extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id); +/* Function to return whether a joystick is a Nintendo GameCube style controller */ +extern SDL_bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id); + /* Function to return whether a joystick is an Amazon Luna controller */ extern SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id); @@ -123,6 +127,9 @@ extern SDL_bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 pr /* Function to return whether a joystick is a Steam Controller */ extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); +/* Function to return whether a joystick is a Steam Deck */ +extern SDL_bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); + /* Function to return whether a joystick guid comes from the XInput driver */ extern SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid); @@ -132,6 +139,9 @@ extern SDL_bool SDL_IsJoystickWGI(SDL_JoystickGUID guid); /* Function to return whether a joystick guid comes from the HIDAPI driver */ extern SDL_bool SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid); +/* Function to return whether a joystick guid comes from the MFI driver */ +extern SDL_bool SDL_IsJoystickMFI(SDL_JoystickGUID guid); + /* Function to return whether a joystick guid comes from the RAWINPUT driver */ extern SDL_bool SDL_IsJoystickRAWINPUT(SDL_JoystickGUID guid); @@ -161,21 +171,27 @@ extern int SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, extern void SDL_SendJoystickBatteryLevel(SDL_Joystick *joystick, SDL_JoystickPowerLevel ePowerLevel); +/* Function to get the Steam virtual gamepad info for a joystick */ +extern const struct SDL_SteamVirtualGamepadInfo *SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickID instance_id); + /* Internal sanity checking functions */ extern SDL_bool SDL_IsJoystickValid(SDL_Joystick *joystick); typedef enum { - EMappingKind_None = 0, - EMappingKind_Button = 1, - EMappingKind_Axis = 2, - EMappingKind_Hat = 3 + EMappingKind_None, + EMappingKind_Button, + EMappingKind_Axis, + EMappingKind_Hat, } EMappingKind; typedef struct SDL_InputMapping { EMappingKind kind; Uint8 target; + SDL_bool axis_reversed; + SDL_bool half_axis_positive; + SDL_bool half_axis_negative; } SDL_InputMapping; typedef struct SDL_GamepadMapping @@ -206,6 +222,7 @@ typedef struct SDL_GamepadMapping SDL_InputMapping righty; SDL_InputMapping lefttrigger; SDL_InputMapping righttrigger; + SDL_InputMapping touchpad; } SDL_GamepadMapping; /* Function to get autodetected gamepad controller mapping from the driver */ @@ -215,12 +232,24 @@ extern SDL_bool SDL_PrivateJoystickGetAutoGamepadMapping(SDL_JoystickID instance typedef struct { - int num_entries; - int max_entries; - Uint32 *entries; + const char *included_hint_name; + int num_included_entries; + int max_included_entries; + Uint32 *included_entries; + + const char *excluded_hint_name; + int num_excluded_entries; + int max_excluded_entries; + Uint32 *excluded_entries; + + int num_initial_entries; + Uint32 *initial_entries; + + SDL_bool initialized; } SDL_vidpid_list; -extern void SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list); +extern void SDL_LoadVIDPIDList(SDL_vidpid_list *list); +extern void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list); extern SDL_bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list); extern void SDL_FreeVIDPIDList(SDL_vidpid_list *list); diff --git a/src/joystick/SDL_steam_virtual_gamepad.c b/src/joystick/SDL_steam_virtual_gamepad.c new file mode 100644 index 00000000..b8b2038a --- /dev/null +++ b/src/joystick/SDL_steam_virtual_gamepad.c @@ -0,0 +1,248 @@ +/* + Simple DirectMedia Layer + 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 + 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_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" + +#ifdef __WIN32__ +#include "../core/windows/SDL_windows.h" +#else +#include +#include +#endif + +#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo" + +static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static SDL_SteamVirtualGamepadInfo **SDL_steam_virtual_gamepad_info SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int SDL_steam_virtual_gamepad_info_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; + + +static Uint64 GetFileModificationTime(const char *file) +{ + Uint64 modification_time = 0; + +#ifdef __WIN32__ + WCHAR *wFile = WIN_UTF8ToStringW(file); + if (wFile) { + HANDLE hFile = CreateFileW(wFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + FILETIME last_write_time; + if (GetFileTime(hFile, NULL, NULL, &last_write_time)) { + modification_time = last_write_time.dwHighDateTime; + modification_time <<= 32; + modification_time |= last_write_time.dwLowDateTime; + } + CloseHandle(hFile); + } + SDL_free(wFile); + } +#else + struct stat sb; + + if (stat(file, &sb) == 0) { + modification_time = (Uint64)sb.st_mtime; + } +#endif + return modification_time; +} + +static void SDL_FreeSteamVirtualGamepadInfo(void) +{ + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < SDL_steam_virtual_gamepad_info_count; ++i) { + SDL_SteamVirtualGamepadInfo *entry = SDL_steam_virtual_gamepad_info[i]; + if (entry) { + SDL_free(entry->name); + SDL_free(entry); + } + } + SDL_free(SDL_steam_virtual_gamepad_info); + SDL_steam_virtual_gamepad_info = NULL; + SDL_steam_virtual_gamepad_info_count = 0; +} + +static void AddVirtualGamepadInfo(int slot, SDL_SteamVirtualGamepadInfo *info) +{ + SDL_SteamVirtualGamepadInfo *new_info; + + SDL_AssertJoysticksLocked(); + + if (slot < 0) { + return; + } + + if (slot >= SDL_steam_virtual_gamepad_info_count) { + SDL_SteamVirtualGamepadInfo **slots = (SDL_SteamVirtualGamepadInfo **)SDL_realloc(SDL_steam_virtual_gamepad_info, (slot + 1)*sizeof(*SDL_steam_virtual_gamepad_info)); + if (!slots) { + return; + } + while (SDL_steam_virtual_gamepad_info_count <= slot) { + slots[SDL_steam_virtual_gamepad_info_count++] = NULL; + } + SDL_steam_virtual_gamepad_info = slots; + } + + if (SDL_steam_virtual_gamepad_info[slot]) { + /* We already have this slot info */ + return; + } + + new_info = (SDL_SteamVirtualGamepadInfo *)SDL_malloc(sizeof(*new_info)); + if (!new_info) { + return; + } + SDL_copyp(new_info, info); + SDL_steam_virtual_gamepad_info[slot] = new_info; + SDL_zerop(info); +} + +void SDL_InitSteamVirtualGamepadInfo(void) +{ + const char *file; + + SDL_AssertJoysticksLocked(); + + file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE); + if (file && *file) { + SDL_steam_virtual_gamepad_info_file = SDL_strdup(file); + } + SDL_UpdateSteamVirtualGamepadInfo(); +} + +SDL_bool SDL_SteamVirtualGamepadEnabled(void) +{ + SDL_AssertJoysticksLocked(); + + return (SDL_steam_virtual_gamepad_info != NULL); +} + +SDL_bool SDL_UpdateSteamVirtualGamepadInfo(void) +{ + const int UPDATE_CHECK_INTERVAL_MS = 3000; + Uint64 now; + Uint64 mtime; + char *data, *end, *next, *line, *value; + size_t size; + int slot, new_slot; + SDL_SteamVirtualGamepadInfo info; + + SDL_AssertJoysticksLocked(); + + if (!SDL_steam_virtual_gamepad_info_file) { + return SDL_FALSE; + } + + now = SDL_GetTicks(); + if (SDL_steam_virtual_gamepad_info_check_time && + now < (SDL_steam_virtual_gamepad_info_check_time + UPDATE_CHECK_INTERVAL_MS)) { + return SDL_FALSE; + } + SDL_steam_virtual_gamepad_info_check_time = now; + + mtime = GetFileModificationTime(SDL_steam_virtual_gamepad_info_file); + if (mtime == 0 || mtime == SDL_steam_virtual_gamepad_info_file_mtime) { + return SDL_FALSE; + } + + data = (char *)SDL_LoadFile(SDL_steam_virtual_gamepad_info_file, &size); + if (!data) { + return SDL_FALSE; + } + + SDL_FreeSteamVirtualGamepadInfo(); + + slot = -1; + SDL_zero(info); + + for (next = data, end = data + size; next < end; ) { + while (next < end && (*next == '\0' || *next == '\r' || *next == '\n')) { + ++next; + } + + line = next; + + while (next < end && (*next != '\r' && *next != '\n')) { + ++next; + } + *next = '\0'; + + if (SDL_sscanf(line, "[slot %d]", &new_slot) == 1) { + if (slot >= 0) { + AddVirtualGamepadInfo(slot, &info); + } + slot = new_slot; + } else { + value = SDL_strchr(line, '='); + if (value) { + *value++ = '\0'; + + if (SDL_strcmp(line, "name") == 0) { + SDL_free(info.name); + info.name = SDL_strdup(value); + } else if (SDL_strcmp(line, "VID") == 0) { + info.vendor_id = (Uint16)SDL_strtoul(value, NULL, 0); + } else if (SDL_strcmp(line, "PID") == 0) { + info.product_id = (Uint16)SDL_strtoul(value, NULL, 0); + } else if (SDL_strcmp(line, "type") == 0) { + info.type = SDL_GetGamepadTypeFromString(value); + } else if (SDL_strcmp(line, "handle") == 0) { + info.handle = SDL_strtoull(value, NULL, 0); + } + } + } + } + if (slot >= 0) { + AddVirtualGamepadInfo(slot, &info); + } + SDL_free(data); + + SDL_steam_virtual_gamepad_info_file_mtime = mtime; + + return SDL_TRUE; +} + +const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot) +{ + SDL_AssertJoysticksLocked(); + + if (slot < 0 || slot >= SDL_steam_virtual_gamepad_info_count) { + return NULL; + } + return SDL_steam_virtual_gamepad_info[slot]; +} + +void SDL_QuitSteamVirtualGamepadInfo(void) +{ + SDL_AssertJoysticksLocked(); + + if (SDL_steam_virtual_gamepad_info_file) { + SDL_FreeSteamVirtualGamepadInfo(); + SDL_free(SDL_steam_virtual_gamepad_info_file); + SDL_steam_virtual_gamepad_info_file = NULL; + } +} diff --git a/src/video/cocoa/SDL_cocoashape.h b/src/joystick/SDL_steam_virtual_gamepad.h similarity index 63% rename from src/video/cocoa/SDL_cocoashape.h rename to src/joystick/SDL_steam_virtual_gamepad.h index a7c6d14a..d6833271 100644 --- a/src/video/cocoa/SDL_cocoashape.h +++ b/src/joystick/SDL_steam_virtual_gamepad.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,21 +18,19 @@ 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_cocoashape_h_ -#define SDL_cocoashape_h_ +typedef struct SDL_SteamVirtualGamepadInfo +{ + Uint64 handle; + char *name; + Uint16 vendor_id; + Uint16 product_id; + SDL_GamepadType type; +} SDL_SteamVirtualGamepadInfo; -#include "../SDL_shape_internals.h" - -@interface SDL_ShapeData : NSObject -@property(nonatomic) NSGraphicsContext *context; -@property(nonatomic) SDL_bool saved; -@property(nonatomic) SDL_ShapeTree *shape; -@end - -extern SDL_WindowShaper *Cocoa_CreateShaper(SDL_Window *window); -extern int Cocoa_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode); - -#endif /* SDL_cocoashape_h_ */ +void SDL_InitSteamVirtualGamepadInfo(void); +SDL_bool SDL_SteamVirtualGamepadEnabled(void); +SDL_bool SDL_UpdateSteamVirtualGamepadInfo(void); +const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot); +void SDL_QuitSteamVirtualGamepadInfo(void); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index f0f547e1..ae50d8f6 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -77,6 +77,7 @@ struct SDL_Joystick char *serial _guarded; /* Joystick serial */ SDL_JoystickGUID guid _guarded; /* Joystick guid */ Uint16 firmware_version _guarded; /* Firmware version, if available */ + Uint64 steam_handle _guarded; /* Steam controller API handle */ int naxes _guarded; /* Number of axis controls on the joystick */ SDL_JoystickAxisInfo *axes _guarded; @@ -125,6 +126,8 @@ struct SDL_Joystick struct joystick_hwdata *hwdata _guarded; /* Driver dependent information */ + SDL_PropertiesID props _guarded; + int ref_count _guarded; /* Reference count for multiple opens */ struct SDL_Joystick *next _guarded; /* pointer to next joystick we have allocated */ @@ -166,6 +169,9 @@ typedef struct SDL_JoystickDriver /* Function to get the device-dependent path of a joystick */ const char *(*GetDevicePath)(int device_index); + /* Function to get the Steam virtual gamepad slot of a joystick */ + int (*GetDeviceSteamVirtualGamepadSlot)(int device_index); + /* Function to get the player index of a joystick */ int (*GetDevicePlayerIndex)(int device_index); diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index d8acacc3..cacb47b9 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/src/joystick/android/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -73,16 +73,16 @@ static int keycode_to_SDL(int keycode) switch (keycode) { /* Some gamepad buttons (API 9) */ case AKEYCODE_BUTTON_A: - button = SDL_GAMEPAD_BUTTON_A; + button = SDL_GAMEPAD_BUTTON_SOUTH; break; case AKEYCODE_BUTTON_B: - button = SDL_GAMEPAD_BUTTON_B; + button = SDL_GAMEPAD_BUTTON_EAST; break; case AKEYCODE_BUTTON_X: - button = SDL_GAMEPAD_BUTTON_X; + button = SDL_GAMEPAD_BUTTON_WEST; break; case AKEYCODE_BUTTON_Y: - button = SDL_GAMEPAD_BUTTON_Y; + button = SDL_GAMEPAD_BUTTON_NORTH; break; case AKEYCODE_BUTTON_L1: button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; @@ -136,7 +136,7 @@ static int keycode_to_SDL(int keycode) case AKEYCODE_DPAD_CENTER: /* This is handled better by applications as the A button */ /*button = 19;*/ - button = SDL_GAMEPAD_BUTTON_A; + button = SDL_GAMEPAD_BUTTON_SOUTH; break; /* More gamepad buttons (API 12), these get mapped to 20...35*/ @@ -174,9 +174,9 @@ static int keycode_to_SDL(int keycode) static SDL_Scancode button_to_scancode(int button) { switch (button) { - case SDL_GAMEPAD_BUTTON_A: + case SDL_GAMEPAD_BUTTON_SOUTH: return SDL_SCANCODE_RETURN; - case SDL_GAMEPAD_BUTTON_B: + case SDL_GAMEPAD_BUTTON_EAST: return SDL_SCANCODE_ESCAPE; case SDL_GAMEPAD_BUTTON_BACK: return SDL_SCANCODE_ESCAPE; @@ -319,7 +319,7 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v } } - if (JoystickByDeviceId(device_id) != NULL || name == NULL) { + if (JoystickByDeviceId(device_id) != NULL || !name) { goto done; } @@ -343,7 +343,7 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v nhats = 0; } - guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor_id, product_id, 0, desc, 0, 0); + guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor_id, product_id, 0, NULL, desc, 0, 0); /* Update the GUID with capability bits */ { @@ -353,7 +353,7 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v } item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item)); - if (item == NULL) { + if (!item) { goto done; } @@ -361,7 +361,7 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v item->guid = guid; item->device_id = device_id; item->name = SDL_CreateJoystickName(vendor_id, product_id, NULL, name); - if (item->name == NULL) { + if (!item->name) { SDL_free(item); goto done; } @@ -378,8 +378,8 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v } item->naxes = naxes; item->nhats = nhats; - item->device_instance = SDL_GetNextJoystickInstanceID(); - if (SDL_joylist_tail == NULL) { + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; } else { SDL_joylist_tail->next = item; @@ -412,7 +412,7 @@ int Android_RemoveJoystick(int device_id) SDL_LockJoysticks(); /* Don't call JoystickByDeviceId here or there'll be an infinite loop! */ - while (item != NULL) { + while (item) { if (item->device_id == device_id) { break; } @@ -420,7 +420,7 @@ int Android_RemoveJoystick(int device_id) item = item->next; } - if (item == NULL) { + if (!item) { goto done; } @@ -428,7 +428,7 @@ int Android_RemoveJoystick(int device_id) item->joystick->hwdata = NULL; } - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_joylist == item); @@ -511,7 +511,7 @@ static SDL_joylist_item *JoystickByDeviceId(int device_id) { SDL_joylist_item *item = SDL_joylist; - while (item != NULL) { + while (item) { if (item->device_id == device_id) { return item; } @@ -521,7 +521,7 @@ static SDL_joylist_item *JoystickByDeviceId(int device_id) /* Joystick not found, try adding it */ ANDROID_JoystickDetect(); - while (item != NULL) { + while (item) { if (item->device_id == device_id) { return item; } @@ -541,6 +541,11 @@ static const char *ANDROID_JoystickGetDevicePath(int device_index) return NULL; } +static int ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int ANDROID_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -564,11 +569,11 @@ static int ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) { SDL_joylist_item *item = GetJoystickByDevIndex(device_index); - if (item == NULL) { + if (!item) { return SDL_SetError("No such device"); } - if (item->joystick != NULL) { + if (item->joystick) { return SDL_SetError("Joystick already opened"); } @@ -616,7 +621,7 @@ static void ANDROID_JoystickUpdate(SDL_Joystick *joystick) { SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; - if (item == NULL) { + if (!item) { return; } @@ -681,6 +686,7 @@ SDL_JoystickDriver SDL_ANDROID_JoystickDriver = { ANDROID_JoystickDetect, ANDROID_JoystickGetDeviceName, ANDROID_JoystickGetDevicePath, + ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot, ANDROID_JoystickGetDevicePlayerIndex, ANDROID_JoystickSetDevicePlayerIndex, ANDROID_JoystickGetDeviceGUID, diff --git a/src/joystick/android/SDL_sysjoystick_c.h b/src/joystick/android/SDL_sysjoystick_c.h index 84106dc6..28dd6b3b 100644 --- a/src/joystick/android/SDL_sysjoystick_c.h +++ b/src/joystick/android/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/apple/SDL_mfijoystick.m b/src/joystick/apple/SDL_mfijoystick.m index 32f6f7ca..3aa0c82a 100644 --- a/src/joystick/apple/SDL_mfijoystick.m +++ b/src/joystick/apple/SDL_mfijoystick.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -50,7 +50,6 @@ static id connectObserver = nil; static id disconnectObserver = nil; -static NSString *GCInputXboxShareButton = @"Button Share"; #include #include @@ -231,6 +230,117 @@ static BOOL IsControllerBackboneOne(GCController *controller) } return FALSE; } +static void CheckControllerSiriRemote(GCController *controller, int *is_siri_remote) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory hasPrefix:@"Siri Remote"]) { + *is_siri_remote = 1; + SDL_sscanf(controller.productCategory.UTF8String, "Siri Remote (%i%*s Generation)", is_siri_remote); + return; + } + } + *is_siri_remote = 0; +} + +static BOOL ElementAlreadyHandled(SDL_JoystickDeviceItem *device, NSString *element, NSDictionary *elements) +{ + if ([element isEqualToString:@"Left Thumbstick Left"] || + [element isEqualToString:@"Left Thumbstick Right"]) { + if (elements[@"Left Thumbstick X Axis"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Left Thumbstick Up"] || + [element isEqualToString:@"Left Thumbstick Down"]) { + if (elements[@"Left Thumbstick Y Axis"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Right Thumbstick Left"] || + [element isEqualToString:@"Right Thumbstick Right"]) { + if (elements[@"Right Thumbstick X Axis"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Right Thumbstick Up"] || + [element isEqualToString:@"Right Thumbstick Down"]) { + if (elements[@"Right Thumbstick Y Axis"]) { + return TRUE; + } + } + if (device->is_siri_remote) { + if ([element isEqualToString:@"Direction Pad Left"] || + [element isEqualToString:@"Direction Pad Right"]) { + if (elements[@"Direction Pad X Axis"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Direction Pad Up"] || + [element isEqualToString:@"Direction Pad Down"]) { + if (elements[@"Direction Pad Y Axis"]) { + return TRUE; + } + } + } else { + if ([element isEqualToString:@"Direction Pad X Axis"]) { + if (elements[@"Direction Pad Left"] && + elements[@"Direction Pad Right"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Direction Pad Y Axis"]) { + if (elements[@"Direction Pad Up"] && + elements[@"Direction Pad Down"]) { + return TRUE; + } + } + } + if ([element isEqualToString:@"Cardinal Direction Pad X Axis"]) { + if (elements[@"Cardinal Direction Pad Left"] && + elements[@"Cardinal Direction Pad Right"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Cardinal Direction Pad Y Axis"]) { + if (elements[@"Cardinal Direction Pad Up"] && + elements[@"Cardinal Direction Pad Down"]) { + return TRUE; + } + } + if ([element isEqualToString:@"Touchpad 1 X Axis"] || + [element isEqualToString:@"Touchpad 1 Y Axis"] || + [element isEqualToString:@"Touchpad 1 Left"] || + [element isEqualToString:@"Touchpad 1 Right"] || + [element isEqualToString:@"Touchpad 1 Up"] || + [element isEqualToString:@"Touchpad 1 Down"] || + [element isEqualToString:@"Touchpad 2 X Axis"] || + [element isEqualToString:@"Touchpad 2 Y Axis"] || + [element isEqualToString:@"Touchpad 2 Left"] || + [element isEqualToString:@"Touchpad 2 Right"] || + [element isEqualToString:@"Touchpad 2 Up"] || + [element isEqualToString:@"Touchpad 2 Down"]) { + /* The touchpad is handled separately */ + return TRUE; + } + if ([element isEqualToString:@"Button Home"]) { + if (device->is_switch_joycon_pair) { + /* The Nintendo Switch JoyCon home button doesn't ever show as being held down */ + return TRUE; + } +#if TARGET_OS_TV + /* The OS uses the home button, it's not available to apps */ + return TRUE; +#endif + } + if ([element isEqualToString:@"Button Share"]) { + if (device->is_backbone_one) { + /* The Backbone app uses share button */ + return TRUE; + } + } + return FALSE; +} + static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) { Uint16 vendor = 0; @@ -259,182 +369,235 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle device->name = SDL_CreateJoystickName(0, 0, NULL, name); #ifdef DEBUG_CONTROLLER_PROFILE + NSLog(@"Product name: %@\n", controller.vendorName); + NSLog(@"Product category: %@\n", controller.productCategory); + NSLog(@"Elements available:\n"); if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { - if (controller.physicalInputProfile) { - for (id key in controller.physicalInputProfile.buttons) { - NSLog(@"Button %@ available\n", key); - } - for (id key in controller.physicalInputProfile.axes) { - NSLog(@"Axis %@ available\n", key); - } + NSDictionary *elements = controller.physicalInputProfile.elements; + for (id key in controller.physicalInputProfile.buttons) { + NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); + } + for (id key in controller.physicalInputProfile.axes) { + NSLog(@"\tAxis: %@\n", key); + } + for (id key in controller.physicalInputProfile.dpads) { + NSLog(@"\tHat: %@\n", key); } } #endif + device->is_xbox = IsControllerXbox(controller); + device->is_ps4 = IsControllerPS4(controller); + device->is_ps5 = IsControllerPS5(controller); + device->is_switch_pro = IsControllerSwitchPro(controller); + device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller); + device->is_stadia = IsControllerStadia(controller); + device->is_backbone_one = IsControllerBackboneOne(controller); + device->is_switch_joyconL = IsControllerSwitchJoyConL(controller); + device->is_switch_joyconR = IsControllerSwitchJoyConR(controller); +#ifdef SDL_JOYSTICK_HIDAPI + if ((device->is_xbox && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE)) || + (device->is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) || + (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) || + (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) || + (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || + (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || + (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + /* The HIDAPI driver is taking care of this device */ + return FALSE; + } +#endif + CheckControllerSiriRemote(controller, &device->is_siri_remote); + + if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) { + /* Ignore remotes, they'll be handled as keyboard input */ + return SDL_FALSE; + } + +#ifdef ENABLE_PHYSICAL_INPUT_PROFILE + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { + device->has_dualshock_touchpad = TRUE; + } + if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) { + device->has_xbox_paddles = TRUE; + } + if (controller.physicalInputProfile.buttons[@"Button Share"] != nil) { + device->has_xbox_share_button = TRUE; + } + } +#endif // ENABLE_PHYSICAL_INPUT_PROFILE + + if (device->is_backbone_one) { + vendor = USB_VENDOR_BACKBONE; + if (device->is_ps5) { + product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5; + } else { + product = USB_PRODUCT_BACKBONE_ONE_IOS; + } + } else if (device->is_xbox) { + vendor = USB_VENDOR_MICROSOFT; + if (device->has_xbox_paddles) { + /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */ + product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH; + } else if (device->has_xbox_share_button) { + /* Assume Xbox Series X Controller unless/until GCController flows VID/PID */ + product = USB_PRODUCT_XBOX_SERIES_X_BLE; + } else { + /* Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID */ + product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH; + } + } else if (device->is_ps4) { + /* Assume DS4 Slim unless/until GCController flows VID/PID */ + vendor = USB_VENDOR_SONY; + product = USB_PRODUCT_SONY_DS4_SLIM; + if (device->has_dualshock_touchpad) { + subtype = 1; + } + } else if (device->is_ps5) { + vendor = USB_VENDOR_SONY; + product = USB_PRODUCT_SONY_DS5; + } else if (device->is_switch_pro) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_PRO; + device->has_nintendo_buttons = TRUE; + } else if (device->is_switch_joycon_pair) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + device->has_nintendo_buttons = TRUE; + } else if (device->is_switch_joyconL) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT; + } else if (device->is_switch_joyconR) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; +#ifdef ENABLE_PHYSICAL_INPUT_PROFILE + } else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + vendor = USB_VENDOR_APPLE; + product = 4; + subtype = 4; +#endif + } else if (controller.extendedGamepad) { + vendor = USB_VENDOR_APPLE; + product = 1; + subtype = 1; + } else if (controller.gamepad) { + vendor = USB_VENDOR_APPLE; + product = 2; + subtype = 2; +#if TARGET_OS_TV + } else if (controller.microGamepad) { + vendor = USB_VENDOR_APPLE; + product = 3; + subtype = 3; +#endif + } else { + /* We don't know how to get input events from this device */ + return SDL_FALSE; + } + +#ifdef ENABLE_PHYSICAL_INPUT_PROFILE + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSDictionary *elements = controller.physicalInputProfile.elements; + + /* Provide both axes and analog buttons as SDL axes */ + device->axes = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { + if (ElementAlreadyHandled(device, (NSString *)object, elements)) { + return NO; + } + + GCControllerElement *element = elements[object]; + if (element.analog) { + if ([element isKindOfClass:[GCControllerAxisInput class]] || + [element isKindOfClass:[GCControllerButtonInput class]]) { + return YES; + } + } + return NO; + }]]; + device->naxes = (int)device->axes.count; + device->buttons = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { + if (ElementAlreadyHandled(device, (NSString *)object, elements)) { + return NO; + } + + GCControllerElement *element = elements[object]; + if ([element isKindOfClass:[GCControllerButtonInput class]]) { + return YES; + } + return NO; + }]]; + device->nbuttons = (int)device->buttons.count; + subtype = 4; + +#ifdef DEBUG_CONTROLLER_PROFILE + NSLog(@"Elements used:\n", controller.vendorName); + for (id key in device->buttons) { + NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); + } + for (id key in device->axes) { + NSLog(@"\tAxis: %@\n", key); + } +#endif /* DEBUG_CONTROLLER_PROFILE */ + +#if TARGET_OS_TV + /* tvOS turns the menu button into a system gesture, so we grab it here instead */ + if (elements[GCInputButtonMenu] && !elements[@"Button Home"]) { + device->pause_button_index = [device->buttons indexOfObject:GCInputButtonMenu]; + } +#endif + } else +#endif if (controller.extendedGamepad) { GCExtendedGamepad *gamepad = controller.extendedGamepad; - BOOL is_xbox = IsControllerXbox(controller); - BOOL is_ps4 = IsControllerPS4(controller); - BOOL is_ps5 = IsControllerPS5(controller); - BOOL is_switch_pro = IsControllerSwitchPro(controller); - BOOL is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller); - BOOL is_stadia = IsControllerStadia(controller); - BOOL is_backbone_one = IsControllerBackboneOne(controller); int nbuttons = 0; - BOOL has_direct_menu; - -#ifdef SDL_JOYSTICK_HIDAPI - if ((is_xbox && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE)) || - (is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) || - (is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) || - (is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) || - (is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || - (is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, ""))) { - /* The HIDAPI driver is taking care of this device */ - return FALSE; - } -#else - (void)is_stadia; -#endif + BOOL has_direct_menu = FALSE; /* These buttons are part of the original MFi spec */ - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_A); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_B); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_X); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_Y); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH); device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); nbuttons += 6; /* These buttons are available on some newer controllers */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability-new" - if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) { - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK); - ++nbuttons; + if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { + if (gamepad.leftThumbstickButton) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK); + ++nbuttons; + } + if (gamepad.rightThumbstickButton) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK); + ++nbuttons; + } } - if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) { - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK); - ++nbuttons; - } - if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) { - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK); - ++nbuttons; - } - /* The Nintendo Switch JoyCon home button doesn't ever show as being held down */ - if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome && !is_switch_joycon_pair) { - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_GUIDE); - ++nbuttons; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (gamepad.buttonOptions) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK); + ++nbuttons; + } } device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START); ++nbuttons; - has_direct_menu = [gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu; - if (!has_direct_menu) { - device->uses_pause_handler = SDL_TRUE; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (gamepad.buttonMenu) { + has_direct_menu = TRUE; + } } #if TARGET_OS_TV /* The single menu button isn't very reliable, at least as of tvOS 16.1 */ if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) == 0) { - device->uses_pause_handler = SDL_TRUE; + has_direct_menu = FALSE; } #endif - -#ifdef ENABLE_PHYSICAL_INPUT_PROFILE - if ([controller respondsToSelector:@selector(physicalInputProfile)]) { - if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { - device->has_dualshock_touchpad = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_MISC1); - ++nbuttons; - } - if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) { - device->has_xbox_paddles = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1); - ++nbuttons; - } - if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) { - /* TODO: Is this right? SDL_gamepad.h says P2 is the lower right */ - device->has_xbox_paddles = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1); - ++nbuttons; - } - if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) { - /* TODO: Is this right? SDL_gamepad.h says P3 is the upper left */ - device->has_xbox_paddles = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2); - ++nbuttons; - } - if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) { - device->has_xbox_paddles = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2); - ++nbuttons; - } - if (controller.physicalInputProfile.buttons[GCInputXboxShareButton] != nil) { - device->has_xbox_share_button = SDL_TRUE; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_MISC1); - ++nbuttons; - } - } -#endif -#pragma clang diagnostic pop - - if (is_backbone_one) { - vendor = USB_VENDOR_BACKBONE; - if (is_ps5) { - product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5; - } else { - product = USB_PRODUCT_BACKBONE_ONE_IOS; - } - subtype = 0; - } else if (is_xbox) { - vendor = USB_VENDOR_MICROSOFT; - if (device->has_xbox_paddles) { - /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */ - product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH; - subtype = 1; - } else if (device->has_xbox_share_button) { - /* Assume Xbox Series X Controller unless/until GCController flows VID/PID */ - product = USB_PRODUCT_XBOX_SERIES_X_BLE; - subtype = 1; - } else { - /* Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID */ - product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH; - subtype = 0; - } - } else if (is_ps4) { - /* Assume DS4 Slim unless/until GCController flows VID/PID */ - vendor = USB_VENDOR_SONY; - product = USB_PRODUCT_SONY_DS4_SLIM; - if (device->has_dualshock_touchpad) { - subtype = 1; - } else { - subtype = 0; - } - } else if (is_ps5) { - vendor = USB_VENDOR_SONY; - product = USB_PRODUCT_SONY_DS5; - subtype = 0; - } else if (is_switch_pro) { - vendor = USB_VENDOR_NINTENDO; - product = USB_PRODUCT_NINTENDO_SWITCH_PRO; - subtype = 0; - } else if (is_switch_joycon_pair) { - vendor = USB_VENDOR_NINTENDO; - product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; - subtype = 0; - } else { - vendor = USB_VENDOR_APPLE; - product = 1; - subtype = 1; - } - - if (is_backbone_one) { - /* The Backbone app uses share button */ - if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC1)) != 0) { - device->button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_MISC1); - --nbuttons; - device->has_xbox_share_button = SDL_FALSE; - } + if (!has_direct_menu) { + device->pause_button_index = (nbuttons - 1); } device->naxes = 6; /* 2 thumbsticks and 2 triggers */ @@ -442,50 +605,18 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle device->nbuttons = nbuttons; } else if (controller.gamepad) { - BOOL is_switch_joyconL = IsControllerSwitchJoyConL(controller); - BOOL is_switch_joyconR = IsControllerSwitchJoyConR(controller); int nbuttons = 0; -#ifdef SDL_JOYSTICK_HIDAPI - if ((is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || - (is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { - /* The HIDAPI driver is taking care of this device */ - return FALSE; - } -#else - (void)is_switch_joyconL; - (void)is_switch_joyconR; -#endif - - if (is_switch_joyconL) { - vendor = USB_VENDOR_NINTENDO; - product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT; - subtype = 0; - } else if (is_switch_joyconR) { - vendor = USB_VENDOR_NINTENDO; - product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; - subtype = 0; - } else { - vendor = USB_VENDOR_APPLE; - product = 2; - subtype = 2; - } - /* These buttons are part of the original MFi spec */ - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_A); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_B); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_X); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_Y); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH); device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); -#if TARGET_OS_TV - /* The menu button is used by the OS and not available to applications */ - nbuttons += 6; -#else device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START); nbuttons += 7; - device->uses_pause_handler = SDL_TRUE; -#endif + device->pause_button_index = (nbuttons - 1); device->naxes = 0; /* no traditional analog inputs */ device->nhats = 1; /* d-pad */ @@ -495,36 +626,43 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle else if (controller.microGamepad) { int nbuttons = 0; - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_A); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_B); /* Button X on microGamepad */ - nbuttons += 2; + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); /* Button X on microGamepad */ + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST); + nbuttons += 3; + device->pause_button_index = (nbuttons - 1); - device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START); - ++nbuttons; - device->uses_pause_handler = SDL_TRUE; - - vendor = USB_VENDOR_APPLE; - product = 3; - subtype = 3; device->naxes = 2; /* treat the touch surface as two axes */ device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */ device->nbuttons = nbuttons; controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE); } -#endif /* TARGET_OS_TV */ - - if (vendor == USB_VENDOR_APPLE) { - /* Note that this is an MFI controller and what subtype it is */ - device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, 0, name, 'm', subtype); - } else { - device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, 0, name, 0, subtype); +#endif + else { + /* We don't know how to get input events from this device */ + return SDL_FALSE; } - /* Update the GUID with capability bits */ - { - Uint16 *guid16 = (Uint16 *)device->guid.data; - guid16[6] = SDL_SwapLE16(device->button_mask); + Uint16 signature; + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + signature = 0; + signature = SDL_crc16(signature, device->name, SDL_strlen(device->name)); + for (id key in device->axes) { + const char *string = ((NSString *)key).UTF8String; + signature = SDL_crc16(signature, string, SDL_strlen(string)); + } + for (id key in device->buttons) { + const char *string = ((NSString *)key).UTF8String; + signature = SDL_crc16(signature, string, SDL_strlen(string)); + } + } else { + signature = device->button_mask; + } + device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, NULL, name, 'm', subtype); + + if (SDL_ShouldIgnoreJoystick(name, device->guid)) { + return SDL_FALSE; } /* This will be set when the first button press of the controller is @@ -539,15 +677,6 @@ static void IOS_AddJoystickDevice(GCController *controller, SDL_bool acceleromet { SDL_JoystickDeviceItem *device = deviceList; -#if TARGET_OS_TV - if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) { - /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */ - if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) { - return; - } - } -#endif - while (device != NULL) { if (device->controller == controller) { return; @@ -561,7 +690,8 @@ static void IOS_AddJoystickDevice(GCController *controller, SDL_bool acceleromet } device->accelerometer = accelerometer; - device->instance_id = SDL_GetNextJoystickInstanceID(); + device->instance_id = SDL_GetNextObjectID(); + device->pause_button_index = -1; if (accelerometer) { #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER @@ -579,6 +709,7 @@ static void IOS_AddJoystickDevice(GCController *controller, SDL_bool acceleromet } else if (controller) { #ifdef SDL_JOYSTICK_MFI if (!IOS_AddMFIJoystickDevice(device, controller)) { + SDL_free(device->name); SDL_free(device); return; } @@ -674,6 +805,10 @@ static void SDLCALL SDL_AppleTVRemoteRotationHintChanged(void *udata, const char static int IOS_JoystickInit(void) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, SDL_TRUE)) { + return 0; + } + #ifdef __MACOS__ #if SDL_HAS_BUILTIN(__builtin_available) if (@available(macOS 10.16, *)) { @@ -768,6 +903,11 @@ static const char *IOS_JoystickGetDevicePath(int device_index) return NULL; } +static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int IOS_JoystickGetDevicePlayerIndex(int device_index) { #ifdef SDL_JOYSTICK_MFI @@ -840,11 +980,11 @@ static int IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) #endif } else { #ifdef SDL_JOYSTICK_MFI - if (device->uses_pause_handler) { + if (device->pause_button_index >= 0) { GCController *controller = device->controller; controller.controllerPausedHandler = ^(GCController *c) { if (joystick->hwdata) { - ++joystick->hwdata->num_pause_presses; + joystick->hwdata->pause_button_pressed = SDL_GetTicks(); } }; } @@ -877,7 +1017,7 @@ static int IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) #endif /* SDL_JOYSTICK_MFI */ } } - if (device->remote) { + if (device->is_siri_remote) { ++SDL_AppleTVRemoteOpenedAsJoystick; } @@ -957,10 +1097,10 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) { #ifdef SDL_JOYSTICK_MFI @autoreleasepool { - GCController *controller = joystick->hwdata->controller; + SDL_JoystickDeviceItem *device = joystick->hwdata; + GCController *controller = device->controller; Uint8 hatstate = SDL_HAT_CENTERED; int i; - int pause_button_index = 0; Uint64 timestamp = SDL_GetTicksNS(); #ifdef DEBUG_CONTROLLER_STATE @@ -976,11 +1116,47 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) if (axis.value != 0.0f) NSLog(@"Axis %@ = %g\n", key, axis.value); } + for (id key in controller.physicalInputProfile.dpads) { + GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key]; + if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) { + NSLog(@"Hat %@ =%s%s%s%s\n", key, + dpad.up.isPressed ? " UP" : "", + dpad.down.isPressed ? " DOWN" : "", + dpad.left.isPressed ? " LEFT" : "", + dpad.right.isPressed ? " RIGHT" : ""); + } + } } } -#endif +#endif /* DEBUG_CONTROLLER_STATE */ - if (controller.extendedGamepad) { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSDictionary *elements = controller.physicalInputProfile.elements; + NSDictionary *buttons = controller.physicalInputProfile.buttons; + + int axis = 0; + for (id key in device->axes) { + Sint16 value; + GCControllerElement *element = elements[key]; + if ([element isKindOfClass:[GCControllerAxisInput class]]) { + value = (Sint16)([(GCControllerAxisInput *)element value] * 32767); + } else { + value = (Sint16)([(GCControllerButtonInput *)element value] * 32767); + } + SDL_SendJoystickAxis(timestamp, joystick, axis++, value); + } + + int button = 0; + for (id key in device->buttons) { + Uint8 value; + if (button == device->pause_button_index) { + value = (device->pause_button_pressed > 0); + } else { + value = buttons[key].isPressed; + } + SDL_SendJoystickButton(timestamp, joystick, button++, value); + } + } else if (controller.extendedGamepad) { SDL_bool isstack; GCExtendedGamepad *gamepad = controller.extendedGamepad; @@ -999,7 +1175,6 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) int button_count = 0; if (buttons == NULL) { - SDL_OutOfMemory(); return; } @@ -1012,79 +1187,30 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) buttons[button_count++] = gamepad.rightShoulder.isPressed; /* These buttons are available on some newer controllers */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability-new" - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { - buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; + if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { + buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; + } + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { + buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; + } } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { - buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + buttons[button_count++] = gamepad.buttonOptions.isPressed; + } } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { - buttons[button_count++] = gamepad.buttonOptions.isPressed; - } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) { - buttons[button_count++] = gamepad.buttonHome.isPressed; - } - /* This must be the last button, so we can optionally handle it with pause_button_index below */ - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { - if (joystick->hwdata->uses_pause_handler) { - pause_button_index = button_count; - buttons[button_count++] = joystick->delayed_guide_button; + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { + if (device->pause_button_index >= 0) { + /* Guaranteed if buttonMenu is not supported on this OS */ + buttons[button_count++] = (device->pause_button_pressed > 0); } else { - buttons[button_count++] = gamepad.buttonMenu.isPressed; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + buttons[button_count++] = gamepad.buttonMenu.isPressed; + } } } -#ifdef ENABLE_PHYSICAL_INPUT_PROFILE - if (joystick->hwdata->has_dualshock_touchpad) { - GCControllerDirectionPad *dpad; - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed; - - dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]; - if (dpad.xAxis.value || dpad.yAxis.value) { - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); - } else { - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, SDL_RELEASED, 0.0f, 0.0f, 1.0f); - } - - dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]; - if (dpad.xAxis.value || dpad.yAxis.value) { - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); - } else { - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, SDL_RELEASED, 0.0f, 0.0f, 1.0f); - } - } - - if (joystick->hwdata->has_xbox_paddles) { - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1)) { - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed; - } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1)) { - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed; - } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2)) { - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed; - } - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2)) { - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed; - } - - /* - SDL_Log("Paddles: [%d,%d,%d,%d]", - controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed, - controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed, - controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed, - controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed); - */ - } - - if (joystick->hwdata->has_xbox_share_button) { - buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxShareButton].isPressed; - } -#endif -#pragma clang diagnostic pop - hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); for (i = 0; i < SDL_arraysize(axes); i++) { @@ -1095,30 +1221,6 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); } -#ifdef ENABLE_MFI_SENSORS - if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { - GCMotion *motion = controller.motion; - if (motion && motion.sensorsActive) { - float data[3]; - - if (motion.hasRotationRate) { - GCRotationRate rate = motion.rotationRate; - data[0] = rate.x; - data[1] = rate.z; - data[2] = -rate.y; - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3); - } - if (motion.hasGravityAndUserAcceleration) { - GCAcceleration accel = motion.acceleration; - data[0] = -accel.x * SDL_STANDARD_GRAVITY; - data[1] = -accel.y * SDL_STANDARD_GRAVITY; - data[2] = -accel.z * SDL_STANDARD_GRAVITY; - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3); - } - } - } -#endif /* ENABLE_MFI_SENSORS */ - SDL_small_free(buttons, isstack); } else if (controller.gamepad) { SDL_bool isstack; @@ -1129,7 +1231,6 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) int button_count = 0; if (buttons == NULL) { - SDL_OutOfMemory(); return; } @@ -1139,8 +1240,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) buttons[button_count++] = gamepad.buttonY.isPressed; buttons[button_count++] = gamepad.leftShoulder.isPressed; buttons[button_count++] = gamepad.rightShoulder.isPressed; - pause_button_index = button_count; - buttons[button_count++] = joystick->delayed_guide_button; + buttons[button_count++] = (device->pause_button_pressed > 0); hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); @@ -1167,18 +1267,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) int button_count = 0; buttons[button_count++] = gamepad.buttonA.isPressed; buttons[button_count++] = gamepad.buttonX.isPressed; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability-new" - /* This must be the last button, so we can optionally handle it with pause_button_index below */ - if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { - if (joystick->hwdata->uses_pause_handler) { - pause_button_index = button_count; - buttons[button_count++] = joystick->delayed_guide_button; - } else { - buttons[button_count++] = gamepad.buttonMenu.isPressed; - } - } -#pragma clang diagnostic pop + buttons[button_count++] = (device->pause_button_pressed > 0); for (i = 0; i < button_count; i++) { SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); @@ -1190,14 +1279,60 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) SDL_SendJoystickHat(timestamp, joystick, 0, hatstate); } - if (joystick->hwdata->uses_pause_handler) { - for (i = 0; i < joystick->hwdata->num_pause_presses; i++) { - SDL_SendJoystickButton(timestamp, joystick, pause_button_index, SDL_PRESSED); - SDL_SendJoystickButton(timestamp, joystick, pause_button_index, SDL_RELEASED); + if (device->pause_button_pressed) { + /* The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly */ + const int PAUSE_BUTTON_PRESS_DURATION_MS = 250; + if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) { + device->pause_button_pressed = 0; } - joystick->hwdata->num_pause_presses = 0; } +#ifdef ENABLE_PHYSICAL_INPUT_PROFILE + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (device->has_dualshock_touchpad) { + GCControllerDirectionPad *dpad; + + dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]; + if (dpad.xAxis.value || dpad.yAxis.value) { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); + } else { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, SDL_RELEASED, 0.0f, 0.0f, 1.0f); + } + + dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]; + if (dpad.xAxis.value || dpad.yAxis.value) { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); + } else { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, SDL_RELEASED, 0.0f, 0.0f, 1.0f); + } + } + } +#endif /* ENABLE_PHYSICAL_INPUT_PROFILE */ + +#ifdef ENABLE_MFI_SENSORS + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCMotion *motion = controller.motion; + if (motion && motion.sensorsActive) { + float data[3]; + + if (motion.hasRotationRate) { + GCRotationRate rate = motion.rotationRate; + data[0] = rate.x; + data[1] = rate.z; + data[2] = -rate.y; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3); + } + if (motion.hasGravityAndUserAcceleration) { + GCAcceleration accel = motion.acceleration; + data[0] = -accel.x * SDL_STANDARD_GRAVITY; + data[1] = -accel.y * SDL_STANDARD_GRAVITY; + data[2] = -accel.z * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3); + } + } + } +#endif /* ENABLE_MFI_SENSORS */ + #ifdef ENABLE_MFI_BATTERY if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { GCDeviceBattery *battery = controller.battery; @@ -1645,7 +1780,7 @@ static void IOS_JoystickClose(SDL_Joystick *joystick) #endif /* SDL_JOYSTICK_MFI */ } } - if (device->remote) { + if (device->is_siri_remote) { --SDL_AppleTVRemoteOpenedAsJoystick; } } @@ -1686,12 +1821,154 @@ static void IOS_JoystickQuit(void) static SDL_bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) { +#ifdef ENABLE_PHYSICAL_INPUT_PROFILE + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + if (device == NULL) { + return SDL_FALSE; + } + if (device->accelerometer) { + return SDL_FALSE; + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + int axis = 0; + for (id key in device->axes) { + if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] || + [(NSString *)key isEqualToString:@"Direction Pad X Axis"]) { + out->leftx.kind = EMappingKind_Axis; + out->leftx.target = axis; + } else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] || + [(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) { + out->lefty.kind = EMappingKind_Axis; + out->lefty.target = axis; + out->lefty.axis_reversed = SDL_TRUE; + } else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) { + out->rightx.kind = EMappingKind_Axis; + out->rightx.target = axis; + } else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) { + out->righty.kind = EMappingKind_Axis; + out->righty.target = axis; + out->righty.axis_reversed = SDL_TRUE; + } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = axis; + out->lefttrigger.half_axis_positive = SDL_TRUE; + } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = axis; + out->righttrigger.half_axis_positive = SDL_TRUE; + } + ++axis; + } + + int button = 0; + for (id key in device->buttons) { + SDL_InputMapping *mapping = NULL; + + if ([(NSString *)key isEqualToString:GCInputButtonA]) { + if (device->is_siri_remote > 1) { + /* GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center" */ + } else if (device->has_nintendo_buttons) { + mapping = &out->b; + } else { + mapping = &out->a; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonB]) { + if (device->has_nintendo_buttons) { + mapping = &out->a; + } else if (device->is_switch_joyconL || device->is_switch_joyconR) { + mapping = &out->x; + } else { + mapping = &out->b; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonX]) { + if (device->has_nintendo_buttons) { + mapping = &out->y; + } else if (device->is_switch_joyconL || device->is_switch_joyconR) { + mapping = &out->b; + } else { + mapping = &out->x; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonY]) { + if (device->has_nintendo_buttons) { + mapping = &out->x; + } else { + mapping = &out->y; + } + } else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) { + mapping = &out->dpleft; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) { + mapping = &out->dpright; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) { + mapping = &out->dpup; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) { + mapping = &out->dpdown; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) { + mapping = &out->dpleft; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) { + mapping = &out->dpright; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) { + mapping = &out->dpup; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) { + mapping = &out->dpdown; + } else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) { + mapping = &out->leftshoulder; + } else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) { + mapping = &out->rightshoulder; + } else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) { + mapping = &out->leftstick; + } else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) { + mapping = &out->rightstick; + } else if ([(NSString *)key isEqualToString:@"Button Home"]) { + mapping = &out->guide; + } else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) { + if (device->is_siri_remote) { + mapping = &out->b; + } else { + mapping = &out->start; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) { + mapping = &out->back; + } else if ([(NSString *)key isEqualToString:@"Button Share"]) { + mapping = &out->misc1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) { + mapping = &out->right_paddle1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) { + mapping = &out->right_paddle2; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) { + mapping = &out->left_paddle1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) { + mapping = &out->left_paddle2; + } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { + mapping = &out->lefttrigger; + } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { + mapping = &out->righttrigger; + } else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) { + mapping = &out->touchpad; + } else if ([(NSString *)key isEqualToString:@"Button Center"]) { + mapping = &out->a; + } + if (mapping && mapping->kind == EMappingKind_None) { + mapping->kind = EMappingKind_Button; + mapping->target = button; + } + ++button; + } + + return SDL_TRUE; + } +#endif /* ENABLE_PHYSICAL_INPUT_PROFILE */ + return SDL_FALSE; } #if defined(SDL_JOYSTICK_MFI) && defined(__MACOS__) SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, SDL_TRUE)) { + return SDL_FALSE; + } + if (@available(macOS 10.16, *)) { const int MAX_ATTEMPTS = 3; for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { @@ -1720,6 +1997,10 @@ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char * static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller) { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + return controller.physicalInputProfile.dpads[GCInputDirectionPad]; + } + if (controller.extendedGamepad) { return controller.extendedGamepad.dpad; } @@ -1748,23 +2029,23 @@ const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_Gamepad if ([controller respondsToSelector:@selector(physicalInputProfile)]) { NSDictionary *elements = controller.physicalInputProfile.elements; switch (button) { - case SDL_GAMEPAD_BUTTON_A: + case SDL_GAMEPAD_BUTTON_SOUTH: GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName); break; - case SDL_GAMEPAD_BUTTON_B: + case SDL_GAMEPAD_BUTTON_EAST: GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName); break; - case SDL_GAMEPAD_BUTTON_X: + case SDL_GAMEPAD_BUTTON_WEST: GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName); break; - case SDL_GAMEPAD_BUTTON_Y: + case SDL_GAMEPAD_BUTTON_NORTH: GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName); break; case SDL_GAMEPAD_BUTTON_BACK: GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName); break; case SDL_GAMEPAD_BUTTON_GUIDE: - GetAppleSFSymbolsNameForElement(elements[GCInputButtonHome], elementName); + GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName); break; case SDL_GAMEPAD_BUTTON_START: GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName); @@ -1832,12 +2113,10 @@ const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_Gamepad GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName); break; case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: - /* TODO: Is this right? SDL_gamepad.h says P2 is the lower right */ - GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName); + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName); break; case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: - /* TODO: Is this right? SDL_gamepad.h says P3 is the upper left */ - GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName); + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName); break; case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName); @@ -1899,6 +2178,7 @@ SDL_JoystickDriver SDL_IOS_JoystickDriver = { IOS_JoystickDetect, IOS_JoystickGetDeviceName, IOS_JoystickGetDevicePath, + IOS_JoystickGetDeviceSteamVirtualGamepadSlot, IOS_JoystickGetDevicePlayerIndex, IOS_JoystickSetDevicePlayerIndex, IOS_JoystickGetDeviceGUID, diff --git a/src/joystick/apple/SDL_mfijoystick_c.h b/src/joystick/apple/SDL_mfijoystick_c.h index 553a6307..0dae2ac6 100644 --- a/src/joystick/apple/SDL_mfijoystick_c.h +++ b/src/joystick/apple/SDL_mfijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,18 +25,18 @@ #include "../SDL_sysjoystick.h" +#include + @class GCController; typedef struct joystick_hwdata { SDL_bool accelerometer; - SDL_bool remote; GCController __unsafe_unretained *controller; void *rumble; - SDL_bool uses_pause_handler; - int num_pause_presses; - Uint32 pause_button_down_time; + int pause_button_index; + Uint64 pause_button_pressed; char *name; SDL_Joystick *joystick; @@ -47,9 +47,24 @@ typedef struct joystick_hwdata int nbuttons; int nhats; Uint32 button_mask; + SDL_bool is_xbox; + SDL_bool is_ps4; + SDL_bool is_ps5; + SDL_bool is_switch_pro; + SDL_bool is_switch_joycon_pair; + SDL_bool is_switch_joyconL; + SDL_bool is_switch_joyconR; + SDL_bool is_stadia; + SDL_bool is_backbone_one; + int is_siri_remote; + + NSArray *axes; + NSArray *buttons; + SDL_bool has_dualshock_touchpad; SDL_bool has_xbox_paddles; SDL_bool has_xbox_share_button; + SDL_bool has_nintendo_buttons; struct joystick_hwdata *next; } joystick_hwdata; diff --git a/src/joystick/bsd/SDL_bsdjoystick.c b/src/joystick/bsd/SDL_bsdjoystick.c index 30ca7965..dcc2980c 100644 --- a/src/joystick/bsd/SDL_bsdjoystick.c +++ b/src/joystick/bsd/SDL_bsdjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -69,7 +69,7 @@ #include #endif -#if SDL_HAVE_MACHINE_JOYSTICK_H +#ifdef SDL_HAVE_MACHINE_JOYSTICK_H #include #endif @@ -268,9 +268,8 @@ CreateHwData(const char *path) hw = (struct joystick_hwdata *) SDL_calloc(1, sizeof(struct joystick_hwdata)); - if (hw == NULL) { + if (!hw) { close(fd); - SDL_OutOfMemory(); return NULL; } hw->fd = fd; @@ -291,7 +290,7 @@ CreateHwData(const char *path) } } hw->repdesc = hid_get_report_desc(fd); - if (hw->repdesc == NULL) { + if (!hw->repdesc) { SDL_SetError("%s: USB_GET_REPORT_DESC: %s", path, strerror(errno)); goto usberr; @@ -318,7 +317,7 @@ CreateHwData(const char *path) #else hdata = hid_start_parse(hw->repdesc, 1 << hid_input); #endif - if (hdata == NULL) { + if (!hdata) { SDL_SetError("%s: Cannot start HID parser", path); goto usberr; } @@ -398,7 +397,7 @@ static int MaybeAddDevice(const char *path) SDL_joylist_item *item; struct joystick_hwdata *hw; - if (path == NULL) { + if (!path) { return -1; } @@ -407,14 +406,14 @@ static int MaybeAddDevice(const char *path) } /* Check to make sure it's not already in list. */ - for (item = SDL_joylist; item != NULL; item = item->next) { + for (item = SDL_joylist; item; item = item->next) { if (sb.st_rdev == item->devnum) { return -1; /* already have this one */ } } hw = CreateHwData(path); - if (hw == NULL) { + if (!hw) { return -1; } @@ -426,7 +425,7 @@ static int MaybeAddDevice(const char *path) struct usb_device_info di; if (ioctl(hw->fd, USB_GET_DEVICEINFO, &di) != -1) { name = SDL_CreateJoystickName(di.udi_vendorNo, di.udi_productNo, di.udi_vendor, di.udi_product); - guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name, 0, 0); + guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, di.udi_vendor, di.udi_product, 0, 0); #ifdef SDL_JOYSTICK_HIDAPI if (HIDAPI_IsDevicePresent(di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name)) { @@ -444,14 +443,14 @@ static int MaybeAddDevice(const char *path) } #endif /* USB_GET_DEVICEINFO */ } - if (name == NULL) { + if (!name) { name = SDL_strdup(path); guid = SDL_CreateJoystickGUIDForName(name); } FreeHwData(hw); item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); - if (item == NULL) { + if (!item) { SDL_free(name); return -1; } @@ -461,13 +460,13 @@ static int MaybeAddDevice(const char *path) item->name = name; item->guid = guid; - if ((item->path == NULL) || (item->name == NULL)) { + if ((!item->path) || (!item->name)) { FreeJoylistItem(item); return -1; } - item->device_instance = SDL_GetNextJoystickInstanceID(); - if (SDL_joylist_tail == NULL) { + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; } else { SDL_joylist_tail->next = item; @@ -544,6 +543,11 @@ static const char *BSD_JoystickGetDevicePath(int device_index) return GetJoystickByDevIndex(device_index)->path; } +static int BSD_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int BSD_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -583,12 +587,12 @@ static int BSD_JoystickOpen(SDL_Joystick *joy, int device_index) SDL_joylist_item *item = GetJoystickByDevIndex(device_index); struct joystick_hwdata *hw; - if (item == NULL) { + if (!item) { return SDL_SetError("No such device"); } hw = CreateHwData(item->path); - if (hw == NULL) { + if (!hw) { return -1; } @@ -664,7 +668,7 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy) #else hdata = hid_start_parse(joy->hwdata->repdesc, 1 << hid_input); #endif - if (hdata == NULL) { + if (!hdata) { /*fprintf(stderr, "%s: Cannot start HID parser\n", joy->hwdata->path);*/ continue; } @@ -694,7 +698,7 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy) * calculate the SDL hat value from the 4 separate values. */ switch (usage) { - case HUG_DPAD_UP: + case HUG_DPAD_UP: dpad[0] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); break; case HUG_DPAD_DOWN: @@ -707,7 +711,7 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy) dpad[3] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); break; //default: - // no-op + // no-op } SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) | (dpad[1] * HAT_DOWN) | @@ -763,7 +767,7 @@ static int report_alloc(struct report *r, struct report_desc *rd, int repind) #ifdef __DragonFly__ len = hid_report_size(rd, repinfo[repind].kind, r->rid); -#elif __FREEBSD__ +#elif defined __FREEBSD__ #if (__FreeBSD_kernel_version >= 460000) || defined(__FreeBSD_kernel__) #if (__FreeBSD_kernel_version <= 500111) len = hid_report_size(rd, r->rid, repinfo[repind].kind); @@ -793,8 +797,8 @@ static int report_alloc(struct report *r, struct report_desc *rd, int repind) r->buf = SDL_malloc(sizeof(*r->buf) - sizeof(REP_BUF_DATA(r)) + r->size); #endif - if (r->buf == NULL) { - return SDL_OutOfMemory(); + if (!r->buf) { + return -1; } } else { r->buf = NULL; @@ -851,6 +855,7 @@ SDL_JoystickDriver SDL_BSD_JoystickDriver = { BSD_JoystickDetect, BSD_JoystickGetDeviceName, BSD_JoystickGetDevicePath, + BSD_JoystickGetDeviceSteamVirtualGamepadSlot, BSD_JoystickGetDevicePlayerIndex, BSD_JoystickSetDevicePlayerIndex, BSD_JoystickGetDeviceGUID, diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h index 0f6a589e..c8ba2c0c 100644 --- a/src/joystick/controller_list.h +++ b/src/joystick/controller_list.h @@ -66,7 +66,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x1a34, 0x0836 ), k_eControllerType_PS3Controller, NULL }, // Afterglow PS3 { MAKE_CONTROLLER_ID( 0x20bc, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // ShanWan PS3 { MAKE_CONTROLLER_ID( 0x20d6, 0x576d ), k_eControllerType_PS3Controller, NULL }, // Power A PS3 - { MAKE_CONTROLLER_ID( 0x20d6, 0xca6d ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x20d6, 0xca6d ), k_eControllerType_PS3Controller, NULL }, // BDA Pro Ex { MAKE_CONTROLLER_ID( 0x2563, 0x0523 ), k_eControllerType_PS3Controller, NULL }, // Digiflip GP006 { MAKE_CONTROLLER_ID( 0x2563, 0x0575 ), k_eControllerType_PS3Controller, NULL }, // From SDL { MAKE_CONTROLLER_ID( 0x25f0, 0x83c3 ), k_eControllerType_PS3Controller, NULL }, // gioteck vx2 @@ -141,6 +141,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x2c22, 0x2303 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Obsidian Arcade Joystick { MAKE_CONTROLLER_ID( 0x2c22, 0x2500 ), k_eControllerType_PS4Controller, NULL }, // Qanba Dragon { MAKE_CONTROLLER_ID( 0x2c22, 0x2503 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Dragon Arcade Joystick + { MAKE_CONTROLLER_ID( 0x3285, 0x0d16 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode with dongle) + { MAKE_CONTROLLER_ID( 0x3285, 0x0d17 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode wired) { MAKE_CONTROLLER_ID( 0x7545, 0x0104 ), k_eControllerType_PS4Controller, NULL }, // Armor 3 or Level Up Cobra - At least one variant has gyro { MAKE_CONTROLLER_ID (0x9886, 0x0024 ), k_eControllerType_XInputPS4Controller, NULL }, // Astro C40 in Xbox 360 mode { MAKE_CONTROLLER_ID( 0x9886, 0x0025 ), k_eControllerType_PS4Controller, NULL }, // Astro C40 @@ -149,11 +151,14 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x054c, 0x0ce6 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Controller { MAKE_CONTROLLER_ID( 0x054c, 0x0df2 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Edge Controller + { MAKE_CONTROLLER_ID( 0x054c, 0x0e5f ), k_eControllerType_PS5Controller, NULL }, // Access Controller for PS5 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0209 ), k_eControllerType_PS5Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS5 mode) { MAKE_CONTROLLER_ID( 0x0f0d, 0x0163 ), k_eControllerType_PS5Controller, NULL }, // HORI Fighting Commander OCTA { MAKE_CONTROLLER_ID( 0x0f0d, 0x0184 ), k_eControllerType_PS5Controller, NULL }, // Hori Fighting Stick α { MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired) { MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless) + { MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle) + { MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired) { MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS { MAKE_CONTROLLER_ID( 0x0079, 0x0006 ), k_eControllerType_UnknownNonSteamController, NULL }, // DragonRise Generic USB PCB, sometimes configured as a PC Twin Shock Controller - looks like a DS3 but the face buttons are 1-4 instead of symbols @@ -161,13 +166,13 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0079, 0x18d4 ), k_eControllerType_XBox360Controller, NULL }, // GPD Win 2 X-Box Controller { MAKE_CONTROLLER_ID( 0x03eb, 0xff02 ), k_eControllerType_XBox360Controller, NULL }, // Wooting Two { MAKE_CONTROLLER_ID( 0x044f, 0xb326 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster Gamepad GP XID - { MAKE_CONTROLLER_ID( 0x045e, 0x028e ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft X-Box 360 pad - { MAKE_CONTROLLER_ID( 0x045e, 0x028f ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft X-Box 360 pad v2 - { MAKE_CONTROLLER_ID( 0x045e, 0x0291 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Xbox 360 Wireless Receiver (XBOX) - { MAKE_CONTROLLER_ID( 0x045e, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // Microsoft X-Box 360 Big Button IR - { MAKE_CONTROLLER_ID( 0x045e, 0x02a1 ), k_eControllerType_XBox360Controller, NULL }, // Microsoft X-Box 360 Wireless Controller with XUSB driver on Windows - { MAKE_CONTROLLER_ID( 0x045e, 0x02a9 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Xbox 360 Wireless Receiver (third party knockoff) - { MAKE_CONTROLLER_ID( 0x045e, 0x0719 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Xbox 360 Wireless Receiver + { MAKE_CONTROLLER_ID( 0x045e, 0x028e ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Wired Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x028f ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Play and Charge Cable + { MAKE_CONTROLLER_ID( 0x045e, 0x0291 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff) + { MAKE_CONTROLLER_ID( 0x045e, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // Microsoft Xbox 360 Big Button IR + { MAKE_CONTROLLER_ID( 0x045e, 0x02a1 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Controller with XUSB driver on Windows + { MAKE_CONTROLLER_ID( 0x045e, 0x02a9 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff) + { MAKE_CONTROLLER_ID( 0x045e, 0x0719 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Receiver { MAKE_CONTROLLER_ID( 0x046d, 0xc21d ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F310 { MAKE_CONTROLLER_ID( 0x046d, 0xc21e ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F510 { MAKE_CONTROLLER_ID( 0x046d, 0xc21f ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F710 @@ -296,24 +301,24 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x24c6, 0xfafd ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 3 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafe ), k_eControllerType_XBox360Controller, NULL }, // Rock Candy Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x03f0, 0x0495 ), k_eControllerType_XBoxOneController, NULL }, // HP HyperX Clutch Gladiate { MAKE_CONTROLLER_ID( 0x044f, 0xd012 ), k_eControllerType_XBoxOneController, NULL }, // ThrustMaster eSwap PRO Controller Xbox - { MAKE_CONTROLLER_ID( 0x045e, 0x02d1 ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft X-Box One pad - { MAKE_CONTROLLER_ID( 0x045e, 0x02dd ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft X-Box One pad (Firmware 2015) - { MAKE_CONTROLLER_ID( 0x045e, 0x02e0 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft X-Box One S pad (Bluetooth) - { MAKE_CONTROLLER_ID( 0x045e, 0x02e3 ), k_eControllerType_XBoxOneController, "Xbox One Elite Controller" }, // Microsoft X-Box One Elite pad - { MAKE_CONTROLLER_ID( 0x045e, 0x02ea ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft X-Box One S pad - { MAKE_CONTROLLER_ID( 0x045e, 0x02fd ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft X-Box One S pad (Bluetooth) - { MAKE_CONTROLLER_ID( 0x045e, 0x02ff ), k_eControllerType_XBoxOneController, NULL }, // Microsoft X-Box One controller with XBOXGIP driver on Windows - { MAKE_CONTROLLER_ID( 0x045e, 0x0b00 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft X-Box One Elite Series 2 pad -// { MAKE_CONTROLLER_ID( 0x045e, 0x0b02 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // The virtual keyboard generated by XboxGip drivers for Xbox One Controllers (see https://github.com/libsdl-org/SDL/pull/5121 for details) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b05 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft X-Box One Elite Series 2 pad (Bluetooth) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b0a ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft X-Box Adaptive pad - { MAKE_CONTROLLER_ID( 0x045e, 0x0b0c ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft X-Box Adaptive pad (Bluetooth) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b12 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft X-Box Series X pad - { MAKE_CONTROLLER_ID( 0x045e, 0x0b13 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft X-Box Series X pad (BLE) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b20 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft X-Box One S pad (BLE) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b21 ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft X-Box Adaptive pad (BLE) - { MAKE_CONTROLLER_ID( 0x045e, 0x0b22 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft X-Box One Elite Series 2 pad (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x02d1 ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02dd ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller (Firmware 2015) + { MAKE_CONTROLLER_ID( 0x045e, 0x02e0 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x02e3 ), k_eControllerType_XBoxOneController, "Xbox One Elite Controller" }, // Microsoft Xbox One Elite Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02ea ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02fd ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x02ff ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller with XBOXGIP driver on Windows + { MAKE_CONTROLLER_ID( 0x045e, 0x0b00 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b05 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b0a ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b0c ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b12 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b13 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b20 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b21 ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b22 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (BLE) { MAKE_CONTROLLER_ID( 0x0738, 0x4a01 ), k_eControllerType_XBoxOneController, NULL }, // Mad Catz FightStick TE 2 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0139 ), k_eControllerType_XBoxOneController, "PDP Xbox One Afterglow" }, // PDP Afterglow Wired Controller for Xbox One { MAKE_CONTROLLER_ID( 0x0e6f, 0x013B ), k_eControllerType_XBoxOneController, "PDP Xbox One Face-Off Controller" }, // PDP Face-Off Gamepad for Xbox One @@ -566,7 +571,7 @@ static const ControllerDescription_t arrControllers[] = { // The first two, at least, shouldn't have their buttons remapped, and since we // can't tell which model we're actually using, we won't do any button remapping // for any of them. - { MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL }, // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL }, // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol, there is also a version of this that looks like a GameCube controller { MAKE_CONTROLLER_ID( 0x0e6f, 0x0180 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Pro Controller for Nintendo Switch { MAKE_CONTROLLER_ID( 0x0e6f, 0x0181 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Deluxe Wired Pro Controller for Nintendo Switch { MAKE_CONTROLLER_ID( 0x0e6f, 0x0184 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Deluxe+ Audio Controller @@ -581,6 +586,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x20d6, 0xa714 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Spectra Controller { MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009 { MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device + { MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller // Valve products { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls @@ -588,7 +594,9 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x28de, 0x1102 ), k_eControllerType_SteamController, NULL }, // Valve wired Steam Controller (D0G) { MAKE_CONTROLLER_ID( 0x28de, 0x1105 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G) { MAKE_CONTROLLER_ID( 0x28de, 0x1106 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G) + { MAKE_CONTROLLER_ID( 0x28de, 0x11ff ), k_eControllerType_UnknownNonSteamController, "Steam Virtual Gamepad" }, // Steam virtual gamepad { MAKE_CONTROLLER_ID( 0x28de, 0x1142 ), k_eControllerType_SteamController, NULL }, // Valve wireless Steam Controller { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB) + { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller }; diff --git a/src/joystick/controller_type.h b/src/joystick/controller_type.h index cf892395..155c8ad1 100644 --- a/src/joystick/controller_type.h +++ b/src/joystick/controller_type.h @@ -37,6 +37,7 @@ typedef enum k_eControllerType_UnknownSteamController = 1, k_eControllerType_SteamController = 2, k_eControllerType_SteamControllerV2 = 3, + k_eControllerType_SteamControllerNeptune = 4, // Other Controllers k_eControllerType_UnknownNonSteamController = 30, diff --git a/src/joystick/darwin/SDL_iokitjoystick.c b/src/joystick/darwin/SDL_iokitjoystick.c index 7bbc4bd9..036fdd41 100644 --- a/src/joystick/darwin/SDL_iokitjoystick.c +++ b/src/joystick/darwin/SDL_iokitjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,7 +40,7 @@ static recDevice *gpDeviceList = NULL; void FreeRumbleEffectData(FFEFFECT *effect) { - if (effect == NULL) { + if (!effect) { return; } SDL_free(effect->rgdwAxes); @@ -56,7 +56,7 @@ FFEFFECT *CreateRumbleEffectData(Sint16 magnitude) /* Create the effect */ effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect)); - if (effect == NULL) { + if (!effect) { return NULL; } effect->dwSize = sizeof(*effect); @@ -80,7 +80,7 @@ FFEFFECT *CreateRumbleEffectData(Sint16 magnitude) effect->dwFlags |= FFEFF_CARTESIAN; periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic)); - if (periodic == NULL) { + if (!periodic) { FreeRumbleEffectData(effect); return NULL; } @@ -405,6 +405,19 @@ static void AddHIDElement(const void *value, void *parameter) } } +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) { + /* Gamepad name is "GamePad-N", where N is slot + 1 */ + if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) { + slot -= 1; + } + } + return slot; +} + static SDL_bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) { Sint32 vendor = 0; @@ -484,7 +497,8 @@ static SDL_bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) } #endif - pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, pDevice->product, 0, 0); + pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0); + pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string); array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); if (array) { @@ -506,7 +520,7 @@ static SDL_bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) } #endif - for (i = gpDeviceList; i != NULL; i = i->pNext) { + for (i = gpDeviceList; i; i = i->pNext) { if (i->deviceRef == ioHIDDeviceObject) { return SDL_TRUE; } @@ -528,8 +542,7 @@ static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender } device = (recDevice *)SDL_calloc(1, sizeof(recDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return; } @@ -549,7 +562,7 @@ static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender device->runLoopAttached = SDL_TRUE; /* Allocate an instance ID for this device */ - device->instance_id = SDL_GetNextJoystickInstanceID(); + device->instance_id = SDL_GetNextObjectID(); /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */ ioservice = IOHIDDeviceGetService(ioHIDDeviceObject); @@ -561,13 +574,13 @@ static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender } /* Add device to the end of the list */ - if (gpDeviceList == NULL) { + if (!gpDeviceList) { gpDeviceList = device; } else { recDevice *curdevice; curdevice = gpDeviceList; - while (curdevice->pNext != NULL) { + while (curdevice->pNext) { curdevice = curdevice->pNext; } curdevice->pNext = device; @@ -655,8 +668,8 @@ static SDL_bool CreateHIDManager(void) static int DARWIN_JoystickInit(void) { - if (gpDeviceList) { - return SDL_SetError("Joystick: Device list already inited."); + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_IOKIT, SDL_TRUE)) { + return 0; } if (!CreateHIDManager()) { @@ -692,10 +705,12 @@ static void DARWIN_JoystickDetect(void) } } - /* run this after the checks above so we don't set device->removed and delete the device before - DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */ - while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */ + if (hidman) { + /* run this after the checks above so we don't set device->removed and delete the device before + DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */ + while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */ + } } } @@ -710,6 +725,12 @@ const char *DARWIN_JoystickGetDevicePath(int device_index) return NULL; } +static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + return device ? device->steam_virtual_gamepad_slot : -1; +} + static int DARWIN_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -832,7 +853,7 @@ static int DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude) /* Create the effect */ device->ffeffect = CreateRumbleEffectData(magnitude); if (!device->ffeffect) { - return SDL_OutOfMemory(); + return -1; } result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID, @@ -851,7 +872,7 @@ static int DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_ru /* Scale and average the two rumble strengths */ Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); - if (device == NULL) { + if (!device) { return SDL_SetError("Rumble failed, device disconnected"); } @@ -892,7 +913,7 @@ static Uint32 DARWIN_JoystickGetCapabilities(SDL_Joystick *joystick) recDevice *device = joystick->hwdata; Uint32 result = 0; - if (device == NULL) { + if (!device) { return 0; } @@ -926,7 +947,7 @@ static void DARWIN_JoystickUpdate(SDL_Joystick *joystick) int i, goodRead = SDL_FALSE; Uint64 timestamp = SDL_GetTicksNS(); - if (device == NULL) { + if (!device) { return; } @@ -1055,6 +1076,7 @@ SDL_JoystickDriver SDL_DARWIN_JoystickDriver = { DARWIN_JoystickDetect, DARWIN_JoystickGetDeviceName, DARWIN_JoystickGetDevicePath, + DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot, DARWIN_JoystickGetDevicePlayerIndex, DARWIN_JoystickSetDevicePlayerIndex, DARWIN_JoystickGetDeviceGUID, diff --git a/src/joystick/darwin/SDL_iokitjoystick_c.h b/src/joystick/darwin/SDL_iokitjoystick_c.h index 30bad169..756e9b7a 100644 --- a/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -71,6 +71,7 @@ struct joystick_hwdata int instance_id; SDL_JoystickGUID guid; + int steam_virtual_gamepad_slot; struct joystick_hwdata *pNext; /* next device */ }; diff --git a/src/joystick/dummy/SDL_sysjoystick.c b/src/joystick/dummy/SDL_sysjoystick.c index 81a678d0..125e3c03 100644 --- a/src/joystick/dummy/SDL_sysjoystick.c +++ b/src/joystick/dummy/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,6 +51,11 @@ static const char *DUMMY_JoystickGetDevicePath(int device_index) return NULL; } +static int DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int DUMMY_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -130,6 +135,7 @@ SDL_JoystickDriver SDL_DUMMY_JoystickDriver = { DUMMY_JoystickDetect, DUMMY_JoystickGetDeviceName, DUMMY_JoystickGetDevicePath, + DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot, DUMMY_JoystickGetDevicePlayerIndex, DUMMY_JoystickSetDevicePlayerIndex, DUMMY_JoystickGetDeviceGUID, diff --git a/src/joystick/emscripten/SDL_sysjoystick.c b/src/joystick/emscripten/SDL_sysjoystick.c index 902c91d0..3a6cd9d0 100644 --- a/src/joystick/emscripten/SDL_sysjoystick.c +++ b/src/joystick/emscripten/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,7 +33,6 @@ static SDL_joylist_item *JoystickByIndex(int index); static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; -static int instance_counter = 0; static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { @@ -46,7 +45,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep } item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item)); - if (item == NULL) { + if (!item) { return 1; } @@ -54,13 +53,13 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep item->index = gamepadEvent->index; item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id); - if (item->name == NULL) { + if (!item->name) { SDL_free(item); return 1; } item->mapping = SDL_strdup(gamepadEvent->mapping); - if (item->mapping == NULL) { + if (!item->mapping) { SDL_free(item->name); SDL_free(item); return 1; @@ -68,7 +67,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep item->naxes = gamepadEvent->numAxes; item->nbuttons = gamepadEvent->numButtons; - item->device_instance = instance_counter++; + item->device_instance = SDL_GetNextObjectID(); item->timestamp = gamepadEvent->timestamp; @@ -81,7 +80,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep item->digitalButton[i] = gamepadEvent->digitalButton[i]; } - if (SDL_joylist_tail == NULL) { + if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; } else { SDL_joylist_tail->next = item; @@ -95,7 +94,6 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep #ifdef DEBUG_JOYSTICK SDL_Log("Number of joysticks is %d", numjoysticks); #endif - #ifdef DEBUG_JOYSTICK SDL_Log("Added joystick with index %d", item->index); #endif @@ -108,7 +106,7 @@ static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGa SDL_joylist_item *item = SDL_joylist; SDL_joylist_item *prev = NULL; - while (item != NULL) { + while (item) { if (item->index == gamepadEvent->index) { break; } @@ -116,7 +114,7 @@ static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGa item = item->next; } - if (item == NULL) { + if (!item) { return 1; } @@ -124,7 +122,7 @@ static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGa item->joystick->hwdata = NULL; } - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_joylist == item); @@ -164,7 +162,6 @@ static void EMSCRIPTEN_JoystickQuit(void) SDL_joylist = SDL_joylist_tail = NULL; numjoysticks = 0; - instance_counter = 0; emscripten_set_gamepadconnected_callback(NULL, 0, NULL); emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL); @@ -243,7 +240,7 @@ static SDL_joylist_item *JoystickByIndex(int index) return NULL; } - while (item != NULL) { + while (item) { if (item->index == index) { break; } @@ -272,6 +269,11 @@ static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index) return NULL; } +static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -295,11 +297,11 @@ static int EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) { SDL_joylist_item *item = JoystickByDeviceIndex(device_index); - if (item == NULL) { + if (!item) { return SDL_SetError("No such device"); } - if (item->joystick != NULL) { + if (item->joystick) { return SDL_SetError("Joystick already opened"); } @@ -419,6 +421,7 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = { EMSCRIPTEN_JoystickDetect, EMSCRIPTEN_JoystickGetDeviceName, EMSCRIPTEN_JoystickGetDevicePath, + EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot, EMSCRIPTEN_JoystickGetDevicePlayerIndex, EMSCRIPTEN_JoystickSetDevicePlayerIndex, EMSCRIPTEN_JoystickGetDeviceGUID, diff --git a/src/joystick/emscripten/SDL_sysjoystick_c.h b/src/joystick/emscripten/SDL_sysjoystick_c.h index 33eb96e1..9ba6b35e 100644 --- a/src/joystick/emscripten/SDL_sysjoystick_c.h +++ b/src/joystick/emscripten/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/haiku/SDL_haikujoystick.cc b/src/joystick/haiku/SDL_haikujoystick.cc index 49e4d69f..9110b0ad 100644 --- a/src/joystick/haiku/SDL_haikujoystick.cc +++ b/src/joystick/haiku/SDL_haikujoystick.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -101,6 +101,11 @@ extern "C" return SDL_joyport[device_index]; } + static int HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) + { + return -1; + } + static int HAIKU_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -113,7 +118,7 @@ extern "C" /* Function to perform the mapping from device index to the instance id for this index */ static SDL_JoystickID HAIKU_JoystickGetDeviceInstanceID(int device_index) { - return device_index; + return device_index + 1; } static void HAIKU_JoystickClose(SDL_Joystick *joystick); @@ -129,12 +134,10 @@ extern "C" /* Create the joystick data structure */ joystick->instance_id = device_index; - joystick->hwdata = (struct joystick_hwdata *) - SDL_malloc(sizeof(*joystick->hwdata)); + joystick->hwdata = (struct joystick_hwdata *) SDL_calloc(1, sizeof(*joystick->hwdata)); if (joystick->hwdata == NULL) { - return SDL_OutOfMemory(); + return -1; } - SDL_memset(joystick->hwdata, 0, sizeof(*joystick->hwdata)); stick = new BJoystick; joystick->hwdata->stick = stick; @@ -152,13 +155,11 @@ extern "C" joystick->naxes = stick->CountAxes(); joystick->nhats = stick->CountHats(); - joystick->hwdata->new_axes = (int16 *) - SDL_malloc(joystick->naxes * sizeof(int16)); - joystick->hwdata->new_hats = (uint8 *) - SDL_malloc(joystick->nhats * sizeof(uint8)); + joystick->hwdata->new_axes = (int16 *) SDL_calloc(joystick->naxes, sizeof(int16)); + joystick->hwdata->new_hats = (uint8 *) SDL_calloc(joystick->nhats, sizeof(uint8)); if (!joystick->hwdata->new_hats || !joystick->hwdata->new_axes) { HAIKU_JoystickClose(joystick); - return SDL_OutOfMemory(); + return -1; } /* We're done! */ @@ -299,6 +300,7 @@ extern "C" HAIKU_JoystickDetect, HAIKU_JoystickGetDeviceName, HAIKU_JoystickGetDevicePath, + HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot, HAIKU_JoystickGetDevicePlayerIndex, HAIKU_JoystickSetDevicePlayerIndex, HAIKU_JoystickGetDeviceGUID, diff --git a/src/joystick/hidapi/SDL_hidapi_combined.c b/src/joystick/hidapi/SDL_hidapi_combined.c index 57fb7556..3d090242 100644 --- a/src/joystick/hidapi/SDL_hidapi_combined.c +++ b/src/joystick/hidapi/SDL_hidapi_combined.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c index c108e5cc..ee90fdf5 100644 --- a/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -46,7 +46,6 @@ typedef struct Uint8 rumble[1 + MAX_CONTROLLERS]; /* Without this variable, hid_write starts to lag a TON */ SDL_bool rumbleUpdate; - SDL_bool m_bUseButtonLabels; SDL_bool useRumbleBrake; } SDL_DriverGameCube_Context; @@ -73,7 +72,9 @@ static SDL_bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *devic /* Nintendo Co., Ltd. Wii U GameCube Controller Adapter */ return SDL_TRUE; } - if (vendor_id == USB_VENDOR_DRAGONRISE && product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER) { + if (vendor_id == USB_VENDOR_DRAGONRISE && + (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) { /* EVORETRO GameCube Controller Adapter */ return SDL_TRUE; } @@ -90,12 +91,6 @@ static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index) ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_MAX + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40; } -static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata; - ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE); -} - static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { if (hint) { @@ -104,22 +99,6 @@ static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, c } } -static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button) -{ - if (!ctx->m_bUseButtonLabels) { - /* Use button positions */ - switch (button) { - case SDL_GAMEPAD_BUTTON_B: - return SDL_GAMEPAD_BUTTON_X; - case SDL_GAMEPAD_BUTTON_X: - return SDL_GAMEPAD_BUTTON_B; - default: - break; - } - } - return button; -} - static SDL_bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) { SDL_DriverGameCube_Context *ctx; @@ -135,8 +114,7 @@ static SDL_bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) #endif ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } device->context = ctx; @@ -203,8 +181,6 @@ static SDL_bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) SDL_AddHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE, SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); - SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); @@ -245,21 +221,21 @@ static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device } joystick = SDL_GetJoystickFromInstanceID(ctx->joysticks[i]); - if (joystick == NULL) { + if (!joystick) { /* Hasn't been opened yet, skip */ return; } #define READ_BUTTON(off, flag, button) \ - SDL_SendJoystickButton( \ + SDL_SendJoystickButton( \ timestamp, \ joystick, \ - RemapButton(ctx, button), \ + button, \ (packet[off] & flag) ? SDL_PRESSED : SDL_RELEASED); READ_BUTTON(1, 0x02, 0) /* A */ READ_BUTTON(1, 0x04, 1) /* B */ - READ_BUTTON(1, 0x01, 2) /* X */ READ_BUTTON(1, 0x08, 3) /* Y */ + READ_BUTTON(1, 0x01, 2) /* X */ READ_BUTTON(2, 0x80, 4) /* DPAD_LEFT */ READ_BUTTON(2, 0x20, 5) /* DPAD_RIGHT */ READ_BUTTON(2, 0x40, 6) /* DPAD_DOWN */ @@ -286,8 +262,8 @@ static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device joystick, \ axis, axis_value); READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) - READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 0) - READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 1) + READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) + READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) @@ -322,7 +298,7 @@ static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device joystick = SDL_GetJoystickFromInstanceID(ctx->joysticks[i]); /* Hasn't been opened yet, skip */ - if (joystick == NULL) { + if (!joystick) { continue; } } else { @@ -334,14 +310,14 @@ static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device } #define READ_BUTTON(off, flag, button) \ - SDL_SendJoystickButton( \ + SDL_SendJoystickButton( \ timestamp, \ joystick, \ - RemapButton(ctx, button), \ + button, \ (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED); READ_BUTTON(1, 0x01, 0) /* A */ - READ_BUTTON(1, 0x04, 1) /* B */ - READ_BUTTON(1, 0x02, 2) /* X */ + READ_BUTTON(1, 0x02, 1) /* B */ + READ_BUTTON(1, 0x04, 2) /* X */ READ_BUTTON(1, 0x08, 3) /* Y */ READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */ READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */ @@ -386,7 +362,7 @@ static SDL_bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) /* Read input packet */ while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { #ifdef DEBUG_GAMECUBE_PROTOCOL - // HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); + HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); #endif if (ctx->pc_mode) { HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); @@ -523,8 +499,6 @@ static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device) { SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; - SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); SDL_DelHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE, SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); } diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c index f0f1923c..ed408a2f 100644 --- a/src/joystick/hidapi/SDL_hidapi_luna.c +++ b/src/joystick/hidapi/SDL_hidapi_luna.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -38,8 +38,8 @@ enum { - SDL_CONTROLLER_BUTTON_LUNA_MIC = 15, - SDL_CONTROLLER_NUM_LUNA_BUTTONS, + SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11, + SDL_GAMEPAD_NUM_LUNA_BUTTONS, }; typedef struct @@ -72,8 +72,7 @@ static SDL_bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverLuna_Context *ctx; ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } device->context = ctx; @@ -101,8 +100,9 @@ static SDL_bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo SDL_zeroa(ctx->last_state); /* Initialize the joystick capabilities */ - joystick->nbuttons = SDL_CONTROLLER_NUM_LUNA_BUTTONS; + joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; return SDL_TRUE; @@ -169,10 +169,10 @@ static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_D Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[1] != data[1]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[1] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -180,53 +180,44 @@ static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_D } if (ctx->last_state[2] != data[2]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_LUNA_MIC, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[3] != data[3]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; - switch (data[3] & 0xf) { + switch (data[3] & 0x0f) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } #define READ_STICK_AXIS(offset) \ @@ -288,54 +279,45 @@ static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, } if (ctx->last_state[13] != data[13]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; - switch (data[13] & 0xf) { + switch (data[13] & 0x0f) { case 1: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 2: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 3: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 4: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 5: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 6: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 7: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 8: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (ctx->last_state[14] != data[14]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[14] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } @@ -346,7 +328,7 @@ static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, } if (ctx->last_state[16] != data[16]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_LUNA_MIC, (data[16] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, (data[16] & 0x02) ? SDL_PRESSED : SDL_RELEASED); } #define READ_STICK_AXIS(offset) \ @@ -393,7 +375,7 @@ static SDL_bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_LUNA_PROTOCOL HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size); #endif - if (joystick == NULL) { + if (!joystick) { continue; } diff --git a/src/joystick/hidapi/SDL_hidapi_nintendo.h b/src/joystick/hidapi/SDL_hidapi_nintendo.h index daa1e92a..e12c61aa 100644 --- a/src/joystick/hidapi/SDL_hidapi_nintendo.h +++ b/src/joystick/hidapi/SDL_hidapi_nintendo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/hidapi/SDL_hidapi_ps3.c b/src/joystick/hidapi/SDL_hidapi_ps3.c index a84dd4b4..8992f0d8 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps3.c +++ b/src/joystick/hidapi/SDL_hidapi_ps3.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -134,8 +134,7 @@ static SDL_bool HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device) } ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -215,7 +214,7 @@ static void HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL { SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; - if (ctx == NULL) { + if (!ctx) { return; } @@ -241,8 +240,9 @@ static SDL_bool HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); /* Initialize the joystick capabilities */ - joystick->nbuttons = 15; + joystick->nbuttons = 11; joystick->naxes = 16; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); @@ -315,52 +315,43 @@ static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_D Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[4] != data[4]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[4] & 0x0f) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[5] != data[5]) { @@ -392,23 +383,35 @@ static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_Drive Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[2] != data[2]) { + Uint8 hat = 0; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + + if (data[2] & 0x10) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x20) { + hat |= SDL_HAT_RIGHT; + } + if (data[2] & 0x40) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x80) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (ctx->last_state[3] != data[3]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[4] != data[4]) { @@ -431,10 +434,10 @@ static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_Drive /* Buttons are mapped as axes in the order they appear in the button enumeration */ { static int button_axis_offsets[] = { - 24, /* SDL_GAMEPAD_BUTTON_A */ - 23, /* SDL_GAMEPAD_BUTTON_B */ - 25, /* SDL_GAMEPAD_BUTTON_X */ - 22, /* SDL_GAMEPAD_BUTTON_Y */ + 24, /* SDL_GAMEPAD_BUTTON_SOUTH */ + 23, /* SDL_GAMEPAD_BUTTON_EAST */ + 25, /* SDL_GAMEPAD_BUTTON_WEST */ + 22, /* SDL_GAMEPAD_BUTTON_NORTH */ 0, /* SDL_GAMEPAD_BUTTON_BACK */ 0, /* SDL_GAMEPAD_BUTTON_GUIDE */ 0, /* SDL_GAMEPAD_BUTTON_START */ @@ -491,7 +494,7 @@ static SDL_bool HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_PS3_PROTOCOL HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); #endif - if (joystick == NULL) { + if (!joystick) { continue; } @@ -604,8 +607,7 @@ static SDL_bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverPS3_Context *ctx; ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -641,8 +643,9 @@ static SDL_bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *devic SDL_zeroa(ctx->last_state); /* Initialize the joystick capabilities */ - joystick->nbuttons = 15; + joystick->nbuttons = 11; joystick->naxes = 16; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; return SDL_TRUE; @@ -684,19 +687,16 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystic Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[0] != data[0]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[1] != data[1]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); @@ -705,40 +705,34 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystic switch (data[1] >> 4) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } axis = ((int)data[16] * 257) - 32768; @@ -757,10 +751,10 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystic /* Buttons are mapped as axes in the order they appear in the button enumeration */ { static int button_axis_offsets[] = { - 12, /* SDL_GAMEPAD_BUTTON_A */ - 11, /* SDL_GAMEPAD_BUTTON_B */ - 13, /* SDL_GAMEPAD_BUTTON_X */ - 10, /* SDL_GAMEPAD_BUTTON_Y */ + 12, /* SDL_GAMEPAD_BUTTON_SOUTH */ + 11, /* SDL_GAMEPAD_BUTTON_EAST */ + 13, /* SDL_GAMEPAD_BUTTON_WEST */ + 10, /* SDL_GAMEPAD_BUTTON_NORTH */ 0, /* SDL_GAMEPAD_BUTTON_BACK */ 0, /* SDL_GAMEPAD_BUTTON_GUIDE */ 0, /* SDL_GAMEPAD_BUTTON_START */ @@ -797,10 +791,10 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystic Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[0] != data[0]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[0] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED); } @@ -815,53 +809,55 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystic if (ctx->device->vendor_id == USB_VENDOR_SAITEK && ctx->device->product_id == USB_PRODUCT_SAITEK_CYBORG_V3) { /* Cyborg V.3 Rumble Pad doesn't set the dpad bits as expected, so use the axes instead */ - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, data[10] ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, data[9] ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, data[7] ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, data[8] ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data[7]) { + hat |= SDL_HAT_RIGHT; + } + if (data[8]) { + hat |= SDL_HAT_LEFT; + } + if (data[9]) { + hat |= SDL_HAT_UP; + } + if (data[10]) { + hat |= SDL_HAT_DOWN; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } else { if (ctx->last_state[2] != data[2]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[2] & 0x0f) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } } @@ -881,10 +877,10 @@ static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystic /* Buttons are mapped as axes in the order they appear in the button enumeration */ { static int button_axis_offsets[] = { - 13, /* SDL_GAMEPAD_BUTTON_A */ - 12, /* SDL_GAMEPAD_BUTTON_B */ - 14, /* SDL_GAMEPAD_BUTTON_X */ - 11, /* SDL_GAMEPAD_BUTTON_Y */ + 13, /* SDL_GAMEPAD_BUTTON_SOUTH */ + 12, /* SDL_GAMEPAD_BUTTON_EAST */ + 14, /* SDL_GAMEPAD_BUTTON_WEST */ + 11, /* SDL_GAMEPAD_BUTTON_NORTH */ 0, /* SDL_GAMEPAD_BUTTON_BACK */ 0, /* SDL_GAMEPAD_BUTTON_GUIDE */ 0, /* SDL_GAMEPAD_BUTTON_START */ @@ -932,7 +928,7 @@ static SDL_bool HIDAPI_DriverPS3ThirdParty_UpdateDevice(SDL_HIDAPI_Device *devic #ifdef DEBUG_PS3_PROTOCOL HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); #endif - if (joystick == NULL) { + if (!joystick) { continue; } diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index 47ea2904..91ce0f5c 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -50,6 +50,11 @@ (((Uint32)(C)) << 16) | \ (((Uint32)(D)) << 24)) +enum +{ + SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11 +}; + typedef enum { k_EPS4ReportIdUsbState = 1, @@ -142,6 +147,7 @@ typedef struct SDL_HIDAPI_Device *device; SDL_Joystick *joystick; SDL_bool is_dongle; + SDL_bool is_nacon_dongle; SDL_bool official_controller; SDL_bool sensors_supported; SDL_bool lightbar_supported; @@ -275,8 +281,7 @@ static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -413,6 +418,11 @@ static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) } ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported); + if (device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) { + ctx->is_nacon_dongle = SDL_TRUE; + } + if (device->vendor_id == USB_VENDOR_PDP && (device->product_id == USB_PRODUCT_VICTRIX_FS_PRO || device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) { @@ -438,7 +448,7 @@ static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) } else { HIDAPI_DisconnectBluetoothDevice(device->serial); } - if (ctx->is_dongle && serial[0] == '\0') { + if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') { /* Not yet connected */ return SDL_TRUE; } @@ -584,7 +594,7 @@ static SDL_bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device * SDL_Log("calibration[%d] bias = %d, sensitivity = %f\n", i, ctx->calibration[i].bias, ctx->calibration[i].scale); #endif /* Some controllers have a bad calibration */ - if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabs(1.0f - ctx->calibration[i].scale) > 0.5f) { + if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) { #ifdef DEBUG_PS4_CALIBRATION SDL_Log("invalid calibration, ignoring\n"); #endif @@ -685,10 +695,14 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); } } else { +#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controllers, except it + * only sends reports when the state changes, so we can't disconnect here. + */ /* We can't even send an invalid effects packet, or it will put the controller in enhanced mode */ if (device->num_joysticks > 0) { HIDAPI_JoystickDisconnected(device, device->joysticks[0]); } +#endif } } @@ -811,8 +825,12 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); /* Initialize the joystick capabilities */ - joystick->nbuttons = ctx->touchpad_supported ? 16 : 15; + joystick->nbuttons = 11; + if (ctx->touchpad_supported) { + joystick->nbuttons += 1; + } joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; if (device->is_bluetooth) { /* We'll update this once we're in enhanced mode */ joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN; @@ -970,54 +988,45 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d { Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); } { + Uint8 hat; Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; switch (data) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } } @@ -1045,7 +1054,7 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, 15, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); } axis = ((int)packet->ucTriggerLeft * 257) - 32768; @@ -1131,7 +1140,7 @@ static SDL_bool VerifyCRC(Uint8 *data, int size) packetCRC[1], packetCRC[2], packetCRC[3]); - return (unCRC == unPacketCRC) ? SDL_TRUE : SDL_FALSE; + return (unCRC == unPacketCRC); } static SDL_bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size) @@ -1143,6 +1152,21 @@ static SDL_bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 return SDL_TRUE; } + if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) { + /* The report timestamp doesn't change when the controller isn't connected */ + PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1]; + if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) { + return SDL_FALSE; + } + if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 && + ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 && + ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) { + /* We don't have any state to compare yet, go ahead and copy it */ + SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t)); + return SDL_FALSE; + } + } + /* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0'). * For non-dongle, this bit is always 0 (connected). * This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this @@ -1207,7 +1231,7 @@ static SDL_bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) ++packet_count; ctx->last_packet = now; - if (joystick == NULL) { + if (!joystick) { continue; } @@ -1254,7 +1278,7 @@ static SDL_bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (ctx->is_dongle) { + if (ctx->is_dongle || ctx->is_nacon_dongle) { if (packet_count == 0) { if (device->num_joysticks > 0) { /* Check to see if it looks like the device disconnected */ @@ -1276,7 +1300,7 @@ static SDL_bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (size < 0 && device->num_joysticks > 0) { + if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { /* Read error, device is disconnected */ HIDAPI_JoystickDisconnected(device, device->joysticks[0]); } diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index 8446c553..7ce112c9 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,11 +51,12 @@ enum { - SDL_CONTROLLER_BUTTON_PS5_TOUCHPAD = SDL_GAMEPAD_BUTTON_MISC1 + 1, - SDL_CONTROLLER_BUTTON_PS5_LEFT_FUNCTION, - SDL_CONTROLLER_BUTTON_PS5_RIGHT_FUNCTION, - SDL_CONTROLLER_BUTTON_PS5_LEFT_PADDLE, - SDL_CONTROLLER_BUTTON_PS5_RIGHT_PADDLE + SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11, + SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, + SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, + SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, + SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, + SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE }; typedef enum @@ -231,6 +232,7 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; + SDL_bool is_nacon_dongle; SDL_bool use_alternate_report; SDL_bool sensors_supported; SDL_bool lightbar_supported; @@ -264,6 +266,7 @@ typedef struct { PS5SimpleStatePacket_t simple; PS5StatePacketCommon_t state; + PS5StatePacketAlt_t alt_state; PS5StatePacket_t full_state; Uint8 data[64]; } last_state; @@ -351,7 +354,8 @@ static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index 0x04, 0x0A, 0x15, - 0x1B + 0x1B, + 0x1F }; if (player_index >= 0) { @@ -372,8 +376,7 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -490,10 +493,17 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } ctx->use_alternate_report = SDL_TRUE; + + if (device->vendor_id == USB_VENDOR_NACON_ALT && + (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED || + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) { + /* This doesn't report vibration capability, but it can do rumble */ + ctx->vibration_supported = SDL_TRUE; + } } else if (device->vendor_id == USB_VENDOR_RAZER && (device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED || device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) { - /* The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors, but no vibration */ + /* The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration */ ctx->sensors_supported = SDL_TRUE; ctx->touchpad_supported = SDL_TRUE; ctx->use_alternate_report = SDL_TRUE; @@ -501,6 +511,11 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported); + if (device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) { + ctx->is_nacon_dongle = SDL_TRUE; + } + device->joystick_type = joystick_type; device->type = SDL_GAMEPAD_TYPE_PS5; if (device->vendor_id == USB_VENDOR_SONY) { @@ -512,6 +527,11 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } HIDAPI_SetDeviceSerial(device, serial); + if (ctx->is_nacon_dongle) { + /* We don't know if this is connected yet, wait for reports */ + return SDL_TRUE; + } + /* Prefer the USB device over the Bluetooth device */ if (device->is_bluetooth) { if (HIDAPI_HasConnectedUSBDevice(device->serial)) { @@ -610,7 +630,7 @@ static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device) SDL_Log("calibration[%d] bias = %d, sensitivity = %f\n", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity); #endif /* Some controllers have a bad calibration */ - if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabs(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) { + if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) { #ifdef DEBUG_PS5_CALIBRATION SDL_Log("invalid calibration, ignoring\n"); #endif @@ -926,13 +946,14 @@ static SDL_bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy /* Initialize the joystick capabilities */ if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) { - joystick->nbuttons = 21; + joystick->nbuttons = 17; /* paddles and touchpad and microphone */ } else if (ctx->touchpad_supported) { - joystick->nbuttons = 17; + joystick->nbuttons = 13; /* touchpad and microphone */ } else { - joystick->nbuttons = 15; + joystick->nbuttons = 11; } joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; if (device->is_bluetooth) { /* We'll update this once we're in enhanced mode */ joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN; @@ -1113,54 +1134,45 @@ static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL { Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); } { Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } } @@ -1179,7 +1191,7 @@ static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); } if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) { @@ -1214,54 +1226,45 @@ static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL { Uint8 data = (packet->rgucButtonsAndHat[0] >> 4); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); } { Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F); - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } } @@ -1280,12 +1283,12 @@ static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL Uint8 data = packet->rgucButtonsAndHat[2]; SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_PS5_TOUCHPAD, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_PS5_LEFT_FUNCTION, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_PS5_RIGHT_FUNCTION, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_PS5_LEFT_PADDLE, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_PS5_RIGHT_PADDLE, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) { @@ -1429,13 +1432,27 @@ static SDL_bool VerifyCRC(Uint8 *data, int size) packetCRC[1], packetCRC[2], packetCRC[3]); - return (unCRC == unPacketCRC) ? SDL_TRUE : SDL_FALSE; + return (unCRC == unPacketCRC); } static SDL_bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size) { switch (data[0]) { case k_EPS5ReportIdState: + if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { + /* The report timestamp doesn't change when the controller isn't connected */ + PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1]; + if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) { + return SDL_FALSE; + } + if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 && + ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 && + ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) { + /* We don't have any state to compare yet, go ahead and copy it */ + SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t)); + return SDL_FALSE; + } + } return SDL_TRUE; case k_EPS5ReportIdBluetoothState: @@ -1475,7 +1492,7 @@ static SDL_bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) ++packet_count; ctx->last_packet = now; - if (joystick == NULL) { + if (!joystick) { continue; } @@ -1530,7 +1547,22 @@ static SDL_bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (size < 0 && device->num_joysticks > 0) { + if (ctx->is_nacon_dongle) { + if (packet_count == 0) { + if (device->num_joysticks > 0) { + /* Check to see if it looks like the device disconnected */ + if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } else { + if (device->num_joysticks == 0) { + HIDAPI_JoystickConnected(device, NULL); + } + } + } + + if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { /* Read error, device is disconnected */ HIDAPI_JoystickDisconnected(device, device->joysticks[0]); } diff --git a/src/joystick/hidapi/SDL_hidapi_rumble.c b/src/joystick/hidapi/SDL_hidapi_rumble.c index 4035b45f..5d270fc1 100644 --- a/src/joystick/hidapi/SDL_hidapi_rumble.c +++ b/src/joystick/hidapi/SDL_hidapi_rumble.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -214,9 +214,9 @@ int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const } request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request)); - if (request == NULL) { + if (!request) { SDL_HIDAPI_UnlockRumble(); - return SDL_OutOfMemory(); + return -1; } request->device = device; SDL_memcpy(request->data, data, size); diff --git a/src/joystick/hidapi/SDL_hidapi_rumble.h b/src/joystick/hidapi/SDL_hidapi_rumble.h index 8e30847e..0a1ccb9d 100644 --- a/src/joystick/hidapi/SDL_hidapi_rumble.h +++ b/src/joystick/hidapi/SDL_hidapi_rumble.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/hidapi/SDL_hidapi_shield.c b/src/joystick/hidapi/SDL_hidapi_shield.c index 25f81a3d..0128f698 100644 --- a/src/joystick/hidapi/SDL_hidapi_shield.c +++ b/src/joystick/hidapi/SDL_hidapi_shield.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -46,12 +46,13 @@ enum { - SDL_CONTROLLER_BUTTON_SHIELD_V103_TOUCHPAD = SDL_GAMEPAD_BUTTON_MISC1 + 1, - SDL_CONTROLLER_BUTTON_SHIELD_V103_MINUS, - SDL_CONTROLLER_BUTTON_SHIELD_V103_PLUS, - SDL_CONTROLLER_NUM_SHIELD_V103_BUTTONS, + SDL_GAMEPAD_BUTTON_SHIELD_SHARE = 11, + SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, + SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, + SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, + SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS, - SDL_CONTROLLER_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_MISC1 + 1, + SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_SHIELD_SHARE + 1, }; typedef enum @@ -114,8 +115,7 @@ static SDL_bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverShield_Context *ctx; ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } device->context = ctx; @@ -181,14 +181,16 @@ static SDL_bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_ /* Initialize the joystick capabilities */ if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { - joystick->nbuttons = SDL_CONTROLLER_NUM_SHIELD_V103_BUTTONS; + joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; SDL_PrivateJoystickAddTouchpad(joystick, 1); } else { - joystick->nbuttons = SDL_CONTROLLER_NUM_SHIELD_V104_BUTTONS; + joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN; } @@ -289,54 +291,45 @@ static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SD Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[3] != data[3]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[3]) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (ctx->last_state[1] != data[1]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[1] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -345,11 +338,11 @@ static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SD if (ctx->last_state[2] != data[2]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_PLUS, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_MINUS, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_SwapLE16(*(Sint16 *)&data[4]) - 0x8000); @@ -373,7 +366,7 @@ static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SD float touchpad_x, touchpad_y; Uint64 timestamp = SDL_GetTicksNS(); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_SHIELD_V103_TOUCHPAD, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); /* It's a triangular pad, but just use the center as the usable touch area */ touchpad_state = !(data[1] & 0x80) ? SDL_PRESSED : SDL_RELEASED; @@ -391,54 +384,45 @@ static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SD } if (ctx->last_state[2] != data[2]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[2]) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (ctx->last_state[3] != data[3]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -459,7 +443,7 @@ static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SD SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_SwapLE16(*(Sint16 *)&data[21]) - 0x8000); if (ctx->last_state[17] != data[17]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[17] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, (data[17] & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[17] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[17] & 0x04) ? SDL_PRESSED : SDL_RELEASED); } @@ -489,7 +473,7 @@ static SDL_bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) /* Byte 0 is HID report ID */ switch (data[0]) { case k_ShieldReportIdControllerState: - if (joystick == NULL) { + if (!joystick) { break; } if (size == 16) { @@ -499,7 +483,7 @@ static SDL_bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) } break; case k_ShieldReportIdControllerTouch: - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size); diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c index 9d7bb0bc..f55664bc 100644 --- a/src/joystick/hidapi/SDL_hidapi_stadia.c +++ b/src/joystick/hidapi/SDL_hidapi_stadia.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,9 +33,9 @@ enum { - SDL_CONTROLLER_BUTTON_STADIA_SHARE = 15, - SDL_CONTROLLER_BUTTON_STADIA_GOOGLE_ASSISTANT, - SDL_CONTROLLER_NUM_STADIA_BUTTONS, + SDL_GAMEPAD_BUTTON_STADIA_SHARE = 11, + SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, + SDL_GAMEPAD_NUM_STADIA_BUTTONS, }; typedef struct @@ -69,8 +69,7 @@ static SDL_bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverStadia_Context *ctx; ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } device->context = ctx; @@ -107,8 +106,9 @@ static SDL_bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_ SDL_zeroa(ctx->last_state); /* Initialize the joystick capabilities */ - joystick->nbuttons = SDL_CONTROLLER_NUM_STADIA_BUTTONS; + joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; return SDL_TRUE; @@ -179,47 +179,38 @@ static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr } if (ctx->last_state[1] != data[1]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[1]) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (ctx->last_state[2] != data[2]) { @@ -227,15 +218,15 @@ static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_STADIA_SHARE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_CONTROLLER_BUTTON_STADIA_GOOGLE_ASSISTANT, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_SHARE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[3] != data[3]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[3] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); @@ -285,7 +276,7 @@ static SDL_bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_STADIA_PROTOCOL HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size); #endif - if (joystick == NULL) { + if (!joystick) { continue; } diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c index d01d5642..2e09abee 100644 --- a/src/joystick/hidapi/SDL_hidapi_steam.c +++ b/src/joystick/hidapi/SDL_hidapi_steam.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,29 +29,31 @@ /*****************************************************************************************************/ -#include - #define bool SDL_bool #define true SDL_TRUE #define false SDL_FALSE -typedef uint32_t uint32; -typedef uint64_t uint64; - #include "steam/controller_constants.h" #include "steam/controller_structs.h" +enum +{ + SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11, + SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, + SDL_GAMEPAD_NUM_STEAM_BUTTONS, +}; + typedef struct SteamControllerStateInternal_t { // Controller Type for this Controller State - uint32 eControllerType; + Uint32 eControllerType; // If packet num matches that on your prior call, then the controller state hasn't been changed since // your last call and there is no need to process it - uint32 unPacketNum; + Uint32 unPacketNum; // bit flags for each of the buttons - uint64 ulButtons; + Uint64 ulButtons; // Left pad coordinates short sLeftPadX; @@ -973,8 +975,7 @@ static SDL_bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverSteam_Context *ctx; ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } device->context = ctx; @@ -1024,8 +1025,9 @@ static SDL_bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J InitializeSteamControllerPacketAssembler(&ctx->m_assembler); /* Initialize the joystick capabilities */ - joystick->nbuttons = 17; + joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); @@ -1070,9 +1072,9 @@ static int HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_J SDL_memset(buf, 0, 65); buf[1] = ID_SET_SETTINGS_VALUES; if (enabled) { - ADD_SETTING(SETTING_GYRO_MODE, 0x18 /* SETTING_GYRO_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO */); + ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); } else { - ADD_SETTING(SETTING_GYRO_MODE, 0x00 /* SETTING_GYRO_MODE_OFF */); + ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF); } buf[2] = (unsigned char)(nSettings * 3); if (SetFeatureReport(device->dev, buf, 3 + nSettings * 3) < 0) { @@ -1105,7 +1107,7 @@ static SDL_bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) break; } - if (joystick == NULL) { + if (!joystick) { continue; } @@ -1120,16 +1122,18 @@ static SDL_bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) Uint64 timestamp = SDL_GetTicksNS(); if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (ctx->m_state.ulButtons & STEAM_BUTTON_3_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (ctx->m_state.ulButtons & STEAM_BUTTON_1_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (ctx->m_state.ulButtons & STEAM_BUTTON_2_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (ctx->m_state.ulButtons & STEAM_BUTTON_0_MASK) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, @@ -1149,19 +1153,24 @@ static SDL_bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1 + 0, + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, (ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1 + 1, + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE, (ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, - (ctx->m_state.ulButtons & STEAM_TOUCH_0_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, - (ctx->m_state.ulButtons & STEAM_TOUCH_3_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, - (ctx->m_state.ulButtons & STEAM_TOUCH_2_MASK) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, - (ctx->m_state.ulButtons & STEAM_TOUCH_1_MASK) ? SDL_PRESSED : SDL_RELEASED); + if (ctx->m_state.ulButtons & STEAM_TOUCH_0_MASK) { + hat |= SDL_HAT_UP; + } + if (ctx->m_state.ulButtons & STEAM_TOUCH_3_MASK) { + hat |= SDL_HAT_DOWN; + } + if (ctx->m_state.ulButtons & STEAM_TOUCH_2_MASK) { + hat |= SDL_HAT_LEFT; + } + if (ctx->m_state.ulButtons & STEAM_TOUCH_1_MASK) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768); diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c new file mode 100644 index 00000000..b387add9 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -0,0 +1,443 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2023 Max Maisel + + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + +/*****************************************************************************************************/ + +#include "steam/controller_constants.h" +#include "steam/controller_structs.h" + +enum +{ + SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS, +}; + +typedef enum +{ + STEAMDECK_LBUTTON_R2 = 0x00000001, + STEAMDECK_LBUTTON_L2 = 0x00000002, + STEAMDECK_LBUTTON_R = 0x00000004, + STEAMDECK_LBUTTON_L = 0x00000008, + STEAMDECK_LBUTTON_Y = 0x00000010, + STEAMDECK_LBUTTON_B = 0x00000020, + STEAMDECK_LBUTTON_X = 0x00000040, + STEAMDECK_LBUTTON_A = 0x00000080, + STEAMDECK_LBUTTON_DPAD_UP = 0x00000100, + STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200, + STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400, + STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800, + STEAMDECK_LBUTTON_VIEW = 0x00001000, + STEAMDECK_LBUTTON_STEAM = 0x00002000, + STEAMDECK_LBUTTON_MENU = 0x00004000, + STEAMDECK_LBUTTON_L5 = 0x00008000, + STEAMDECK_LBUTTON_R5 = 0x00010000, + STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000, + STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000, + STEAMDECK_LBUTTON_L3 = 0x00400000, + STEAMDECK_LBUTTON_R3 = 0x04000000, + + STEAMDECK_HBUTTON_L4 = 0x00000200, + STEAMDECK_HBUTTON_R4 = 0x00000400, + STEAMDECK_HBUTTON_QAM = 0x00040000, +} SteamDeckButtons; + +typedef struct +{ + Uint32 update_rate_us; + Uint32 sensor_timestamp_us; + Uint64 last_button_state; + Uint8 watchdog_counter; +} SDL_DriverSteamDeck_Context; + +static SDL_bool DisableDeckLizardMode(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return SDL_FALSE; + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 5 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE; + msg->payload.setSettingsValues.settings[0].settingValue = 0; + msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE; + msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE; + msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse + msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE; + msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad + msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF; + msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad + msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return SDL_FALSE; + + // There may be a lingering report read back after changing settings. + // Discard it. + SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); + + return SDL_TRUE; +} + +static SDL_bool FeedDeckLizardWatchdog(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return SDL_FALSE; + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE; + msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return SDL_FALSE; + + // There may be a lingering report read back after changing settings. + // Discard it. + SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); + + return SDL_TRUE; +} + +/*****************************************************************************************************/ + +static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); +} + +static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); +} + +static SDL_bool HIDAPI_DriverSteamDeck_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static SDL_bool HIDAPI_DriverSteamDeck_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + return SDL_IsJoystickSteamDeck(vendor_id, product_id); +} + +static SDL_bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device) +{ + int size; + Uint8 data[64]; + SDL_DriverSteamDeck_Context *ctx; + + ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + + // Always 1kHz according to USB descriptor + ctx->update_rate_us = 1000; + + device->context = ctx; + + // Read a report to see if this is the correct endpoint. + // Mouse, Keyboard and Controller have the same VID/PID but + // only the controller hidraw device receives hid reports. + size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); + if (size == 0) + return SDL_FALSE; + + if (!DisableDeckLizardMode(device->dev)) + return SDL_FALSE; + + HIDAPI_SetDeviceName(device, "Steam Deck"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static SDL_bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; + SDL_Joystick *joystick = NULL; + int r; + uint8_t data[64]; + float values[3]; + ValveInReport_t *pInReport = (ValveInReport_t *)data; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]); + if (joystick == NULL) { + return SDL_FALSE; + } + } else { + return SDL_FALSE; + } + + if (ctx->watchdog_counter++ > 200) { + ctx->watchdog_counter = 0; + if (!FeedDeckLizardWatchdog(device->dev)) + return SDL_FALSE; + } + + SDL_memset(data, 0, sizeof(data)); + r = SDL_hid_read(device->dev, data, sizeof(data)); + if (r == 0) { + return SDL_FALSE; + } else if (r <= 0) { + /* Failed to read from controller */ + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + return SDL_FALSE; + } + + if (!(r == 64 && pInReport->header.unReportVersion == k_ValveInReportMsgVersion && pInReport->header.ucType == ID_CONTROLLER_DECK_STATE && pInReport->header.ucLength == 64)) { + return SDL_FALSE; + } + + Uint64 timestamp = SDL_GetTicksNS(); + + if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) ? SDL_PRESSED : SDL_RELEASED); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) ? SDL_PRESSED : SDL_RELEASED); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM, + (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) ? SDL_PRESSED : SDL_RELEASED); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) ? SDL_PRESSED : SDL_RELEASED); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) ? SDL_PRESSED : SDL_RELEASED); + + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + ctx->last_button_state = pInReport->payload.deckState.ulButtons; + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + (int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + (int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, + pInReport->payload.deckState.sLeftStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, + -pInReport->payload.deckState.sLeftStickY); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, + pInReport->payload.deckState.sRightStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, + -pInReport->payload.deckState.sRightStickY); + + ctx->sensor_timestamp_us += ctx->update_rate_us; + + values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3); + + values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); + + return SDL_TRUE; +} + +static SDL_bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; + float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f; + + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + + return SDL_TRUE; +} + +static int HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_TRIGGER_RUMBLE_CMD; + msg->payload.simpleRumble.unRumbleType = 0; + msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM; + msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble; + msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble; + msg->payload.simpleRumble.nLeftGain = 2; + msg->payload.simpleRumble.nRightGain = 0; + + rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return -1; + return 0; +} + +static int HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYCAP_RUMBLE; +} + +static int HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static int HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static int HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled) +{ + // On steam deck, sensors are enabled by default. Nothing to do here. + return 0; +} + +static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // Lizard mode id automatically re-enabled by watchdog. Nothing to do here. +} + +static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = { + SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, + SDL_TRUE, + HIDAPI_DriverSteamDeck_RegisterHints, + HIDAPI_DriverSteamDeck_UnregisterHints, + HIDAPI_DriverSteamDeck_IsEnabled, + HIDAPI_DriverSteamDeck_IsSupportedDevice, + HIDAPI_DriverSteamDeck_InitDevice, + HIDAPI_DriverSteamDeck_GetDevicePlayerIndex, + HIDAPI_DriverSteamDeck_SetDevicePlayerIndex, + HIDAPI_DriverSteamDeck_UpdateDevice, + HIDAPI_DriverSteamDeck_OpenJoystick, + HIDAPI_DriverSteamDeck_RumbleJoystick, + HIDAPI_DriverSteamDeck_RumbleJoystickTriggers, + HIDAPI_DriverSteamDeck_GetJoystickCapabilities, + HIDAPI_DriverSteamDeck_SetJoystickLED, + HIDAPI_DriverSteamDeck_SendJoystickEffect, + HIDAPI_DriverSteamDeck_SetSensorsEnabled, + HIDAPI_DriverSteamDeck_CloseJoystick, + HIDAPI_DriverSteamDeck_FreeDevice, +}; + +#endif /* SDL_JOYSTICK_HIDAPI_STEAMDECK */ + +#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 0da65eb7..c0f8a64b 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,6 +63,16 @@ #define SWITCH_ACCEL_SCALE_OFFSET 16384.0f #define SWITCH_ACCEL_SCALE_MULT 4.0f +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_SWITCH_BUTTONS, +}; + typedef enum { k_eSwitchInputReportIDs_SubcommandReply = 0x21, @@ -263,13 +273,14 @@ typedef struct SDL_HIDAPI_Device *device; SDL_Joystick *joystick; SDL_bool m_bInputOnly; - SDL_bool m_bIsGameCube; SDL_bool m_bUseButtonLabels; SDL_bool m_bPlayerLights; int m_nPlayerIndex; SDL_bool m_bSyncWrite; int m_nMaxWriteAttempts; ESwitchDeviceInfoControllerType m_eControllerType; + Uint8 m_nInitialInputMode; + Uint8 m_nCurrentInputMode; Uint8 m_rgucMACAddress[6]; Uint8 m_nCommandNumber; SwitchCommonOutputPacket_t m_RumblePacket; @@ -438,7 +449,7 @@ static SDL_bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommand SwitchSubcommandInputPacket_t *reply = NULL; int nTries; - for (nTries = 1; reply == NULL && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { + for (nTries = 1; !reply && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { SwitchSubcommandOutputPacket_t commandPacket; ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket); @@ -462,7 +473,7 @@ static SDL_bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprieta for (nTries = 1; nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { SwitchProprietaryOutputPacket_t packet; - if ((pBuf == NULL && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) { + if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) { return SDL_FALSE; } @@ -644,7 +655,13 @@ static SDL_bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled } static SDL_bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode) { - return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL); + if (input_mode == ctx->m_nCurrentInputMode) { + return SDL_TRUE; + } else { + ctx->m_nCurrentInputMode = input_mode; + + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL); + } } static SDL_bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness) @@ -713,15 +730,29 @@ static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, c } } +static Uint8 GetInitialInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode = 0; + + if (ReadInput(ctx) > 0) { + input_mode = ctx->m_rgucReadBuffer[0]; + } + return input_mode; +} + static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) { Uint8 input_mode; /* Determine the desired input mode */ - if (ctx->device->is_bluetooth) { - input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + if (ctx->m_nInitialInputMode) { + input_mode = ctx->m_nInitialInputMode; } else { - input_mode = k_eSwitchInputReportIDs_FullControllerState; + if (ctx->device->is_bluetooth) { + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + } else { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } } /* The official Nintendo Switch Pro Controller supports FullControllerState over Bluetooth @@ -735,6 +766,20 @@ static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) return input_mode; } +static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode; + + /* Determine the desired input mode */ + if (!ctx->m_nInitialInputMode || + ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } else { + input_mode = ctx->m_nInitialInputMode; + } + return input_mode; +} + static SDL_bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, SDL_bool enabled) { Uint8 imu_data = enabled ? 1 : 0; @@ -909,8 +954,7 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; } - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, - SDL_MIN_SINT16, SDL_MAX_SINT16); + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) @@ -927,42 +971,24 @@ static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nSt ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; } - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, - SDL_MIN_SINT16, SDL_MAX_SINT16); -} - -static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; - ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE); + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) { - if (!ctx->m_bUseButtonLabels) { - /* Use button positions */ - if (ctx->m_bIsGameCube) { - switch (button) { - case SDL_GAMEPAD_BUTTON_B: - return SDL_GAMEPAD_BUTTON_X; - case SDL_GAMEPAD_BUTTON_X: - return SDL_GAMEPAD_BUTTON_B; - default: - break; - } - } else { - switch (button) { - case SDL_GAMEPAD_BUTTON_A: - return SDL_GAMEPAD_BUTTON_B; - case SDL_GAMEPAD_BUTTON_B: - return SDL_GAMEPAD_BUTTON_A; - case SDL_GAMEPAD_BUTTON_X: - return SDL_GAMEPAD_BUTTON_Y; - case SDL_GAMEPAD_BUTTON_Y: - return SDL_GAMEPAD_BUTTON_X; - default: - break; - } + if (ctx->m_bUseButtonLabels) { + /* Use button labels instead of positions, e.g. Nintendo Online Classic controllers */ + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_GAMEPAD_BUTTON_EAST; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_SOUTH; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_NORTH; + case SDL_GAMEPAD_BUTTON_NORTH: + return SDL_GAMEPAD_BUTTON_WEST; + default: + break; } } return button; @@ -1049,9 +1075,12 @@ static SDL_bool HasHomeLED(SDL_DriverSwitch_Context *ctx) return SDL_TRUE; } -static SDL_bool AlwaysUsesLabels(int vendor_id, int product_id, ESwitchDeviceInfoControllerType eControllerType) +static SDL_bool AlwaysUsesLabels(Uint16 vendor_id, Uint16 product_id, ESwitchDeviceInfoControllerType eControllerType) { - /* These controllers don't have a diamond button configuration, so always use labels */ + /* Some controllers don't have a diamond button configuration, so should always use labels */ + if (SDL_IsJoystickGameCube(vendor_id, product_id)) { + return SDL_TRUE; + } switch (eControllerType) { case k_eSwitchDeviceInfoControllerType_HVCLeft: case k_eSwitchDeviceInfoControllerType_HVCRight: @@ -1065,23 +1094,6 @@ static SDL_bool AlwaysUsesLabels(int vendor_id, int product_id, ESwitchDeviceInf } } -static SDL_bool IsGameCubeFormFactor(int vendor_id, int product_id) -{ - static Uint32 gamecube_formfactor[] = { - MAKE_VIDPID(0x0e6f, 0x0185), /* PDP Wired Fight Pad Pro for Nintendo Switch */ - MAKE_VIDPID(0x20d6, 0xa711), /* Core (Plus) Wired Controller */ - }; - Uint32 id = MAKE_VIDPID(vendor_id, product_id); - int i; - - for (i = 0; i < SDL_arraysize(gamecube_formfactor); ++i) { - if (id == gamecube_formfactor[i]) { - return SDL_TRUE; - } - } - return SDL_FALSE; -} - static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata) { SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata); @@ -1192,79 +1204,86 @@ static SDL_bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, return SDL_FALSE; } - return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) ? SDL_TRUE : SDL_FALSE; + return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); } static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) { SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; - char serial[18]; - switch (ctx->m_eControllerType) { - case k_eSwitchDeviceInfoControllerType_JoyConLeft: - HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT); - device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; - break; - case k_eSwitchDeviceInfoControllerType_JoyConRight: - HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT); - device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; - break; - case k_eSwitchDeviceInfoControllerType_ProController: - case k_eSwitchDeviceInfoControllerType_LicProController: - HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO); - device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; - break; - case k_eSwitchDeviceInfoControllerType_HVCLeft: - HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)"); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_HVCRight: - HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)"); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_NESLeft: - HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)"); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_NESRight: - HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)"); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_SNES: - HIDAPI_SetDeviceName(device, "Nintendo SNES Controller"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_N64: - HIDAPI_SetDeviceName(device, "Nintendo N64 Controller"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: - HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller"); - HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER); - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; - case k_eSwitchDeviceInfoControllerType_Unknown: - /* We couldn't read the device info for this controller, might not be fully compliant */ - return; - default: - device->type = SDL_GAMEPAD_TYPE_STANDARD; - break; + if (ctx->m_bInputOnly) { + if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) { + device->type = SDL_GAMEPAD_TYPE_STANDARD; + } + } else { + char serial[18]; + + switch (ctx->m_eControllerType) { + case k_eSwitchDeviceInfoControllerType_JoyConLeft: + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; + break; + case k_eSwitchDeviceInfoControllerType_JoyConRight: + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; + break; + case k_eSwitchDeviceInfoControllerType_ProController: + case k_eSwitchDeviceInfoControllerType_LicProController: + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + break; + case k_eSwitchDeviceInfoControllerType_HVCLeft: + HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_HVCRight: + HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_NESLeft: + HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_NESRight: + HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_SNES: + HIDAPI_SetDeviceName(device, "Nintendo SNES Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_N64: + HIDAPI_SetDeviceName(device, "Nintendo N64 Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: + HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_Unknown: + /* We couldn't read the device info for this controller, might not be fully compliant */ + return; + default: + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + } + device->guid.data[15] = ctx->m_eControllerType; + + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + ctx->m_rgucMACAddress[0], + ctx->m_rgucMACAddress[1], + ctx->m_rgucMACAddress[2], + ctx->m_rgucMACAddress[3], + ctx->m_rgucMACAddress[4], + ctx->m_rgucMACAddress[5]); + HIDAPI_SetDeviceSerial(device, serial); } - device->guid.data[15] = ctx->m_eControllerType; - - (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", - ctx->m_rgucMACAddress[0], - ctx->m_rgucMACAddress[1], - ctx->m_rgucMACAddress[2], - ctx->m_rgucMACAddress[3], - ctx->m_rgucMACAddress[4], - ctx->m_rgucMACAddress[5]); - HIDAPI_SetDeviceSerial(device, serial); } static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) @@ -1272,8 +1291,7 @@ static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverSwitch_Context *ctx; ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -1282,11 +1300,6 @@ static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device); ctx->m_bSyncWrite = SDL_TRUE; - if (IsGameCubeFormFactor(device->vendor_id, device->product_id)) { - /* This is a controller shaped like a GameCube controller, with a large central A button */ - ctx->m_bIsGameCube = SDL_TRUE; - } - /* Find out whether or not we can send output reports */ ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id); if (!ctx->m_bInputOnly) { @@ -1295,8 +1308,8 @@ static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); BReadDeviceInfo(ctx); - UpdateDeviceIdentity(device); } + UpdateDeviceIdentity(device); /* Prefer the USB device over the Bluetooth device */ if (device->is_bluetooth) { @@ -1338,6 +1351,9 @@ static SDL_bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_ ctx->m_bSyncWrite = SDL_TRUE; if (!ctx->m_bInputOnly) { + ctx->m_nInitialInputMode = GetInitialInputMode(ctx); + ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; + /* Initialize rumble data */ SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); @@ -1416,9 +1432,6 @@ static SDL_bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_ if (AlwaysUsesLabels(device->vendor_id, device->product_id, ctx->m_eControllerType)) { ctx->m_bUseButtonLabels = SDL_TRUE; - } else { - SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); } /* Initialize player index (needed for setting LEDs) */ @@ -1430,8 +1443,9 @@ static SDL_bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_ SDL_PlayerLEDHintChanged, ctx); /* Initialize the joystick capabilities */ - joystick->nbuttons = 20; + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = device->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; /* Set up for input */ @@ -1466,7 +1480,7 @@ static int HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context * SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); } - ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble) ? SDL_TRUE : SDL_FALSE; + ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble); if (!WriteRumble(ctx)) { return SDL_SetError("Couldn't send rumble packet"); @@ -1626,7 +1640,7 @@ static int HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *devi Uint8 input_mode; if (enabled) { - input_mode = k_eSwitchInputReportIDs_FullControllerState; + input_mode = GetSensorInputMode(ctx); } else { input_mode = GetDefaultInputMode(ctx); } @@ -1647,10 +1661,10 @@ static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwi if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) { Uint8 data = packet->rgucButtons[0]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); @@ -1668,51 +1682,42 @@ static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwi SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); } if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (packet->ucStickHat) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) { @@ -1745,10 +1750,10 @@ static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { Uint8 data = packet->rgucButtons[0]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); @@ -1766,51 +1771,42 @@ static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); } if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (packet->ucStickHat) { case 0: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 1: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 2: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 3: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 4: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 5: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 6: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 7: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } axis = ApplySimpleStickCalibration(ctx, 0, 0, packet->sJoystickLeft[0]); @@ -1879,17 +1875,29 @@ static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joyst Uint8 data = packet->controllerState.rgucButtons[1]; SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); } if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { Uint8 data = packet->controllerState.rgucButtons[2]; - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data & 0x02) { + hat |= SDL_HAT_UP; + } + if (data & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); @@ -1917,14 +1925,14 @@ static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { Uint8 data = packet->controllerState.rgucButtons[2]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); } axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); @@ -1942,12 +1950,12 @@ static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joyst if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { Uint8 data = packet->controllerState.rgucButtons[0]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); @@ -1975,14 +1983,14 @@ static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { Uint8 data = packet->controllerState.rgucButtons[0]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { @@ -2022,10 +2030,10 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { Uint8 data = packet->controllerState.rgucButtons[0]; - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_A), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_X), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); @@ -2039,15 +2047,27 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); } if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { Uint8 data = packet->controllerState.rgucButtons[2]; - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data & 0x02) { + hat |= SDL_HAT_UP; + } + if (data & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); @@ -2070,24 +2090,26 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); } - /* High nibble of battery/connection byte is battery level, low nibble is connection status - * LSB of connection nibble is USB/Switch connection status - */ - if (packet->controllerState.ucBatteryAndConnection & 0x1) { - SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED); - } else { - /* LSB of the battery nibble is used to report charging. - * The battery level is reported from 0(empty)-8(full) + if (ctx->device->is_bluetooth) { + /* High nibble of battery/connection byte is battery level, low nibble is connection status + * LSB of connection nibble is USB/Switch connection status */ - int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; - if (level == 0) { - SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY); - } else if (level <= 2) { - SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW); - } else if (level <= 6) { - SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM); + if (packet->controllerState.ucBatteryAndConnection & 0x1) { + SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED); } else { - SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL); + /* LSB of the battery nibble is used to report charging. + * The battery level is reported from 0(empty)-8(full) + */ + int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; + if (level == 0) { + SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY); + } else if (level <= 2) { + SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW); + } else if (level <= 6) { + SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM); + } else { + SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL); + } } } @@ -2203,13 +2225,15 @@ static SDL_bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) ++packet_count; ctx->m_ulLastInput = now; - if (joystick == NULL) { + if (!joystick) { continue; } if (ctx->m_bInputOnly) { HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]); } else { + ctx->m_nCurrentInputMode = ctx->m_rgucReadBuffer[0]; + switch (ctx->m_rgucReadBuffer[0]) { case k_eSwitchInputReportIDs_SimpleControllerState: HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]); @@ -2276,12 +2300,12 @@ static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joy if (!ctx->m_bInputOnly) { /* Restore simple input mode for other applications */ - SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + if (!ctx->m_nInitialInputMode || + ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { + SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + } } - SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); - if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, diff --git a/src/joystick/hidapi/SDL_hidapi_wii.c b/src/joystick/hidapi/SDL_hidapi_wii.c index 397d5550..065c3b97 100644 --- a/src/joystick/hidapi/SDL_hidapi_wii.c +++ b/src/joystick/hidapi/SDL_hidapi_wii.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -135,7 +135,6 @@ typedef struct Uint64 timestamp; EWiiCommunicationState m_eCommState; EWiiExtensionControllerType m_eExtensionControllerType; - SDL_bool m_bUseButtonLabels; SDL_bool m_bPlayerLights; int m_nPlayerIndex; SDL_bool m_bRumbleActive; @@ -231,7 +230,7 @@ static SDL_bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs exp int nRead = 0; while ((nRead = ReadInput(ctx)) != -1) { if (nRead > 0) { - if (ctx->m_rgucReadBuffer[0] == expectedID && (isMine == NULL || isMine(ctx->m_rgucReadBuffer))) { + if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) { return SDL_TRUE; } } else { @@ -273,7 +272,7 @@ static SDL_bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const return SDL_FALSE; } if (ctx->m_rgucReadBuffer[4]) { - SDL_SetError("Write memory failed: %d", ctx->m_rgucReadBuffer[4]); + SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]); return SDL_FALSE; } } @@ -336,7 +335,7 @@ static SDL_bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint1 } if (error) { - SDL_SetError("Failed to read extension type: %d", error); + SDL_SetError("Failed to read extension type: %u", error); } else { SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1); } @@ -505,8 +504,8 @@ static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte) static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte) { - SDL_bool charging = extensionBatteryByte & 0x08 ? SDL_FALSE : SDL_TRUE; - SDL_bool pluggedIn = extensionBatteryByte & 0x04 ? SDL_FALSE : SDL_TRUE; + SDL_bool charging = !(extensionBatteryByte & 0x08); + SDL_bool pluggedIn = !(extensionBatteryByte & 0x04); Uint8 batteryLevel = extensionBatteryByte >> 4; /* Not sure if all Wii U Pro controllers act like this, but on mine @@ -552,7 +551,7 @@ static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx) static SDL_bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type) { Uint8 data[3]; - Uint8 tt = ctx->m_bRumbleActive; + Uint8 tt = (Uint8)ctx->m_bRumbleActive; /* Continuous reporting off, tt & 4 == 0 */ if (ENABLE_CONTINUOUS_REPORTING) { @@ -610,19 +609,13 @@ static void InitializeExtension(SDL_DriverWii_Context *ctx) ResetButtonPacketType(ctx); } -static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) -{ - SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata; - ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE); -} - static void UpdateSlotLED(SDL_DriverWii_Context *ctx) { Uint8 leds; Uint8 data[2]; /* The lowest bit needs to have the rumble status */ - leds = ctx->m_bRumbleActive; + leds = (Uint8)ctx->m_bRumbleActive; if (ctx->m_bPlayerLights) { /* Use the same LED codes as Smash 8-player for 5-7 */ @@ -725,8 +718,7 @@ static SDL_bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverWii_Context *ctx; ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -786,9 +778,6 @@ static SDL_bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy } } - SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); - /* Initialize player index (needed for setting LEDs) */ ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick); ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, SDL_TRUE); @@ -814,13 +803,13 @@ static SDL_bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy static int HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; - SDL_bool active = (low_frequency_rumble || high_frequency_rumble) ? SDL_TRUE : SDL_FALSE; + SDL_bool active = (low_frequency_rumble || high_frequency_rumble); if (active != ctx->m_bRumbleActive) { Uint8 data[2]; data[0] = k_eWiiOutputReportIDs_Rumble; - data[1] = active; + data[1] = (Uint8)active; WriteOutput(ctx, data, sizeof(data), SDL_FALSE); ctx->m_bRumbleActive = active; @@ -940,43 +929,10 @@ static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = { SDL_GAMEPAD_BUTTON_DPAD_UP, SDL_GAMEPAD_BUTTON_DPAD_LEFT, 0xFF /* ZR */, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_Y, - SDL_GAMEPAD_BUTTON_B, - 0xFF /*ZL*/, - }, - { - SDL_GAMEPAD_BUTTON_RIGHT_STICK, - SDL_GAMEPAD_BUTTON_LEFT_STICK, - 0xFF /* Charging */, - 0xFF /* Plugged In */, - 0xFF /* Unused */, - 0xFF /* Unused */, - 0xFF /* Unused */, - 0xFF /* Unused */, - } -}; - -static const Uint8 GAMEPAD_BUTTON_DEFS_POSITIONAL[3][8] = { - { - 0xFF /* Unused */, - SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, - SDL_GAMEPAD_BUTTON_START, - SDL_GAMEPAD_BUTTON_GUIDE, - SDL_GAMEPAD_BUTTON_BACK, - SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, - SDL_GAMEPAD_BUTTON_DPAD_DOWN, - SDL_GAMEPAD_BUTTON_DPAD_RIGHT, - }, - { - SDL_GAMEPAD_BUTTON_DPAD_UP, - SDL_GAMEPAD_BUTTON_DPAD_LEFT, - 0xFF /* ZR */, - SDL_GAMEPAD_BUTTON_Y, - SDL_GAMEPAD_BUTTON_B, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_A, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, 0xFF /*ZL*/, }, { @@ -1006,43 +962,10 @@ static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = { 0xFF /* Motion Plus data */, 0xFF /* Motion Plus data */, 0xFF /* ZR */, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_Y, - SDL_GAMEPAD_BUTTON_B, - 0xFF /*ZL*/, - }, - { - SDL_GAMEPAD_BUTTON_RIGHT_STICK, - SDL_GAMEPAD_BUTTON_LEFT_STICK, - 0xFF /* Charging */, - 0xFF /* Plugged In */, - 0xFF /* Unused */, - 0xFF /* Unused */, - 0xFF /* Unused */, - 0xFF /* Unused */, - } -}; - -static const Uint8 MP_GAMEPAD_BUTTON_DEFS_POSITIONAL[3][8] = { - { - 0xFF /* Unused */, - SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, - SDL_GAMEPAD_BUTTON_START, - SDL_GAMEPAD_BUTTON_GUIDE, - SDL_GAMEPAD_BUTTON_BACK, - SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, - SDL_GAMEPAD_BUTTON_DPAD_DOWN, - SDL_GAMEPAD_BUTTON_DPAD_RIGHT, - }, - { - 0xFF /* Motion Plus data */, - 0xFF /* Motion Plus data */, - 0xFF /* ZR */, - SDL_GAMEPAD_BUTTON_Y, - SDL_GAMEPAD_BUTTON_B, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_A, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, 0xFF /*ZL*/, }, { @@ -1083,7 +1006,7 @@ static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = { static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) { static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY }; - const Uint8(*buttons)[8] = ctx->m_bUseButtonLabels ? GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS_POSITIONAL; + const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS; Uint8 zl, zr; int i; @@ -1112,7 +1035,7 @@ static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *jo static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) { - const Uint8(*buttons)[8] = ctx->m_bUseButtonLabels ? ((ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS) : ((ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS_POSITIONAL : GAMEPAD_BUTTON_DEFS_POSITIONAL); + const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS; Uint8 lx, ly, rx, ry, zl, zr; if (data->ucNExtensionBytes < 6) { @@ -1194,10 +1117,10 @@ static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx 0xFF /* Unused */, }, { - SDL_GAMEPAD_BUTTON_Y, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_B, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, SDL_GAMEPAD_BUTTON_BACK, 0xFF /* Unused */, 0xFF /* Unused */, @@ -1407,7 +1330,7 @@ static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size) static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) { SDL_bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None; - SDL_bool hasExtension = ctx->m_rgucReadBuffer[3] & 2 ? SDL_TRUE : SDL_FALSE; + SDL_bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? SDL_TRUE : SDL_FALSE; WiiButtonData data; SDL_zero(data); GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); @@ -1618,7 +1541,7 @@ static SDL_bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device) Uint8 data[2]; data[0] = k_eWiiOutputReportIDs_StatusRequest; - data[1] = ctx->m_bRumbleActive; + data[1] = (Uint8)ctx->m_bRumbleActive; WriteOutput(ctx, data, sizeof(data), SDL_FALSE); ctx->m_ulLastStatus = now; @@ -1637,9 +1560,6 @@ static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti { SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; - SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, - SDL_GameControllerButtonReportingHintChanged, ctx); - SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, SDL_PlayerLEDHintChanged, ctx); diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c index c2dddccf..d0097fb7 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -71,7 +71,7 @@ static SDL_bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device /* This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol */ return SDL_FALSE; } - if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == 0x0291 || product_id == 0x0719)) || + if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) || (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { /* This is the wireless dongle, which talks a different protocol */ return SDL_FALSE; @@ -94,9 +94,9 @@ static SDL_bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device if (SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { return SDL_FALSE; } - return (type == SDL_GAMEPAD_TYPE_XBOX360 || type == SDL_GAMEPAD_TYPE_XBOXONE) ? SDL_TRUE : SDL_FALSE; + return (type == SDL_GAMEPAD_TYPE_XBOX360 || type == SDL_GAMEPAD_TYPE_XBOXONE); #else - return (type == SDL_GAMEPAD_TYPE_XBOX360) ? SDL_TRUE : SDL_FALSE; + return (type == SDL_GAMEPAD_TYPE_XBOX360); #endif } @@ -139,8 +139,7 @@ static SDL_bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverXbox360_Context *ctx; ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -188,8 +187,9 @@ static SDL_bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL SDL_PlayerLEDHintChanged, ctx); /* Initialize the joystick capabilities */ - joystick->nbuttons = 15; + joystick->nbuttons = 11; joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; return SDL_TRUE; @@ -270,10 +270,22 @@ static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_D Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[2] != data[2]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data[2] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[2] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -284,10 +296,10 @@ static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_D SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } axis = ((int)data[4] * 257) - 32768; @@ -329,7 +341,7 @@ static SDL_bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_XBOX_PROTOCOL HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size); #endif - if (joystick == NULL) { + if (!joystick) { continue; } diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c index 30db93a2..db16c376 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,7 +65,7 @@ static SDL_bool HIDAPI_DriverXbox360W_IsSupportedDevice(SDL_HIDAPI_Device *devic { const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */ - if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == 0x0291 || product_id == 0x02a9 || product_id == 0x0719) && interface_protocol == 0) || + if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) && interface_protocol == 0) || (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { return SDL_TRUE; } @@ -131,8 +131,7 @@ static SDL_bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device) HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller"); ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -158,7 +157,7 @@ static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device { SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; - if (ctx == NULL) { + if (!ctx) { return; } @@ -237,10 +236,22 @@ static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_ Uint64 timestamp = SDL_GetTicksNS(); if (ctx->last_state[2] != data[2]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data[2] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[2] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -251,10 +262,10 @@ static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } axis = ((int)data[4] * 257) - 32768; diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index 1a49990e..4631f1fa 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -49,6 +49,11 @@ #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 /* Uint8 30 scaled to Sint16 full range */ +enum +{ + SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON = 11 +}; + /* Power on */ static const Uint8 xbox_init_power_on[] = { 0x05, 0x20, 0x00, 0x01, 0x00 @@ -351,7 +356,7 @@ static SDL_bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device return SDL_FALSE; } #endif - return (type == SDL_GAMEPAD_TYPE_XBOXONE) ? SDL_TRUE : SDL_FALSE; + return (type == SDL_GAMEPAD_TYPE_XBOXONE); } static SDL_bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) @@ -359,8 +364,7 @@ static SDL_bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) SDL_DriverXboxOne_Context *ctx; ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - SDL_OutOfMemory(); + if (!ctx) { return SDL_FALSE; } ctx->device = device; @@ -418,7 +422,7 @@ static SDL_bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL SDL_zeroa(ctx->last_state); /* Initialize the joystick capabilities */ - joystick->nbuttons = 15; + joystick->nbuttons = 11; if (ctx->has_share_button) { joystick->nbuttons += 1; } @@ -426,6 +430,7 @@ static SDL_bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL joystick->nbuttons += 4; } joystick->naxes = SDL_GAMEPAD_AXIS_MAX; + joystick->nhats = 1; if (!device->is_bluetooth) { joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; @@ -631,7 +636,7 @@ static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystic } if (ctx->last_paddle_state != data[paddle_index]) { - Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_MISC1 + ctx->has_share_button); /* Next available button */ + Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); /* Next available button */ SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button1_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button2_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button3_bit) ? SDL_PRESSED : SDL_RELEASED); @@ -659,17 +664,29 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D if (ctx->last_state[0] != data[0]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[0] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[0] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->last_state[1] != data[1]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + Uint8 hat = 0; + + if (data[2] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[2] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) { /* The Razer Atrox has the right and left shoulder bits reversed */ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED); @@ -691,19 +708,19 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D */ if (size < 44) { if (ctx->last_state[14] != data[14]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } } else if (size == 44) { if (ctx->last_state[18] != data[18]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[18] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, (data[18] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } } else if (size == 46) { if (ctx->last_state[28] != data[28]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[28] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, (data[28] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } } else if (size == 60) { if (ctx->last_state[42] != data[42]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[42] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, (data[42] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } } } @@ -786,7 +803,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } if (ctx->last_paddle_state != data[paddle_index]) { - Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_MISC1 + ctx->has_share_button); /* Next available button */ + Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); /* Next available button */ SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button1_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button2_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button3_bit) ? SDL_PRESSED : SDL_RELEASED); @@ -848,10 +865,10 @@ static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_Dr static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { if (ctx->last_state[14] != data[14]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[14] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[14] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[14] & 0x20) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED); @@ -875,10 +892,10 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_ static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) { if (ctx->last_state[14] != data[14]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[14] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } @@ -894,7 +911,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Jo if (ctx->has_share_button) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[15] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } else { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) || (data[16] & 0x01)) ? SDL_PRESSED : SDL_RELEASED); } @@ -953,7 +970,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Jo } if (ctx->last_paddle_state != data[paddle_index]) { - Uint8 nButton = SDL_GAMEPAD_BUTTON_MISC1; /* Next available button */ + Uint8 nButton = SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON; /* Next available button */ SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button1_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button2_bit) ? SDL_PRESSED : SDL_RELEASED); SDL_SendJoystickButton(timestamp, joystick, nButton++, (data[paddle_index] & button3_bit) ? SDL_PRESSED : SDL_RELEASED); @@ -981,47 +998,38 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joysti } if (ctx->last_state[13] != data[13]) { - SDL_bool dpad_up = SDL_FALSE; - SDL_bool dpad_down = SDL_FALSE; - SDL_bool dpad_left = SDL_FALSE; - SDL_bool dpad_right = SDL_FALSE; + Uint8 hat; switch (data[13]) { case 1: - dpad_up = SDL_TRUE; + hat = SDL_HAT_UP; break; case 2: - dpad_up = SDL_TRUE; - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHTUP; break; case 3: - dpad_right = SDL_TRUE; + hat = SDL_HAT_RIGHT; break; case 4: - dpad_right = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_RIGHTDOWN; break; case 5: - dpad_down = SDL_TRUE; + hat = SDL_HAT_DOWN; break; case 6: - dpad_left = SDL_TRUE; - dpad_down = SDL_TRUE; + hat = SDL_HAT_LEFTDOWN; break; case 7: - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFT; break; case 8: - dpad_up = SDL_TRUE; - dpad_left = SDL_TRUE; + hat = SDL_HAT_LEFTUP; break; default: + hat = SDL_HAT_CENTERED; break; } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, dpad_down); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, dpad_up); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, dpad_right); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, dpad_left); + SDL_SendJoystickHat(timestamp, joystick, 0, hat); } axis = ((int)SDL_SwapLE16(*(Sint16 *)(&data[9])) * 64) - 32768; @@ -1146,6 +1154,10 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context * return SDL_TRUE; } +/* GIP protocol handling adapted under the Zlib license with permission from @medusalix: + * https://github.com/medusalix/xone/blob/master/bus/protocol.h + * https://github.com/medusalix/xone/blob/master/bus/protocol.c + */ #define GIP_HEADER_MIN_LENGTH 3 /* Internal commands */ @@ -1401,7 +1413,7 @@ static SDL_bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXbox /* Ignore this packet */ break; case GIP_CMD_VIRTUAL_KEY: - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size); @@ -1436,13 +1448,13 @@ static SDL_bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXbox #endif break; } - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size); break; case GIP_CMD_UNMAPPED_STATE: - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size); @@ -1568,7 +1580,7 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) if (device->is_bluetooth) { switch (data[0]) { case 0x01: - if (joystick == NULL) { + if (!joystick) { break; } if (size >= 16) { @@ -1580,13 +1592,13 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) } break; case 0x02: - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(joystick, ctx, data, size); break; case 0x04: - if (joystick == NULL) { + if (!joystick) { break; } HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(joystick, ctx, data, size); diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 06e6d260..bb1f9376 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -66,6 +66,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_STEAM &SDL_HIDAPI_DriverSteam, #endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + &SDL_HIDAPI_DriverSteamDeck, +#endif #ifdef SDL_JOYSTICK_HIDAPI_SWITCH &SDL_HIDAPI_DriverNintendoClassic, &SDL_HIDAPI_DriverJoyCons, @@ -99,7 +102,7 @@ static char *HIDAPI_ConvertString(const wchar_t *wide_string) if (wide_string) { string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); - if (string == NULL) { + if (!string) { switch (sizeof(wchar_t)) { case 2: string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); @@ -149,14 +152,20 @@ SDL_bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product) case USB_VENDOR_HORI: return SDL_TRUE; case USB_VENDOR_LOGITECH: - /* Most Logitech devices are fine with this, but the F310 will lock up */ + /* Most Logitech devices are fine with this, but there are a few exceptions */ if (product == USB_PRODUCT_LOGITECH_F310) { + /* The Logitech F310 gamepad will lock up */ + return SDL_FALSE; + } + if (product == 0xc33f) { + /* The Logitech G815 keyboard will reset the LEDs */ return SDL_FALSE; } return SDL_TRUE; case USB_VENDOR_MADCATZ: return SDL_TRUE; case USB_VENDOR_NACON: + case USB_VENDOR_NACON_ALT: return SDL_TRUE; case USB_VENDOR_PDP: return SDL_TRUE; @@ -260,6 +269,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U interface_protocol == XBONE_IFACE_PROTOCOL) { static const int SUPPORTED_VENDORS[] = { + 0x03f0, /* HP */ 0x044f, /* Thrustmaster */ 0x045e, /* Microsoft */ 0x0738, /* Mad Catz */ @@ -607,7 +617,7 @@ static int HIDAPI_JoystickInit(void) static SDL_bool HIDAPI_AddJoystickInstanceToDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) { SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1) * sizeof(*device->joysticks)); - if (joysticks == NULL) { + if (!joysticks) { return SDL_FALSE; } @@ -656,7 +666,7 @@ void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name) void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id) { /* Don't set the device product ID directly, or we'll constantly re-enumerate this device */ - device->guid = SDL_CreateJoystickGUID(device->guid.data[0], vendor_id, product_id, device->version, device->name, 'h', 0); + device->guid = SDL_CreateJoystickGUID(device->guid.data[0], vendor_id, product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); } static void HIDAPI_UpdateJoystickSerial(SDL_HIDAPI_Device *device) @@ -729,7 +739,7 @@ SDL_bool HIDAPI_HasConnectedUSBDevice(const char *serial) SDL_AssertJoysticksLocked(); - if (serial == NULL) { + if (!serial) { return SDL_FALSE; } @@ -755,7 +765,7 @@ void HIDAPI_DisconnectBluetoothDevice(const char *serial) SDL_AssertJoysticksLocked(); - if (serial == NULL) { + if (!serial) { return; } @@ -790,7 +800,7 @@ SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJo } } - joystickID = SDL_GetNextJoystickInstanceID(); + joystickID = SDL_GetNextObjectID(); HIDAPI_AddJoystickInstanceToDevice(device, joystickID); for (i = 0; i < device->num_children; ++i) { @@ -865,7 +875,7 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf } device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); - if (device == NULL) { + if (!device) { return NULL; } device->magic = &SDL_HIDAPI_device_magic; @@ -889,18 +899,11 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf /* Need the device name before getting the driver to know whether to ignore this device */ { - char *manufacturer_string = HIDAPI_ConvertString(info->manufacturer_string); - char *product_string = HIDAPI_ConvertString(info->product_string); char *serial_number = HIDAPI_ConvertString(info->serial_number); - device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); - - if (manufacturer_string) { - SDL_free(manufacturer_string); - } - if (product_string) { - SDL_free(product_string); - } + device->manufacturer_string = HIDAPI_ConvertString(info->manufacturer_string); + device->product_string = HIDAPI_ConvertString(info->product_string); + device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, device->manufacturer_string, device->product_string); if (serial_number && *serial_number) { device->serial = serial_number; @@ -909,6 +912,8 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf } if (!device->name) { + SDL_free(device->manufacturer_string); + SDL_free(device->product_string); SDL_free(device->serial); SDL_free(device->path); SDL_free(device); @@ -921,7 +926,7 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf } else { bus = SDL_HARDWARE_BUS_USB; } - device->guid = SDL_CreateJoystickGUID(bus, device->vendor_id, device->product_id, device->version, device->name, 'h', 0); + device->guid = SDL_CreateJoystickGUID(bus, device->vendor_id, device->product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; device->type = SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol); @@ -949,7 +954,9 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf } #ifdef DEBUG_HIDAPI - SDL_Log("Added HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)\n", device->name, device->vendor_id, device->product_id, device->version, device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); + SDL_Log("Added HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)\n", device->name, device->vendor_id, device->product_id, device->version, + device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, + device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); #endif return device; @@ -963,7 +970,9 @@ static void HIDAPI_DelDevice(SDL_HIDAPI_Device *device) SDL_AssertJoysticksLocked(); #ifdef DEBUG_HIDAPI - SDL_Log("Removing HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)\n", device->name, device->vendor_id, device->product_id, device->version, device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); + SDL_Log("Removing HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)\n", device->name, device->vendor_id, device->product_id, device->version, + device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, + device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); #endif for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { @@ -987,6 +996,8 @@ static void HIDAPI_DelDevice(SDL_HIDAPI_Device *device) device->magic = NULL; SDL_DestroyMutex(device->dev_lock); + SDL_free(device->manufacturer_string); + SDL_free(device->product_string); SDL_free(device->serial); SDL_free(device->name); SDL_free(device->path); @@ -1037,7 +1048,7 @@ static SDL_bool HIDAPI_CreateCombinedJoyCons(void) if (joycons[0] && joycons[1]) { SDL_hid_device_info info; SDL_HIDAPI_Device **children = (SDL_HIDAPI_Device **)SDL_malloc(2 * sizeof(SDL_HIDAPI_Device *)); - if (children == NULL) { + if (!children) { return SDL_FALSE; } children[0] = joycons[0]; @@ -1168,7 +1179,7 @@ static SDL_bool HIDAPI_IsEquivalentToDevice(Uint16 vendor_id, Uint16 product_id, } /* If we're looking for an XInput controller, match it against any other Xbox controller */ - if (product_id == USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER) { + if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER) { if (device->type == SDL_GAMEPAD_TYPE_XBOX360 || device->type == SDL_GAMEPAD_TYPE_XBOXONE) { return SDL_TRUE; } @@ -1367,6 +1378,11 @@ static const char *HIDAPI_JoystickGetDevicePath(int device_index) return path; } +static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int HIDAPI_JoystickGetDevicePlayerIndex(int device_index) { SDL_HIDAPI_Device *device; @@ -1422,14 +1438,14 @@ static int HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_AssertJoysticksLocked(); - if (device == NULL || !device->driver) { + if (!device || !device->driver) { /* This should never happen - validated before being called */ return SDL_SetError("Couldn't find HIDAPI device at index %d\n", device_index); } hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); - if (hwdata == NULL) { - return SDL_OutOfMemory(); + if (!hwdata) { + return -1; } hwdata->device = device; @@ -1440,6 +1456,12 @@ static int HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index) device->updating = SDL_FALSE; SDL_UnlockMutex(device->dev_lock); + /* UpdateDevice() may have called HIDAPI_JoystickDisconnected() if the device went away */ + if (device->num_joysticks == 0) { + SDL_free(hwdata); + return SDL_SetError("HIDAPI device disconnected while opening"); + } + if (!device->driver->OpenJoystick(device, joystick)) { /* The open failed, mark this device as disconnected and update devices */ HIDAPI_JoystickDisconnected(device, joystickID); @@ -1639,6 +1661,7 @@ SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = { HIDAPI_JoystickDetect, HIDAPI_JoystickGetDeviceName, HIDAPI_JoystickGetDevicePath, + HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot, HIDAPI_JoystickGetDevicePlayerIndex, HIDAPI_JoystickSetDevicePlayerIndex, HIDAPI_JoystickGetDeviceGUID, diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 04961b2e..9cb17021 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,6 +33,7 @@ #define SDL_JOYSTICK_HIDAPI_PS5 #define SDL_JOYSTICK_HIDAPI_STADIA #define SDL_JOYSTICK_HIDAPI_STEAM /* Simple support for BLE Steam Controller, hint is disabled by default */ +#define SDL_JOYSTICK_HIDAPI_STEAMDECK #define SDL_JOYSTICK_HIDAPI_SWITCH #define SDL_JOYSTICK_HIDAPI_WII #define SDL_JOYSTICK_HIDAPI_XBOX360 @@ -52,6 +53,8 @@ typedef struct SDL_HIDAPI_Device { const void *magic; char *name; + char *manufacturer_string; + char *product_string; char *path; Uint16 vendor_id; Uint16 product_id; @@ -126,6 +129,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield; 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_DriverWii; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; diff --git a/src/joystick/hidapi/steam/controller_constants.h b/src/joystick/hidapi/steam/controller_constants.h index d0d56885..ab4ac2ee 100644 --- a/src/joystick/hidapi/steam/controller_constants.h +++ b/src/joystick/hidapi/steam/controller_constants.h @@ -47,6 +47,8 @@ enum ValveControllerPID D0G_BLE_PID = 0x1105, D0G_BLE2_PID = 0x1106, D0GGLE_PID = 0x1142, + + JUPITER_PID = 0x1205, }; // This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order) @@ -68,10 +70,11 @@ enum FeatureReportMessageIDs ID_SET_CONTROLLER_MODE = 0x8D, ID_LOAD_DEFAULT_SETTINGS = 0x8E, ID_TRIGGER_HAPTIC_PULSE = 0x8F, + ID_TURN_OFF_CONTROLLER = 0x9F, ID_GET_DEVICE_INFO = 0xA1, - + ID_CALIBRATE_TRACKPADS = 0xA7, ID_RESERVED_0 = 0xA8, ID_SET_SERIAL_NUMBER = 0xA9, @@ -99,6 +102,12 @@ enum FeatureReportMessageIDs ID_CHECK_GYRO_FW_LOAD = 0xC2, ID_CALIBRATE_ANALOG = 0xC3, ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4, + + ID_RESET_IMU = 0xCE, + + // Deck only + ID_TRIGGER_HAPTIC_CMD = 0xEA, + ID_TRIGGER_RUMBLE_CMD = 0xEB, }; @@ -215,6 +224,32 @@ typedef enum IO_RAW_JOYSTICK_X, IO_RAW_JOYSTICK_Y, IO_GYRO_TILT_VEC, + IO_PRESSURE_LEFT_PAD, + IO_PRESSURE_RIGHT_PAD, + IO_PRESSURE_LEFT_BUMPER, + IO_PRESSURE_RIGHT_BUMPER, + IO_PRESSURE_LEFT_GRIP, + IO_PRESSURE_RIGHT_GRIP, + IO_ANALOG_LEFT_TRIGGER_THRESHOLD, + IO_ANALOG_RIGHT_TRIGGER_THRESHOLD, + IO_PRESSURE_RIGHT_PAD_THRESHOLD, + IO_PRESSURE_LEFT_PAD_THRESHOLD, + IO_PRESSURE_RIGHT_BUMPER_THRESHOLD, + IO_PRESSURE_LEFT_BUMPER_THRESHOLD, + IO_PRESSURE_RIGHT_GRIP_THRESHOLD, + IO_PRESSURE_LEFT_GRIP_THRESHOLD, + IO_PRESSURE_RIGHT_PAD_RAW, + IO_PRESSURE_LEFT_PAD_RAW, + IO_PRESSURE_RIGHT_BUMPER_RAW, + IO_PRESSURE_LEFT_BUMPER_RAW, + IO_PRESSURE_RIGHT_GRIP_RAW, + IO_PRESSURE_LEFT_GRIP_RAW, + IO_PRESSURE_RIGHT_GRIP2_THRESHOLD, + IO_PRESSURE_LEFT_GRIP2_THRESHOLD, + IO_PRESSURE_LEFT_GRIP2, + IO_PRESSURE_RIGHT_GRIP2, + IO_PRESSURE_RIGHT_GRIP2_RAW, + IO_PRESSURE_LEFT_GRIP2_RAW, IO_ANALOG_COUNT } AnalogIO; @@ -382,13 +417,15 @@ typedef enum SETTING_MOUSE_SENSITIVITY, SETTING_MOUSE_ACCELERATION, SETTING_TRACKBALL_ROTATION_ANGLE, - SETTING_HAPTIC_INTENSITY, + SETTING_HAPTIC_INTENSITY_UNUSED, SETTING_LEFT_GAMEPAD_STICK_ENABLED, SETTING_RIGHT_GAMEPAD_STICK_ENABLED, SETTING_USB_DEBUG_MODE, SETTING_LEFT_TRACKPAD_MODE, SETTING_RIGHT_TRACKPAD_MODE, SETTING_MOUSE_POINTER_ENABLED, + + // 10 SETTING_DPAD_DEADZONE, SETTING_MINIMUM_MOMENTUM_VEL, SETTING_MOMENTUM_DECAY_AMMOUNT, @@ -399,6 +436,8 @@ typedef enum SETTING_MOMENTUM_VERTICAL_DIVISOR, SETTING_MOMENTUM_MAXIMUM_VELOCITY, SETTING_TRACKPAD_Z_ON, + + // 20 SETTING_TRACKPAD_Z_OFF, SETTING_SENSITIVY_SCALE_AMMOUNT, SETTING_LEFT_TRACKPAD_SECONDARY_MODE, @@ -409,6 +448,8 @@ typedef enum SETTING_TRACKPAD_OUTER_RADIUS, SETTING_TRACKPAD_Z_ON_LEFT, SETTING_TRACKPAD_Z_OFF_LEFT, + + // 30 SETTING_TRACKPAD_OUTER_SPIN_VEL, SETTING_TRACKPAD_OUTER_SPIN_RADIUS, SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY, @@ -419,6 +460,8 @@ typedef enum SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD, SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT, SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION, + + // 40 SETTING_RADIAL_MODE_ANGLE, SETTING_HAPTIC_INTENSITY_MOUSE_MODE, SETTING_LEFT_DPAD_REQUIRES_CLICK, @@ -427,9 +470,48 @@ typedef enum SETTING_LED_USER_BRIGHTNESS, SETTING_ENABLE_RAW_JOYSTICK, SETTING_ENABLE_FAST_SCAN, - SETTING_GYRO_MODE, + SETTING_IMU_MODE, SETTING_WIRELESS_PACKET_VERSION, + + // 50 SETTING_SLEEP_INACTIVITY_TIMEOUT, + SETTING_TRACKPAD_NOISE_THRESHOLD, + SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, + SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, + SETTING_LEFT_BUMPER_CLICK_PRESSURE, + SETTING_RIGHT_BUMPER_CLICK_PRESSURE, + SETTING_LEFT_GRIP_CLICK_PRESSURE, + SETTING_RIGHT_GRIP_CLICK_PRESSURE, + SETTING_LEFT_GRIP2_CLICK_PRESSURE, + SETTING_RIGHT_GRIP2_CLICK_PRESSURE, + + // 60 + SETTING_PRESSURE_MODE, + SETTING_CONTROLLER_TEST_MODE, + SETTING_TRIGGER_MODE, + SETTING_TRACKPAD_Z_THRESHOLD, + SETTING_FRAME_RATE, + SETTING_TRACKPAD_FILT_CTRL, + SETTING_TRACKPAD_CLIP, + SETTING_DEBUG_OUTPUT_SELECT, + SETTING_TRIGGER_THRESHOLD_PERCENT, + SETTING_TRACKPAD_FREQUENCY_HOPPING, + + // 70 + SETTING_HAPTICS_ENABLED, + SETTING_STEAM_WATCHDOG_ENABLE, + SETTING_TIMP_TOUCH_THRESHOLD_ON, + SETTING_TIMP_TOUCH_THRESHOLD_OFF, + SETTING_FREQ_HOPPING, + SETTING_TEST_CONTROL, + SETTING_HAPTIC_MASTER_GAIN_DB, + SETTING_THUMB_TOUCH_THRESH, + SETTING_DEVICE_POWER_STATUS, + SETTING_HAPTIC_INTENSITY, + + // 80 + SETTING_STABILIZER_ENABLED, + SETTING_TIMP_MODE_MTE, SETTING_COUNT, // This is a special setting value use for callbacks and should not be set/get explicitly. @@ -461,6 +543,7 @@ typedef enum HAPTIC_PULSE_NORMAL = 0x0000, HAPTIC_PULSE_HIGH_PRIORITY = 0x0001, HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002, + HAPTIC_PULSE_IGNORE_USER_PREFS = 0x0003, } SettingHapticPulseFlags; typedef struct diff --git a/src/joystick/hidapi/steam/controller_structs.h b/src/joystick/hidapi/steam/controller_structs.h index e60428e2..69db0ee6 100644 --- a/src/joystick/hidapi/steam/controller_structs.h +++ b/src/joystick/hidapi/steam/controller_structs.h @@ -32,6 +32,13 @@ typedef struct unsigned char length; } FeatureReportHeader; +// Generic controller settings structure +typedef struct +{ + unsigned char settingNum; + unsigned short settingValue; +} ControllerSetting; + // Generic controller attribute structure typedef struct { @@ -39,12 +46,89 @@ typedef struct uint32_t attributeValue; } ControllerAttribute; +// Generic controller settings structure +typedef struct +{ + ControllerSetting settings[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerSetting ) ]; +} MsgSetSettingsValues, MsgGetSettingsValues, MsgGetSettingsDefaults, MsgGetSettingsMaxs; + // Generic controller settings structure typedef struct { ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ]; } MsgGetAttributes; +typedef struct +{ + unsigned char attributeTag; + char attributeValue[20]; +} MsgGetStringAttribute; + +typedef struct +{ + unsigned char mode; +} MsgSetControllerMode; + +// Trigger a haptic pulse +typedef struct { + unsigned char which_pad; + unsigned short pulse_duration; + unsigned short pulse_interval; + unsigned short pulse_count; + short dBgain; + unsigned char priority; +} MsgFireHapticPulse; + +typedef struct { + uint8_t mode; +} MsgHapticSetMode; + +typedef enum { + HAPTIC_TYPE_OFF, + HAPTIC_TYPE_TICK, + HAPTIC_TYPE_CLICK, + HAPTIC_TYPE_TONE, + HAPTIC_TYPE_RUMBLE, + HAPTIC_TYPE_NOISE, + HAPTIC_TYPE_SCRIPT, + HAPTIC_TYPE_LOG_SWEEP, +} haptic_type_t; + +typedef enum { + HAPTIC_INTENSITY_SYSTEM, + HAPTIC_INTENSITY_SHORT, + HAPTIC_INTENSITY_MEDIUM, + HAPTIC_INTENSITY_LONG, + HAPTIC_INTENSITY_INSANE, +} haptic_intensity_t; + +typedef struct { + uint8_t side; // 0x01 = L, 0x02 = R, 0x03 = Both + uint8_t cmd; // 0 = Off, 1 = tick, 2 = click, 3 = tone, 4 = rumble, 5 = + // rumble_noise, 6 = script, 7 = sweep, + uint8_t ui_intensity; // 0-4 (0 = default) + int8_t dBgain; // dB Can be positive (reasonable clipping / limiting will apply) + uint16_t freq; // Frequency of tone (if applicable) + int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite) + + uint16_t noise_intensity; + uint16_t lfo_freq; // Drives both tone and rumble geneators + uint8_t lfo_depth; // percentage, typically 100 + uint8_t rand_tone_gain; // Randomize each LFO cycle's gain + uint8_t script_id; // Used w/ dBgain for scripted haptics + + uint16_t lss_start_freq; // Used w/ Log Sine Sweep + uint16_t lss_end_freq; // Ditto +} MsgTriggerHaptic; + +typedef struct { + uint8_t unRumbleType; + uint16_t unIntensity; + uint16_t unLeftMotorSpeed; + uint16_t unRightMotorSpeed; + int8_t nLeftGain; + int8_t nRightGain; +} MsgSimpleRumbleCmd; // This is the only message struct that application code should use to interact with feature request messages. Any new // messages should be added to the union. The structures defined here should correspond to the ones defined in @@ -55,7 +139,17 @@ typedef struct FeatureReportHeader header; union { - MsgGetAttributes getAttributes; + MsgSetSettingsValues setSettingsValues; + MsgGetSettingsValues getSettingsValues; + MsgGetSettingsMaxs getSettingsMaxs; + MsgGetSettingsDefaults getSettingsDefaults; + MsgGetAttributes getAttributes; + MsgSetControllerMode controllerMode; + MsgFireHapticPulse fireHapticPulse; + MsgGetStringAttribute getStringAttribute; + MsgHapticSetMode hapticMode; + MsgTriggerHaptic triggerHaptic; + MsgSimpleRumbleCmd simpleRumble; } payload; } FeatureReportMsg; @@ -77,6 +171,7 @@ typedef enum ID_CONTROLLER_DEBUG2 = 5, ID_CONTROLLER_SECONDARY_STATE = 6, ID_CONTROLLER_BLE_STATE = 7, + ID_CONTROLLER_DECK_STATE = 9, ID_CONTROLLER_MSG_COUNT } ValveInReportMessageIDs; @@ -94,12 +189,12 @@ typedef struct { // If packet num matches that on your prior call, then the controller state hasn't been changed since // your last call and there is no need to process it - uint32 unPacketNum; + Uint32 unPacketNum; // Button bitmask and trigger data. union { - uint64 ulButtons; + Uint64 ulButtons; struct { unsigned char _pad0[3]; @@ -143,12 +238,12 @@ typedef struct { // If packet num matches that on your prior call, then the controller state hasn't been changed since // your last call and there is no need to process it - uint32 unPacketNum; + Uint32 unPacketNum; // Button bitmask and trigger data. union { - uint64 ulButtons; + Uint64 ulButtons; struct { unsigned char _pad0[3]; @@ -258,6 +353,66 @@ typedef struct unsigned char ucBatteryLevel; } SteamControllerStatusEvent_t; +// Deck State payload +typedef struct +{ + // If packet num matches that on your prior call, then the controller + // state hasn't been changed since your last call and there is no need to + // process it + Uint32 unPacketNum; + + // Button bitmask and trigger data. + union + { + Uint64 ulButtons; + struct + { + Uint32 ulButtonsL; + Uint32 ulButtonsH; + }; + }; + + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + // Accelerometer values + short sAccelX; + short sAccelY; + short sAccelZ; + + // Gyroscope values + short sGyroX; + short sGyroY; + short sGyroZ; + + // Gyro quaternions + short sGyroQuatW; + short sGyroQuatX; + short sGyroQuatY; + short sGyroQuatZ; + + // Uncalibrated trigger values + unsigned short sTriggerRawL; + unsigned short sTriggerRawR; + + // Left stick values + short sLeftStickX; + short sLeftStickY; + + // Right stick values + short sRightStickX; + short sRightStickY; + + // Touchpad pressures + unsigned short sPressurePadLeft; + unsigned short sPressurePadRight; +} SteamDeckStatePacket_t; + typedef struct { ValveInReportHeader_t header; @@ -271,6 +426,7 @@ typedef struct ValveControllerRawTrackpadImage_t rawPadImage; SteamControllerWirelessEvent_t wirelessEvent; SteamControllerStatusEvent_t statusEvent; + SteamDeckStatePacket_t deckState; } payload; } ValveInReport_t; diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index cca2016e..742bbf42 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -145,8 +145,8 @@ typedef enum static EnumerationMethod enumeration_method = ENUMERATION_UNSET; static SDL_bool IsJoystickJSNode(const char *node); -static int MaybeAddDevice(const char *path); -static int MaybeRemoveDevice(const char *path); +static void MaybeAddDevice(const char *path); +static void MaybeRemoveDevice(const char *path); /* A linked list of available joysticks */ typedef struct SDL_joylist_item @@ -156,6 +156,7 @@ typedef struct SDL_joylist_item char *name; /* "SideWinder 3D Pro" or whatever */ SDL_JoystickGUID guid; dev_t devnum; + int steam_virtual_gamepad_slot; struct joystick_hwdata *hwdata; struct SDL_joylist_item *next; @@ -176,10 +177,10 @@ typedef struct SDL_sensorlist_item } SDL_sensorlist_item; static SDL_bool SDL_classic_joysticks = SDL_FALSE; -static SDL_joylist_item *SDL_joylist = NULL; -static SDL_joylist_item *SDL_joylist_tail = NULL; -static int numjoysticks = 0; -static SDL_sensorlist_item *SDL_sensorlist = NULL; +static SDL_joylist_item *SDL_joylist SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_joylist_item *SDL_joylist_tail SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int numjoysticks SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static SDL_sensorlist_item *SDL_sensorlist SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static int inotify_fd = -1; static Uint64 last_joy_detect_time; @@ -219,6 +220,23 @@ static SDL_bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, } #endif /* SDL_JOYSTICK_HIDAPI */ +static SDL_bool GetSteamVirtualGamepadSlot(int fd, int *slot) +{ + char name[128]; + + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0) { + const char *digits = SDL_strstr(name, "pad "); + if (digits) { + digits += 4; + if (SDL_isdigit(*digits)) { + *slot = SDL_atoi(digits); + return SDL_TRUE; + } + } + } + return SDL_FALSE; +} + static int GuessDeviceClass(int fd) { unsigned long propbit[NBITS(INPUT_PROP_MAX)] = { 0 }; @@ -259,23 +277,25 @@ static int GuessIsSensor(int fd) return 0; } -static int IsJoystick(const char *path, int fd, char **name_return, SDL_JoystickGUID *guid) +static int IsJoystick(const char *path, int fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_JoystickGUID *guid) { struct input_id inpid; char *name; char product_string[128]; + int class = 0; - if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) >= 0) { - SDL_zero(inpid); + SDL_zero(inpid); #ifdef SDL_USE_LIBUDEV - SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version); + SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class); #endif - } else { + if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) { /* When udev is enabled we only get joystick devices here, so there's no need to test them */ - if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) { + if (enumeration_method != ENUMERATION_LIBUDEV && + !(class & SDL_UDEV_DEVICE_JOYSTICK) && ( class || !GuessIsJoystick(fd))) { return 0; } + /* Could have vendor and product already from udev, but should agree with evdev */ if (ioctl(fd, EVIOCGID, &inpid) < 0) { return 0; } @@ -286,7 +306,7 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick } name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); - if (name == NULL) { + if (!name) { return 0; } @@ -305,13 +325,15 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick SDL_Log("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version); #endif - *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, name, 0, 0); + *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0); if (SDL_ShouldIgnoreJoystick(name, *guid)) { SDL_free(name); return 0; } *name_return = name; + *vendor_return = inpid.vendor; + *product_return = inpid.product; return 1; } @@ -335,7 +357,7 @@ static int IsSensor(const char *path, int fd) #ifdef SDL_USE_LIBUDEV static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) { - if (devpath == NULL) { + if (!devpath) { return; } @@ -384,67 +406,75 @@ static void FreeSensorlistItem(SDL_sensorlist_item *item) SDL_free(item); } -static int MaybeAddDevice(const char *path) +static void MaybeAddDevice(const char *path) { struct stat sb; int fd = -1; char *name = NULL; + Uint16 vendor, product; SDL_JoystickGUID guid; SDL_joylist_item *item; SDL_sensorlist_item *item_sensor; - if (path == NULL) { - return -1; + if (!path) { + return; } if (stat(path, &sb) == -1) { - return -1; + return; } + SDL_LockJoysticks(); + /* Check to make sure it's not already in list. */ - for (item = SDL_joylist; item != NULL; item = item->next) { + for (item = SDL_joylist; item; item = item->next) { if (sb.st_rdev == item->devnum) { - return -1; /* already have this one */ + goto done; /* already have this one */ } } - for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { if (sb.st_rdev == item_sensor->devnum) { - return -1; /* already have this one */ + goto done; /* already have this one */ } } fd = open(path, O_RDONLY | O_CLOEXEC, 0); if (fd < 0) { - return -1; + goto done; } #ifdef DEBUG_INPUT_EVENTS SDL_Log("Checking %s\n", path); #endif - if (IsJoystick(path, fd, &name, &guid)) { + if (IsJoystick(path, fd, &name, &vendor, &product, &guid)) { #ifdef DEBUG_INPUT_EVENTS SDL_Log("found joystick: %s\n", path); #endif - close(fd); item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); - if (item == NULL) { + if (!item) { SDL_free(name); - return -1; + goto done; } item->devnum = sb.st_rdev; + item->steam_virtual_gamepad_slot = -1; item->path = SDL_strdup(path); item->name = name; item->guid = guid; - if ((item->path == NULL) || (item->name == NULL)) { - FreeJoylistItem(item); - return -1; + if (vendor == USB_VENDOR_VALVE && + product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + GetSteamVirtualGamepadSlot(fd, &item->steam_virtual_gamepad_slot); } - item->device_instance = SDL_GetNextJoystickInstanceID(); - if (SDL_joylist_tail == NULL) { + if ((!item->path) || (!item->name)) { + FreeJoylistItem(item); + goto done; + } + + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; } else { SDL_joylist_tail->next = item; @@ -455,42 +485,46 @@ static int MaybeAddDevice(const char *path) ++numjoysticks; SDL_PrivateJoystickAdded(item->device_instance); - return numjoysticks; + goto done; } if (IsSensor(path, fd)) { #ifdef DEBUG_INPUT_EVENTS SDL_Log("found sensor: %s\n", path); #endif - close(fd); item_sensor = (SDL_sensorlist_item *)SDL_calloc(1, sizeof(SDL_sensorlist_item)); - if (item_sensor == NULL) { - return -1; + if (!item_sensor) { + goto done; } item_sensor->devnum = sb.st_rdev; item_sensor->path = SDL_strdup(path); - if (item_sensor->path == NULL) { + if (!item_sensor->path) { FreeSensorlistItem(item_sensor); - return -1; + goto done; } item_sensor->next = SDL_sensorlist; SDL_sensorlist = item_sensor; - return -1; + goto done; } - close(fd); - return -1; +done: + if (fd >= 0) { + close(fd); + } + SDL_UnlockJoysticks(); } static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev) { + SDL_AssertJoysticksLocked(); + if (item->hwdata) { item->hwdata->item = NULL; } - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_joylist == item); @@ -510,11 +544,13 @@ static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev) static void RemoveSensorlistItem(SDL_sensorlist_item *item, SDL_sensorlist_item *prev) { + SDL_AssertJoysticksLocked(); + if (item->hwdata) { item->hwdata->item_sensor = NULL; } - if (prev != NULL) { + if (prev) { prev->next = item->next; } else { SDL_assert(SDL_sensorlist == item); @@ -526,50 +562,53 @@ static void RemoveSensorlistItem(SDL_sensorlist_item *item, SDL_sensorlist_item FreeSensorlistItem(item); } -static int MaybeRemoveDevice(const char *path) +static void MaybeRemoveDevice(const char *path) { SDL_joylist_item *item; SDL_joylist_item *prev = NULL; SDL_sensorlist_item *item_sensor; SDL_sensorlist_item *prev_sensor = NULL; - if (path == NULL) { - return -1; + if (!path) { + return; } - for (item = SDL_joylist; item != NULL; item = item->next) { + SDL_LockJoysticks(); + for (item = SDL_joylist; item; item = item->next) { /* found it, remove it. */ if (SDL_strcmp(path, item->path) == 0) { - const int retval = item->device_instance; RemoveJoylistItem(item, prev); - return retval; + goto done; } prev = item; } - for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { /* found it, remove it. */ if (SDL_strcmp(path, item_sensor->path) == 0) { RemoveSensorlistItem(item_sensor, prev_sensor); - return -1; + goto done; } prev_sensor = item_sensor; } - - return -1; +done: + SDL_UnlockJoysticks(); } static void HandlePendingRemovals(void) { SDL_joylist_item *prev = NULL; - SDL_joylist_item *item = SDL_joylist; + SDL_joylist_item *item = NULL; SDL_sensorlist_item *prev_sensor = NULL; - SDL_sensorlist_item *item_sensor = SDL_sensorlist; + SDL_sensorlist_item *item_sensor = NULL; - while (item != NULL) { + SDL_AssertJoysticksLocked(); + + item = SDL_joylist; + while (item) { if (item->hwdata && item->hwdata->gone) { RemoveJoylistItem(item, prev); - if (prev != NULL) { + if (prev) { item = prev->next; } else { item = SDL_joylist; @@ -580,11 +619,12 @@ static void HandlePendingRemovals(void) } } - while (item_sensor != NULL) { + item_sensor = SDL_sensorlist; + while (item_sensor) { if (item_sensor->hwdata && item_sensor->hwdata->sensor_gone) { RemoveSensorlistItem(item_sensor, prev_sensor); - if (prev_sensor != NULL) { + if (prev_sensor) { item_sensor = prev_sensor->next; } else { item_sensor = SDL_sensorlist; @@ -601,7 +641,7 @@ static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickG SDL_joylist_item *item; item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); - if (item == NULL) { + if (!item) { return SDL_FALSE; } @@ -610,13 +650,14 @@ static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickG item->guid = guid; item->m_bSteamController = SDL_TRUE; - if ((item->path == NULL) || (item->name == NULL)) { + if ((!item->path) || (!item->name)) { FreeJoylistItem(item); return SDL_FALSE; } - *device_instance = item->device_instance = SDL_GetNextJoystickInstanceID(); - if (SDL_joylist_tail == NULL) { + *device_instance = item->device_instance = SDL_GetNextObjectID(); + SDL_LockJoysticks(); + if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; } else { SDL_joylist_tail->next = item; @@ -627,6 +668,7 @@ static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickG ++numjoysticks; SDL_PrivateJoystickAdded(item->device_instance); + SDL_UnlockJoysticks(); return SDL_TRUE; } @@ -636,14 +678,16 @@ static void SteamControllerDisconnectedCallback(SDL_JoystickID device_instance) SDL_joylist_item *item; SDL_joylist_item *prev = NULL; - for (item = SDL_joylist; item != NULL; item = item->next) { + SDL_LockJoysticks(); + for (item = SDL_joylist; item; item = item->next) { /* found it, remove it. */ if (item->device_instance == device_instance) { RemoveJoylistItem(item, prev); - return; + break; } prev = item; } + SDL_UnlockJoysticks(); } static int StrHasPrefix(const char *string, const char *prefix) @@ -817,6 +861,89 @@ static int SDLCALL sort_entries(const void *_a, const void *_b) return numA - numB; } +typedef struct +{ + char *path; + int slot; +} VirtualGamepadEntry; + +static int SDLCALL sort_virtual_gamepads(const void *_a, const void *_b) +{ + const VirtualGamepadEntry *a = (const VirtualGamepadEntry *)_a; + const VirtualGamepadEntry *b = (const VirtualGamepadEntry *)_b; + return a->slot - b->slot; +} + +static void LINUX_ScanSteamVirtualGamepads(void) +{ + int i, count; + int fd; + struct dirent **entries = NULL; + char path[PATH_MAX]; + struct input_id inpid; + int num_virtual_gamepads = 0; + int virtual_gamepad_slot; + VirtualGamepadEntry *virtual_gamepads = NULL; + + count = scandir("/dev/input", &entries, filter_entries, NULL); + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + + fd = open(path, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) { + if (ioctl(fd, EVIOCGID, &inpid) == 0 && + inpid.vendor == USB_VENDOR_VALVE && + inpid.product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD && + GetSteamVirtualGamepadSlot(fd, &virtual_gamepad_slot)) { + VirtualGamepadEntry *new_virtual_gamepads = (VirtualGamepadEntry *)SDL_realloc(virtual_gamepads, (num_virtual_gamepads + 1) * sizeof(*virtual_gamepads)); + if (new_virtual_gamepads) { + VirtualGamepadEntry *entry = &new_virtual_gamepads[num_virtual_gamepads]; + entry->path = SDL_strdup(path); + entry->slot = virtual_gamepad_slot; + if (entry->path) { + virtual_gamepads = new_virtual_gamepads; + ++num_virtual_gamepads; + } else { + SDL_free(entry->path); + SDL_free(new_virtual_gamepads); + } + } + } + close(fd); + } + free(entries[i]); /* This should NOT be SDL_free() */ + } + free(entries); /* This should NOT be SDL_free() */ + + if (num_virtual_gamepads > 1) { + SDL_qsort(virtual_gamepads, num_virtual_gamepads, sizeof(*virtual_gamepads), sort_virtual_gamepads); + } + for (i = 0; i < num_virtual_gamepads; ++i) { + MaybeAddDevice(virtual_gamepads[i].path); + SDL_free(virtual_gamepads[i].path); + } + SDL_free(virtual_gamepads); +} + +static void LINUX_ScanInputDevices(void) +{ + int i, count; + struct dirent **entries = NULL; + char path[PATH_MAX]; + + count = scandir("/dev/input", &entries, filter_entries, NULL); + if (count > 1) { + SDL_qsort(entries, count, sizeof(*entries), sort_entries); + } + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + MaybeAddDevice(path); + + free(entries[i]); /* This should NOT be SDL_free() */ + } + free(entries); /* This should NOT be SDL_free() */ +} + static void LINUX_FallbackJoystickDetect(void) { const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000; /* Update every 3 seconds */ @@ -827,21 +954,10 @@ static void LINUX_FallbackJoystickDetect(void) /* Opening input devices can generate synchronous device I/O, so avoid it if we can */ if (stat("/dev/input", &sb) == 0 && sb.st_mtime != last_input_dir_mtime) { - int i, count; - struct dirent **entries = NULL; - char path[PATH_MAX]; + /* Look for Steam virtual gamepads first, and sort by Steam controller slot */ + LINUX_ScanSteamVirtualGamepads(); - count = scandir("/dev/input", &entries, filter_entries, NULL); - if (count > 1) { - SDL_qsort(entries, count, sizeof(*entries), sort_entries); - } - for (i = 0; i < count; ++i) { - (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); - MaybeAddDevice(path); - - free(entries[i]); /* This should NOT be SDL_free() */ - } - free(entries); /* This should NOT be SDL_free() */ + LINUX_ScanInputDevices(); last_input_dir_mtime = sb.st_mtime; } @@ -880,13 +996,13 @@ static int LINUX_JoystickInit(void) enumeration_method = ENUMERATION_UNSET; /* First see if the user specified one or more joysticks to use */ - if (devices != NULL) { + if (devices) { char *envcopy, *envpath, *delim; envcopy = SDL_strdup(devices); envpath = envcopy; - while (envpath != NULL) { + while (envpath) { delim = SDL_strchr(envpath, ':'); - if (delim != NULL) { + if (delim) { *delim++ = '\0'; } MaybeAddDevice(envpath); @@ -969,17 +1085,22 @@ static int LINUX_JoystickInit(void) static int LINUX_JoystickGetCount(void) { + SDL_AssertJoysticksLocked(); + return numjoysticks; } static SDL_joylist_item *GetJoystickByDevIndex(int device_index) { - SDL_joylist_item *item = SDL_joylist; + SDL_joylist_item *item; + + SDL_AssertJoysticksLocked(); if ((device_index < 0) || (device_index >= numjoysticks)) { return NULL; } + item = SDL_joylist; while (device_index > 0) { SDL_assert(item != NULL); device_index--; @@ -999,6 +1120,11 @@ static const char *LINUX_JoystickGetDevicePath(int device_index) return GetJoystickByDevIndex(device_index)->path; } +static int LINUX_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return GetJoystickByDevIndex(device_index)->steam_virtual_gamepad_slot; +} + static int LINUX_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -1028,7 +1154,7 @@ static int allocate_hatdata(SDL_Joystick *joystick) joystick->hwdata->hats = (struct hwdata_hat *)SDL_malloc(joystick->nhats * sizeof(struct hwdata_hat)); - if (joystick->hwdata->hats == NULL) { + if (!joystick->hwdata->hats) { return -1; } for (i = 0; i < joystick->nhats; ++i) { @@ -1047,7 +1173,7 @@ static SDL_bool GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct * other continuous analog axis, so we have to guess. */ /* If both axes are missing, they're not anything. */ - if (absinfo_x == NULL && absinfo_y == NULL) { + if (!absinfo_x && !absinfo_y) { return SDL_FALSE; } @@ -1057,12 +1183,12 @@ static SDL_bool GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct } /* If both axes have ranges constrained between -1 and 1, they're definitely digital. */ - if ((absinfo_x == NULL || (absinfo_x->minimum == -1 && absinfo_x->maximum == 1)) && (absinfo_y == NULL || (absinfo_y->minimum == -1 && absinfo_y->maximum == 1))) { + if ((!absinfo_x || (absinfo_x->minimum == -1 && absinfo_x->maximum == 1)) && (!absinfo_y || (absinfo_y->minimum == -1 && absinfo_y->maximum == 1))) { return SDL_TRUE; } /* If both axes lack fuzz, flat, and resolution values, they're probably digital. */ - if ((absinfo_x == NULL || (!absinfo_x->fuzz && !absinfo_x->flat && !absinfo_x->resolution)) && (absinfo_y == NULL || (!absinfo_y->fuzz && !absinfo_y->flat && !absinfo_y->resolution))) { + if ((!absinfo_x || (!absinfo_x->fuzz && !absinfo_x->flat && !absinfo_x->resolution)) && (!absinfo_y || (!absinfo_y->fuzz && !absinfo_y->flat && !absinfo_y->resolution))) { return SDL_TRUE; } @@ -1261,9 +1387,11 @@ static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor) /* Sensors are only available through the new unified event API */ if (fd_sensor >= 0 && (ioctl(fd_sensor, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0)) { if (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && test_bit(ABS_Z, absbit)) { + joystick->hwdata->has_accelerometer = SDL_TRUE; for (i = 0; i < 3; ++i) { struct input_absinfo absinfo; if (ioctl(fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) < 0) { + joystick->hwdata->has_accelerometer = SDL_FALSE; break; /* do not report an accelerometer if we can't read all axes */ } joystick->hwdata->accelerometer_scale[i] = absinfo.resolution; @@ -1274,14 +1402,15 @@ static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor) absinfo.fuzz, absinfo.flat, absinfo.resolution); #endif /* DEBUG_INPUT_EVENTS */ } - joystick->hwdata->has_accelerometer = SDL_TRUE; } if (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit) && test_bit(ABS_RZ, absbit)) { + joystick->hwdata->has_gyro = SDL_TRUE; for (i = 0; i < 3; ++i) { struct input_absinfo absinfo; if (ioctl(fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) < 0) { - break; /* do not report an gyro if we can't read all axes */ + joystick->hwdata->has_gyro = SDL_FALSE; + break; /* do not report a gyro if we can't read all axes */ } joystick->hwdata->gyro_scale[i] = absinfo.resolution; #ifdef DEBUG_INPUT_EVENTS @@ -1291,7 +1420,6 @@ static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor) absinfo.fuzz, absinfo.flat, absinfo.resolution); #endif /* DEBUG_INPUT_EVENTS */ } - joystick->hwdata->has_gyro = SDL_TRUE; } } @@ -1347,19 +1475,19 @@ static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item, return SDL_SetError("Unable to open %s", item->path); } /* If opening sensor fail, continue with buttons and axes only */ - if (item_sensor != NULL) { + if (item_sensor) { fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); } joystick->hwdata->fd = fd; joystick->hwdata->fd_sensor = fd_sensor; joystick->hwdata->fname = SDL_strdup(item->path); - if (joystick->hwdata->fname == NULL) { + if (!joystick->hwdata->fname) { close(fd); if (fd_sensor >= 0) { close(fd_sensor); } - return SDL_OutOfMemory(); + return -1; } /* Set the joystick to non-blocking read mode */ @@ -1380,7 +1508,9 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) char uniq_item[128]; int fd_item = -1; - if (item == NULL || SDL_sensorlist == NULL) { + SDL_AssertJoysticksLocked(); + + if (!item || !SDL_sensorlist) { return NULL; } @@ -1397,10 +1527,10 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) SDL_Log("Joystick UNIQ: %s\n", uniq_item); #endif /* DEBUG_INPUT_EVENTS */ - for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { char uniq_sensor[128]; int fd_sensor = -1; - if (item_sensor->hwdata != NULL) { + if (item_sensor->hwdata) { /* already associated with another joystick */ continue; } @@ -1433,22 +1563,24 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) */ static int LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) { - SDL_joylist_item *item = GetJoystickByDevIndex(device_index); - SDL_sensorlist_item *item_sensor = GetSensor(item); + SDL_joylist_item *item; + SDL_sensorlist_item *item_sensor; SDL_AssertJoysticksLocked(); - if (item == NULL) { + item = GetJoystickByDevIndex(device_index); + if (!item) { return SDL_SetError("No such device"); } joystick->instance_id = item->device_instance; joystick->hwdata = (struct joystick_hwdata *) SDL_calloc(1, sizeof(*joystick->hwdata)); - if (joystick->hwdata == NULL) { - return SDL_OutOfMemory(); + if (!joystick->hwdata) { + return -1; } + item_sensor = GetSensor(item); if (PrepareJoystickHwdata(joystick, item, item_sensor) == -1) { SDL_free(joystick->hwdata); joystick->hwdata = NULL; @@ -1458,7 +1590,7 @@ static int LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_assert(item->hwdata == NULL); SDL_assert(!item_sensor || item_sensor->hwdata == NULL); item->hwdata = joystick->hwdata; - if (item_sensor != NULL) { + if (item_sensor) { item_sensor->hwdata = joystick->hwdata; } @@ -1563,7 +1695,9 @@ static int LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enab } if (enabled) { - SDL_assert(joystick->hwdata->item_sensor); + if (!joystick->hwdata->item_sensor) { + return SDL_SetError("Sensors unplugged."); + } joystick->hwdata->fd_sensor = open(joystick->hwdata->item_sensor->path, O_RDONLY | O_CLOEXEC, 0); if (joystick->hwdata->fd_sensor < 0) { return SDL_SetError("Couldn't open sensor file %s.", joystick->hwdata->item_sensor->path); @@ -2047,6 +2181,8 @@ static void LINUX_JoystickQuit(void) SDL_sensorlist_item *item_sensor = NULL; SDL_sensorlist_item *next_sensor = NULL; + SDL_AssertJoysticksLocked(); + if (inotify_fd >= 0) { close(inotify_fd); inotify_fd = -1; @@ -2116,18 +2252,16 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap /* We temporarily open the device to check how it's configured. Make a fake SDL_Joystick object to do so. */ joystick = (SDL_Joystick *)SDL_calloc(sizeof(*joystick), 1); - joystick->magic = &SDL_joystick_magic; - if (joystick == NULL) { - SDL_OutOfMemory(); + if (!joystick) { return SDL_FALSE; } + joystick->magic = &SDL_joystick_magic; SDL_memcpy(&joystick->guid, &item->guid, sizeof(item->guid)); joystick->hwdata = (struct joystick_hwdata *) SDL_calloc(1, sizeof(*joystick->hwdata)); - if (joystick->hwdata == NULL) { + if (!joystick->hwdata) { SDL_free(joystick); - SDL_OutOfMemory(); return SDL_FALSE; } @@ -2564,6 +2698,7 @@ SDL_JoystickDriver SDL_LINUX_JoystickDriver = { LINUX_JoystickDetect, LINUX_JoystickGetDeviceName, LINUX_JoystickGetDevicePath, + LINUX_JoystickGetDeviceSteamVirtualGamepadSlot, LINUX_JoystickGetDevicePlayerIndex, LINUX_JoystickSetDevicePlayerIndex, LINUX_JoystickGetDeviceGUID, diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h index e737332d..98bee204 100644 --- a/src/joystick/linux/SDL_sysjoystick_c.h +++ b/src/joystick/linux/SDL_sysjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/n3ds/SDL_sysjoystick.c b/src/joystick/n3ds/SDL_sysjoystick.c index 5e17010f..79a666f6 100644 --- a/src/joystick/n3ds/SDL_sysjoystick.c +++ b/src/joystick/n3ds/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,13 +36,23 @@ This correction is applied to axis values so they fit better in SDL's value range. */ -#define CORRECT_AXIS_X(X) ((X * SDL_JOYSTICK_AXIS_MAX) / 160) +static inline int Correct_Axis_X(int X) { + if (X > 160) { + return SDL_JOYSTICK_AXIS_MAX; + } + else if (X < -160) { + return -SDL_JOYSTICK_AXIS_MAX; + } + return (X * SDL_JOYSTICK_AXIS_MAX) / 160; +} /* The Y axis needs to be flipped because SDL's "up" is reversed compared to libctru's "up" */ -#define CORRECT_AXIS_Y(Y) CORRECT_AXIS_X(-Y) +static inline int Correct_Axis_Y(int Y) { + return Correct_Axis_X(-Y); +} static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick); static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick); @@ -73,7 +83,7 @@ static SDL_JoystickGUID N3DS_JoystickGetDeviceGUID(int device_index) static SDL_JoystickID N3DS_JoystickGetDeviceInstanceID(int device_index) { - return device_index; + return device_index + 1; } static int N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index) @@ -141,12 +151,12 @@ static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick) if (previous_state.dx != current_state.dx) { SDL_SendJoystickAxis(timestamp, joystick, 0, - CORRECT_AXIS_X(current_state.dx)); + Correct_Axis_X(current_state.dx)); } if (previous_state.dy != current_state.dy) { SDL_SendJoystickAxis(timestamp, joystick, 1, - CORRECT_AXIS_Y(current_state.dy)); + Correct_Axis_Y(current_state.dy)); } previous_state = current_state; } @@ -159,12 +169,12 @@ static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick) if (previous_state.dx != current_state.dx) { SDL_SendJoystickAxis(timestamp, joystick, 2, - CORRECT_AXIS_X(current_state.dx)); + Correct_Axis_X(current_state.dx)); } if (previous_state.dy != current_state.dy) { SDL_SendJoystickAxis(timestamp, joystick, 3, - CORRECT_AXIS_Y(current_state.dy)); + Correct_Axis_Y(current_state.dy)); } previous_state = current_state; } @@ -221,6 +231,11 @@ static const char *N3DS_JoystickGetDevicePath(int device_index) return NULL; } +static int N3DS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int N3DS_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -261,6 +276,7 @@ SDL_JoystickDriver SDL_N3DS_JoystickDriver = { .Detect = N3DS_JoystickDetect, .GetDeviceName = N3DS_JoystickGetDeviceName, .GetDevicePath = N3DS_JoystickGetDevicePath, + .GetDeviceSteamVirtualGamepadSlot = N3DS_JoystickGetDeviceSteamVirtualGamepadSlot, .GetDevicePlayerIndex = N3DS_JoystickGetDevicePlayerIndex, .SetDevicePlayerIndex = N3DS_JoystickSetDevicePlayerIndex, .GetDeviceGUID = N3DS_JoystickGetDeviceGUID, diff --git a/src/joystick/ps2/SDL_sysjoystick.c b/src/joystick/ps2/SDL_sysjoystick.c index 45ad16a6..5fc5df32 100644 --- a/src/joystick/ps2/SDL_sysjoystick.c +++ b/src/joystick/ps2/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -157,6 +157,12 @@ static const char *PS2_JoystickGetDevicePath(int index) return NULL; } +/* Function to get the Steam virtual gamepad slot of a joystick */ +static int PS2_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + /* Function to get the player index of a joystick */ static int PS2_JoystickGetDevicePlayerIndex(int device_index) { @@ -179,7 +185,7 @@ static SDL_JoystickGUID PS2_JoystickGetDeviceGUID(int device_index) /* Function to get the current instance id of the joystick located at device_index */ static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index) { - return device_index; + return device_index + 1; } /* Function to open a joystick for use. @@ -341,6 +347,7 @@ SDL_JoystickDriver SDL_PS2_JoystickDriver = { PS2_JoystickDetect, PS2_JoystickGetDeviceName, PS2_JoystickGetDevicePath, + PS2_JoystickGetDeviceSteamVirtualGamepadSlot, PS2_JoystickGetDevicePlayerIndex, PS2_JoystickSetDevicePlayerIndex, PS2_JoystickGetDeviceGUID, diff --git a/src/joystick/psp/SDL_sysjoystick.c b/src/joystick/psp/SDL_sysjoystick.c index 8db17dce..f66abfaa 100644 --- a/src/joystick/psp/SDL_sysjoystick.c +++ b/src/joystick/psp/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -121,6 +121,11 @@ static const char *PSP_JoystickGetDevicePath(int index) return NULL; } +static int PSP_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int PSP_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -140,7 +145,7 @@ static SDL_JoystickGUID PSP_JoystickGetDeviceGUID(int device_index) /* Function to perform the mapping from device index to the instance id for this index */ static SDL_JoystickID PSP_JoystickGetDeviceInstanceID(int device_index) { - return device_index; + return device_index + 1; } /* Function to open a joystick for use. @@ -253,6 +258,7 @@ SDL_JoystickDriver SDL_PSP_JoystickDriver = { PSP_JoystickDetect, PSP_JoystickGetDeviceName, PSP_JoystickGetDevicePath, + PSP_JoystickGetDeviceSteamVirtualGamepadSlot, PSP_JoystickGetDevicePlayerIndex, PSP_JoystickSetDevicePlayerIndex, PSP_JoystickGetDeviceGUID, diff --git a/src/joystick/steam/SDL_steamcontroller.c b/src/joystick/steam/SDL_steamcontroller.c index 5ec4ad7b..d2bd3ecc 100644 --- a/src/joystick/steam/SDL_steamcontroller.c +++ b/src/joystick/steam/SDL_steamcontroller.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/steam/SDL_steamcontroller.h b/src/joystick/steam/SDL_steamcontroller.h index f3755af0..b5301628 100644 --- a/src/joystick/steam/SDL_steamcontroller.h +++ b/src/joystick/steam/SDL_steamcontroller.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 773e58f7..0db98ac9 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,11 +33,13 @@ #define USB_VENDOR_DRAGONRISE 0x0079 #define USB_VENDOR_GOOGLE 0x18d1 #define USB_VENDOR_HORI 0x0f0d +#define USB_VENDOR_HP 0x03f0 #define USB_VENDOR_HYPERKIN 0x2e24 #define USB_VENDOR_LOGITECH 0x046d #define USB_VENDOR_MADCATZ 0x0738 #define USB_VENDOR_MICROSOFT 0x045e #define USB_VENDOR_NACON 0x146b +#define USB_VENDOR_NACON_ALT 0x3285 #define USB_VENDOR_NINTENDO 0x057e #define USB_VENDOR_NVIDIA 0x0955 #define USB_VENDOR_PDP 0x0e6f @@ -54,20 +56,27 @@ #define USB_VENDOR_VALVE 0x28de #define USB_VENDOR_ZEROPLUS 0x0c12 -#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER 0x2002 +#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 /* Ultimate Wired Controller for Xbox */ +#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 /* Ultimate Wireless / Pro 2 Wired Controller */ #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 #define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 #define USB_PRODUCT_GAMESIR_G7 0x1001 #define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400 -#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER 0x1846 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846 #define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150 #define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 +#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 #define USB_PRODUCT_LOGITECH_F310 0xc216 #define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS 0x0d16 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19 #define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337 #define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 #define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e @@ -112,6 +121,8 @@ #define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 /* XUSB driver software PID */ #define USB_PRODUCT_XBOX360_WIRED_CONTROLLER 0x028e #define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER 0x0719 +#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 0x02a9 +#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 0x0291 #define USB_PRODUCT_XBOX_ONE_ADAPTIVE 0x0b0a #define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLUETOOTH 0x0b0c #define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLE 0x0b21 @@ -125,14 +136,15 @@ #define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20 #define USB_PRODUCT_XBOX_SERIES_X 0x0b12 #define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13 -#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6 -#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9 +#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6 +#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0 #define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da +#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9 #define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001 #define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b #define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002 +#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6 #define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff /* XBOXGIP driver software PID */ -#define USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER 0x02fe /* Made up product ID for XInput */ #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff /* USB usage pages */ diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c index b22ae6ac..d7859bf3 100644 --- a/src/joystick/virtual/SDL_virtualjoystick.c +++ b/src/joystick/virtual/SDL_virtualjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -66,7 +66,7 @@ static void VIRTUAL_FreeHWData(joystick_hwdata *hwdata) SDL_AssertJoysticksLocked(); - if (hwdata == NULL) { + if (!hwdata) { return; } @@ -114,18 +114,18 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des SDL_AssertJoysticksLocked(); - if (desc == NULL) { + if (!desc) { return SDL_InvalidParamError("desc"); } if (desc->version != SDL_VIRTUAL_JOYSTICK_DESC_VERSION) { /* Is this an old version that we can support? */ - return SDL_SetError("Unsupported virtual joystick description version %d", desc->version); + return SDL_SetError("Unsupported virtual joystick description version %u", desc->version); } hwdata = SDL_calloc(1, sizeof(joystick_hwdata)); - if (hwdata == NULL) { + if (!hwdata) { VIRTUAL_FreeHWData(hwdata); - return SDL_OutOfMemory(); + return 0; } SDL_memcpy(&hwdata->desc, desc, sizeof(*desc)); @@ -203,14 +203,14 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des } } - hwdata->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_VIRTUAL, hwdata->desc.vendor_id, hwdata->desc.product_id, 0, name, 'v', (Uint8)hwdata->desc.type); + hwdata->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_VIRTUAL, hwdata->desc.vendor_id, hwdata->desc.product_id, 0, NULL, name, 'v', (Uint8)hwdata->desc.type); /* Allocate fields for different control-types */ if (hwdata->desc.naxes > 0) { hwdata->axes = SDL_calloc(hwdata->desc.naxes, sizeof(Sint16)); if (!hwdata->axes) { VIRTUAL_FreeHWData(hwdata); - return SDL_OutOfMemory(); + return 0; } /* Trigger axes are at minimum value at rest */ @@ -225,19 +225,19 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des hwdata->buttons = SDL_calloc(hwdata->desc.nbuttons, sizeof(Uint8)); if (!hwdata->buttons) { VIRTUAL_FreeHWData(hwdata); - return SDL_OutOfMemory(); + return 0; } } if (hwdata->desc.nhats > 0) { hwdata->hats = SDL_calloc(hwdata->desc.nhats, sizeof(Uint8)); if (!hwdata->hats) { VIRTUAL_FreeHWData(hwdata); - return SDL_OutOfMemory(); + return 0; } } /* Allocate an instance ID for this device */ - hwdata->instance_id = SDL_GetNextJoystickInstanceID(); + hwdata->instance_id = SDL_GetNextObjectID(); /* Add virtual joystick to SDL-global lists */ if (g_VJoys) { @@ -257,7 +257,7 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des int SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id) { joystick_hwdata *hwdata = VIRTUAL_HWDataForInstance(instance_id); - if (hwdata == NULL) { + if (!hwdata) { return SDL_SetError("Virtual joystick data not found"); } VIRTUAL_FreeHWData(hwdata); @@ -271,7 +271,7 @@ int SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 val SDL_AssertJoysticksLocked(); - if (joystick == NULL || !joystick->hwdata) { + if (!joystick || !joystick->hwdata) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } @@ -293,7 +293,7 @@ int SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 SDL_AssertJoysticksLocked(); - if (joystick == NULL || !joystick->hwdata) { + if (!joystick || !joystick->hwdata) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } @@ -315,7 +315,7 @@ int SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value) SDL_AssertJoysticksLocked(); - if (joystick == NULL || !joystick->hwdata) { + if (!joystick || !joystick->hwdata) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } @@ -356,7 +356,7 @@ static void VIRTUAL_JoystickDetect(void) static const char *VIRTUAL_JoystickGetDeviceName(int device_index) { joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); - if (hwdata == NULL) { + if (!hwdata) { return NULL; } return hwdata->name; @@ -367,6 +367,11 @@ static const char *VIRTUAL_JoystickGetDevicePath(int device_index) return NULL; } +static int VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int VIRTUAL_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -384,7 +389,7 @@ static void VIRTUAL_JoystickSetDevicePlayerIndex(int device_index, int player_in static SDL_JoystickGUID VIRTUAL_JoystickGetDeviceGUID(int device_index) { joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); - if (hwdata == NULL) { + if (!hwdata) { SDL_JoystickGUID guid; SDL_zero(guid); return guid; @@ -395,7 +400,7 @@ static SDL_JoystickGUID VIRTUAL_JoystickGetDeviceGUID(int device_index) static SDL_JoystickID VIRTUAL_JoystickGetDeviceInstanceID(int device_index) { joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); - if (hwdata == NULL) { + if (!hwdata) { return 0; } return hwdata->instance_id; @@ -408,7 +413,7 @@ static int VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_AssertJoysticksLocked(); hwdata = VIRTUAL_HWDataForIndex(device_index); - if (hwdata == NULL) { + if (!hwdata) { return SDL_SetError("No such device"); } joystick->instance_id = hwdata->instance_id; @@ -535,7 +540,7 @@ static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick) SDL_AssertJoysticksLocked(); - if (joystick == NULL) { + if (!joystick) { return; } if (!joystick->hwdata) { @@ -589,22 +594,22 @@ static SDL_bool VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadM return SDL_FALSE; } - if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_A))) { + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH))) { out->a.kind = EMappingKind_Button; out->a.target = current_button++; } - if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_B))) { + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST))) { out->b.kind = EMappingKind_Button; out->b.target = current_button++; } - if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_X))) { + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST))) { out->x.kind = EMappingKind_Button; out->x.target = current_button++; } - if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_Y))) { + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH))) { out->y.kind = EMappingKind_Button; out->y.target = current_button++; } @@ -728,6 +733,7 @@ SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = { VIRTUAL_JoystickDetect, VIRTUAL_JoystickGetDeviceName, VIRTUAL_JoystickGetDevicePath, + VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot, VIRTUAL_JoystickGetDevicePlayerIndex, VIRTUAL_JoystickSetDevicePlayerIndex, VIRTUAL_JoystickGetDeviceGUID, diff --git a/src/joystick/virtual/SDL_virtualjoystick_c.h b/src/joystick/virtual/SDL_virtualjoystick_c.h index b672b862..2f16cc6c 100644 --- a/src/joystick/virtual/SDL_virtualjoystick_c.h +++ b/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/vita/SDL_sysjoystick.c b/src/joystick/vita/SDL_sysjoystick.c index cf0585f6..4eb9edfd 100644 --- a/src/joystick/vita/SDL_sysjoystick.c +++ b/src/joystick/vita/SDL_sysjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -151,7 +151,7 @@ void VITA_JoystickDetect() /* Function to perform the mapping from device index to the instance id for this index */ SDL_JoystickID VITA_JoystickGetDeviceInstanceID(int device_index) { - return device_index; + return device_index + 1; } const char *VITA_JoystickGetDeviceName(int index) @@ -181,6 +181,11 @@ const char *VITA_JoystickGetDevicePath(int index) return NULL; } +static int VITA_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + static int VITA_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -378,6 +383,7 @@ SDL_JoystickDriver SDL_VITA_JoystickDriver = { VITA_JoystickDetect, VITA_JoystickGetDeviceName, VITA_JoystickGetDevicePath, + VITA_JoystickGetDeviceSteamVirtualGamepadSlot, VITA_JoystickGetDevicePlayerIndex, VITA_JoystickSetDevicePlayerIndex, VITA_JoystickGetDeviceGUID, diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index b6868891..63c31a3c 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/src/joystick/windows/SDL_dinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -271,7 +271,7 @@ static SDL_bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, char **device_name) { DIPROPSTRING dipstr; - if (!device || device_name == NULL) { + if (!device || !device_name) { return SDL_FALSE; } @@ -293,7 +293,7 @@ static SDL_bool QueryDevicePath(LPDIRECTINPUTDEVICE8 device, char **device_path) { DIPROPGUIDANDPATH dippath; - if (!device || device_path == NULL) { + if (!device || !device_path) { return SDL_FALSE; } @@ -318,7 +318,7 @@ static SDL_bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, { DIPROPDWORD dipdw; - if (!device || vendor_id == NULL || product_id == NULL) { + if (!device || !vendor_id || !product_id) { return SDL_FALSE; } @@ -340,7 +340,7 @@ static SDL_bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, void FreeRumbleEffectData(DIEFFECT *effect) { - if (effect == NULL) { + if (!effect) { return; } SDL_free(effect->rgdwAxes); @@ -356,7 +356,7 @@ DIEFFECT *CreateRumbleEffectData(Sint16 magnitude) /* Create the effect */ effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect)); - if (effect == NULL) { + if (!effect) { return NULL; } effect->dwSize = sizeof(*effect); @@ -380,7 +380,7 @@ DIEFFECT *CreateRumbleEffectData(Sint16 magnitude) effect->dwFlags |= DIEFF_CARTESIAN; periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic)); - if (periodic == NULL) { + if (!periodic) { FreeRumbleEffectData(effect); return NULL; } @@ -420,7 +420,7 @@ int SDL_DINPUT_JoystickInit(void) /* Because we used CoCreateInstance, we need to Initialize it, first. */ instance = GetModuleHandle(NULL); - if (instance == NULL) { + if (!instance) { IDirectInput8_Release(dinput); dinput = NULL; return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError()); @@ -435,6 +435,17 @@ int SDL_DINPUT_JoystickInit(void) return 0; } +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot); + } + return slot; +} + /* helper function for direct input, gets called for each connected joystick */ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext) { @@ -451,7 +462,6 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta char *hidPath = NULL; char *name = NULL; LPDIRECTINPUTDEVICE8 device = NULL; - DIDEVCAPS caps; /* We are only supporting HID devices. */ CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); @@ -461,13 +471,6 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta CHECK(QueryDevicePath(device, &hidPath)); CHECK(QueryDeviceInfo(device, &vendor, &product)); - /* Check to make sure the device has buttons and axes. - * This fixes incorrectly detecting the ROG CHAKRAM X mouse as a game controller on Windows 10 - */ - caps.dwSize = sizeof(caps); - CHECK(SUCCEEDED(IDirectInputDevice8_GetCapabilities(device, &caps))); - CHECK(caps.dwAxes > 0 && caps.dwButtons > 0); - CHECK(!SDL_IsXInputDevice(vendor, product, hidPath)); pNewJoystick = *(JoyStick_DeviceData **)pContext; @@ -495,10 +498,10 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta pNewJoystick = pNewJoystick->pNext; } - pNewJoystick = (JoyStick_DeviceData *)SDL_malloc(sizeof(JoyStick_DeviceData)); + pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); CHECK(pNewJoystick); - SDL_zerop(pNewJoystick); + pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath); SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path)); SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE)); @@ -506,9 +509,9 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta CHECK(pNewJoystick->joystickname); if (vendor && product) { - pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, pNewJoystick->joystickname, 0, 0); + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 0, 0); } else { - pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, pNewJoystick->joystickname, 0, 0); + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, NULL, name, 0, 0); } CHECK(!SDL_ShouldIgnoreJoystick(pNewJoystick->joystickname, pNewJoystick->guid)); @@ -543,7 +546,7 @@ err: void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext) { - if (dinput == NULL) { + if (!dinput) { return; } @@ -595,7 +598,7 @@ SDL_bool SDL_DINPUT_JoystickPresent(Uint16 vendor_id, Uint16 product_id, Uint16 { Joystick_PresentData data; - if (dinput == NULL) { + if (!dinput) { return SDL_FALSE; } @@ -886,7 +889,7 @@ static int SDL_DINPUT_JoystickInitRumble(SDL_Joystick *joystick, Sint16 magnitud /* Create the effect */ joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude); if (!joystick->hwdata->ffeffect) { - return SDL_OutOfMemory(); + return -1; } result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine, diff --git a/src/joystick/windows/SDL_dinputjoystick_c.h b/src/joystick/windows/SDL_dinputjoystick_c.h index 95cf5df7..d864f737 100644 --- a/src/joystick/windows/SDL_dinputjoystick_c.h +++ b/src/joystick/windows/SDL_dinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/windows/SDL_rawinputjoystick.c b/src/joystick/windows/SDL_rawinputjoystick.c index fd2e8424..77add539 100644 --- a/src/joystick/windows/SDL_rawinputjoystick.c +++ b/src/joystick/windows/SDL_rawinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2023 Sam Lantinga + Copyright (C) 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 @@ -114,6 +114,7 @@ typedef struct SDL_RAWINPUT_Device SDL_JoystickGUID guid; SDL_bool is_xinput; SDL_bool is_xboxone; + int steam_virtual_gamepad_slot; PHIDP_PREPARSED_DATA preparsed_data; HANDLE hDevice; @@ -249,10 +250,10 @@ static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state /* Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU */ (WORD)(match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11); /* Explicit - ((match_state & (1<> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6; /* Explicit - ((match_state & (1<gamepad = gamepad; gamepad_state->connected = SDL_TRUE; @@ -839,6 +843,19 @@ static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice) return NULL; } +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) +{ + int slot = -1; + + // The format for the raw input device path is documented here: + // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); + } + return slot; +} + static void RAWINPUT_AddDevice(HANDLE hDevice) { #define CHECK(expression) \ @@ -850,7 +867,7 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) SDL_RAWINPUT_Device *curr, *last; RID_DEVICE_INFO rdi; UINT size; - char dev_name[MAX_PATH]; + char dev_name[MAX_PATH] = { 0 }; HANDLE hFile = INVALID_HANDLE_VALUE; /* Make sure we're not trying to add the same device twice */ @@ -860,6 +877,7 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) /* Figure out what kind of device it is */ size = sizeof(rdi); + SDL_zero(rdi); CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1); CHECK(rdi.dwType == RIM_TYPEHID); @@ -880,6 +898,7 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) device->version = (Uint16)rdi.hid.dwVersionNumber; device->is_xinput = SDL_TRUE; device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id); + device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name); /* Get HID Top-Level Collection Preparsed Data */ size = 0; @@ -904,6 +923,7 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) } device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); + device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0); if (manufacturer_string) { SDL_free(manufacturer_string); @@ -913,14 +933,12 @@ static void RAWINPUT_AddDevice(HANDLE hDevice) } } - device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, device->name, 'r', 0); - device->path = SDL_strdup(dev_name); CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; - device->joystick_id = SDL_GetNextJoystickInstanceID(); + device->joystick_id = SDL_GetNextObjectID(); #ifdef DEBUG_RAWINPUT SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x\n", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); @@ -991,7 +1009,8 @@ static void RAWINPUT_DetectDevices(void) devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); if (devices) { - if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) { + device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)); + if (device_count != (UINT)-1) { for (i = 0; i < device_count; ++i) { RAWINPUT_AddDevice(devices[i].hDevice); } @@ -1185,6 +1204,11 @@ static const char *RAWINPUT_JoystickGetDevicePath(int device_index) return RAWINPUT_GetDeviceByIndex(device_index)->path; } +static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot; +} + static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -1223,8 +1247,8 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) ULONG i; ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext)); - if (ctx == NULL) { - return SDL_OutOfMemory(); + if (!ctx) { + return -1; } joystick->hwdata = ctx; @@ -1236,7 +1260,7 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) #ifdef SDL_JOYSTICK_RAWINPUT_XINPUT xinput_device_change = SDL_TRUE; ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, SDL_TRUE); - if (ctx->xinput_enabled && (WIN_LoadXInputDLL() < 0 || XINPUTGETSTATE == NULL)) { + if (ctx->xinput_enabled && (WIN_LoadXInputDLL() < 0 || !XINPUTGETSTATE)) { ctx->xinput_enabled = SDL_FALSE; } ctx->xinput_slot = XUSER_INDEX_ANY; @@ -1256,7 +1280,7 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data)); if (!ctx->data) { RAWINPUT_JoystickClose(joystick); - return SDL_OutOfMemory(); + return -1; } if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) { @@ -1301,7 +1325,7 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices)); if (!ctx->button_indices) { RAWINPUT_JoystickClose(joystick); - return SDL_OutOfMemory(); + return -1; } for (i = 0; i < caps.NumberInputButtonCaps; ++i) { @@ -1354,7 +1378,7 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices)); if (!ctx->axis_indices) { RAWINPUT_JoystickClose(joystick); - return SDL_OutOfMemory(); + return -1; } for (i = 0; i < caps.NumberInputValueCaps; ++i) { @@ -1387,7 +1411,7 @@ static int RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices)); if (!ctx->hat_indices) { RAWINPUT_JoystickClose(joystick); - return SDL_OutOfMemory(); + return -1; } for (i = 0; i < caps.NumberInputValueCaps; ++i) { @@ -1422,7 +1446,7 @@ static int RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_ if (!rumbled && ctx->xinput_correlated) { XINPUT_VIBRATION XVibration; - if (XINPUTSETSTATE == NULL) { + if (!XINPUTSETSTATE) { return SDL_Unsupported(); } @@ -1552,10 +1576,10 @@ static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int #ifdef SDL_JOYSTICK_RAWINPUT_MATCHING /* Map new buttons and axes into game controller controls */ static const int button_map[] = { - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_B, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_Y, + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, SDL_GAMEPAD_BUTTON_BACK, @@ -1779,7 +1803,7 @@ static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick) } } if (!ctx->wgi_correlated) { - SDL_bool new_correlation_count = 0; + Uint8 new_correlation_count = 0; if (RAWINPUT_MissingWindowsGamingInputSlot()) { Uint8 correlation_id = 0; WindowsGamingInputGamepadState *slot_idx = NULL; @@ -2194,6 +2218,7 @@ SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = { RAWINPUT_JoystickDetect, RAWINPUT_JoystickGetDeviceName, RAWINPUT_JoystickGetDevicePath, + RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, RAWINPUT_JoystickGetDevicePlayerIndex, RAWINPUT_JoystickSetDevicePlayerIndex, RAWINPUT_JoystickGetDeviceGUID, diff --git a/src/joystick/windows/SDL_rawinputjoystick_c.h b/src/joystick/windows/SDL_rawinputjoystick_c.h index 534ae088..d4f9e79f 100644 --- a/src/joystick/windows/SDL_rawinputjoystick_c.h +++ b/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/joystick/windows/SDL_windows_gaming_input.c b/src/joystick/windows/SDL_windows_gaming_input.c index 68c5e371..c79cf8e7 100644 --- a/src/joystick/windows/SDL_windows_gaming_input.c +++ b/src/joystick/windows/SDL_windows_gaming_input.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef ____FIReference_1_INT32_INTERFACE_DEFINED__ /* MinGW-64 uses __FIReference_1_INT32 instead of Microsoft's __FIReference_1_int */ @@ -43,7 +44,7 @@ struct joystick_hwdata { __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller; - __x_ABI_CWindows_CGaming_CInput_CIGameController *gamecontroller; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller; __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo *battery; __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; @@ -57,14 +58,23 @@ typedef struct WindowsGamingInputControllerState char *name; SDL_JoystickGUID guid; SDL_JoystickType type; - int naxes; - int nhats; - int nbuttons; + int steam_virtual_gamepad_slot; } WindowsGamingInputControllerState; +typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(PVOID *pCookie); +typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); +typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); +typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string); +typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *length); + static struct { - __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *statics; + CoIncrementMTAUsage_t CoIncrementMTAUsage; + RoGetActivationFactory_t RoGetActivationFactory; + WindowsCreateStringReference_t WindowsCreateStringReference; + WindowsDeleteString_t WindowsDeleteString; + WindowsGetStringRawBuffer_t WindowsGetStringRawBuffer; + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *controller_statics; __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics *arcade_stick_statics; __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2 *arcade_stick_statics2; __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics *flight_stick_statics; @@ -75,31 +85,25 @@ static struct EventRegistrationToken controller_added_token; EventRegistrationToken controller_removed_token; int controller_count; - SDL_bool ro_initialized; WindowsGamingInputControllerState *controllers; } wgi; -static const IID IID_IRawGameControllerStatics = { 0xEB8D0792, 0xE95A, 0x4B19, { 0xAF, 0xC7, 0x0A, 0x59, 0xF8, 0xBF, 0x75, 0x9E } }; -static const IID IID_IRawGameController = { 0x7CAD6D91, 0xA7E1, 0x4F71, { 0x9A, 0x78, 0x33, 0xE9, 0xC5, 0xDF, 0xEA, 0x62 } }; -static const IID IID_IRawGameController2 = { 0x43C0C035, 0xBB73, 0x4756, { 0xA7, 0x87, 0x3E, 0xD6, 0xBE, 0xA6, 0x17, 0xBD } }; -static const IID IID_IEventHandler_RawGameController = { 0x00621c22, 0x42e8, 0x529f, { 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72 } }; -static const IID IID_IGameController = { 0x1BAF6522, 0x5F64, 0x42C5, { 0x82, 0x67, 0xB9, 0xFE, 0x22, 0x15, 0xBF, 0xBD } }; -static const IID IID_IGameControllerBatteryInfo = { 0xDCECC681, 0x3963, 0x4DA6, { 0x95, 0x5D, 0x55, 0x3F, 0x3B, 0x6F, 0x61, 0x61 } }; -static const IID IID_IArcadeStickStatics = { 0x5C37B8C8, 0x37B1, 0x4AD8, { 0x94, 0x58, 0x20, 0x0F, 0x1A, 0x30, 0x01, 0x8E } }; -static const IID IID_IArcadeStickStatics2 = { 0x52B5D744, 0xBB86, 0x445A, { 0xB5, 0x9C, 0x59, 0x6F, 0x0E, 0x2A, 0x49, 0xDF } }; -/*static const IID IID_IArcadeStick = { 0xB14A539D, 0xBEFB, 0x4C81, { 0x80, 0x51, 0x15, 0xEC, 0xF3, 0xB1, 0x30, 0x36 } };*/ -static const IID IID_IFlightStickStatics = { 0x5514924A, 0xFECC, 0x435E, { 0x83, 0xDC, 0x5C, 0xEC, 0x8A, 0x18, 0xA5, 0x20 } }; -/*static const IID IID_IFlightStick = { 0xB4A2C01C, 0xB83B, 0x4459, { 0xA1, 0xA9, 0x97, 0xB0, 0x3C, 0x33, 0xDA, 0x7C } };*/ -static const IID IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } }; -static const IID IID_IGamepadStatics2 = { 0x42676DC5, 0x0856, 0x47C4, { 0x92, 0x13, 0xB3, 0x95, 0x50, 0x4C, 0x3A, 0x3C } }; -/*static const IID IID_IGamepad = { 0xBC7BB43C, 0x0A69, 0x3903, { 0x9E, 0x9D, 0xA5, 0x0F, 0x86, 0xA4, 0x5D, 0xE5 } };*/ -static const IID IID_IRacingWheelStatics = { 0x3AC12CD5, 0x581B, 0x4936, { 0x9F, 0x94, 0x69, 0xF1, 0xE6, 0x51, 0x4C, 0x7D } }; -static const IID IID_IRacingWheelStatics2 = { 0xE666BCAA, 0xEDFD, 0x4323, { 0xA9, 0xF6, 0x3C, 0x38, 0x40, 0x48, 0xD1, 0xED } }; -/*static const IID IID_IRacingWheel = { 0xF546656F, 0xE106, 0x4C82, { 0xA9, 0x0F, 0x55, 0x40, 0x12, 0x90, 0x4B, 0x85 } };*/ - -typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); -typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); - +/* WinRT headers in official Windows SDK contain only declarations, and we have to define these GUIDs ourselves. + * https://stackoverflow.com/a/55605485/1795050 + */ +DEFINE_GUID(IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController, 0x00621c22, 0x42e8, 0x529f, 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, 0x5c37b8c8, 0x37b1, 0x4ad8, 0x94, 0x58, 0x20, 0x0f, 0x1a, 0x30, 0x01, 0x8e); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, 0x52b5d744, 0xbb86, 0x445a, 0xb5, 0x9c, 0x59, 0x6f, 0x0e, 0x2a, 0x49, 0xdf); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, 0x5514924a, 0xfecc, 0x435e, 0x83, 0xdc, 0x5c, 0xec, 0x8a, 0x18, 0xa5, 0x20); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameController, 0x1baf6522, 0x5f64, 0x42c5, 0x82, 0x67, 0xb9, 0xfe, 0x22, 0x15, 0xbf, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, 0xdcecc681, 0x3963, 0x4da6, 0x95, 0x5d, 0x55, 0x3f, 0x3b, 0x6f, 0x61, 0x61); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, 0x8bbce529, 0xd49c, 0x39e9, 0x95, 0x60, 0xe4, 0x7d, 0xde, 0x96, 0xb7, 0xc8); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, 0x42676dc5, 0x0856, 0x47c4, 0x92, 0x13, 0xb3, 0x95, 0x50, 0x4c, 0x3a, 0x3c); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, 0x3ac12cd5, 0x581b, 0x4936, 0x9f, 0x94, 0x69, 0xf1, 0xe6, 0x51, 0x4c, 0x7d); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, 0xe666bcaa, 0xedfd, 0x4323, 0xa9, 0xf6, 0x3c, 0x38, 0x40, 0x48, 0xd1, 0xed); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, 0x7cad6d91, 0xa7e1, 0x4f71, 0x9a, 0x78, 0x33, 0xe9, 0xc5, 0xdf, 0xea, 0x62); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, 0x43c0c035, 0xbb73, 0x4756, 0xa7, 0x87, 0x3e, 0xd6, 0xbe, 0xa6, 0x17, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, 0xeb8d0792, 0xe95a, 0x4b19, 0xaf, 0xc7, 0x0a, 0x59, 0xf8, 0xbf, 0x75, 0x9e); extern SDL_bool SDL_XINPUT_Enabled(void); extern SDL_bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version); @@ -127,12 +131,12 @@ static SDL_bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product) } raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count); - if (raw_devices == NULL) { - SDL_OutOfMemory(); + if (!raw_devices) { return SDL_FALSE; } - if (GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { + raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST)); + if (raw_device_count == (UINT)-1) { SDL_free(raw_devices); raw_devices = NULL; return SDL_FALSE; /* oh well. */ @@ -140,7 +144,7 @@ static SDL_bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product) for (i = 0; i < raw_device_count; i++) { RID_DEVICE_INFO rdi; - char devName[MAX_PATH]; + char devName[MAX_PATH] = { 0 }; UINT rdiSize = sizeof(rdi); UINT nameSize = SDL_arraysize(devName); DEVINST devNode; @@ -212,100 +216,73 @@ static SDL_bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product) static void WGI_LoadRawGameControllerStatics() { - WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = NULL; - RoGetActivationFactory_t RoGetActivationFactoryFunc = NULL; HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; -#ifdef __WINRT__ - WindowsCreateStringReferenceFunc = WindowsCreateStringReference; - RoGetActivationFactoryFunc = RoGetActivationFactory; -#else - { - WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); - RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); - } -#endif /* __WINRT__ */ - if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) { - PCWSTR pNamespace; - HSTRING_HEADER hNamespaceStringHeader; - HSTRING hNamespaceString; - - pNamespace = L"Windows.Gaming.Input.RawGameController"; - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); - if (SUCCEEDED(hr)) { - hr = RoGetActivationFactoryFunc(hNamespaceString, &IID_IRawGameControllerStatics, (void **)&wgi.statics); - if (!SUCCEEDED(hr)) { - SDL_SetError("Couldn't find IRawGameControllerStatics: 0x%lx", hr); - } + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RawGameController, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RawGameController), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, (void **)&wgi.controller_statics); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRawGameControllerStatics", hr); } } } static void WGI_LoadOtherControllerStatics() { - WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = NULL; - RoGetActivationFactory_t RoGetActivationFactoryFunc = NULL; HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; -#ifdef __WINRT__ - WindowsCreateStringReferenceFunc = WindowsCreateStringReference; - RoGetActivationFactoryFunc = RoGetActivationFactory; -#else - { - WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); - RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); + if (!wgi.arcade_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_ArcadeStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_ArcadeStick), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, (void **)&wgi.arcade_stick_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IArcadeStickStatics", hr); + } + } } -#endif /* __WINRT__ */ - if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) { - PCWSTR pNamespace; - HSTRING_HEADER hNamespaceStringHeader; - HSTRING hNamespaceString; - pNamespace = L"Windows.Gaming.Input.ArcadeStick"; - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); + if (!wgi.flight_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_FlightStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_FlightStick), &class_name_header, &class_name); if (SUCCEEDED(hr)) { - hr = RoGetActivationFactoryFunc(hNamespaceString, &IID_IArcadeStickStatics, (void **)&wgi.arcade_stick_statics); - if (SUCCEEDED(hr)) { - __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID_IArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2); - } else { - SDL_SetError("Couldn't find IID_IArcadeStickStatics: 0x%lx", hr); - } - } - - pNamespace = L"Windows.Gaming.Input.FlightStick"; - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); - if (SUCCEEDED(hr)) { - hr = RoGetActivationFactoryFunc(hNamespaceString, &IID_IFlightStickStatics, (void **)&wgi.flight_stick_statics); + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, (void **)&wgi.flight_stick_statics); if (!SUCCEEDED(hr)) { - SDL_SetError("Couldn't find IID_IFlightStickStatics: 0x%lx", hr); + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IFlightStickStatics", hr); } } + } - pNamespace = L"Windows.Gaming.Input.Gamepad"; - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); + if (!wgi.gamepad_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_Gamepad), &class_name_header, &class_name); if (SUCCEEDED(hr)) { - hr = RoGetActivationFactoryFunc(hNamespaceString, &IID_IGamepadStatics, (void **)&wgi.gamepad_statics); + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void **)&wgi.gamepad_statics); if (SUCCEEDED(hr)) { - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID_IGamepadStatics2, (void **)&wgi.gamepad_statics2); + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, (void **)&wgi.gamepad_statics2); } else { - SDL_SetError("Couldn't find IGamepadStatics: 0x%lx", hr); + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IGamepadStatics", hr); } } + } - pNamespace = L"Windows.Gaming.Input.RacingWheel"; - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); + if (!wgi.racing_wheel_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RacingWheel, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RacingWheel), &class_name_header, &class_name); if (SUCCEEDED(hr)) { - hr = RoGetActivationFactoryFunc(hNamespaceString, &IID_IRacingWheelStatics, (void **)&wgi.racing_wheel_statics); + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, (void **)&wgi.racing_wheel_statics); if (SUCCEEDED(hr)) { - __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID_IRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2); + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2); } else { - SDL_SetError("Couldn't find IRacingWheelStatics: 0x%lx", hr); + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRacingWheelStatics", hr); } } } } -static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *gamecontroller) +static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller) { __x_ABI_CWindows_CGaming_CInput_CIArcadeStick *arcade_stick = NULL; __x_ABI_CWindows_CGaming_CInput_CIFlightStick *flight_stick = NULL; @@ -317,22 +294,22 @@ static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CI */ WGI_LoadOtherControllerStatics(); - if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, gamecontroller, &gamepad)) && gamepad) { + if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, game_controller, &gamepad)) && gamepad) { __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); return SDL_JOYSTICK_TYPE_GAMEPAD; } - if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, gamecontroller, &arcade_stick)) && arcade_stick) { + if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, game_controller, &arcade_stick)) && arcade_stick) { __x_ABI_CWindows_CGaming_CInput_CIArcadeStick_Release(arcade_stick); return SDL_JOYSTICK_TYPE_ARCADE_STICK; } - if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, gamecontroller, &flight_stick)) && flight_stick) { + if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, game_controller, &flight_stick)) && flight_stick) { __x_ABI_CWindows_CGaming_CInput_CIFlightStick_Release(flight_stick); return SDL_JOYSTICK_TYPE_FLIGHT_STICK; } - if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, gamecontroller, &racing_wheel)) && racing_wheel) { + if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, game_controller, &racing_wheel)) && racing_wheel) { __x_ABI_CWindows_CGaming_CInput_CIRacingWheel_Release(racing_wheel); return SDL_JOYSTICK_TYPE_WHEEL; } @@ -348,12 +325,12 @@ typedef struct RawGameControllerDelegate static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, REFIID riid, void **ppvObject) { - if (ppvObject == NULL) { + if (!ppvObject) { return E_INVALIDARG; } *ppvObject = NULL; - if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_RawGameController)) { + if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController)) { *ppvObject = This; __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController_AddRef(This); return S_OK; @@ -380,6 +357,34 @@ static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FI return rc; } +static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + char *id = WIN_StringToUTF8W(string); + if (id) { + (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot); + SDL_free(id); + } + } + wgi.WindowsDeleteString(hString); + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); + } + } + return slot; +} + static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e) { HRESULT hr; @@ -393,7 +398,7 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde return S_OK; } - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID_IRawGameController, (void **)&controller); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); if (SUCCEEDED(hr)) { char *name = NULL; SDL_JoystickGUID guid = { 0 }; @@ -403,42 +408,43 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde Uint16 version = 0; SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN; __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; - __x_ABI_CWindows_CGaming_CInput_CIGameController *gamecontroller = NULL; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller = NULL; SDL_bool ignore_joystick = SDL_FALSE; __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareVendorId(controller, &vendor); __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareProductId(controller, &product); - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID_IRawGameController2, (void **)&controller2); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&game_controller); if (SUCCEEDED(hr)) { - typedef PCWSTR(WINAPI * WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 * length); - typedef HRESULT(WINAPI * WindowsDeleteString_t)(HSTRING string); + boolean wireless = 0; + hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(game_controller, &wireless); + if (SUCCEEDED(hr) && wireless) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; - WindowsGetStringRawBuffer_t WindowsGetStringRawBufferFunc = NULL; - WindowsDeleteString_t WindowsDeleteStringFunc = NULL; -#ifdef __WINRT__ - WindowsGetStringRawBufferFunc = WindowsGetStringRawBuffer; - WindowsDeleteStringFunc = WindowsDeleteString; -#else - { - WindowsGetStringRawBufferFunc = (WindowsGetStringRawBuffer_t)WIN_LoadComBaseFunction("WindowsGetStringRawBuffer"); - WindowsDeleteStringFunc = (WindowsDeleteString_t)WIN_LoadComBaseFunction("WindowsDeleteString"); - } -#endif /* __WINRT__ */ - if (WindowsGetStringRawBufferFunc && WindowsDeleteStringFunc) { - HSTRING hString; - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString); - if (SUCCEEDED(hr)) { - PCWSTR string = WindowsGetStringRawBufferFunc(hString, NULL); - if (string) { - name = WIN_StringToUTF8W(string); - } - WindowsDeleteStringFunc(hString); + /* Fixup for Wireless Xbox 360 Controller */ + if (product == 0) { + vendor = USB_VENDOR_MICROSOFT; + product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; } } + + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(game_controller); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + name = WIN_StringToUTF8W(string); + } + wgi.WindowsDeleteString(hString); + } __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); } - if (name == NULL) { + if (!name) { name = SDL_strdup(""); } @@ -463,21 +469,11 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde } if (!ignore_joystick) { - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID_IGameController, (void **)&gamecontroller); - if (SUCCEEDED(hr)) { - boolean wireless; - - type = GetGameControllerType(gamecontroller); - - hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(gamecontroller, &wireless); - if (SUCCEEDED(hr) && wireless) { - bus = SDL_HARDWARE_BUS_BLUETOOTH; - } - - __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(gamecontroller); + if (game_controller) { + type = GetGameControllerType(game_controller); } - guid = SDL_CreateJoystickGUID(bus, vendor, product, version, name, 'w', (Uint8)type); + guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, name, 'w', (Uint8)type); if (SDL_ShouldIgnoreJoystick(name, guid)) { ignore_joystick = SDL_TRUE; @@ -489,7 +485,7 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde WindowsGamingInputControllerState *controllers = SDL_realloc(wgi.controllers, sizeof(wgi.controllers[0]) * (wgi.controller_count + 1)); if (controllers) { WindowsGamingInputControllerState *state = &controllers[wgi.controller_count]; - SDL_JoystickID joystickID = SDL_GetNextJoystickInstanceID(); + SDL_JoystickID joystickID = SDL_GetNextObjectID(); SDL_zerop(state); state->instance_id = joystickID; @@ -497,10 +493,7 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde state->name = name; state->guid = guid; state->type = type; - - __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(controller, &state->nbuttons); - __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(controller, &state->naxes); - __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(controller, &state->nhats); + state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product); __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller); @@ -536,7 +529,7 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeRemo return S_OK; } - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID_IRawGameController, (void **)&controller); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); if (SUCCEEDED(hr)) { int i; @@ -609,7 +602,22 @@ static int WGI_JoystickInit(void) if (FAILED(WIN_RoInitialize())) { return SDL_SetError("RoInitialize() failed"); } - wgi.ro_initialized = SDL_TRUE; + +#ifdef __WINRT__ + wgi.CoIncrementMTAUsage = CoIncrementMTAUsage; + wgi.RoGetActivationFactory = RoGetActivationFactory; + wgi.WindowsCreateStringReference = WindowsCreateStringReference; + wgi.WindowsDeleteString = WindowsDeleteString; + wgi.WindowsGetStringRawBuffer = WindowsGetStringRawBuffer; +#else +#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x); + RESOLVE(CoIncrementMTAUsage); + RESOLVE(RoGetActivationFactory); + RESOLVE(WindowsCreateStringReference); + RESOLVE(WindowsDeleteString); + RESOLVE(WindowsGetStringRawBuffer); +#undef RESOLVE +#endif /* __WINRT__ */ #ifndef __WINRT__ { @@ -620,15 +628,9 @@ static int WGI_JoystickInit(void) */ static PVOID cookie = NULL; if (!cookie) { - typedef HRESULT(WINAPI * CoIncrementMTAUsage_t)(PVOID * pCookie); - CoIncrementMTAUsage_t CoIncrementMTAUsageFunc = (CoIncrementMTAUsage_t)WIN_LoadComBaseFunction("CoIncrementMTAUsage"); - if (CoIncrementMTAUsageFunc) { - if (FAILED(CoIncrementMTAUsageFunc(&cookie))) { - return SDL_SetError("CoIncrementMTAUsage() failed"); - } - } else { - /* CoIncrementMTAUsage() is present since Win8, so we should never make it here. */ - return SDL_SetError("CoIncrementMTAUsage() not found"); + hr = wgi.CoIncrementMTAUsage(&cookie); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("CoIncrementMTAUsage() failed", hr); } } } @@ -636,20 +638,20 @@ static int WGI_JoystickInit(void) WGI_LoadRawGameControllerStatics(); - if (wgi.statics) { + if (wgi.controller_statics) { __FIVectorView_1_Windows__CGaming__CInput__CRawGameController *controllers; - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.statics, &controller_added.iface, &wgi.controller_added_token); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.controller_statics, &controller_added.iface, &wgi.controller_added_token); if (!SUCCEEDED(hr)) { - SDL_SetError("add_RawGameControllerAdded() failed: 0x%lx\n", hr); + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerAdded failed", hr); } - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.statics, &controller_removed.iface, &wgi.controller_removed_token); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.controller_statics, &controller_removed.iface, &wgi.controller_removed_token); if (!SUCCEEDED(hr)) { - SDL_SetError("add_RawGameControllerRemoved() failed: 0x%lx\n", hr); + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerRemoved failed", hr); } - hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.statics, &controllers); + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.controller_statics, &controllers); if (SUCCEEDED(hr)) { unsigned i, count = 0; @@ -692,6 +694,11 @@ static const char *WGI_JoystickGetDevicePath(int device_index) return NULL; } +static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return wgi.controllers[device_index].steam_virtual_gamepad_slot; +} + static int WGI_JoystickGetDevicePlayerIndex(int device_index) { return -1; @@ -718,67 +725,30 @@ static int WGI_JoystickOpen(SDL_Joystick *joystick, int device_index) boolean wireless = SDL_FALSE; hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); - if (hwdata == NULL) { - return SDL_OutOfMemory(); + if (!hwdata) { + return -1; } joystick->hwdata = hwdata; hwdata->controller = state->controller; __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(hwdata->controller); - __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID_IGameController, (void **)&hwdata->gamecontroller); - __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID_IGameControllerBatteryInfo, (void **)&hwdata->battery); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&hwdata->game_controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, (void **)&hwdata->battery); if (wgi.gamepad_statics2) { - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->gamecontroller, &hwdata->gamepad); + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->game_controller, &hwdata->gamepad); } - if (hwdata->gamecontroller) { - __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->gamecontroller, &wireless); + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->game_controller, &wireless); } /* Initialize the joystick capabilities */ - joystick->nbuttons = state->nbuttons; - joystick->naxes = state->naxes; - joystick->nhats = state->nhats; joystick->epowerlevel = wireless ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(hwdata->controller, &joystick->nbuttons); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(hwdata->controller, &joystick->naxes); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(hwdata->controller, &joystick->nhats); - if (wireless && hwdata->battery) { - HRESULT hr; - __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report; - - hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report); - if (SUCCEEDED(hr) && report) { - int full_capacity = 0, curr_capacity = 0; - __FIReference_1_int *full_capacityP, *curr_capacityP; - - hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP); - if (SUCCEEDED(hr)) { - __FIReference_1_int_get_Value(full_capacityP, &full_capacity); - __FIReference_1_int_Release(full_capacityP); - } - - hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP); - if (SUCCEEDED(hr)) { - __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity); - __FIReference_1_int_Release(curr_capacityP); - } - - if (full_capacity > 0) { - float ratio = (float)curr_capacity / full_capacity; - - if (ratio <= 0.05f) { - joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; - } else if (ratio <= 0.20f) { - joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; - } else if (ratio <= 0.70f) { - joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; - } else { - joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; - } - } - __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report); - } - } return 0; } @@ -789,13 +759,14 @@ static int WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumbl if (hwdata->gamepad) { HRESULT hr; + /* Note: reusing partially filled vibration data struct */ hwdata->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; hwdata->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); if (SUCCEEDED(hr)) { return 0; } else { - return SDL_SetError("Setting vibration failed: 0x%lx\n", hr); + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); } } else { return SDL_Unsupported(); @@ -809,13 +780,14 @@ static int WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble if (hwdata->gamepad) { HRESULT hr; + /* Note: reusing partially filled vibration data struct */ hwdata->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; hwdata->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); if (SUCCEEDED(hr)) { return 0; } else { - return SDL_SetError("Setting vibration failed: 0x%lx\n", hr); + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); } } else { return SDL_Unsupported(); @@ -932,6 +904,43 @@ static void WGI_JoystickUpdate(SDL_Joystick *joystick) SDL_stack_free(buttons); SDL_stack_free(hats); SDL_stack_free(axes); + + if (joystick->epowerlevel != SDL_JOYSTICK_POWER_WIRED && hwdata->battery) { + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report = NULL; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report); + if (SUCCEEDED(hr) && report) { + int full_capacity = 0, curr_capacity = 0; + __FIReference_1_int *full_capacityP, *curr_capacityP; + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(full_capacityP, &full_capacity); + __FIReference_1_int_Release(full_capacityP); + } + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity); + __FIReference_1_int_Release(curr_capacityP); + } + + if (full_capacity > 0) { + float ratio = (float)curr_capacity / full_capacity; + + if (ratio <= 0.05f) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; + } else if (ratio <= 0.20f) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; + } else if (ratio <= 0.70f) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; + } else { + joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; + } + } + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report); + } + } } static void WGI_JoystickClose(SDL_Joystick *joystick) @@ -942,8 +951,8 @@ static void WGI_JoystickClose(SDL_Joystick *joystick) if (hwdata->controller) { __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(hwdata->controller); } - if (hwdata->gamecontroller) { - __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->gamecontroller); + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->game_controller); } if (hwdata->battery) { __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_Release(hwdata->battery); @@ -958,7 +967,7 @@ static void WGI_JoystickClose(SDL_Joystick *joystick) static void WGI_JoystickQuit(void) { - if (wgi.statics) { + if (wgi.controller_statics) { while (wgi.controller_count > 0) { IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller); } @@ -988,14 +997,12 @@ static void WGI_JoystickQuit(void) __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_Release(wgi.racing_wheel_statics2); } - __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.statics, wgi.controller_added_token); - __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.statics, wgi.controller_removed_token); - __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.statics); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.controller_statics, wgi.controller_added_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.controller_statics, wgi.controller_removed_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics); } - if (wgi.ro_initialized) { - WIN_RoUninitialize(); - } + WIN_RoUninitialize(); SDL_zero(wgi); } @@ -1011,6 +1018,7 @@ SDL_JoystickDriver SDL_WGI_JoystickDriver = { WGI_JoystickDetect, WGI_JoystickGetDeviceName, WGI_JoystickGetDevicePath, + WGI_JoystickGetDeviceSteamVirtualGamepadSlot, WGI_JoystickGetDevicePlayerIndex, WGI_JoystickSetDevicePlayerIndex, WGI_JoystickGetDeviceGUID, diff --git a/src/joystick/windows/SDL_windowsjoystick.c b/src/joystick/windows/SDL_windowsjoystick.c index cfb8908a..11aa16a9 100644 --- a/src/joystick/windows/SDL_windowsjoystick.c +++ b/src/joystick/windows/SDL_windowsjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -176,7 +176,7 @@ static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID static void SDL_CleanupDeviceNotificationFunc(void) { if (cfgmgr32_lib_handle) { - if (s_DeviceNotificationFuncHandle != NULL && CM_Unregister_Notification != NULL) { + if (s_DeviceNotificationFuncHandle != NULL && CM_Unregister_Notification) { CM_Unregister_Notification(s_DeviceNotificationFuncHandle); s_DeviceNotificationFuncHandle = NULL; } @@ -294,7 +294,7 @@ static int SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data) } data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); - if (data->messageWindow == NULL) { + if (!data->messageWindow) { WIN_SetError("Failed to create message window for joystick autodetect"); SDL_CleanupDeviceNotification(data); return -1; @@ -336,7 +336,7 @@ static SDL_bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, } } SDL_LockMutex(mutex); - return (lastret != -1) ? SDL_TRUE : SDL_FALSE; + return (lastret != -1); } #endif /* !defined(__WINRT__) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__) */ @@ -377,7 +377,7 @@ static int SDLCALL SDL_JoystickThread(void *_data) for (userId = 0; userId < XUSER_MAX_COUNT; userId++) { XINPUT_CAPABILITIES capabilities; const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities); - const SDL_bool available = (result == ERROR_SUCCESS) ? SDL_TRUE : SDL_FALSE; + const SDL_bool available = (result == ERROR_SUCCESS); if (bOpenedXInputDevices[userId] != available) { s_bWindowsDeviceChanged = SDL_TRUE; bOpenedXInputDevices[userId] = available; @@ -404,18 +404,18 @@ static int SDLCALL SDL_JoystickThread(void *_data) static int SDL_StartJoystickThread(void) { s_mutexJoyStickEnum = SDL_CreateMutex(); - if (s_mutexJoyStickEnum == NULL) { + if (!s_mutexJoyStickEnum) { return -1; } s_condJoystickThread = SDL_CreateCondition(); - if (s_condJoystickThread == NULL) { + if (!s_condJoystickThread) { return -1; } s_bJoystickThreadQuit = SDL_FALSE; s_joystickThread = SDL_CreateThreadInternal(SDL_JoystickThread, "SDL_joystick", 64 * 1024, NULL); - if (s_joystickThread == NULL) { + if (!s_joystickThread) { return -1; } return 0; @@ -423,7 +423,7 @@ static int SDL_StartJoystickThread(void) static void SDL_StopJoystickThread(void) { - if (s_joystickThread == NULL) { + if (!s_joystickThread) { return; } @@ -453,7 +453,7 @@ static void SDL_StopJoystickThread(void) void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device) { device->send_add_event = SDL_TRUE; - device->nInstanceID = SDL_GetNextJoystickInstanceID(); + device->nInstanceID = SDL_GetNextObjectID(); device->pNext = SYS_Joystick; SYS_Joystick = device; } @@ -612,6 +612,23 @@ static const char *WINDOWS_JoystickGetDevicePath(int device_index) return device->path; } +static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + if (device->bXInputDevice) { + /* The slot for XInput devices can change as controllers are seated */ + return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId); + } else { + return device->steam_virtual_gamepad_slot; + } +} + static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) { JoyStick_DeviceData *device = SYS_Joystick; @@ -669,13 +686,10 @@ static int WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index) } /* allocate memory for system specific hardware data */ - joystick->instance_id = device->nInstanceID; - joystick->hwdata = - (struct joystick_hwdata *)SDL_malloc(sizeof(struct joystick_hwdata)); - if (joystick->hwdata == NULL) { - return SDL_OutOfMemory(); + joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata)); + if (!joystick->hwdata) { + return -1; } - SDL_zerop(joystick->hwdata); joystick->hwdata->guid = device->guid; if (device->bXInputDevice) { @@ -794,6 +808,7 @@ SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = { WINDOWS_JoystickDetect, WINDOWS_JoystickGetDeviceName, WINDOWS_JoystickGetDevicePath, + WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot, WINDOWS_JoystickGetDevicePlayerIndex, WINDOWS_JoystickSetDevicePlayerIndex, WINDOWS_JoystickGetDeviceGUID, diff --git a/src/joystick/windows/SDL_windowsjoystick_c.h b/src/joystick/windows/SDL_windowsjoystick_c.h index 9cd70108..cea67f91 100644 --- a/src/joystick/windows/SDL_windowsjoystick_c.h +++ b/src/joystick/windows/SDL_windowsjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,6 +42,7 @@ typedef struct JoyStick_DeviceData Uint8 XInputUserId; DIDEVICEINSTANCE dxdevice; char path[MAX_PATH]; + int steam_virtual_gamepad_slot; struct JoyStick_DeviceData *pNext; } JoyStick_DeviceData; diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c index debc6d44..42a5f41e 100644 --- a/src/joystick/windows/SDL_xinputjoystick.c +++ b/src/joystick/windows/SDL_xinputjoystick.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -38,7 +38,6 @@ extern "C" { * Internal stuff. */ static SDL_bool s_bXInputEnabled = SDL_TRUE; -static char *s_arrXInputDevicePath[XUSER_MAX_COUNT]; static SDL_bool SDL_XInputUseOldJoystickMapping() { @@ -77,156 +76,85 @@ static const char *GetXInputName(const Uint8 userid, BYTE SubType) static char name[32]; if (SDL_XInputUseOldJoystickMapping()) { - (void)SDL_snprintf(name, sizeof(name), "X360 Controller #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "X360 Controller #%d", 1 + userid); } else { switch (SubType) { case XINPUT_DEVSUBTYPE_GAMEPAD: - (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_WHEEL: - (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_ARCADE_STICK: - (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: - (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_DANCE_PAD: - (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_GUITAR: case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: case XINPUT_DEVSUBTYPE_GUITAR_BASS: - (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_DRUM_KIT: - (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid); break; case XINPUT_DEVSUBTYPE_ARCADE_PAD: - (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid); break; default: - (void)SDL_snprintf(name, sizeof(name), "XInput Device #%u", 1 + userid); + (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid); break; } } return name; } -/* We can't really tell what device is being used for XInput, but we can guess - and we'll be correct for the case where only one device is connected. - */ -static void GuessXInputDevice(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) +static SDL_bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) { -#if !defined(__WINRT__) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__) /* TODO: remove this ifndef __WINRT__ block, but only after integrating with UWP/WinRT's HID API */ - PRAWINPUTDEVICELIST devices = NULL; - UINT i, j, device_count = 0; + XINPUT_CAPABILITIES_EX capabilities; - if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) { - return; /* oh well. */ + if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) { + return SDL_FALSE; } - devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); - if (devices == NULL) { - return; + /* Fixup for Wireless Xbox 360 Controller */ + if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) { + capabilities.VendorId = USB_VENDOR_MICROSOFT; + capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; } - if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { - SDL_free(devices); - return; /* oh well. */ + if (pVID) { + *pVID = capabilities.VendorId; } - - /* First see if we have a cached entry for this index */ - if (s_arrXInputDevicePath[userid]) { - for (i = 0; i < device_count; i++) { - RID_DEVICE_INFO rdi; - char devName[128]; - UINT rdiSize = sizeof(rdi); - UINT nameSize = SDL_arraysize(devName); - - rdi.cbSize = sizeof(rdi); - if (devices[i].dwType == RIM_TYPEHID && - GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 && - GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) { - if (SDL_strcmp(devName, s_arrXInputDevicePath[userid]) == 0) { - *pVID = (Uint16)rdi.hid.dwVendorId; - *pPID = (Uint16)rdi.hid.dwProductId; - *pVersion = (Uint16)rdi.hid.dwVersionNumber; - SDL_free(devices); - return; - } - } - } + if (pPID) { + *pPID = capabilities.ProductId; } - - for (i = 0; i < device_count; i++) { - RID_DEVICE_INFO rdi; - char devName[MAX_PATH]; - UINT rdiSize = sizeof(rdi); - UINT nameSize = SDL_arraysize(devName); - - rdi.cbSize = sizeof(rdi); - if (devices[i].dwType == RIM_TYPEHID && - GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 && - GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) { -#ifdef DEBUG_JOYSTICK - SDL_Log("Raw input device: VID = 0x%x, PID = 0x%x, %s\n", rdi.hid.dwVendorId, rdi.hid.dwProductId, devName); -#endif - if (SDL_strstr(devName, "IG_") != NULL) { - SDL_bool found = SDL_FALSE; - for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) { - if (!s_arrXInputDevicePath[j]) { - continue; - } - if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) { - found = SDL_TRUE; - break; - } - } - if (found) { - /* We already have this device in our XInput device list */ - continue; - } - - /* We don't actually know if this is the right device for this - * userid, but we'll record it so we'll at least be consistent - * when the raw device list changes. - */ - if (rdi.hid.dwVendorId == USB_VENDOR_VALVE && - rdi.hid.dwProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { - /* Steam encodes the real device in the path */ - int realVID = rdi.hid.dwVendorId; - int realPID = rdi.hid.dwProductId; - (void)SDL_sscanf(devName, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%x&%x&", &realVID, &realPID); - *pVID = (Uint16)realVID; - *pPID = (Uint16)realPID; - *pVersion = 0; - } else { - *pVID = (Uint16)rdi.hid.dwVendorId; - *pPID = (Uint16)rdi.hid.dwProductId; - *pVersion = (Uint16)rdi.hid.dwVersionNumber; - } - if (s_arrXInputDevicePath[userid]) { - SDL_free(s_arrXInputDevicePath[userid]); - } - s_arrXInputDevicePath[userid] = SDL_strdup(devName); - SDL_free(devices); - return; - } - } + if (pVersion) { + *pVersion = capabilities.ProductVersion; } - SDL_free(devices); -#endif /* !__WINRT__ */ + return SDL_TRUE; +} - /* The device wasn't in the raw HID device list, it's probably Bluetooth */ - *pVID = 0x045e; /* Microsoft */ - *pPID = 0x02fd; /* XBox One S Bluetooth */ - *pVersion = 0; +int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid) +{ + XINPUT_CAPABILITIES_EX capabilities; + + if (XINPUTGETCAPABILITIESEX && + XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS && + capabilities.VendorId == USB_VENDOR_VALVE && + capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + return (int)capabilities.unk2; + } + return -1; } static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) { + const char *name = NULL; Uint16 vendor = 0; Uint16 product = 0; Uint16 version = 0; @@ -274,21 +202,21 @@ static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pC } pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); - if (pNewJoystick == NULL) { + if (!pNewJoystick) { return; /* better luck next time? */ } + name = GetXInputName(userid, SubType); + GetXInputDeviceInfo(userid, &vendor, &product, &version); pNewJoystick->bXInputDevice = SDL_TRUE; - pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, GetXInputName(userid, SubType)); + pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name); if (!pNewJoystick->joystickname) { SDL_free(pNewJoystick); return; /* better luck next time? */ } - (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%d", userid); + (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid); if (!SDL_XInputUseOldJoystickMapping()) { - GuessXInputDevice(userid, &vendor, &product, &version); - - pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, pNewJoystick->joystickname, 'x', SubType); + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType); } pNewJoystick->SubType = SubType; pNewJoystick->XInputUserId = userid; @@ -300,7 +228,7 @@ static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pC #ifdef SDL_JOYSTICK_HIDAPI /* Since we're guessing about the VID/PID, use a hard-coded VID/PID to represent XInput */ - if (HIDAPI_IsDevicePresent(USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER, version, pNewJoystick->joystickname)) { + if (HIDAPI_IsDevicePresent(USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX360_XUSB_CONTROLLER, version, pNewJoystick->joystickname)) { /* The HIDAPI driver is taking care of this device */ SDL_free(pNewJoystick); return; @@ -318,14 +246,6 @@ static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pC WINDOWS_AddJoystickDevice(pNewJoystick); } -static void DelXInputDevice(Uint8 userid) -{ - if (s_arrXInputDevicePath[userid]) { - SDL_free(s_arrXInputDevicePath[userid]); - s_arrXInputDevicePath[userid] = NULL; - } -} - void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) { int iuserid; @@ -339,21 +259,7 @@ void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) const Uint8 userid = (Uint8)iuserid; XINPUT_CAPABILITIES capabilities; if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { - /* Adding a new device, must handle all removes first, or GuessXInputDevice goes terribly wrong (returns - a product/vendor ID that is not even attached to the system) when we get a remove and add on the same tick - (e.g. when disconnecting a device and the OS reassigns which userid an already-attached controller is) - */ - int iuserid2; - for (iuserid2 = iuserid - 1; iuserid2 >= 0; iuserid2--) { - const Uint8 userid2 = (Uint8)iuserid2; - XINPUT_CAPABILITIES capabilities2; - if (XINPUTGETCAPABILITIES(userid2, XINPUT_FLAG_GAMEPAD, &capabilities2) != ERROR_SUCCESS) { - DelXInputDevice(userid2); - } - } AddXInputDevice(userid, capabilities.SubType, pContext); - } else { - DelXInputDevice(userid); } } } @@ -377,7 +283,7 @@ int SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystic return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?"); } SDL_zero(state); - joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS) ? SDL_TRUE : SDL_FALSE; + joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS); joystick->hwdata->userid = userId; /* The XInput API has a hard coded button/axis mapping, so we just match it */ @@ -420,7 +326,7 @@ static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPU } } -static void UpdateXInputJoystickState_OLD(SDL_Joystick *joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +static void UpdateXInputJoystickState_OLD(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) { static WORD s_XInputButtons[] = { XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT, @@ -447,7 +353,7 @@ static void UpdateXInputJoystickState_OLD(SDL_Joystick *joystick, XINPUT_STATE_E UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); } -static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) { static WORD s_XInputButtons[] = { XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, @@ -511,8 +417,8 @@ Uint32 SDL_XINPUT_JoystickGetCapabilities(SDL_Joystick *joystick) void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) { - HRESULT result; - XINPUT_STATE_EX XInputState; + DWORD result; + XINPUT_STATE XInputState; XINPUT_BATTERY_INFORMATION_EX XBatteryInformation; if (!XINPUTGETSTATE) { diff --git a/src/joystick/windows/SDL_xinputjoystick_c.h b/src/joystick/windows/SDL_xinputjoystick_c.h index 1ce467cb..18008638 100644 --- a/src/joystick/windows/SDL_xinputjoystick_c.h +++ b/src/joystick/windows/SDL_xinputjoystick_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,6 +36,7 @@ extern Uint32 SDL_XINPUT_JoystickGetCapabilities(SDL_Joystick *joystick); extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick); extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick); extern void SDL_XINPUT_JoystickQuit(void); +extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/libm/e_exp.c b/src/libm/e_exp.c index 19f8ff7d..a4c8cc89 100644 --- a/src/libm/e_exp.c +++ b/src/libm/e_exp.c @@ -98,6 +98,13 @@ P3 = 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ P4 = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ P5 = 4.13813679705723846039e-08; /* 0x3E663769, 0x72BEA4D0 */ +union { + Uint64 u64; + double d; +} inf_union = { + 0x7ff0000000000000 /* Binary representation of a 64-bit infinite double (sign=0, exponent=2047, mantissa=0) */ +}; + double __ieee754_exp(double x) /* default IEEE double exp */ { double y; @@ -123,6 +130,8 @@ double __ieee754_exp(double x) /* default IEEE double exp */ else return (xsb==0)? x:0.0; /* exp(+-inf)={inf,0} */ } #if 1 + if(x > o_threshold) return inf_union.d; /* overflow */ + #elif 1 if(x > o_threshold) return huge*huge; /* overflow */ #else /* !!! FIXME: check this: "huge * huge" is a compiler warning, maybe they wanted +Inf? */ if(x > o_threshold) return INFINITY; /* overflow */ diff --git a/src/libm/math_libm.h b/src/libm/math_libm.h index 510a5c24..1313a075 100644 --- a/src/libm/math_libm.h +++ b/src/libm/math_libm.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/loadso/dlopen/SDL_sysloadso.c b/src/loadso/dlopen/SDL_sysloadso.c index 43ec162b..b7a37a4b 100644 --- a/src/loadso/dlopen/SDL_sysloadso.c +++ b/src/loadso/dlopen/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -46,7 +46,7 @@ void *SDL_LoadObject(const char *sofile) handle = dlopen(sofile, RTLD_NOW | RTLD_LOCAL); loaderror = dlerror(); - if (handle == NULL) { + if (!handle) { SDL_SetError("Failed loading %s: %s", sofile, loaderror); } return handle; @@ -55,7 +55,7 @@ void *SDL_LoadObject(const char *sofile) SDL_FunctionPointer SDL_LoadFunction(void *handle, const char *name) { void *symbol = dlsym(handle, name); - if (symbol == NULL) { + if (!symbol) { /* prepend an underscore for platforms that need that. */ SDL_bool isstack; size_t len = SDL_strlen(name) + 1; @@ -64,7 +64,7 @@ SDL_FunctionPointer SDL_LoadFunction(void *handle, const char *name) SDL_memcpy(&_name[1], name, len); symbol = dlsym(handle, _name); SDL_small_free(_name, isstack); - if (symbol == NULL) { + if (!symbol) { SDL_SetError("Failed loading %s: %s", name, (const char *)dlerror()); } @@ -74,7 +74,7 @@ SDL_FunctionPointer SDL_LoadFunction(void *handle, const char *name) void SDL_UnloadObject(void *handle) { - if (handle != NULL) { + if (handle) { dlclose(handle); } } diff --git a/src/loadso/dummy/SDL_sysloadso.c b/src/loadso/dummy/SDL_sysloadso.c index ba18a7a2..9a2f524b 100644 --- a/src/loadso/dummy/SDL_sysloadso.c +++ b/src/loadso/dummy/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/loadso/windows/SDL_sysloadso.c b/src/loadso/windows/SDL_sysloadso.c index 94198f16..92a61beb 100644 --- a/src/loadso/windows/SDL_sysloadso.c +++ b/src/loadso/windows/SDL_sysloadso.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,7 +32,7 @@ void *SDL_LoadObject(const char *sofile) void *handle; LPTSTR tstr; - if (sofile == NULL) { + if (!sofile) { SDL_InvalidParamError("sofile"); return NULL; } @@ -49,7 +49,7 @@ void *SDL_LoadObject(const char *sofile) SDL_free(tstr); /* Generate an error message if all loads failed */ - if (handle == NULL) { + if (!handle) { char errbuf[512]; SDL_strlcpy(errbuf, "Failed loading ", SDL_arraysize(errbuf)); SDL_strlcat(errbuf, sofile, SDL_arraysize(errbuf)); @@ -61,7 +61,7 @@ void *SDL_LoadObject(const char *sofile) SDL_FunctionPointer SDL_LoadFunction(void *handle, const char *name) { void *symbol = (void *)GetProcAddress((HMODULE)handle, name); - if (symbol == NULL) { + if (!symbol) { char errbuf[512]; SDL_strlcpy(errbuf, "Failed loading ", SDL_arraysize(errbuf)); SDL_strlcat(errbuf, name, SDL_arraysize(errbuf)); @@ -72,7 +72,7 @@ SDL_FunctionPointer SDL_LoadFunction(void *handle, const char *name) void SDL_UnloadObject(void *handle) { - if (handle != NULL) { + if (handle) { FreeLibrary((HMODULE)handle); } } diff --git a/src/locale/SDL_locale.c b/src/locale/SDL_locale.c index 46a5a5db..5ad03127 100644 --- a/src/locale/SDL_locale.c +++ b/src/locale/SDL_locale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,7 +31,7 @@ static SDL_Locale *build_locales_from_csv_string(char *csv) SDL_Locale *loc; SDL_Locale *retval; - if (csv == NULL || !csv[0]) { + if (!csv || !csv[0]) { return NULL; /* nothing to report */ } @@ -47,8 +47,7 @@ static SDL_Locale *build_locales_from_csv_string(char *csv) alloclen = slen + (num_locales * sizeof(SDL_Locale)); loc = retval = (SDL_Locale *)SDL_calloc(1, alloclen); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; /* oh well */ } ptr = (char *)(retval + num_locales); diff --git a/src/locale/SDL_syslocale.h b/src/locale/SDL_syslocale.h index 52851e53..0bcc0ad9 100644 --- a/src/locale/SDL_syslocale.h +++ b/src/locale/SDL_syslocale.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/android/SDL_syslocale.c b/src/locale/android/SDL_syslocale.c index 99b3bcdf..03a68748 100644 --- a/src/locale/android/SDL_syslocale.c +++ b/src/locale/android/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/dummy/SDL_syslocale.c b/src/locale/dummy/SDL_syslocale.c index 742d5b0d..769dcc8f 100644 --- a/src/locale/dummy/SDL_syslocale.c +++ b/src/locale/dummy/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/emscripten/SDL_syslocale.c b/src/locale/emscripten/SDL_syslocale.c index c22681ff..fe06151a 100644 --- a/src/locale/emscripten/SDL_syslocale.c +++ b/src/locale/emscripten/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/haiku/SDL_syslocale.cc b/src/locale/haiku/SDL_syslocale.cc index 08932e1a..5e5da395 100644 --- a/src/locale/haiku/SDL_syslocale.cc +++ b/src/locale/haiku/SDL_syslocale.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/macos/SDL_syslocale.m b/src/locale/macos/SDL_syslocale.m index 2aece9e2..a9d1af3c 100644 --- a/src/locale/macos/SDL_syslocale.m +++ b/src/locale/macos/SDL_syslocale.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/n3ds/SDL_syslocale.c b/src/locale/n3ds/SDL_syslocale.c index 1d389c64..02ba03a4 100644 --- a/src/locale/n3ds/SDL_syslocale.c +++ b/src/locale/n3ds/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/unix/SDL_syslocale.c b/src/locale/unix/SDL_syslocale.c index cf48be96..a03849dd 100644 --- a/src/locale/unix/SDL_syslocale.c +++ b/src/locale/unix/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,12 +27,12 @@ static void normalize_locale_str(char *dst, char *str, size_t buflen) char *ptr; ptr = SDL_strchr(str, '.'); /* chop off encoding if specified. */ - if (ptr != NULL) { + if (ptr) { *ptr = '\0'; } ptr = SDL_strchr(str, '@'); /* chop off extra bits if specified. */ - if (ptr != NULL) { + if (ptr) { *ptr = '\0'; } @@ -71,8 +71,8 @@ int SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) SDL_assert(buflen > 0); tmp = SDL_small_alloc(char, buflen, &isstack); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } *tmp = '\0'; diff --git a/src/locale/vita/SDL_syslocale.c b/src/locale/vita/SDL_syslocale.c index 3a8c1f22..42bdcd2c 100644 --- a/src/locale/vita/SDL_syslocale.c +++ b/src/locale/vita/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/locale/windows/SDL_syslocale.c b/src/locale/windows/SDL_syslocale.c index 60dc98eb..ec8d44ad 100644 --- a/src/locale/windows/SDL_syslocale.c +++ b/src/locale/windows/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,8 +65,8 @@ static int SDL_SYS_GetPreferredLocales_vista(char *buf, size_t buflen) pGetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numlangs, NULL, &wbuflen); wbuf = SDL_small_alloc(WCHAR, wbuflen, &isstack); - if (wbuf == NULL) { - return SDL_OutOfMemory(); + if (!wbuf) { + return -1; } if (!pGetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numlangs, wbuf, &wbuflen)) { @@ -102,7 +102,7 @@ int SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) } } - if (pGetUserPreferredUILanguages == NULL) { + if (!pGetUserPreferredUILanguages) { SDL_SYS_GetPreferredLocales_winxp(buf, buflen); /* this is always available */ } else { SDL_SYS_GetPreferredLocales_vista(buf, buflen); /* available on Vista and later. */ diff --git a/src/locale/winrt/SDL_syslocale.c b/src/locale/winrt/SDL_syslocale.c index f1fe7d09..8e0550d5 100644 --- a/src/locale/winrt/SDL_syslocale.c +++ b/src/locale/winrt/SDL_syslocale.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c new file mode 100644 index 00000000..a89ffb91 --- /dev/null +++ b/src/main/SDL_main_callbacks.c @@ -0,0 +1,142 @@ +/* + Simple DirectMedia Layer + 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 + 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_main_callbacks.h" + +static SDL_AppEvent_func SDL_main_event_callback; +static SDL_AppIterate_func SDL_main_iteration_callback; +static SDL_AppQuit_func SDL_main_quit_callback; +static SDL_AtomicInt apprc; // use an atomic, since events might land from any thread and we don't want to wrap this all in a mutex. A CAS makes sure we only move from zero once. + +// Return true if this event needs to be processed before returning from the event watcher +static SDL_bool ShouldDispatchImmediately(SDL_Event *event) +{ + switch (event->type) { + case SDL_EVENT_TERMINATING: + case SDL_EVENT_LOW_MEMORY: + case SDL_EVENT_WILL_ENTER_BACKGROUND: + case SDL_EVENT_DID_ENTER_BACKGROUND: + case SDL_EVENT_WILL_ENTER_FOREGROUND: + case SDL_EVENT_DID_ENTER_FOREGROUND: + return SDL_TRUE; + default: + return SDL_FALSE; + } +} + +static void SDL_DispatchMainCallbackEvent(SDL_Event *event) +{ + if (SDL_AtomicGet(&apprc) == 0) { // if already quitting, don't send the event to the app. + SDL_AtomicCAS(&apprc, 0, SDL_main_event_callback(event)); + } +} + +static void SDL_DispatchMainCallbackEvents() +{ + SDL_Event events[16]; + + for (;;) { + int count = SDL_PeepEvents(events, SDL_arraysize(events), SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); + if (count <= 0) { + break; + } + for (int i = 0; i < count; ++i) { + SDL_Event *event = &events[i]; + if (!ShouldDispatchImmediately(event)) { + SDL_DispatchMainCallbackEvent(event); + } + } + } +} + +static int SDLCALL SDL_MainCallbackEventWatcher(void *userdata, SDL_Event *event) +{ + if (ShouldDispatchImmediately(event)) { + // Make sure any currently queued events are processed then dispatch this before continuing + SDL_DispatchMainCallbackEvents(); + SDL_DispatchMainCallbackEvent(event); + } else { + // We'll process this event later from the main event queue + } + return 0; +} + +SDL_bool SDL_HasMainCallbacks() +{ + if (SDL_main_iteration_callback) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + SDL_main_iteration_callback = appiter; + SDL_main_event_callback = appevent; + SDL_main_quit_callback = appquit; + SDL_AtomicSet(&apprc, 0); + + const int rc = appinit(argc, argv); + if (SDL_AtomicCAS(&apprc, 0, rc) && (rc == 0)) { // bounce if SDL_AppInit already said abort, otherwise... + // make sure we definitely have events initialized, even if the app didn't do it. + if (SDL_InitSubSystem(SDL_INIT_EVENTS) == -1) { + SDL_AtomicSet(&apprc, -1); + return -1; + } + + if (SDL_AddEventWatch(SDL_MainCallbackEventWatcher, NULL) < 0) { + SDL_AtomicSet(&apprc, -1); + return -1; + } + } + + return SDL_AtomicGet(&apprc); +} + +int SDL_IterateMainCallbacks(SDL_bool pump_events) +{ + if (pump_events) { + SDL_PumpEvents(); + } + SDL_DispatchMainCallbackEvents(); + + int rc = SDL_AtomicGet(&apprc); + if (rc == 0) { + rc = SDL_main_iteration_callback(); + if (!SDL_AtomicCAS(&apprc, 0, rc)) { + rc = SDL_AtomicGet(&apprc); // something else already set a quit result, keep that. + } + } + return rc; +} + +void SDL_QuitMainCallbacks(void) +{ + SDL_DelEventWatch(SDL_MainCallbackEventWatcher, NULL); + SDL_main_quit_callback(); + + // for symmetry, you should explicitly Quit what you Init, but we might come through here uninitialized and SDL_Quit() will clear everything anyhow. + //SDL_QuitSubSystem(SDL_INIT_EVENTS); + + SDL_Quit(); +} + diff --git a/src/video/x11/SDL_x11shape.h b/src/main/SDL_main_callbacks.h similarity index 66% rename from src/video/x11/SDL_x11shape.h rename to src/main/SDL_main_callbacks.h index 96ec1218..5a906cfc 100644 --- a/src/video/x11/SDL_x11shape.h +++ b/src/main/SDL_main_callbacks.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,15 @@ 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_x11shape_h_ -#define SDL_x11shape_h_ +#ifndef SDL_main_callbacks_h_ +#define SDL_main_callbacks_h_ -#include "../SDL_sysvideo.h" +SDL_bool SDL_HasMainCallbacks(); +int SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func _appiter, SDL_AppEvent_func _appevent, SDL_AppQuit_func _appquit); +int SDL_IterateMainCallbacks(SDL_bool pump_events); +void SDL_QuitMainCallbacks(void); -typedef struct -{ - void *bitmap; - Uint32 bitmapsize; -} SDL_ShapeData; +#endif // SDL_main_callbacks_h_ -extern SDL_WindowShaper *X11_CreateShaper(SDL_Window *window); -extern int X11_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode); -#endif /* SDL_x11shape_h_ */ diff --git a/src/main/emscripten/SDL_sysmain_callbacks.c b/src/main/emscripten/SDL_sysmain_callbacks.c new file mode 100644 index 00000000..cb9fbb4c --- /dev/null +++ b/src/main/emscripten/SDL_sysmain_callbacks.c @@ -0,0 +1,47 @@ +/* + Simple DirectMedia Layer + 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 + 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_main_callbacks.h" + +#include + +static void EmscriptenInternalMainloop(void) +{ + const int rc = SDL_IterateMainCallbacks(SDL_TRUE); + if (rc != 0) { + SDL_QuitMainCallbacks(); + emscripten_cancel_main_loop(); // kill" the mainloop, so it stops calling back into it. + exit((rc < 0) ? 1 : 0); // hopefully this takes down everything else, too. + } +} + +int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); + if (rc == 0) { + emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // run at refresh rate, don't throw an exception since we do an orderly return. + } else { + SDL_QuitMainCallbacks(); + } + return (rc < 0) ? 1 : 0; +} + diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c new file mode 100644 index 00000000..8adbf6c4 --- /dev/null +++ b/src/main/generic/SDL_sysmain_callbacks.c @@ -0,0 +1,83 @@ +/* + Simple DirectMedia Layer + 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 + 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_main_callbacks.h" +#include "../../video/SDL_sysvideo.h" + +#ifndef __IOS__ + +static int callback_rate_increment = 0; + +static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + const int callback_rate = newValue ? SDL_atoi(newValue) : 60; + if (callback_rate > 0) { + callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate); + } else { + callback_rate_increment = 0; + } +} + +int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); + if (rc == 0) { + SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); + + Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0; + + while ((rc = SDL_IterateMainCallbacks(SDL_TRUE)) == 0) { + // !!! FIXME: this can be made more complicated if we decide to + // !!! FIXME: optionally hand off callback responsibility to the + // !!! FIXME: video subsystem (for example, if Wayland has a + // !!! FIXME: protocol to drive an animation loop, maybe we hand + // !!! FIXME: off to them here if/when the video subsystem becomes + // !!! FIXME: initialized). + + // !!! FIXME: maybe respect this hint even if there _is_ a window. + // if there's no window, try to run at about 60fps (or whatever rate + // the hint requested). This makes this not eat all the CPU in + // simple things like loopwave. If there's a window, we run as fast + // as possible, which means we'll clamp to vsync in common cases, + // and won't be restrained to vsync if the app is doing a benchmark + // or doesn't want to be, based on how they've set up that window. + if ((callback_rate_increment == 0) || SDL_HasWindows()) { + next_iteration = 0; // just clear the timer and run at the pace the video subsystem allows. + } else { + const Uint64 now = SDL_GetTicksNS(); + if (next_iteration > now) { // Running faster than the limit, sleep a little. + SDL_DelayNS(next_iteration - now); + } else { + next_iteration = now; // running behind (or just lost the window)...reset the timer. + } + next_iteration += callback_rate_increment; + } + } + + SDL_DelHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL); + } + SDL_QuitMainCallbacks(); + + return (rc < 0) ? 1 : 0; +} + +#endif // !__IOS__ diff --git a/src/main/ios/SDL_sysmain_callbacks.m b/src/main/ios/SDL_sysmain_callbacks.m new file mode 100644 index 00000000..09bc9932 --- /dev/null +++ b/src/main/ios/SDL_sysmain_callbacks.m @@ -0,0 +1,82 @@ +/* + Simple DirectMedia Layer + 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 + 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_main_callbacks.h" + +#ifdef __IOS__ + +#import + +@interface SDLIosMainCallbacksDisplayLink : NSObject +@property(nonatomic, retain) CADisplayLink *displayLink; +- (void)appIteration:(CADisplayLink *)sender; +- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit; +@end + +static SDLIosMainCallbacksDisplayLink *globalDisplayLink; + +@implementation SDLIosMainCallbacksDisplayLink + +- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit; +{ + if ((self = [super init])) { + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(appIteration:)]; + [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + } + return self; +} + +- (void)appIteration:(CADisplayLink *)sender +{ + const int rc = SDL_IterateMainCallbacks(SDL_TRUE); + if (rc != 0) { + [self.displayLink invalidate]; + self.displayLink = nil; + globalDisplayLink = nil; + SDL_QuitMainCallbacks(); + exit((rc < 0) ? 1 : 0); + } +} +@end + +// SDL_RunApp will land in UIApplicationMain, which calls SDL_main from postFinishLaunch, which calls this. +// When we return from here, we're living in the RunLoop, and a CADisplayLink is firing regularly for us. +int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); + if (rc == 0) { + globalDisplayLink = [[SDLIosMainCallbacksDisplayLink alloc] init:appiter quitfunc:appquit]; + if (globalDisplayLink != nil) { + return 0; // this will fall all the way out of SDL_main, where UIApplicationMain will keep running the RunLoop. + } + } + + // appinit requested quit, just bounce out now. + SDL_QuitMainCallbacks(); + exit((rc < 0) ? 1 : 0); + + return 1; // just in case. +} + +#endif + + diff --git a/src/misc/SDL_sysurl.h b/src/misc/SDL_sysurl.h index ca9bcc49..0eca8ac1 100644 --- a/src/misc/SDL_sysurl.h +++ b/src/misc/SDL_sysurl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/SDL_url.c b/src/misc/SDL_url.c index a79a109e..34016bf9 100644 --- a/src/misc/SDL_url.c +++ b/src/misc/SDL_url.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,7 +24,7 @@ int SDL_OpenURL(const char *url) { - if (url == NULL) { + if (!url) { return SDL_InvalidParamError("url"); } return SDL_SYS_OpenURL(url); diff --git a/src/misc/android/SDL_sysurl.c b/src/misc/android/SDL_sysurl.c index 05b741fb..07067de1 100644 --- a/src/misc/android/SDL_sysurl.c +++ b/src/misc/android/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/dummy/SDL_sysurl.c b/src/misc/dummy/SDL_sysurl.c index 7e60e49c..358b3e6d 100644 --- a/src/misc/dummy/SDL_sysurl.c +++ b/src/misc/dummy/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/emscripten/SDL_sysurl.c b/src/misc/emscripten/SDL_sysurl.c index 8926a929..023bd15b 100644 --- a/src/misc/emscripten/SDL_sysurl.c +++ b/src/misc/emscripten/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/haiku/SDL_sysurl.cc b/src/misc/haiku/SDL_sysurl.cc index cf619ef0..5c6dd00c 100644 --- a/src/misc/haiku/SDL_sysurl.cc +++ b/src/misc/haiku/SDL_sysurl.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/ios/SDL_sysurl.m b/src/misc/ios/SDL_sysurl.m index 7db7f48d..719e0b2c 100644 --- a/src/misc/ios/SDL_sysurl.m +++ b/src/misc/ios/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/macos/SDL_sysurl.m b/src/misc/macos/SDL_sysurl.m index b372cec4..2eff382a 100644 --- a/src/misc/macos/SDL_sysurl.m +++ b/src/misc/macos/SDL_sysurl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/riscos/SDL_sysurl.c b/src/misc/riscos/SDL_sysurl.c index 67206e37..f9285b76 100644 --- a/src/misc/riscos/SDL_sysurl.c +++ b/src/misc/riscos/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/unix/SDL_sysurl.c b/src/misc/unix/SDL_sysurl.c index 709a5842..b9c910e0 100644 --- a/src/misc/unix/SDL_sysurl.c +++ b/src/misc/unix/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/vita/SDL_sysurl.c b/src/misc/vita/SDL_sysurl.c index ac3c449b..5e0cb1d3 100644 --- a/src/misc/vita/SDL_sysurl.c +++ b/src/misc/vita/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/misc/windows/SDL_sysurl.c b/src/misc/windows/SDL_sysurl.c index 03ac819f..11d720cf 100644 --- a/src/misc/windows/SDL_sysurl.c +++ b/src/misc/windows/SDL_sysurl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -45,9 +45,9 @@ int SDL_SYS_OpenURL(const char *url) } wurl = WIN_UTF8ToStringW(url); - if (wurl == NULL) { + if (!wurl) { WIN_CoUninitialize(); - return SDL_OutOfMemory(); + return -1; } /* Success returns value greater than 32. Less is an error. */ diff --git a/src/misc/winrt/SDL_sysurl.cpp b/src/misc/winrt/SDL_sysurl.cpp index e117a4e1..cd6ae2f5 100644 --- a/src/misc/winrt/SDL_sysurl.cpp +++ b/src/misc/winrt/SDL_sysurl.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,7 +27,7 @@ int SDL_SYS_OpenURL(const char *url) { WCHAR *wurl = WIN_UTF8ToStringW(url); if (!wurl) { - return SDL_OutOfMemory(); + return -1; } auto strurl = ref new Platform::String(wurl); SDL_free(wurl); diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c index 8676a316..af1e5bfb 100644 --- a/src/power/SDL_power.c +++ b/src/power/SDL_power.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -94,10 +94,10 @@ SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent) int _seconds, _percent; /* Make these never NULL for platform-specific implementations. */ - if (seconds == NULL) { + if (!seconds) { seconds = &_seconds; } - if (percent == NULL) { + if (!percent) { percent = &_percent; } diff --git a/src/power/SDL_syspower.h b/src/power/SDL_syspower.h index 3c79f0de..31328162 100644 --- a/src/power/SDL_syspower.h +++ b/src/power/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/android/SDL_syspower.c b/src/power/android/SDL_syspower.c index 1e3c2488..0cae9d33 100644 --- a/src/power/android/SDL_syspower.c +++ b/src/power/android/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/emscripten/SDL_syspower.c b/src/power/emscripten/SDL_syspower.c index c316a9e4..05dec175 100644 --- a/src/power/emscripten/SDL_syspower.c +++ b/src/power/emscripten/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/haiku/SDL_syspower.c b/src/power/haiku/SDL_syspower.c index 6298ee58..14bf6423 100644 --- a/src/power/haiku/SDL_syspower.c +++ b/src/power/haiku/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/linux/SDL_syspower.c b/src/power/linux/SDL_syspower.c index 53fb6f53..e4a17a08 100644 --- a/src/power/linux/SDL_syspower.c +++ b/src/power/linux/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -45,7 +45,7 @@ static int open_power_file(const char *base, const char *node, const char *key) int fd; const size_t pathlen = SDL_strlen(base) + SDL_strlen(node) + SDL_strlen(key) + 3; char *path = SDL_stack_alloc(char, pathlen); - if (path == NULL) { + if (!path) { return -1; /* oh well. */ } @@ -241,7 +241,7 @@ SDL_bool SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState *state, int *seconds, i *state = SDL_POWERSTATE_UNKNOWN; dirp = opendir(proc_acpi_battery_path); - if (dirp == NULL) { + if (!dirp) { return SDL_FALSE; /* can't use this interface. */ } else { while ((dent = readdir(dirp)) != NULL) { @@ -253,7 +253,7 @@ SDL_bool SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState *state, int *seconds, i } dirp = opendir(proc_acpi_ac_adapter_path); - if (dirp == NULL) { + if (!dirp) { return SDL_FALSE; /* can't use this interface. */ } else { while ((dent = readdir(dirp)) != NULL) { @@ -424,7 +424,7 @@ SDL_bool SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, in DIR *dirp; dirp = opendir(base); - if (dirp == NULL) { + if (!dirp) { return SDL_FALSE; } @@ -623,7 +623,7 @@ SDL_bool SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, in char **paths = NULL; int i, numpaths = 0; - if (dbus == NULL || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", + if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", DBUS_TYPE_INVALID, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { return SDL_FALSE; /* try a different approach than UPower. */ diff --git a/src/power/macos/SDL_syspower.c b/src/power/macos/SDL_syspower.c index 7c472e33..13ea6f36 100644 --- a/src/power/macos/SDL_syspower.c +++ b/src/power/macos/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/n3ds/SDL_syspower.c b/src/power/n3ds/SDL_syspower.c index 511f1f98..57a745b3 100644 --- a/src/power/n3ds/SDL_syspower.c +++ b/src/power/n3ds/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/psp/SDL_syspower.c b/src/power/psp/SDL_syspower.c index db670a37..96d37926 100644 --- a/src/power/psp/SDL_syspower.c +++ b/src/power/psp/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/uikit/SDL_syspower.h b/src/power/uikit/SDL_syspower.h index 40755ae6..8365c75b 100644 --- a/src/power/uikit/SDL_syspower.h +++ b/src/power/uikit/SDL_syspower.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/uikit/SDL_syspower.m b/src/power/uikit/SDL_syspower.m index f1d29d39..3049c1e0 100644 --- a/src/power/uikit/SDL_syspower.m +++ b/src/power/uikit/SDL_syspower.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/vita/SDL_syspower.c b/src/power/vita/SDL_syspower.c index 3f1d9a0c..5e216abe 100644 --- a/src/power/vita/SDL_syspower.c +++ b/src/power/vita/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/windows/SDL_syspower.c b/src/power/windows/SDL_syspower.c index 7f978c59..4525c867 100644 --- a/src/power/windows/SDL_syspower.c +++ b/src/power/windows/SDL_syspower.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/power/winrt/SDL_syspower.cpp b/src/power/winrt/SDL_syspower.cpp index d9a423a5..4df72797 100644 --- a/src/power/winrt/SDL_syspower.cpp +++ b/src/power/winrt/SDL_syspower.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/SDL_d3dmath.c b/src/render/SDL_d3dmath.c index 1d68320b..7b50487c 100644 --- a/src/render/SDL_d3dmath.c +++ b/src/render/SDL_d3dmath.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/SDL_d3dmath.h b/src/render/SDL_d3dmath.h index 6ced717b..95b52601 100644 --- a/src/render/SDL_d3dmath.h +++ b/src/render/SDL_d3dmath.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 1df03498..a0396323 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,16 +43,16 @@ this should probably be removed at some point in the future. --ryan. */ #define DONT_DRAW_WHILE_HIDDEN 0 #endif -#define SDL_WINDOWRENDERDATA "_SDL_WindowRenderData" +#define SDL_PROPERTY_WINDOW_RENDERER "SDL.internal.window.renderer" #define CHECK_RENDERER_MAGIC(renderer, retval) \ - if (!(renderer) || (renderer)->magic != &renderer_magic) { \ + if (!(renderer) || (renderer)->magic != &SDL_renderer_magic) { \ SDL_InvalidParamError("renderer"); \ return retval; \ } #define CHECK_TEXTURE_MAGIC(texture, retval) \ - if (!(texture) || (texture)->magic != &texture_magic) { \ + if (!(texture) || (texture)->magic != &SDL_texture_magic) { \ SDL_InvalidParamError("texture"); \ return retval; \ } @@ -122,8 +122,8 @@ static const SDL_RenderDriver *render_drivers[] = { }; #endif /* !SDL_RENDER_DISABLED */ -static char renderer_magic; -static char texture_magic; +char SDL_renderer_magic; +char SDL_texture_magic; static SDL_INLINE void DebugLogRenderCommands(const SDL_RenderCommand *cmd) { @@ -231,7 +231,7 @@ static int FlushRenderCommands(SDL_Renderer *renderer) SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL)); - if (renderer->render_commands == NULL) { /* nothing to do! */ + if (!renderer->render_commands) { /* nothing to do! */ SDL_assert(renderer->vertex_data_used == 0); return 0; } @@ -241,7 +241,7 @@ static int FlushRenderCommands(SDL_Renderer *renderer) retval = renderer->RunCommandQueue(renderer, renderer->render_commands, renderer->vertex_data, renderer->vertex_data_used); /* Move the whole render command queue to the unused pool so we can reuse them next time. */ - if (renderer->render_commands_tail != NULL) { + if (renderer->render_commands_tail) { renderer->render_commands_tail->next = renderer->render_commands_pool; renderer->render_commands_pool = renderer->render_commands; renderer->render_commands_tail = NULL; @@ -265,14 +265,13 @@ static int FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture) return 0; } -static SDL_INLINE int FlushRenderCommandsIfNotBatching(SDL_Renderer *renderer) +int SDL_FlushRenderer(SDL_Renderer *renderer) { - return renderer->batching ? 0 : FlushRenderCommands(renderer); -} - -int SDL_RenderFlush(SDL_Renderer *renderer) -{ - return FlushRenderCommands(renderer); + if (FlushRenderCommands(renderer) == -1) { + return -1; + } + renderer->InvalidateCachedState(renderer); + return 0; } void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, const size_t alignment, size_t *offset) @@ -293,8 +292,7 @@ void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, ptr = SDL_realloc(renderer->vertex_data, newsize); - if (ptr == NULL) { - SDL_OutOfMemory(); + if (!ptr) { return NULL; } renderer->vertex_data = ptr; @@ -316,19 +314,18 @@ static SDL_RenderCommand *AllocateRenderCommand(SDL_Renderer *renderer) /* !!! FIXME: are there threading limitations in SDL's render API? If not, we need to mutex this. */ retval = renderer->render_commands_pool; - if (retval != NULL) { + if (retval) { renderer->render_commands_pool = retval->next; retval->next = NULL; } else { retval = SDL_calloc(1, sizeof(*retval)); - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } } SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL)); - if (renderer->render_commands_tail != NULL) { + if (renderer->render_commands_tail) { renderer->render_commands_tail->next = retval; } else { renderer->render_commands = retval; @@ -364,7 +361,7 @@ static int QueueCmdSetViewport(SDL_Renderer *renderer) if (!renderer->viewport_queued || SDL_memcmp(&viewport, &renderer->last_queued_viewport, sizeof(viewport)) != 0) { SDL_RenderCommand *cmd = AllocateRenderCommand(renderer); - if (cmd != NULL) { + if (cmd) { cmd->command = SDL_RENDERCMD_SETVIEWPORT; cmd->data.viewport.first = 0; /* render backend will fill this in. */ SDL_copyp(&cmd->data.viewport.rect, &viewport); @@ -396,7 +393,7 @@ static int QueueCmdSetClipRect(SDL_Renderer *renderer) renderer->view->clipping_enabled != renderer->last_queued_cliprect_enabled || SDL_memcmp(&clip_rect, &renderer->last_queued_cliprect, sizeof(clip_rect)) != 0) { SDL_RenderCommand *cmd = AllocateRenderCommand(renderer); - if (cmd != NULL) { + if (cmd) { cmd->command = SDL_RENDERCMD_SETCLIPRECT; cmd->data.cliprect.enabled = renderer->view->clipping_enabled; SDL_copyp(&cmd->data.cliprect.rect, &clip_rect); @@ -419,7 +416,7 @@ static int QueueCmdSetDrawColor(SDL_Renderer *renderer, SDL_Color *col) SDL_RenderCommand *cmd = AllocateRenderCommand(renderer); retval = -1; - if (cmd != NULL) { + if (cmd) { cmd->command = SDL_RENDERCMD_SETDRAWCOLOR; cmd->data.color.first = 0; /* render backend will fill this in. */ cmd->data.color.r = col->r; @@ -441,7 +438,7 @@ static int QueueCmdSetDrawColor(SDL_Renderer *renderer, SDL_Color *col) static int QueueCmdClear(SDL_Renderer *renderer) { SDL_RenderCommand *cmd = AllocateRenderCommand(renderer); - if (cmd == NULL) { + if (!cmd) { return -1; } @@ -484,7 +481,7 @@ static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_Ren if (retval == 0) { cmd = AllocateRenderCommand(renderer); - if (cmd != NULL) { + if (cmd) { cmd->command = cmdtype; cmd->data.draw.first = 0; /* render backend will fill this in. */ cmd->data.draw.count = 0; /* render backend will fill this in. */ @@ -503,7 +500,7 @@ static int QueueCmdDrawPoints(SDL_Renderer *renderer, const SDL_FPoint *points, { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_DRAW_POINTS, NULL); int retval = -1; - if (cmd != NULL) { + if (cmd) { retval = renderer->QueueDrawPoints(renderer, cmd, points, count); if (retval < 0) { cmd->command = SDL_RENDERCMD_NO_OP; @@ -516,7 +513,7 @@ static int QueueCmdDrawLines(SDL_Renderer *renderer, const SDL_FPoint *points, c { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_DRAW_LINES, NULL); int retval = -1; - if (cmd != NULL) { + if (cmd) { retval = renderer->QueueDrawLines(renderer, cmd, points, count); if (retval < 0) { cmd->command = SDL_RENDERCMD_NO_OP; @@ -529,11 +526,11 @@ static int QueueCmdFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, con { SDL_RenderCommand *cmd; int retval = -1; - const int use_rendergeometry = (renderer->QueueFillRects == NULL); + const int use_rendergeometry = (!renderer->QueueFillRects); cmd = PrepQueueCmdDraw(renderer, (use_rendergeometry ? SDL_RENDERCMD_GEOMETRY : SDL_RENDERCMD_FILL_RECTS), NULL); - if (cmd != NULL) { + if (cmd) { if (use_rendergeometry) { SDL_bool isstack1; SDL_bool isstack2; @@ -603,7 +600,7 @@ static int QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_ { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY, texture); int retval = -1; - if (cmd != NULL) { + if (cmd) { retval = renderer->QueueCopy(renderer, cmd, texture, srcrect, dstrect); if (retval < 0) { cmd->command = SDL_RENDERCMD_NO_OP; @@ -618,7 +615,7 @@ static int QueueCmdCopyEx(SDL_Renderer *renderer, SDL_Texture *texture, { SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY_EX, texture); int retval = -1; - if (cmd != NULL) { + if (cmd) { retval = renderer->QueueCopyEx(renderer, cmd, texture, srcquad, dstrect, angle, center, flip, scale_x, scale_y); if (retval < 0) { cmd->command = SDL_RENDERCMD_NO_OP; @@ -638,7 +635,7 @@ static int QueueCmdGeometry(SDL_Renderer *renderer, SDL_Texture *texture, SDL_RenderCommand *cmd; int retval = -1; cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_GEOMETRY, texture); - if (cmd != NULL) { + if (cmd) { retval = renderer->QueueGeometry(renderer, cmd, texture, xy, xy_stride, color, color_stride, uv, uv_stride, @@ -804,20 +801,27 @@ static void SDL_CalculateSimulatedVSyncInterval(SDL_Renderer *renderer, SDL_Wind #endif /* !SDL_RENDER_DISABLED */ -SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 flags) + +SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) { #ifndef SDL_RENDER_DISABLED + SDL_Window *window = SDL_GetProperty(props, SDL_PROPERTY_RENDERER_CREATE_WINDOW_POINTER, NULL); + SDL_Surface *surface = SDL_GetProperty(props, SDL_PROPERTY_RENDERER_CREATE_SURFACE_POINTER, NULL); + const char *name = SDL_GetStringProperty(props, SDL_PROPERTY_RENDERER_CREATE_NAME_STRING, NULL); SDL_Renderer *renderer = NULL; const int n = SDL_GetNumRenderDrivers(); - SDL_bool batching = SDL_TRUE; const char *hint; int i; + if (!window && surface) { + return SDL_CreateSoftwareRenderer(surface); + } + #ifdef __ANDROID__ Android_ActivityMutex_Lock_Running(); #endif - if (window == NULL) { + if (!window) { SDL_InvalidParamError("window"); goto error; } @@ -834,11 +838,7 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 fl hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC); if (hint && *hint) { - if (SDL_GetHintBoolean(SDL_HINT_RENDER_VSYNC, SDL_TRUE)) { - flags |= SDL_RENDERER_PRESENTVSYNC; - } else { - flags &= ~SDL_RENDERER_PRESENTVSYNC; - } + SDL_SetBooleanProperty(props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_GetHintBoolean(SDL_HINT_RENDER_VSYNC, SDL_TRUE)); } if (!name) { @@ -850,33 +850,28 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 fl const SDL_RenderDriver *driver = render_drivers[i]; if (SDL_strcasecmp(name, driver->info.name) == 0) { /* Create a new renderer instance */ - renderer = driver->CreateRenderer(window, flags); - if (renderer) { - batching = SDL_FALSE; - } + renderer = driver->CreateRenderer(window, props); break; } } } else { for (i = 0; i < n; i++) { const SDL_RenderDriver *driver = render_drivers[i]; - if ((driver->info.flags & flags) == flags) { - /* Create a new renderer instance */ - renderer = driver->CreateRenderer(window, flags); - if (renderer) { - /* Yay, we got one! */ - break; - } + /* Create a new renderer instance */ + renderer = driver->CreateRenderer(window, props); + if (renderer) { + /* Yay, we got one! */ + break; } } } - if (renderer == NULL) { + if (!renderer) { SDL_SetError("Couldn't find matching render driver"); goto error; } - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { renderer->wanted_vsync = SDL_TRUE; if (!(renderer->info.flags & SDL_RENDERER_PRESENTVSYNC)) { @@ -888,15 +883,7 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 fl VerifyDrawQueueFunctions(renderer); - /* let app/user override batching decisions. */ - if (renderer->always_batch) { - batching = SDL_TRUE; - } else if (SDL_GetHint(SDL_HINT_RENDER_BATCHING)) { - batching = SDL_GetHintBoolean(SDL_HINT_RENDER_BATCHING, SDL_TRUE); - } - - renderer->batching = batching; - renderer->magic = &renderer_magic; + renderer->magic = &SDL_renderer_magic; renderer->window = window; renderer->target_mutex = SDL_CreateMutex(); renderer->main_view.viewport.w = -1; @@ -929,7 +916,7 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 fl renderer->hidden = SDL_FALSE; } - SDL_SetWindowData(window, SDL_WINDOWRENDERDATA, renderer); + SDL_SetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_RENDERER, renderer); SDL_SetRenderViewport(renderer, NULL); @@ -956,6 +943,24 @@ error: #endif } +SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name, Uint32 flags) +{ + SDL_Renderer *renderer; + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetProperty(props, SDL_PROPERTY_RENDERER_CREATE_WINDOW_POINTER, window); + if (flags & SDL_RENDERER_SOFTWARE) { + SDL_SetStringProperty(props, SDL_PROPERTY_RENDERER_CREATE_NAME_STRING, "software"); + } else { + SDL_SetStringProperty(props, SDL_PROPERTY_RENDERER_CREATE_NAME_STRING, name); + } + if (flags & SDL_RENDERER_PRESENTVSYNC) { + SDL_SetBooleanProperty(props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_TRUE); + } + renderer = SDL_CreateRendererWithProperties(props); + SDL_DestroyProperties(props); + return renderer; +} + SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) { #if !defined(SDL_RENDER_DISABLED) && SDL_VIDEO_RENDER_SW @@ -965,7 +970,7 @@ SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) if (renderer) { VerifyDrawQueueFunctions(renderer); - renderer->magic = &renderer_magic; + renderer->magic = &SDL_renderer_magic; renderer->target_mutex = SDL_CreateMutex(); renderer->main_view.pixel_w = surface->w; renderer->main_view.pixel_h = surface->h; @@ -994,7 +999,7 @@ SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) SDL_Renderer *SDL_GetRenderer(SDL_Window *window) { - return (SDL_Renderer *)SDL_GetWindowData(window, SDL_WINDOWRENDERDATA); + return (SDL_Renderer *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_RENDERER, NULL); } SDL_Window *SDL_GetRenderWindow(SDL_Renderer *renderer) @@ -1007,10 +1012,20 @@ int SDL_GetRendererInfo(SDL_Renderer *renderer, SDL_RendererInfo *info) { CHECK_RENDERER_MAGIC(renderer, -1); - *info = renderer->info; + SDL_copyp(info, &renderer->info); return 0; } +SDL_PropertiesID SDL_GetRendererProperties(SDL_Renderer *renderer) +{ + CHECK_RENDERER_MAGIC(renderer, 0); + + if (renderer->props == 0) { + renderer->props = SDL_CreateProperties(); + } + return renderer->props; +} + int SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h) { CHECK_RENDERER_MAGIC(renderer, -1); @@ -1095,7 +1110,7 @@ static SDL_ScaleMode SDL_GetScaleMode(void) { const char *hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); - if (hint == NULL || SDL_strcasecmp(hint, "nearest") == 0) { + if (!hint || SDL_strcasecmp(hint, "nearest") == 0) { return SDL_SCALEMODE_NEAREST; } else if (SDL_strcasecmp(hint, "linear") == 0) { return SDL_SCALEMODE_LINEAR; @@ -1106,9 +1121,13 @@ static SDL_ScaleMode SDL_GetScaleMode(void) } } -SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access, int w, int h) +SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props) { SDL_Texture *texture; + Uint32 format = (Uint32)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_UNKNOWN); + int access = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); + int w = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_WIDTH_NUMBER, 0); + int h = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_HEIGHT_NUMBER, 0); SDL_bool texture_is_fourcc_and_target; CHECK_RENDERER_MAGIC(renderer, NULL); @@ -1136,11 +1155,10 @@ SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access return NULL; } texture = (SDL_Texture *)SDL_calloc(1, sizeof(*texture)); - if (texture == NULL) { - SDL_OutOfMemory(); + if (!texture) { return NULL; } - texture->magic = &texture_magic; + texture->magic = &SDL_texture_magic; texture->format = format; texture->access = access; texture->w = w; @@ -1167,7 +1185,7 @@ SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access texture_is_fourcc_and_target = (access == SDL_TEXTUREACCESS_TARGET && SDL_ISPIXELFORMAT_FOURCC(texture->format)); if (texture_is_fourcc_and_target == SDL_FALSE && IsSupportedFormat(renderer, format)) { - if (renderer->CreateTexture(renderer, texture) < 0) { + if (renderer->CreateTexture(renderer, texture, props) < 0) { SDL_DestroyTexture(texture); return NULL; } @@ -1222,6 +1240,19 @@ SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access return texture; } +SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access, int w, int h) +{ + SDL_Texture *texture; + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_FORMAT_NUMBER, format); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_ACCESS_NUMBER, access); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_CREATE_HEIGHT_NUMBER, h); + texture = SDL_CreateTextureWithProperties(renderer, props); + SDL_DestroyProperties(props); + return texture; +} + SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface) { const SDL_PixelFormat *fmt; @@ -1233,7 +1264,7 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s CHECK_RENDERER_MAGIC(renderer, NULL); - if (surface == NULL) { + if (!surface) { SDL_InvalidParamError("SDL_CreateTextureFromSurface(): surface"); return NULL; } @@ -1297,7 +1328,7 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, surface->w, surface->h); - if (texture == NULL) { + if (!texture) { return NULL; } @@ -1329,7 +1360,7 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s /* Set up a destination surface for the texture update */ dst_fmt = SDL_CreatePixelFormat(format); - if (dst_fmt == NULL) { + if (!dst_fmt) { SDL_DestroyTexture(texture); return NULL; } @@ -1365,6 +1396,22 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s return texture; } +SDL_Renderer *SDL_GetRendererFromTexture(SDL_Texture *texture) +{ + CHECK_TEXTURE_MAGIC(texture, NULL); + return texture->renderer; +} + +SDL_PropertiesID SDL_GetTextureProperties(SDL_Texture *texture) +{ + CHECK_TEXTURE_MAGIC(texture, 0); + + if (texture->props == 0) { + texture->props = SDL_CreateProperties(); + } + return texture->props; +} + int SDL_QueryTexture(SDL_Texture *texture, Uint32 *format, int *access, int *w, int *h) { CHECK_TEXTURE_MAGIC(texture, -1); @@ -1388,11 +1435,6 @@ int SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b) { CHECK_TEXTURE_MAGIC(texture, -1); - if (r < 255 || g < 255 || b < 255) { - texture->modMode |= SDL_TEXTUREMODULATE_COLOR; - } else { - texture->modMode &= ~SDL_TEXTUREMODULATE_COLOR; - } texture->color.r = r; texture->color.g = g; texture->color.b = b; @@ -1422,11 +1464,6 @@ int SDL_SetTextureAlphaMod(SDL_Texture *texture, Uint8 alpha) { CHECK_TEXTURE_MAGIC(texture, -1); - if (alpha < 255) { - texture->modMode |= SDL_TEXTUREMODULATE_ALPHA; - } else { - texture->modMode &= ~SDL_TEXTUREMODULATE_ALPHA; - } texture->color.a = alpha; if (texture->native) { return SDL_SetTextureAlphaMod(texture->native, alpha); @@ -1497,21 +1534,6 @@ int SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode) return 0; } -int SDL_SetTextureUserData(SDL_Texture *texture, void *userdata) -{ - CHECK_TEXTURE_MAGIC(texture, -1); - - texture->userdata = userdata; - return 0; -} - -void *SDL_GetTextureUserData(SDL_Texture *texture) -{ - CHECK_TEXTURE_MAGIC(texture, NULL); - - return texture->userdata; -} - #if SDL_HAVE_YUV static int SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch) @@ -1546,8 +1568,8 @@ static int SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect, const size_t alloclen = (size_t)rect->h * temp_pitch; if (alloclen > 0) { void *temp_pixels = SDL_malloc(alloclen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); @@ -1586,8 +1608,8 @@ static int SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect, const size_t alloclen = (size_t)rect->h * temp_pitch; if (alloclen > 0) { void *temp_pixels = SDL_malloc(alloclen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_ConvertPixels(rect->w, rect->h, texture->format, pixels, pitch, @@ -1605,7 +1627,7 @@ int SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pi CHECK_TEXTURE_MAGIC(texture, -1); - if (pixels == NULL) { + if (!pixels) { return SDL_InvalidParamError("pixels"); } if (!pitch) { @@ -1679,8 +1701,8 @@ static int SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rect const size_t alloclen = (size_t)rect->h * temp_pitch; if (alloclen > 0) { void *temp_pixels = SDL_malloc(alloclen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); @@ -1729,8 +1751,8 @@ static int SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect, const size_t alloclen = (size_t)rect->h * temp_pitch; if (alloclen > 0) { void *temp_pixels = SDL_malloc(alloclen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, rect->w, rect->h, temp_pixels, temp_pitch); @@ -1754,19 +1776,19 @@ int SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, -1); - if (Yplane == NULL) { + if (!Yplane) { return SDL_InvalidParamError("Yplane"); } if (!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (Uplane == NULL) { + if (!Uplane) { return SDL_InvalidParamError("Uplane"); } if (!Upitch) { return SDL_InvalidParamError("Upitch"); } - if (Vplane == NULL) { + if (!Vplane) { return SDL_InvalidParamError("Vplane"); } if (!Vpitch) { @@ -1820,13 +1842,13 @@ int SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect, CHECK_TEXTURE_MAGIC(texture, -1); - if (Yplane == NULL) { + if (!Yplane) { return SDL_InvalidParamError("Yplane"); } if (!Ypitch) { return SDL_InvalidParamError("Ypitch"); } - if (UVplane == NULL) { + if (!UVplane) { return SDL_InvalidParamError("UVplane"); } if (!UVpitch) { @@ -1899,7 +1921,7 @@ int SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, i return SDL_SetError("SDL_LockTexture(): texture must be streaming"); } - if (rect == NULL) { + if (!rect) { full_rect.x = 0; full_rect.y = 0; full_rect.w = texture->w; @@ -1934,7 +1956,7 @@ int SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Sur int pitch = 0; /* fix static analysis */ int ret; - if (texture == NULL || surface == NULL) { + if (!texture || !surface) { return -1; } @@ -1952,7 +1974,7 @@ int SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Sur } texture->locked_surface = SDL_CreateSurfaceFrom(pixels, real_rect.w, real_rect.h, pitch, texture->format); - if (texture->locked_surface == NULL) { + if (!texture->locked_surface) { SDL_UnlockTexture(texture); return -1; } @@ -2074,12 +2096,12 @@ static int SDL_SetRenderTargetInternal(SDL_Renderer *renderer, SDL_Texture *text } /* All set! */ - return FlushRenderCommandsIfNotBatching(renderer); + return 0; } int SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) { - if (texture == NULL && renderer->logical_target) { + if (!texture && renderer->logical_target) { return SDL_SetRenderTargetInternal(renderer, renderer->logical_target); } else { return SDL_SetRenderTargetInternal(renderer, texture); @@ -2468,13 +2490,20 @@ int SDL_ConvertEventToRenderCoordinates(SDL_Renderer *renderer, SDL_Event *event } SDL_RenderCoordinatesFromWindow(renderer, event->tfinger.x * w, event->tfinger.y * h, &event->tfinger.x, &event->tfinger.y); } + } else if (event->type == SDL_EVENT_DROP_POSITION || + event->type == SDL_EVENT_DROP_FILE || + event->type == SDL_EVENT_DROP_TEXT || + event->type == SDL_EVENT_DROP_COMPLETE) { + SDL_Window *window = SDL_GetWindowFromID(event->drop.windowID); + if (window == renderer->window) { + SDL_RenderCoordinatesFromWindow(renderer, event->drop.x, event->drop.y, &event->drop.x, &event->drop.y); + } } return 0; } int SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect) { - int retval; CHECK_RENDERER_MAGIC(renderer, -1); if (rect) { @@ -2488,8 +2517,7 @@ int SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect) renderer->view->viewport.w = -1; renderer->view->viewport.h = -1; } - retval = QueueCmdSetViewport(renderer); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return QueueCmdSetViewport(renderer); } int SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect) @@ -2531,7 +2559,6 @@ static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect) int SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect) { - int retval; CHECK_RENDERER_MAGIC(renderer, -1) if (rect && rect->w >= 0 && rect->h >= 0) { @@ -2545,8 +2572,7 @@ int SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect) SDL_zero(renderer->view->clip_rect); } - retval = QueueCmdSetClipRect(renderer); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return QueueCmdSetClipRect(renderer); } int SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect) @@ -2585,7 +2611,7 @@ int SDL_SetRenderScale(SDL_Renderer *renderer, float scaleX, float scaleY) /* The scale affects the existing viewport and clip rectangle */ retval += QueueCmdSetViewport(renderer); retval += QueueCmdSetClipRect(renderer); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_GetRenderScale(SDL_Renderer *renderer, float *scaleX, float *scaleY) @@ -2655,7 +2681,7 @@ int SDL_RenderClear(SDL_Renderer *renderer) int retval; CHECK_RENDERER_MAGIC(renderer, -1); retval = QueueCmdClear(renderer); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderPoint(SDL_Renderer *renderer, float x, float y) @@ -2678,8 +2704,8 @@ static int RenderPointsWithRects(SDL_Renderer *renderer, const SDL_FPoint *fpoin } frects = SDL_small_alloc(SDL_FRect, count, &isstack); - if (frects == NULL) { - return SDL_OutOfMemory(); + if (!frects) { + return -1; } for (i = 0; i < count; ++i) { @@ -2702,7 +2728,7 @@ int SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int count CHECK_RENDERER_MAGIC(renderer, -1); - if (points == NULL) { + if (!points) { return SDL_InvalidParamError("SDL_RenderPoints(): points"); } if (count < 1) { @@ -2721,7 +2747,7 @@ int SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int count } else { retval = QueueCmdDrawPoints(renderer, points, count); } - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderLine(SDL_Renderer *renderer, float x1, float y1, float x2, float y2) @@ -2744,6 +2770,15 @@ static int RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, i int retval; SDL_bool isstack; SDL_FPoint *points; + SDL_Rect viewport; + + /* the backend might clip this further to the clipping rect, but we + just want a basic safety against generating millions of points for + massive lines. */ + GetRenderViewportInPixels(renderer, &viewport); + if (!SDL_GetRectAndLineIntersection(&viewport, &x1, &y1, &x2, &y2)) { + return 0; + } deltax = SDL_abs(x2 - x1); deltay = SDL_abs(y2 - y1); @@ -2789,8 +2824,8 @@ static int RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, i } points = SDL_small_alloc(SDL_FPoint, numpixels, &isstack); - if (points == NULL) { - return SDL_OutOfMemory(); + if (!points) { + return -1; } for (i = 0; i < numpixels; ++i) { points[i].x = (float)x; @@ -2832,8 +2867,8 @@ static int RenderLinesWithRectsF(SDL_Renderer *renderer, SDL_bool draw_last = SDL_FALSE; frects = SDL_small_alloc(SDL_FRect, count - 1, &isstack); - if (frects == NULL) { - return SDL_OutOfMemory(); + if (!frects) { + return -1; } for (i = 0; i < count - 1; ++i) { @@ -2898,7 +2933,7 @@ int SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count) CHECK_RENDERER_MAGIC(renderer, -1); - if (points == NULL) { + if (!points) { return SDL_InvalidParamError("SDL_RenderLines(): points"); } if (count < 2) { @@ -3040,7 +3075,7 @@ int SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count) retval = QueueCmdDrawLines(renderer, points, count); } - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderRect(SDL_Renderer *renderer, const SDL_FRect *rect) @@ -3051,7 +3086,7 @@ int SDL_RenderRect(SDL_Renderer *renderer, const SDL_FRect *rect) CHECK_RENDERER_MAGIC(renderer, -1); /* If 'rect' == NULL, then outline the whole surface */ - if (rect == NULL) { + if (!rect) { GetRenderViewportSize(renderer, &frect); rect = &frect; } @@ -3075,7 +3110,7 @@ int SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count) CHECK_RENDERER_MAGIC(renderer, -1); - if (rects == NULL) { + if (!rects) { return SDL_InvalidParamError("SDL_RenderRects(): rects"); } if (count < 1) { @@ -3104,7 +3139,7 @@ int SDL_RenderFillRect(SDL_Renderer *renderer, const SDL_FRect *rect) CHECK_RENDERER_MAGIC(renderer, -1); /* If 'rect' == NULL, then fill the whole surface */ - if (rect == NULL) { + if (!rect) { GetRenderViewportSize(renderer, &frect); rect = &frect; } @@ -3120,7 +3155,7 @@ int SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int coun CHECK_RENDERER_MAGIC(renderer, -1); - if (rects == NULL) { + if (!rects) { return SDL_InvalidParamError("SDL_RenderFillRects(): rects"); } if (count < 1) { @@ -3135,8 +3170,8 @@ int SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int coun #endif frects = SDL_small_alloc(SDL_FRect, count, &isstack); - if (frects == NULL) { - return SDL_OutOfMemory(); + if (!frects) { + return -1; } for (i = 0; i < count; ++i) { frects[i].x = rects[i].x * renderer->view->scale.x; @@ -3149,7 +3184,7 @@ int SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int coun SDL_small_free(frects, isstack); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect) @@ -3173,7 +3208,7 @@ int SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FR } #endif - use_rendergeometry = (renderer->QueueCopy == NULL); + use_rendergeometry = (!renderer->QueueCopy); real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; @@ -3254,7 +3289,7 @@ int SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FR retval = QueueCmdCopy(renderer, texture, &real_srcrect, &real_dstrect); } - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, @@ -3288,7 +3323,7 @@ int SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, } #endif - use_rendergeometry = (renderer->QueueCopyEx == NULL); + use_rendergeometry = (!renderer->QueueCopyEx); real_srcrect.x = 0.0f; real_srcrect.y = 0.0f; @@ -3409,7 +3444,7 @@ int SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, renderer->view->scale.x, renderer->view->scale.y); } - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderGeometry(SDL_Renderer *renderer, @@ -3766,8 +3801,6 @@ static int SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, renderer->view->scale.y); if (retval < 0) { goto end; - } else { - FlushRenderCommandsIfNotBatching(renderer); } } @@ -3789,8 +3822,6 @@ static int SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, renderer->view->scale.y); if (retval < 0) { goto end; - } else { - FlushRenderCommandsIfNotBatching(renderer); } } @@ -3828,15 +3859,15 @@ int SDL_RenderGeometryRaw(SDL_Renderer *renderer, } } - if (xy == NULL) { + if (!xy) { return SDL_InvalidParamError("xy"); } - if (color == NULL) { + if (!color) { return SDL_InvalidParamError("color"); } - if (texture && uv == NULL) { + if (texture && !uv) { return SDL_InvalidParamError("uv"); } @@ -3912,7 +3943,7 @@ int SDL_RenderGeometryRaw(SDL_Renderer *renderer, renderer->view->scale.x, renderer->view->scale.y); - return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer); + return retval; } int SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, Uint32 format, void *pixels, int pitch) @@ -3928,7 +3959,7 @@ int SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, Uint32 fo FlushRenderCommands(renderer); /* we need to render before we read the results. */ if (!format) { - if (renderer->target == NULL) { + if (!renderer->target) { format = SDL_GetWindowPixelFormat(renderer->window); } else { format = renderer->target->format; @@ -4021,6 +4052,8 @@ static int SDL_DestroyTextureInternal(SDL_Texture *texture, SDL_bool is_destroyi CHECK_TEXTURE_MAGIC(texture, -1); + SDL_DestroyProperties(texture->props); + renderer = texture->renderer; if (is_destroying) { /* Renderer get destroyed, avoid to queue more commands */ @@ -4081,7 +4114,7 @@ static void SDL_DiscardAllCommands(SDL_Renderer *renderer) { SDL_RenderCommand *cmd; - if (renderer->render_commands_tail != NULL) { + if (renderer->render_commands_tail) { renderer->render_commands_tail->next = renderer->render_commands_pool; cmd = renderer->render_commands; } else { @@ -4092,7 +4125,7 @@ static void SDL_DiscardAllCommands(SDL_Renderer *renderer) renderer->render_commands_tail = NULL; renderer->render_commands = NULL; - while (cmd != NULL) { + while (cmd) { SDL_RenderCommand *next = cmd->next; SDL_free(cmd); cmd = next; @@ -4103,6 +4136,8 @@ void SDL_DestroyRenderer(SDL_Renderer *renderer) { CHECK_RENDERER_MAGIC(renderer,); + SDL_DestroyProperties(renderer->props); + SDL_DelEventWatch(SDL_RendererEventWatch, renderer); SDL_DiscardAllCommands(renderer); @@ -4118,7 +4153,7 @@ void SDL_DestroyRenderer(SDL_Renderer *renderer) SDL_free(renderer->vertex_data); if (renderer->window) { - SDL_SetWindowData(renderer->window, SDL_WINDOWRENDERDATA, NULL); + SDL_ClearProperty(SDL_GetWindowProperties(renderer->window), SDL_PROPERTY_WINDOW_RENDERER); } /* It's no longer magical... */ @@ -4132,38 +4167,6 @@ void SDL_DestroyRenderer(SDL_Renderer *renderer) renderer->DestroyRenderer(renderer); } -int SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh) -{ - SDL_Renderer *renderer; - - CHECK_TEXTURE_MAGIC(texture, -1); - renderer = texture->renderer; - if (texture->native) { - return SDL_GL_BindTexture(texture->native, texw, texh); - } else if (renderer && renderer->GL_BindTexture) { - FlushRenderCommandsIfTextureNeeded(texture); /* in case the app is going to mess with it. */ - return renderer->GL_BindTexture(renderer, texture, texw, texh); - } else { - return SDL_Unsupported(); - } -} - -int SDL_GL_UnbindTexture(SDL_Texture *texture) -{ - SDL_Renderer *renderer; - - CHECK_TEXTURE_MAGIC(texture, -1); - renderer = texture->renderer; - if (texture->native) { - return SDL_GL_UnbindTexture(texture->native); - } else if (renderer && renderer->GL_UnbindTexture) { - FlushRenderCommandsIfTextureNeeded(texture); /* in case the app messed with it. */ - return renderer->GL_UnbindTexture(renderer, texture); - } - - return SDL_Unsupported(); -} - void *SDL_GetRenderMetalLayer(SDL_Renderer *renderer) { CHECK_RENDERER_MAGIC(renderer, NULL); @@ -4307,7 +4310,7 @@ int SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync) int SDL_GetRenderVSync(SDL_Renderer *renderer, int *vsync) { CHECK_RENDERER_MAGIC(renderer, -1); - if (vsync == NULL) { + if (!vsync) { return SDL_InvalidParamError("vsync"); } *vsync = renderer->wanted_vsync; diff --git a/src/timer/dummy/SDL_systimer.c b/src/render/SDL_render_unsupported.c similarity index 70% rename from src/timer/dummy/SDL_systimer.c rename to src/render/SDL_render_unsupported.c index 351f35ce..214b5fff 100644 --- a/src/timer/dummy/SDL_systimer.c +++ b/src/render/SDL_render_unsupported.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,22 +20,3 @@ */ #include "SDL_internal.h" -#if defined(SDL_TIMER_DUMMY) || defined(SDL_TIMERS_DISABLED) - -Uint64 SDL_GetPerformanceCounter(void) -{ - SDL_Unsupported(); - return 0; -} - -Uint64 SDL_GetPerformanceFrequency(void) -{ - return 1; -} - -void SDL_DelayNS(Uint64 ns) -{ - SDL_Unsupported(); -} - -#endif /* SDL_TIMER_DUMMY || SDL_TIMERS_DISABLED */ diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index c7033f8f..ec4b34ab 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -44,6 +44,8 @@ typedef struct SDL_DRect /* The SDL 2D rendering system */ typedef struct SDL_RenderDriver SDL_RenderDriver; +extern char SDL_renderer_magic; +extern char SDL_texture_magic; /* Rendering view state */ typedef struct SDL_RenderViewState @@ -65,7 +67,6 @@ struct SDL_Texture int access; /**< SDL_TextureAccess */ int w; /**< The width of the texture */ int h; /**< The height of the texture */ - int modMode; /**< The texture modulation mode */ SDL_BlendMode blendMode; /**< The texture blend mode */ SDL_ScaleMode scaleMode; /**< The texture scale mode */ SDL_Color color; /**< Texture modulation values */ @@ -83,8 +84,9 @@ struct SDL_Texture Uint32 last_command_generation; /* last command queue generation this texture was in. */ + SDL_PropertiesID props; + void *driverdata; /**< Driver specific texture representation */ - void *userdata; SDL_Texture *prev; SDL_Texture *next; @@ -158,7 +160,7 @@ struct SDL_Renderer void (*WindowEvent)(SDL_Renderer *renderer, const SDL_WindowEvent *event); int (*GetOutputSize)(SDL_Renderer *renderer, int *w, int *h); SDL_bool (*SupportsBlendMode)(SDL_Renderer *renderer, SDL_BlendMode blendMode); - int (*CreateTexture)(SDL_Renderer *renderer, SDL_Texture *texture); + int (*CreateTexture)(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props); int (*QueueSetViewport)(SDL_Renderer *renderer, SDL_RenderCommand *cmd); int (*QueueSetDrawColor)(SDL_Renderer *renderer, SDL_RenderCommand *cmd); int (*QueueDrawPoints)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, @@ -177,6 +179,7 @@ struct SDL_Renderer int num_vertices, const void *indices, int num_indices, int size_indices, float scale_x, float scale_y); + void (*InvalidateCachedState)(SDL_Renderer *renderer); int (*RunCommandQueue)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize); int (*UpdateTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, @@ -206,9 +209,6 @@ struct SDL_Renderer int (*SetVSync)(SDL_Renderer *renderer, int vsync); - int (*GL_BindTexture)(SDL_Renderer *renderer, SDL_Texture *texture, float *texw, float *texh); - int (*GL_UnbindTexture)(SDL_Renderer *renderer, SDL_Texture *texture); - void *(*GetMetalLayer)(SDL_Renderer *renderer); void *(*GetMetalCommandEncoder)(SDL_Renderer *renderer); @@ -252,8 +252,6 @@ struct SDL_Renderer SDL_Color color; /**< Color for drawing operations values */ SDL_BlendMode blendMode; /**< The drawing blend mode */ - SDL_bool always_batch; - SDL_bool batching; SDL_RenderCommand *render_commands; SDL_RenderCommand *render_commands_tail; SDL_RenderCommand *render_commands_pool; @@ -270,13 +268,15 @@ struct SDL_Renderer size_t vertex_data_used; size_t vertex_data_allocation; + SDL_PropertiesID props; + void *driverdata; }; /* Define the SDL render driver structure */ struct SDL_RenderDriver { - SDL_Renderer *(*CreateRenderer)(SDL_Window *window, Uint32 flags); + SDL_Renderer *(*CreateRenderer)(SDL_Window *window, SDL_PropertiesID props); /* Info about the renderer capabilities */ SDL_RendererInfo info; @@ -307,9 +307,6 @@ extern SDL_BlendOperation SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode the next call, because it might be in an array that gets realloc()'d. */ extern void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, const size_t alignment, size_t *offset); -extern int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode); -extern int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect, SDL_ScaleMode scaleMode); - /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/render/SDL_yuv_sw.c b/src/render/SDL_yuv_sw.c index bcc7cdfc..0257c5d6 100644 --- a/src/render/SDL_yuv_sw.c +++ b/src/render/SDL_yuv_sw.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -46,8 +46,7 @@ SDL_SW_YUVTexture *SDL_SW_CreateYUVTexture(Uint32 format, int w, int h) } swdata = (SDL_SW_YUVTexture *)SDL_calloc(1, sizeof(*swdata)); - if (swdata == NULL) { - SDL_OutOfMemory(); + if (!swdata) { return NULL; } @@ -59,13 +58,11 @@ SDL_SW_YUVTexture *SDL_SW_CreateYUVTexture(Uint32 format, int w, int h) size_t dst_size; if (SDL_CalculateYUVSize(format, w, h, &dst_size, NULL) < 0) { SDL_SW_DestroyYUVTexture(swdata); - SDL_OutOfMemory(); return NULL; } swdata->pixels = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), dst_size); if (!swdata->pixels) { SDL_SW_DestroyYUVTexture(swdata); - SDL_OutOfMemory(); return NULL; } } @@ -385,7 +382,7 @@ int SDL_SW_CopyYUVToRGB(SDL_SW_YUVTexture *swdata, const SDL_Rect *srcrect, } if (stretch) { SDL_Rect rect = *srcrect; - SDL_SoftStretch(swdata->stretch, &rect, swdata->display, NULL); + SDL_SoftStretch(swdata->stretch, &rect, swdata->display, NULL, SDL_SCALEMODE_NEAREST); } return 0; } diff --git a/src/render/SDL_yuv_sw_c.h b/src/render/SDL_yuv_sw_c.h index 85d67f04..b3c0f5ef 100644 --- a/src/render/SDL_yuv_sw_c.h +++ b/src/render/SDL_yuv_sw_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c index e04ae375..8c5ef457 100644 --- a/src/render/direct3d/SDL_render_d3d.c +++ b/src/render/direct3d/SDL_render_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,8 +28,6 @@ #include "../SDL_d3dmath.h" #include "../../video/windows/SDL_windowsvideo.h" -#include - #ifdef SDL_VIDEO_RENDER_D3D #define D3D_DEBUG_INFO #include @@ -434,7 +432,7 @@ static int D3D_CreateStagingTexture(IDirect3DDevice9 *device, D3D_TextureRep *te { HRESULT result; - if (texture->staging == NULL) { + if (!texture->staging) { result = IDirect3DDevice9_CreateTexture(device, texture->w, texture->h, 1, 0, texture->d3dfmt, D3DPOOL_SYSTEMMEM, &texture->staging, NULL); if (FAILED(result)) { @@ -519,15 +517,15 @@ static void D3D_DestroyTextureRep(D3D_TextureRep *texture) } } -static int D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; D3D_TextureData *texturedata; DWORD usage; texturedata = (D3D_TextureData *)SDL_calloc(1, sizeof(*texturedata)); - if (texturedata == NULL) { - return SDL_OutOfMemory(); + if (!texturedata) { + return -1; } texturedata->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3DTEXF_POINT : D3DTEXF_LINEAR; @@ -564,7 +562,7 @@ static int D3D_RecreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return 0; } @@ -591,7 +589,7 @@ static int D3D_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return SDL_SetError("Texture is not currently available"); } @@ -627,7 +625,7 @@ static int D3D_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return SDL_SetError("Texture is not currently available"); } @@ -651,7 +649,7 @@ static int D3D_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; IDirect3DDevice9 *device = data->device; - if (texturedata == NULL) { + if (!texturedata) { return SDL_SetError("Texture is not currently available"); } #if SDL_HAVE_YUV @@ -663,7 +661,7 @@ static int D3D_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, texturedata->pitch = texture->w; texturedata->pixels = (Uint8 *)SDL_malloc((texture->h * texturedata->pitch * 3) / 2); if (!texturedata->pixels) { - return SDL_OutOfMemory(); + return -1; } } *pixels = @@ -701,7 +699,7 @@ static void D3D_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return; } #if SDL_HAVE_YUV @@ -729,7 +727,7 @@ static void D3D_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture { D3D_TextureData *texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return; } @@ -745,18 +743,18 @@ static int D3D_SetRenderTargetInternal(SDL_Renderer *renderer, SDL_Texture *text IDirect3DDevice9 *device = data->device; /* Release the previous render target if it wasn't the default one */ - if (data->currentRenderTarget != NULL) { + if (data->currentRenderTarget) { IDirect3DSurface9_Release(data->currentRenderTarget); data->currentRenderTarget = NULL; } - if (texture == NULL) { + if (!texture) { IDirect3DDevice9_SetRenderTarget(data->device, 0, data->defaultRenderTarget); return 0; } texturedata = (D3D_TextureData *)texture->driverdata; - if (texturedata == NULL) { + if (!texturedata) { return SDL_SetError("Texture is not currently available"); } @@ -811,7 +809,7 @@ static int D3D_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, c Vertex *verts = (Vertex *)SDL_AllocateRenderVertices(renderer, vertslen, 0, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -836,7 +834,7 @@ static int D3D_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL int count = indices ? num_indices : num_vertices; Vertex *verts = (Vertex *)SDL_AllocateRenderVertices(renderer, count * sizeof(Vertex), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -932,7 +930,7 @@ static int SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, LPDIREC SDL_assert(*shader == NULL); - if (texturedata == NULL) { + if (!texturedata) { return SDL_SetError("Texture is not currently available"); } @@ -984,11 +982,11 @@ static int SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) LPDIRECT3DPIXELSHADER9 shader = NULL; /* disable any enabled textures we aren't going to use, let SetupTextureState() do the rest. */ - if (texture == NULL) { + if (!texture) { IDirect3DDevice9_SetTexture(data->device, 0, NULL); } #if SDL_HAVE_YUV - if ((newtexturedata == NULL || !newtexturedata->yuv) && (oldtexturedata && oldtexturedata->yuv)) { + if ((!newtexturedata || !newtexturedata->yuv) && (oldtexturedata && oldtexturedata->yuv)) { IDirect3DDevice9_SetTexture(data->device, 1, NULL); IDirect3DDevice9_SetTexture(data->device, 2, NULL); } @@ -1088,6 +1086,17 @@ static int SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) return 0; } +static void D3D_InvalidateCachedState(SDL_Renderer *renderer) +{ + D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; + data->drawstate.viewport_dirty = SDL_TRUE; + data->drawstate.cliprect_enabled_dirty = SDL_TRUE; + data->drawstate.cliprect_dirty = SDL_TRUE; + data->drawstate.blend = SDL_BLENDMODE_INVALID; + data->drawstate.texture = NULL; + data->drawstate.shader = NULL; +} + static int D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; @@ -1186,7 +1195,7 @@ static int D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v const SDL_Rect *viewport = &data->drawstate.viewport; const int backw = istarget ? renderer->target->w : data->pparams.BackBufferWidth; const int backh = istarget ? renderer->target->h : data->pparams.BackBufferHeight; - const SDL_bool viewport_equal = ((viewport->x == 0) && (viewport->y == 0) && (viewport->w == backw) && (viewport->h == backh)) ? SDL_TRUE : SDL_FALSE; + const SDL_bool viewport_equal = ((viewport->x == 0) && (viewport->y == 0) && (viewport->w == backw) && (viewport->h == backh)); if (data->drawstate.cliprect_enabled || data->drawstate.cliprect_enabled_dirty) { IDirect3DDevice9_SetRenderState(data->device, D3DRS_SCISSORTESTENABLE, FALSE); @@ -1382,7 +1391,7 @@ static void D3D_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) #endif } - if (data == NULL) { + if (!data) { return; } @@ -1408,7 +1417,7 @@ static void D3D_DestroyRenderer(SDL_Renderer *renderer) IDirect3DSurface9_Release(data->defaultRenderTarget); data->defaultRenderTarget = NULL; } - if (data->currentRenderTarget != NULL) { + if (data->currentRenderTarget) { IDirect3DSurface9_Release(data->currentRenderTarget); data->currentRenderTarget = NULL; } @@ -1459,7 +1468,7 @@ static int D3D_Reset(SDL_Renderer *renderer) IDirect3DSurface9_Release(data->defaultRenderTarget); data->defaultRenderTarget = NULL; } - if (data->currentRenderTarget != NULL) { + if (data->currentRenderTarget) { IDirect3DSurface9_Release(data->currentRenderTarget); data->currentRenderTarget = NULL; } @@ -1495,19 +1504,16 @@ static int D3D_Reset(SDL_Renderer *renderer) /* Allocate application render targets */ for (texture = renderer->textures; texture; texture = texture->next) { if (texture->access == SDL_TEXTUREACCESS_TARGET) { - D3D_CreateTexture(renderer, texture); + D3D_CreateTexture(renderer, texture, 0); } } IDirect3DDevice9_GetRenderTarget(data->device, 0, &data->defaultRenderTarget); D3D_InitRenderState(data); D3D_SetRenderTargetInternal(renderer, renderer->target); - data->drawstate.viewport_dirty = SDL_TRUE; - data->drawstate.cliprect_dirty = SDL_TRUE; - data->drawstate.cliprect_enabled_dirty = SDL_TRUE; - data->drawstate.texture = NULL; - data->drawstate.shader = NULL; - data->drawstate.blend = SDL_BLENDMODE_INVALID; + + D3D_InvalidateCachedState(renderer); + IDirect3DDevice9_SetTransform(data->device, D3DTS_VIEW, (D3DMATRIX *)&d3dmatrix); /* Let the application know that render targets were reset */ @@ -1538,11 +1544,10 @@ static int D3D_SetVSync(SDL_Renderer *renderer, const int vsync) return 0; } -SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) +SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; D3D_RenderData *data; - SDL_SysWMinfo windowinfo; HRESULT result; D3DPRESENT_PARAMETERS pparams; IDirect3DSwapChain9 *chain; @@ -1552,22 +1557,15 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) SDL_DisplayID displayID; const SDL_DisplayMode *fullscreen_mode = NULL; - if (SDL_GetWindowWMInfo(window, &windowinfo, SDL_SYSWM_CURRENT_VERSION) < 0 || - windowinfo.subsystem != SDL_SYSWM_WINDOWS) { - SDL_SetError("Couldn't get window handle"); - return NULL; - } - renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } + renderer->magic = &SDL_renderer_magic; data = (D3D_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); return NULL; } @@ -1594,6 +1592,7 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = D3D_QueueDrawPoints; renderer->QueueDrawLines = D3D_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = D3D_QueueGeometry; + renderer->InvalidateCachedState = D3D_InvalidateCachedState; renderer->RunCommandQueue = D3D_RunCommandQueue; renderer->RenderReadPixels = D3D_RenderReadPixels; renderer->RenderPresent = D3D_RenderPresent; @@ -1603,6 +1602,7 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = D3D_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + D3D_InvalidateCachedState(renderer); SDL_GetWindowSizeInPixels(window, &w, &h); if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { @@ -1610,7 +1610,7 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) } SDL_zero(pparams); - pparams.hDeviceWindow = windowinfo.info.win.window; + pparams.hDeviceWindow = (HWND)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER, NULL); pparams.BackBufferWidth = w; pparams.BackBufferHeight = h; pparams.BackBufferCount = 1; @@ -1625,7 +1625,7 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) pparams.BackBufferFormat = D3DFMT_UNKNOWN; pparams.FullScreen_RefreshRateInHz = 0; } - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { pparams.PresentationInterval = D3DPRESENT_INTERVAL_ONE; } else { pparams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; @@ -1708,10 +1708,8 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, Uint32 flags) } } #endif - data->drawstate.viewport_dirty = SDL_TRUE; - data->drawstate.cliprect_dirty = SDL_TRUE; - data->drawstate.cliprect_enabled_dirty = SDL_TRUE; - data->drawstate.blend = SDL_BLENDMODE_INVALID; + + SDL_SetProperty(SDL_GetRendererProperties(renderer), SDL_PROPERTY_RENDERER_D3D9_DEVICE_POINTER, data->device); return renderer; } @@ -1726,28 +1724,3 @@ SDL_RenderDriver D3D_RenderDriver = { 0 } }; #endif /* SDL_VIDEO_RENDER_D3D && !SDL_RENDER_DISABLED */ - -#if defined(__WIN32__) || defined(__WINGDK__) -/* This function needs to always exist on Windows, for the Dynamic API. */ -IDirect3DDevice9 *SDL_GetRenderD3D9Device(SDL_Renderer *renderer) -{ - IDirect3DDevice9 *device = NULL; - -#if defined(SDL_VIDEO_RENDER_D3D) && !defined(SDL_RENDER_DISABLED) - D3D_RenderData *data = (D3D_RenderData *)renderer->driverdata; - - /* Make sure that this is a D3D renderer */ - if (renderer->DestroyRenderer != D3D_DestroyRenderer) { - SDL_SetError("Renderer is not a D3D renderer"); - return NULL; - } - - device = data->device; - if (device) { - IDirect3DDevice9_AddRef(device); - } -#endif /* SDL_VIDEO_RENDER_D3D && !SDL_RENDER_DISABLED */ - - return device; -} -#endif /* defined(__WIN32__) || defined(__WINGDK__) */ diff --git a/src/render/direct3d/SDL_shaders_d3d.c b/src/render/direct3d/SDL_shaders_d3d.c index c1d914f2..a05d23da 100644 --- a/src/render/direct3d/SDL_shaders_d3d.c +++ b/src/render/direct3d/SDL_shaders_d3d.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d/SDL_shaders_d3d.h b/src/render/direct3d/SDL_shaders_d3d.h index 922afdc0..13ecf6fc 100644 --- a/src/render/direct3d/SDL_shaders_d3d.h +++ b/src/render/direct3d/SDL_shaders_d3d.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c index 5e1b825b..007f833c 100644 --- a/src/render/direct3d11/SDL_render_d3d11.c +++ b/src/render/direct3d11/SDL_render_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,8 +30,6 @@ #include "../SDL_sysrender.h" #include "../SDL_d3dmath.h" -#include - #include #include "SDL_shaders_d3d11.h" @@ -102,7 +100,6 @@ typedef struct /* NV12 texture support */ SDL_bool nv12; - ID3D11Texture2D *mainTextureNV; ID3D11ShaderResourceView *mainTextureResourceViewNV; Uint8 *pixels; @@ -141,6 +138,7 @@ typedef struct ID3D11SamplerState *nearestPixelSampler; ID3D11SamplerState *linearSampler; D3D_FEATURE_LEVEL featureLevel; + SDL_bool pixelSizeChanged; /* Rasterizers */ ID3D11RasterizerState *mainRasterizer; @@ -177,7 +175,7 @@ typedef struct * linker errors in WinRT/UWP builds.) */ -#ifdef __GNUC__ +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif @@ -192,7 +190,7 @@ static const GUID SDL_IID_ID3D11Device1 = { 0xa04bfb29, 0x08ef, 0x43d6, { 0xa4, static const GUID SDL_IID_ID3D11DeviceContext1 = { 0xbb2c6faa, 0xb5fb, 0x4082, { 0x8e, 0x6b, 0x38, 0x8b, 0x8c, 0xfa, 0x90, 0xe1 } }; /*static const GUID SDL_IID_ID3D11Debug = { 0x79cf2233, 0x7536, 0x4948, { 0x9d, 0x36, 0x1e, 0x46, 0x92, 0xdc, 0x57, 0x60 } };*/ -#ifdef __GNUC__ +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop #endif @@ -208,7 +206,7 @@ Uint32 D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) } } -static DXGI_FORMAT SDLPixelFormatToDXGIFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) { switch (sdlFormat) { case SDL_PIXELFORMAT_ARGB8888: @@ -217,8 +215,26 @@ static DXGI_FORMAT SDLPixelFormatToDXGIFormat(Uint32 sdlFormat) return DXGI_FORMAT_B8G8R8X8_UNORM; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: - case SDL_PIXELFORMAT_NV12: /* For the Y texture */ - case SDL_PIXELFORMAT_NV21: /* For the Y texture */ + return DXGI_FORMAT_R8_UNORM; + case SDL_PIXELFORMAT_NV12: + case SDL_PIXELFORMAT_NV21: + return DXGI_FORMAT_NV12; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 sdlFormat) +{ + switch (sdlFormat) { + case SDL_PIXELFORMAT_ARGB8888: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_XRGB8888: + return DXGI_FORMAT_B8G8R8X8_UNORM; + case SDL_PIXELFORMAT_YV12: + case SDL_PIXELFORMAT_IYUV: + case SDL_PIXELFORMAT_NV12: /* For the Y texture */ + case SDL_PIXELFORMAT_NV21: /* For the Y texture */ return DXGI_FORMAT_R8_UNORM; default: return DXGI_FORMAT_UNKNOWN; @@ -381,9 +397,8 @@ static ID3D11BlendState *D3D11_CreateBlendState(SDL_Renderer *renderer, SDL_Blen } blendModes = (D3D11_BlendMode *)SDL_realloc(data->blendModes, (data->blendModesCount + 1) * sizeof(*blendModes)); - if (blendModes == NULL) { + if (!blendModes) { SAFE_RELEASE(blendState); - SDL_OutOfMemory(); return NULL; } blendModes[data->blendModesCount].blendMode = blendMode; @@ -438,7 +453,7 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) } CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory"); - if (CreateDXGIFactoryFunc == NULL) { + if (!CreateDXGIFactoryFunc) { result = E_FAIL; goto done; } @@ -650,6 +665,8 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) ID3D11DeviceContext_VSSetShader(data->d3dContext, data->vertexShader, NULL, 0); ID3D11DeviceContext_VSSetConstantBuffers(data->d3dContext, 0, 1, &data->vertexShaderConstants); + SDL_SetProperty(SDL_GetRendererProperties(renderer), SDL_PROPERTY_RENDERER_D3D11_DEVICE_POINTER, data->d3dDevice); + done: SAFE_RELEASE(d3dDevice); SAFE_RELEASE(d3dContext); @@ -736,7 +753,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) D3D11_RenderData *data = (D3D11_RenderData *)renderer->driverdata; #ifdef __WINRT__ IUnknown *coreWindow = D3D11_GetCoreWindowFromSDLRenderer(renderer); - const BOOL usingXAML = (coreWindow == NULL); + const BOOL usingXAML = (!coreWindow); #else IUnknown *coreWindow = NULL; const BOOL usingXAML = FALSE; @@ -768,7 +785,12 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) swapChainDesc.Scaling = DXGI_SCALING_STRETCH; } } - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; /* All Windows Store apps must use this SwapEffect. */ + if (SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT) { + swapChainDesc.Scaling = DXGI_SCALING_STRETCH; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + } else { + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; /* All Windows Store apps must use this SwapEffect. */ + } #endif swapChainDesc.Flags = 0; @@ -807,18 +829,11 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) #endif } else { #if defined(__WIN32__) || defined(__WINGDK__) - SDL_SysWMinfo windowinfo; - - if (SDL_GetWindowWMInfo(renderer->window, &windowinfo, SDL_SYSWM_CURRENT_VERSION) < 0 || - windowinfo.subsystem != SDL_SYSWM_WINDOWS) { - SDL_SetError("Couldn't get window handle"); - result = E_FAIL; - goto done; - } + HWND hwnd = (HWND)SDL_GetProperty(SDL_GetWindowProperties(renderer->window), SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER, NULL); result = IDXGIFactory2_CreateSwapChainForHwnd(data->dxgiFactory, (IUnknown *)data->d3dDevice, - windowinfo.info.win.window, + hwnd, &swapChainDesc, NULL, NULL, /* Allow on all displays. */ @@ -828,7 +843,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) goto done; } - IDXGIFactory_MakeWindowAssociation(data->dxgiFactory, windowinfo.info.win.window, DXGI_MWA_NO_WINDOW_CHANGES); + IDXGIFactory_MakeWindowAssociation(data->dxgiFactory, hwnd, DXGI_MWA_NO_WINDOW_CHANGES); #else SDL_SetError(__FUNCTION__ ", Unable to find something to attach a swap chain to"); goto done; @@ -850,8 +865,7 @@ static void D3D11_ReleaseMainRenderTargetView(SDL_Renderer *renderer) static HRESULT D3D11_UpdateForWindowSizeChange(SDL_Renderer *renderer); -HRESULT -D3D11_HandleDeviceLost(SDL_Renderer *renderer) +static HRESULT D3D11_HandleDeviceLost(SDL_Renderer *renderer) { HRESULT result = S_OK; @@ -931,7 +945,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) #endif } else { result = D3D11_CreateSwapChain(renderer, w, h); - if (FAILED(result) || data->swapChain == NULL) { + if (FAILED(result) || !data->swapChain) { goto done; } } @@ -1027,8 +1041,10 @@ void D3D11_Trim(SDL_Renderer *renderer) static void D3D11_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event) { + D3D11_RenderData *data = (D3D11_RenderData *)renderer->driverdata; + if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) { - D3D11_UpdateForWindowSizeChange(renderer); + data->pixelSizeChanged = SDL_TRUE; } } @@ -1050,12 +1066,24 @@ static SDL_bool D3D11_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode bl return SDL_TRUE; } -static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int GetTextureProperty(SDL_PropertiesID props, const char *name, ID3D11Texture2D **texture) +{ + IUnknown *unknown = SDL_GetProperty(props, name, NULL); + if (unknown) { + HRESULT result = IUnknown_QueryInterface(unknown, &SDL_IID_ID3D11Texture2D, (void **)texture); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT(name, result); + } + } + return 0; +} + +static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData; HRESULT result; - DXGI_FORMAT textureFormat = SDLPixelFormatToDXGIFormat(texture->format); + DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format); D3D11_TEXTURE2D_DESC textureDesc; D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; @@ -1065,8 +1093,7 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) } textureData = (D3D11_TextureData *)SDL_calloc(1, sizeof(*textureData)); - if (textureData == NULL) { - SDL_OutOfMemory(); + if (!textureData) { return -1; } textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D11_FILTER_MIN_MAG_MIP_POINT : D3D11_FILTER_MIN_MAG_MIP_LINEAR; @@ -1097,14 +1124,20 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; } - result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, - &textureDesc, - NULL, - &textureData->mainTexture); - if (FAILED(result)) { - D3D11_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + if (GetTextureProperty(create_props, "d3d11.texture", &textureData->mainTexture) < 0) { + return -1; } + if (!textureData->mainTexture) { + result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, + &textureDesc, + NULL, + &textureData->mainTexture); + if (FAILED(result)) { + D3D11_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + } + } + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_POINTER, textureData->mainTexture); #if SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -1113,47 +1146,43 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) textureDesc.Width = (textureDesc.Width + 1) / 2; textureDesc.Height = (textureDesc.Height + 1) / 2; - result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, - &textureDesc, - NULL, - &textureData->mainTextureU); - if (FAILED(result)) { - D3D11_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + if (GetTextureProperty(create_props, "d3d11.texture_u", &textureData->mainTextureU) < 0) { + return -1; } + if (!textureData->mainTextureU) { + result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, + &textureDesc, + NULL, + &textureData->mainTextureU); + if (FAILED(result)) { + D3D11_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + } + } + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_U_POINTER, textureData->mainTextureU); - result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, - &textureDesc, - NULL, - &textureData->mainTextureV); - if (FAILED(result)) { - D3D11_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + if (GetTextureProperty(create_props, "d3d11.texture_v", &textureData->mainTextureV) < 0) { + return -1; } + if (!textureData->mainTextureV) { + result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, + &textureDesc, + NULL, + &textureData->mainTextureV); + if (FAILED(result)) { + D3D11_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); + } + } + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_V_POINTER, textureData->mainTextureV); } - if (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21) { - D3D11_TEXTURE2D_DESC nvTextureDesc = textureDesc; - textureData->nv12 = SDL_TRUE; - - nvTextureDesc.Format = DXGI_FORMAT_R8G8_UNORM; - nvTextureDesc.Width = (textureDesc.Width + 1) / 2; - nvTextureDesc.Height = (textureDesc.Height + 1) / 2; - - result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, - &nvTextureDesc, - NULL, - &textureData->mainTextureNV); - if (FAILED(result)) { - D3D11_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D"), result); - } } #endif /* SDL_HAVE_YUV */ SDL_zero(resourceViewDesc); - resourceViewDesc.Format = textureDesc.Format; + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format); resourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; resourceViewDesc.Texture2D.MostDetailedMip = 0; resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; @@ -1191,7 +1220,7 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) nvResourceViewDesc.Format = DXGI_FORMAT_R8G8_UNORM; result = ID3D11Device_CreateShaderResourceView(rendererData->d3dDevice, - (ID3D11Resource *)textureData->mainTextureNV, + (ID3D11Resource *)textureData->mainTexture, &nvResourceViewDesc, &textureData->mainTextureResourceViewNV); if (FAILED(result)) { @@ -1226,7 +1255,7 @@ static void D3D11_DestroyTexture(SDL_Renderer *renderer, { D3D11_TextureData *data = (D3D11_TextureData *)texture->driverdata; - if (data == NULL) { + if (!data) { return; } @@ -1239,7 +1268,6 @@ static void D3D11_DestroyTexture(SDL_Renderer *renderer, SAFE_RELEASE(data->mainTextureResourceViewU); SAFE_RELEASE(data->mainTextureV); SAFE_RELEASE(data->mainTextureResourceViewV); - SAFE_RELEASE(data->mainTextureNV); SAFE_RELEASE(data->mainTextureResourceViewNV); SDL_free(data->pixels); #endif @@ -1286,6 +1314,11 @@ static int D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Tex return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); } + if (stagingTextureDesc.Format == DXGI_FORMAT_NV12) { + /* Copy the UV plane as well */ + h += (h + 1) / 2; + } + src = (const Uint8 *)pixels; dst = textureMemory.pData; length = w * bpp; @@ -1333,7 +1366,7 @@ static int D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } @@ -1355,15 +1388,6 @@ static int D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, return -1; } } - - if (textureData->nv12) { - /* Skip to the correct offset into the next texture */ - srcPixels = (const void *)((const Uint8 *)srcPixels + rect->h * srcPitch); - - if (D3D11_UpdateTextureInternal(rendererData, textureData->mainTextureNV, 2, rect->x / 2, rect->y / 2, ((rect->w + 1) / 2), (rect->h + 1) / 2, srcPixels, 2 * ((srcPitch + 1) / 2)) < 0) { - return -1; - } - } #endif /* SDL_HAVE_YUV */ return 0; } @@ -1378,7 +1402,7 @@ static int D3D11_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } @@ -1401,18 +1425,110 @@ static int D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->driverdata; + ID3D11Texture2D *stagingTexture; + const Uint8 *src; + Uint8 *dst; + int w, h, row; + UINT length; + HRESULT result; + D3D11_TEXTURE2D_DESC stagingTextureDesc; + D3D11_MAPPED_SUBRESOURCE textureMemory; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } - if (D3D11_UpdateTextureInternal(rendererData, textureData->mainTexture, SDL_BYTESPERPIXEL(texture->format), rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch) < 0) { - return -1; + w = rect->w; + h = rect->h; + + /* Create a 'staging' texture, which will be used to write to a portion of the main texture. */ + ID3D11Texture2D_GetDesc(textureData->mainTexture, &stagingTextureDesc); + stagingTextureDesc.Width = w; + stagingTextureDesc.Height = h; + stagingTextureDesc.BindFlags = 0; + stagingTextureDesc.MiscFlags = 0; + stagingTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + stagingTextureDesc.Usage = D3D11_USAGE_STAGING; + result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, + &stagingTextureDesc, + NULL, + &stagingTexture); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result); } - if (D3D11_UpdateTextureInternal(rendererData, textureData->mainTextureNV, 2, rect->x / 2, rect->y / 2, ((rect->w + 1) / 2), (rect->h + 1) / 2, UVplane, UVpitch) < 0) { - return -1; + /* Get a write-only pointer to data in the staging texture: */ + result = ID3D11DeviceContext_Map(rendererData->d3dContext, + (ID3D11Resource *)stagingTexture, + 0, + D3D11_MAP_WRITE, + 0, + &textureMemory); + if (FAILED(result)) { + SAFE_RELEASE(stagingTexture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result); } + + src = Yplane; + dst = textureMemory.pData; + length = w; + if (length == (UINT)Ypitch && length == textureMemory.RowPitch) { + SDL_memcpy(dst, src, (size_t)length * rect->h); + dst += length * rect->h; + } else { + if (length > (UINT)Ypitch) { + length = Ypitch; + } + if (length > textureMemory.RowPitch) { + length = textureMemory.RowPitch; + } + for (row = 0; row < h; ++row) { + SDL_memcpy(dst, src, length); + src += Ypitch; + dst += textureMemory.RowPitch; + } + } + + /* Adjust dimensions for the UV plane */ + w = ((w + 1) / 2) * 2; + h = ((h + 1) / 2); + + src = UVplane; + length = w; + if (length == (UINT)UVpitch && length == textureMemory.RowPitch) { + SDL_memcpy(dst, src, (size_t)length * h); + } else { + if (length > (UINT)UVpitch) { + length = UVpitch; + } + if (length > textureMemory.RowPitch) { + length = textureMemory.RowPitch; + } + for (row = 0; row < h; ++row) { + SDL_memcpy(dst, src, length); + src += UVpitch; + dst += textureMemory.RowPitch; + } + } + + /* Commit the pixel buffer's changes back to the staging texture: */ + ID3D11DeviceContext_Unmap(rendererData->d3dContext, + (ID3D11Resource *)stagingTexture, + 0); + + /* Copy the staging texture's contents back to the texture: */ + ID3D11DeviceContext_CopySubresourceRegion(rendererData->d3dContext, + (ID3D11Resource *)textureData->mainTexture, + 0, + rect->x, + rect->y, + 0, + (ID3D11Resource *)stagingTexture, + 0, + NULL); + + SAFE_RELEASE(stagingTexture); + return 0; } #endif @@ -1426,7 +1542,7 @@ static int D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, D3D11_TEXTURE2D_DESC stagingTextureDesc; D3D11_MAPPED_SUBRESOURCE textureMemory; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } #if SDL_HAVE_YUV @@ -1436,7 +1552,7 @@ static int D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, textureData->pitch = texture->w; textureData->pixels = (Uint8 *)SDL_malloc((texture->h * textureData->pitch * 3) / 2); if (!textureData->pixels) { - return SDL_OutOfMemory(); + return -1; } } textureData->locked_rect = *rect; @@ -1505,7 +1621,7 @@ static void D3D11_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData = (D3D11_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return; } #if SDL_HAVE_YUV @@ -1541,7 +1657,7 @@ static void D3D11_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *textu { D3D11_TextureData *textureData = (D3D11_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return; } @@ -1553,7 +1669,7 @@ static int D3D11_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData = NULL; - if (texture == NULL) { + if (!texture) { rendererData->currentOffscreenRenderTargetView = NULL; return 0; } @@ -1584,7 +1700,7 @@ static int D3D11_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, color.b = cmd->data.draw.b; color.a = cmd->data.draw.a; - if (verts == NULL) { + if (!verts) { return -1; } @@ -1611,7 +1727,7 @@ static int D3D11_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S int count = indices ? num_indices : num_vertices; VertexPositionColor *verts = (VertexPositionColor *)SDL_AllocateRenderVertices(renderer, count * sizeof(VertexPositionColor), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -1883,9 +1999,9 @@ static int D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *c break; } } - if (blendState == NULL) { + if (!blendState) { blendState = D3D11_CreateBlendState(renderer, blendMode); - if (blendState == NULL) { + if (!blendState) { return -1; } } @@ -2006,11 +2122,29 @@ static void D3D11_DrawPrimitives(SDL_Renderer *renderer, D3D11_PRIMITIVE_TOPOLOG ID3D11DeviceContext_Draw(rendererData->d3dContext, (UINT)vertexCount, (UINT)vertexStart); } +static void D3D11_InvalidateCachedState(SDL_Renderer *renderer) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->driverdata; + data->currentRenderTargetView = NULL; + data->currentRasterizerState = NULL; + data->currentBlendState = NULL; + data->currentShader = NULL; + data->currentShaderResource = NULL; + data->currentSampler = NULL; + data->cliprectDirty = SDL_TRUE; + data->viewportDirty = SDL_TRUE; +} + static int D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; const int viewportRotation = D3D11_GetRotationForCurrentRenderTarget(renderer); + if (rendererData->pixelSizeChanged) { + D3D11_UpdateForWindowSizeChange(renderer); + rendererData->pixelSizeChanged = SDL_FALSE; + } + if (rendererData->currentViewportRotation != viewportRotation) { rendererData->currentViewportRotation = viewportRotation; rendererData->viewportDirty = SDL_TRUE; @@ -2138,13 +2272,13 @@ static int D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, D3D11_MAPPED_SUBRESOURCE textureMemory; renderTargetView = D3D11_GetCurrentRenderTargetView(renderer); - if (renderTargetView == NULL) { + if (!renderTargetView) { SDL_SetError("%s, ID3D11DeviceContext::OMGetRenderTargets failed", __FUNCTION__); goto done; } ID3D11View_GetResource(renderTargetView, (ID3D11Resource **)&backBuffer); - if (backBuffer == NULL) { + if (!backBuffer) { SDL_SetError("%s, ID3D11View::GetResource failed", __FUNCTION__); goto done; } @@ -2293,21 +2427,20 @@ static int D3D11_SetVSync(SDL_Renderer *renderer, const int vsync) } #endif -SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, Uint32 flags) +SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; D3D11_RenderData *data; renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } + renderer->magic = &SDL_renderer_magic; data = (D3D11_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); return NULL; } @@ -2330,6 +2463,7 @@ SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = D3D11_QueueDrawPoints; renderer->QueueDrawLines = D3D11_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = D3D11_QueueGeometry; + renderer->InvalidateCachedState = D3D11_InvalidateCachedState; renderer->RunCommandQueue = D3D11_RunCommandQueue; renderer->RenderReadPixels = D3D11_RenderReadPixels; renderer->RenderPresent = D3D11_RenderPresent; @@ -2338,6 +2472,7 @@ SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = D3D11_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + D3D11_InvalidateCachedState(renderer); #if SDL_WINAPI_FAMILY_PHONE /* VSync is required in Windows Phone, at least for Win Phone 8.0 and 8.1. @@ -2353,7 +2488,7 @@ SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, Uint32 flags) */ renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; #else - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } renderer->SetVSync = D3D11_SetVSync; @@ -2397,28 +2532,3 @@ SDL_RenderDriver D3D11_RenderDriver = { }; #endif /* SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED */ - -#if defined(__WIN32__) || defined(__WINGDK__) -/* This function needs to always exist on Windows, for the Dynamic API. */ -ID3D11Device *SDL_GetRenderD3D11Device(SDL_Renderer *renderer) -{ - ID3D11Device *device = NULL; - -#if defined(SDL_VIDEO_RENDER_D3D11) && !defined(SDL_RENDER_DISABLED) - D3D11_RenderData *data = (D3D11_RenderData *)renderer->driverdata; - - /* Make sure that this is a D3D renderer */ - if (renderer->DestroyRenderer != D3D11_DestroyRenderer) { - SDL_SetError("Renderer is not a D3D11 renderer"); - return NULL; - } - - device = (ID3D11Device *)data->d3dDevice; - if (device) { - ID3D11Device_AddRef(device); - } -#endif /* SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED */ - - return device; -} -#endif /* defined(__WIN32__) || defined(__WINGDK__) */ diff --git a/src/render/direct3d11/SDL_render_winrt.cpp b/src/render/direct3d11/SDL_render_winrt.cpp index 0bf3609c..0378ee28 100644 --- a/src/render/direct3d11/SDL_render_winrt.cpp +++ b/src/render/direct3d11/SDL_render_winrt.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -39,35 +39,14 @@ using namespace Windows::Graphics::Display; #include -#include - #include "SDL_render_winrt.h" extern "C" void * D3D11_GetCoreWindowFromSDLRenderer(SDL_Renderer *renderer) { - SDL_Window *sdlWindow = renderer->window; - if (renderer->window == NULL) { - return NULL; - } - - SDL_SysWMinfo sdlWindowInfo; - if (SDL_GetWindowWMInfo(sdlWindow, &sdlWindowInfo, SDL_SYSWM_CURRENT_VERSION) < 0 || - sdlWindowInfo.subsystem != SDL_SYSWM_WINRT) { - SDL_SetError("Couldn't get window handle"); - return NULL; - } - - if (sdlWindowInfo.subsystem != SDL_SYSWM_WINRT) { - return NULL; - } - - if (!sdlWindowInfo.info.winrt.window) { - return NULL; - } - + IInspectable *window = (IInspectable *)SDL_GetProperty(SDL_GetWindowProperties(renderer->window), SDL_PROPERTY_WINDOW_WINRT_WINDOW_POINTER, NULL); ABI::Windows::UI::Core::ICoreWindow *coreWindow = NULL; - if (FAILED(sdlWindowInfo.info.winrt.window->QueryInterface(&coreWindow))) { + if (!window || FAILED(window->QueryInterface(&coreWindow))) { return NULL; } diff --git a/src/render/direct3d11/SDL_render_winrt.h b/src/render/direct3d11/SDL_render_winrt.h index ce1757a9..cee20b51 100644 --- a/src/render/direct3d11/SDL_render_winrt.h +++ b/src/render/direct3d11/SDL_render_winrt.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d11/SDL_shaders_d3d11.c b/src/render/direct3d11/SDL_shaders_d3d11.c index 3019751b..66f7c8ce 100644 --- a/src/render/direct3d11/SDL_shaders_d3d11.c +++ b/src/render/direct3d11/SDL_shaders_d3d11.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d11/SDL_shaders_d3d11.h b/src/render/direct3d11/SDL_shaders_d3d11.h index 7f49498d..14cbe0e5 100644 --- a/src/render/direct3d11/SDL_shaders_d3d11.h +++ b/src/render/direct3d11/SDL_shaders_d3d11.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c index 43ce78b4..f87fcb91 100644 --- a/src/render/direct3d12/SDL_render_d3d12.c +++ b/src/render/direct3d12/SDL_render_d3d12.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,8 +32,6 @@ #include "../SDL_sysrender.h" #include "../SDL_d3dmath.h" -#include - #if defined(__XBOXONE__) || defined(__XBOXSERIES__) #include "SDL_render_d3d12_xbox.h" #ifndef D3D12_TEXTURE_DATA_PITCH_ALIGNMENT @@ -123,9 +121,7 @@ typedef struct /* NV12 texture support */ SDL_bool nv12; - ID3D12Resource *mainTextureNV; D3D12_CPU_DESCRIPTOR_HANDLE mainTextureResourceViewNV; - D3D12_RESOURCE_STATES mainResourceStateNV; SIZE_T mainSRVIndexNV; Uint8 *pixels; @@ -178,6 +174,7 @@ typedef struct ID3D12GraphicsCommandList2 *commandList; DXGI_SWAP_EFFECT swapEffect; UINT swapFlags; + SDL_bool pixelSizeChanged; /* Descriptor heaps */ ID3D12DescriptorHeap *rtvDescriptorHeap; @@ -238,7 +235,7 @@ typedef struct /* Define D3D GUIDs here so we don't have to include uuid.lib. */ -#if defined(__GNUC__) || defined(__clang__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif @@ -263,7 +260,7 @@ static const GUID SDL_IID_ID3D12PipelineState = { 0x765a30f3, 0xf624, 0x4c6f, { static const GUID SDL_IID_ID3D12Heap = { 0x6b3b2502, 0x6e51, 0x45b3, { 0x90, 0xee, 0x98, 0x84, 0x26, 0x5e, 0x8d, 0xf3 } }; static const GUID SDL_IID_ID3D12InfoQueue = { 0x0742a90b, 0xc387, 0x483f, { 0xb9, 0x46, 0x30, 0xa7, 0xe4, 0xe6, 0x14, 0x58 } }; -#if defined(__GNUC__) || defined(__clang__) +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop #endif @@ -284,7 +281,25 @@ Uint32 D3D12_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) } } -static DXGI_FORMAT SDLPixelFormatToDXGIFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) +{ + switch (sdlFormat) { + case SDL_PIXELFORMAT_ARGB8888: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SDL_PIXELFORMAT_XRGB8888: + return DXGI_FORMAT_B8G8R8X8_UNORM; + case SDL_PIXELFORMAT_YV12: + case SDL_PIXELFORMAT_IYUV: + return DXGI_FORMAT_R8_UNORM; + case SDL_PIXELFORMAT_NV12: + case SDL_PIXELFORMAT_NV21: + return DXGI_FORMAT_NV12; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 sdlFormat) { switch (sdlFormat) { case SDL_PIXELFORMAT_ARGB8888: @@ -308,6 +323,10 @@ static void D3D12_ReleaseAll(SDL_Renderer *renderer) D3D12_RenderData *data = (D3D12_RenderData *)renderer->driverdata; SDL_Texture *texture = NULL; + SDL_PropertiesID props = SDL_GetRendererProperties(renderer); + SDL_SetProperty(props, SDL_PROPERTY_RENDERER_D3D12_DEVICE_POINTER, NULL); + SDL_SetProperty(props, SDL_PROPERTY_RENDERER_D3D12_COMMAND_QUEUE_POINTER, NULL); + /* Release all textures */ for (texture = renderer->textures; texture; texture = texture->next) { D3D12_DestroyTexture(renderer, texture); @@ -461,6 +480,7 @@ static void D3D12_ResetCommandList(D3D12_RenderData *data) data->cliprectDirty = SDL_TRUE; data->viewportDirty = SDL_TRUE; data->currentRenderTargetView.ptr = 0; + /* FIXME should we also clear currentSampler.ptr and currentRenderTargetView.ptr ? (and use D3D12_InvalidateCachedState() instead) */ /* Release any upload buffers that were inflight */ for (i = 0; i < data->currentUploadBuffer; ++i) { @@ -623,9 +643,8 @@ static D3D12_PipelineState *D3D12_CreatePipelineState(SDL_Renderer *renderer, } pipelineStates = (D3D12_PipelineState *)SDL_realloc(data->pipelineStates, (data->pipelineStateCount + 1) * sizeof(*pipelineStates)); - if (pipelineStates == NULL) { + if (!pipelineStates) { SAFE_RELEASE(pipelineState); - SDL_OutOfMemory(); return NULL; } @@ -703,7 +722,7 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) HRESULT result = S_OK; UINT creationFlags = 0; int i, j, k, l; - SDL_bool createDebug = SDL_FALSE; + SDL_bool createDebug; D3D12_COMMAND_QUEUE_DESC queueDesc; D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc; @@ -738,7 +757,7 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) } } #endif - if (CreateEventExFunc == NULL) { + if (!CreateEventExFunc) { result = E_FAIL; goto done; } @@ -751,7 +770,7 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) } CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(data->hDXGIMod, "CreateDXGIFactory2"); - if (CreateDXGIFactoryFunc == NULL) { + if (!CreateDXGIFactoryFunc) { result = E_FAIL; goto done; } @@ -776,8 +795,9 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) result = E_FAIL; goto done; } - D3D12GetDebugInterfaceFunc(D3D_GUID(SDL_IID_ID3D12Debug), (void **)&data->debugInterface); - D3D_CALL(data->debugInterface, EnableDebugLayer); + if (SUCCEEDED(D3D12GetDebugInterfaceFunc(D3D_GUID(SDL_IID_ID3D12Debug), (void **)&data->debugInterface))) { + D3D_CALL(data->debugInterface, EnableDebugLayer); + } } #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ @@ -795,7 +815,7 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) /* If the debug hint is set, also create the DXGI factory in debug mode */ DXGIGetDebugInterfaceFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(data->hDXGIMod, "DXGIGetDebugInterface1"); - if (DXGIGetDebugInterfaceFunc == NULL) { + if (!DXGIGetDebugInterfaceFunc) { result = E_FAIL; goto done; } @@ -1052,6 +1072,11 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer) } } data->srvPoolHead = &data->srvPoolNodes[0]; + + SDL_PropertiesID props = SDL_GetRendererProperties(renderer); + SDL_SetProperty(props, SDL_PROPERTY_RENDERER_D3D12_DEVICE_POINTER, data->d3dDevice); + SDL_SetProperty(props, SDL_PROPERTY_RENDERER_D3D12_COMMAND_QUEUE_POINTER, data->commandQueue); + done: SAFE_RELEASE(d3dDevice); return result; @@ -1133,7 +1158,6 @@ static HRESULT D3D12_CreateSwapChain(SDL_Renderer *renderer, int w, int h) D3D12_RenderData *data = (D3D12_RenderData *)renderer->driverdata; IDXGISwapChain1 *swapChain = NULL; HRESULT result = S_OK; - SDL_SysWMinfo windowinfo; /* Create a swap chain using the same adapter as the existing Direct3D device. */ DXGI_SWAP_CHAIN_DESC1 swapChainDesc; @@ -1155,16 +1179,11 @@ static HRESULT D3D12_CreateSwapChain(SDL_Renderer *renderer, int w, int h) swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT | /* To support SetMaximumFrameLatency */ DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; /* To support presenting with allow tearing on */ - if (SDL_GetWindowWMInfo(renderer->window, &windowinfo, SDL_SYSWM_CURRENT_VERSION) < 0 || - windowinfo.subsystem != SDL_SYSWM_WINDOWS) { - SDL_SetError("Couldn't get window handle"); - result = E_FAIL; - goto done; - } + HWND hwnd = (HWND)SDL_GetProperty(SDL_GetWindowProperties(renderer->window), SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER, NULL); result = D3D_CALL(data->dxgiFactory, CreateSwapChainForHwnd, (IUnknown *)data->commandQueue, - windowinfo.info.win.window, + hwnd, &swapChainDesc, NULL, NULL, /* Allow on all displays. */ @@ -1174,7 +1193,7 @@ static HRESULT D3D12_CreateSwapChain(SDL_Renderer *renderer, int w, int h) goto done; } - D3D_CALL(data->dxgiFactory, MakeWindowAssociation, windowinfo.info.win.window, DXGI_MWA_NO_WINDOW_CHANGES); + D3D_CALL(data->dxgiFactory, MakeWindowAssociation, hwnd, DXGI_MWA_NO_WINDOW_CHANGES); result = D3D_CALL(swapChain, QueryInterface, D3D_GUID(SDL_IID_IDXGISwapChain4), (void **)&data->swapChain); if (FAILED(result)) { @@ -1284,7 +1303,7 @@ static HRESULT D3D12_CreateWindowSizeDependentResources(SDL_Renderer *renderer) } } else { result = D3D12_CreateSwapChain(renderer, w, h); - if (FAILED(result)) { + if (FAILED(result) || !data->swapChain) { goto done; } } @@ -1369,8 +1388,10 @@ static HRESULT D3D12_UpdateForWindowSizeChange(SDL_Renderer *renderer) static void D3D12_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event) { + D3D12_RenderData *data = (D3D12_RenderData *)renderer->driverdata; + if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) { - D3D12_UpdateForWindowSizeChange(renderer); + data->pixelSizeChanged = SDL_TRUE; } } @@ -1412,12 +1433,24 @@ static void D3D12_FreeSRVIndex(SDL_Renderer *renderer, SIZE_T index) rendererData->srvPoolHead = &rendererData->srvPoolNodes[index]; } -static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int GetTextureProperty(SDL_PropertiesID props, const char *name, ID3D12Resource **texture) +{ + IUnknown *unknown = (IUnknown*)SDL_GetProperty(props, name, NULL); + if (unknown) { + HRESULT result = D3D_CALL(unknown, QueryInterface, D3D_GUID(SDL_IID_ID3D12Resource), (void **)texture); + if (FAILED(result)) { + return WIN_SetErrorFromHRESULT(name, result); + } + } + return 0; +} + +static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData; HRESULT result; - DXGI_FORMAT textureFormat = SDLPixelFormatToDXGIFormat(texture->format); + DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format); D3D12_RESOURCE_DESC textureDesc; D3D12_HEAP_PROPERTIES heapProps; D3D12_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; @@ -1427,8 +1460,7 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) } textureData = (D3D12_TextureData *)SDL_calloc(1, sizeof(*textureData)); - if (textureData == NULL) { - SDL_OutOfMemory(); + if (!textureData) { return -1; } textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? D3D12_FILTER_MIN_MAG_MIP_POINT : D3D12_FILTER_MIN_MAG_MIP_LINEAR; @@ -1456,19 +1488,25 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; - result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, - &heapProps, - D3D12_HEAP_FLAG_NONE, - &textureDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - NULL, - D3D_GUID(SDL_IID_ID3D12Resource), - (void **)&textureData->mainTexture); - textureData->mainResourceState = D3D12_RESOURCE_STATE_COPY_DEST; - if (FAILED(result)) { - D3D12_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); + if (GetTextureProperty(create_props, "d3d12.texture", &textureData->mainTexture) < 0) { + return -1; } + if (!textureData->mainTexture) { + result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, + &heapProps, + D3D12_HEAP_FLAG_NONE, + &textureDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + D3D_GUID(SDL_IID_ID3D12Resource), + (void **)&textureData->mainTexture); + if (FAILED(result)) { + D3D12_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); + } + } + textureData->mainResourceState = D3D12_RESOURCE_STATE_COPY_DEST; + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_POINTER, textureData->mainTexture); #if SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || texture->format == SDL_PIXELFORMAT_IYUV) { @@ -1477,65 +1515,56 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) textureDesc.Width = (textureDesc.Width + 1) / 2; textureDesc.Height = (textureDesc.Height + 1) / 2; - result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, - &heapProps, - D3D12_HEAP_FLAG_NONE, - &textureDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - NULL, - D3D_GUID(SDL_IID_ID3D12Resource), - (void **)&textureData->mainTextureU); + if (GetTextureProperty(create_props, "d3d12.texture_u", &textureData->mainTextureU) < 0) { + return -1; + } + if (!textureData->mainTextureU) { + result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, + &heapProps, + D3D12_HEAP_FLAG_NONE, + &textureDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + D3D_GUID(SDL_IID_ID3D12Resource), + (void **)&textureData->mainTextureU); + if (FAILED(result)) { + D3D12_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); + } + } textureData->mainResourceStateU = D3D12_RESOURCE_STATE_COPY_DEST; - if (FAILED(result)) { - D3D12_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); - } + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_U_POINTER, textureData->mainTextureU); - result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, - &heapProps, - D3D12_HEAP_FLAG_NONE, - &textureDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - NULL, - D3D_GUID(SDL_IID_ID3D12Resource), - (void **)&textureData->mainTextureV); - textureData->mainResourceStateV = D3D12_RESOURCE_STATE_COPY_DEST; - if (FAILED(result)) { - D3D12_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); + if (GetTextureProperty(create_props, "d3d12.texture_v", &textureData->mainTextureV) < 0) { + return -1; } + if (!textureData->mainTextureV) { + result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, + &heapProps, + D3D12_HEAP_FLAG_NONE, + &textureDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + D3D_GUID(SDL_IID_ID3D12Resource), + (void **)&textureData->mainTextureV); + if (FAILED(result)) { + D3D12_DestroyTexture(renderer, texture); + return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateCommittedResource [texture]"), result); + } + } + textureData->mainResourceStateV = D3D12_RESOURCE_STATE_COPY_DEST; + SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_D3D12_TEXTURE_V_POINTER, textureData->mainTextureV); } if (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21) { - D3D12_RESOURCE_DESC nvTextureDesc = textureDesc; - textureData->nv12 = SDL_TRUE; - - nvTextureDesc.Format = DXGI_FORMAT_R8G8_UNORM; - nvTextureDesc.Width = (textureDesc.Width + 1) / 2; - nvTextureDesc.Height = (textureDesc.Height + 1) / 2; - - result = D3D_CALL(rendererData->d3dDevice, CreateCommittedResource, - &heapProps, - D3D12_HEAP_FLAG_NONE, - &nvTextureDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - NULL, - D3D_GUID(SDL_IID_ID3D12Resource), - (void **)&textureData->mainTextureNV); - textureData->mainResourceStateNV = D3D12_RESOURCE_STATE_COPY_DEST; - if (FAILED(result)) { - D3D12_DestroyTexture(renderer, texture); - return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Device::CreateTexture2D"), result); - } } #endif /* SDL_HAVE_YUV */ SDL_zero(resourceViewDesc); resourceViewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - resourceViewDesc.Format = textureDesc.Format; + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format); resourceViewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - resourceViewDesc.Texture2D.MostDetailedMip = 0; resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; textureData->mainSRVIndex = D3D12_GetAvailableSRVIndex(renderer); @@ -1569,12 +1598,13 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) D3D12_SHADER_RESOURCE_VIEW_DESC nvResourceViewDesc = resourceViewDesc; nvResourceViewDesc.Format = DXGI_FORMAT_R8G8_UNORM; + nvResourceViewDesc.Texture2D.PlaneSlice = 1; D3D_CALL_RET(rendererData->srvDescriptorHeap, GetCPUDescriptorHandleForHeapStart, &textureData->mainTextureResourceViewNV); textureData->mainSRVIndexNV = D3D12_GetAvailableSRVIndex(renderer); textureData->mainTextureResourceViewNV.ptr += textureData->mainSRVIndexNV * rendererData->srvDescriptorSize; D3D_CALL(rendererData->d3dDevice, CreateShaderResourceView, - textureData->mainTextureNV, + textureData->mainTexture, &nvResourceViewDesc, textureData->mainTextureResourceViewNV); } @@ -1605,7 +1635,7 @@ static void D3D12_DestroyTexture(SDL_Renderer *renderer, D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData = (D3D12_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return; } @@ -1623,8 +1653,7 @@ static void D3D12_DestroyTexture(SDL_Renderer *renderer, D3D12_FreeSRVIndex(renderer, textureData->mainSRVIndexU); D3D12_FreeSRVIndex(renderer, textureData->mainSRVIndexV); } - SAFE_RELEASE(textureData->mainTextureNV); - if (textureData->yuv) { + if (textureData->nv12) { D3D12_FreeSRVIndex(renderer, textureData->mainSRVIndexNV); } SDL_free(textureData->pixels); @@ -1633,22 +1662,22 @@ static void D3D12_DestroyTexture(SDL_Renderer *renderer, texture->driverdata = NULL; } -static int D3D12_UpdateTextureInternal(D3D12_RenderData *rendererData, ID3D12Resource *texture, int bpp, int x, int y, int w, int h, const void *pixels, int pitch, D3D12_RESOURCE_STATES *resourceState) +static int D3D12_UpdateTextureInternal(D3D12_RenderData *rendererData, ID3D12Resource *texture, int plane, int x, int y, int w, int h, const void *pixels, int pitch, D3D12_RESOURCE_STATES *resourceState) { const Uint8 *src; Uint8 *dst; - int row; UINT length; HRESULT result; D3D12_RESOURCE_DESC textureDesc; D3D12_RESOURCE_DESC uploadDesc; D3D12_HEAP_PROPERTIES heapProps; - D3D12_SUBRESOURCE_FOOTPRINT pitchedDesc; D3D12_PLACED_SUBRESOURCE_FOOTPRINT placedTextureDesc; D3D12_TEXTURE_COPY_LOCATION srcLocation; D3D12_TEXTURE_COPY_LOCATION dstLocation; BYTE *textureMemory; ID3D12Resource *uploadBuffer; + UINT row, NumRows, RowPitch; + UINT64 RowLength; /* Create an upload buffer, which will be used to write to the main texture. */ SDL_zero(textureDesc); @@ -1671,13 +1700,14 @@ static int D3D12_UpdateTextureInternal(D3D12_RenderData *rendererData, ID3D12Res /* Figure out how much we need to allocate for the upload buffer */ D3D_CALL(rendererData->d3dDevice, GetCopyableFootprints, &textureDesc, - 0, + plane, 1, 0, - NULL, - NULL, - NULL, + &placedTextureDesc, + &NumRows, + &RowLength, &uploadDesc.Width); + RowPitch = placedTextureDesc.Footprint.RowPitch; SDL_zero(heapProps); heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; @@ -1708,33 +1738,22 @@ static int D3D12_UpdateTextureInternal(D3D12_RenderData *rendererData, ID3D12Res return WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D12Resource::Map [map staging texture]"), result); } - SDL_zero(pitchedDesc); - pitchedDesc.Format = textureDesc.Format; - pitchedDesc.Width = w; - pitchedDesc.Height = h; - pitchedDesc.Depth = 1; - pitchedDesc.RowPitch = D3D12_Align(w * bpp, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); - - SDL_zero(placedTextureDesc); - placedTextureDesc.Offset = 0; - placedTextureDesc.Footprint = pitchedDesc; - src = (const Uint8 *)pixels; dst = textureMemory; - length = w * bpp; - if (length == (UINT)pitch && length == pitchedDesc.RowPitch) { - SDL_memcpy(dst, src, (size_t)length * h); + length = (UINT)RowLength; + if (length == (UINT)pitch && length == RowPitch) { + SDL_memcpy(dst, src, (size_t)length * NumRows); } else { if (length > (UINT)pitch) { length = pitch; } - if (length > pitchedDesc.RowPitch) { - length = pitchedDesc.RowPitch; + if (length > RowPitch) { + length = RowPitch; } - for (row = 0; row < h; ++row) { + for (row = NumRows; row--; ) { SDL_memcpy(dst, src, length); src += pitch; - dst += pitchedDesc.RowPitch; + dst += RowPitch; } } @@ -1748,7 +1767,7 @@ static int D3D12_UpdateTextureInternal(D3D12_RenderData *rendererData, ID3D12Res SDL_zero(dstLocation); dstLocation.pResource = texture; dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dstLocation.SubresourceIndex = 0; + dstLocation.SubresourceIndex = plane; SDL_zero(srcLocation); srcLocation.pResource = rendererData->uploadBuffers[rendererData->currentUploadBuffer]; @@ -1783,11 +1802,11 @@ static int D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData = (D3D12_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, SDL_BYTESPERPIXEL(texture->format), rect->x, rect->y, rect->w, rect->h, srcPixels, srcPitch, &textureData->mainResourceState) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, srcPixels, srcPitch, &textureData->mainResourceState) < 0) { return -1; } #if SDL_HAVE_YUV @@ -1795,13 +1814,13 @@ static int D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, /* Skip to the correct offset into the next texture */ srcPixels = (const void *)((const Uint8 *)srcPixels + rect->h * srcPitch); - if (D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureV : textureData->mainTextureU, SDL_BYTESPERPIXEL(texture->format), rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateV : &textureData->mainResourceStateU) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureV : textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateV : &textureData->mainResourceStateU) < 0) { return -1; } /* Skip to the correct offset into the next texture */ srcPixels = (const void *)((const Uint8 *)srcPixels + ((rect->h + 1) / 2) * ((srcPitch + 1) / 2)); - if (D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureU : textureData->mainTextureV, SDL_BYTESPERPIXEL(texture->format), rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateU : &textureData->mainResourceStateV) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureU : textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateU : &textureData->mainResourceStateV) < 0) { return -1; } } @@ -1810,7 +1829,7 @@ static int D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, /* Skip to the correct offset into the next texture */ srcPixels = (const void *)((const Uint8 *)srcPixels + rect->h * srcPitch); - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureNV, 2, rect->x / 2, rect->y / 2, ((rect->w + 1) / 2), (rect->h + 1) / 2, srcPixels, 2 * ((srcPitch + 1) / 2), &textureData->mainResourceStateNV) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, srcPixels, srcPitch, &textureData->mainResourceState) < 0) { return -1; } } @@ -1828,17 +1847,17 @@ static int D3D12_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData = (D3D12_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, SDL_BYTESPERPIXEL(texture->format), rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState) < 0) { return -1; } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureU, SDL_BYTESPERPIXEL(texture->format), rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Uplane, Upitch, &textureData->mainResourceStateU) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Uplane, Upitch, &textureData->mainResourceStateU) < 0) { return -1; } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, SDL_BYTESPERPIXEL(texture->format), rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Vplane, Vpitch, &textureData->mainResourceStateV) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Vplane, Vpitch, &textureData->mainResourceStateV) < 0) { return -1; } return 0; @@ -1852,15 +1871,15 @@ static int D3D12_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData = (D3D12_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, SDL_BYTESPERPIXEL(texture->format), rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState) < 0) { return -1; } - if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureNV, 2, rect->x / 2, rect->y / 2, ((rect->w + 1) / 2), (rect->h + 1) / 2, UVplane, UVpitch, &textureData->mainResourceStateNV) < 0) { + if (D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, UVplane, UVpitch, &textureData->mainResourceState) < 0) { return -1; } return 0; @@ -1881,7 +1900,7 @@ static int D3D12_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, BYTE *textureMemory; int bpp; - if (textureData == NULL) { + if (!textureData) { return SDL_SetError("Texture is not currently available"); } #if SDL_HAVE_YUV @@ -1891,7 +1910,7 @@ static int D3D12_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, textureData->pitch = texture->w; textureData->pixels = (Uint8 *)SDL_malloc((texture->h * textureData->pitch * 3) / 2); if (!textureData->pixels) { - return SDL_OutOfMemory(); + return -1; } } textureData->lockedRect = *rect; @@ -2000,7 +2019,7 @@ static void D3D12_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) D3D12_TEXTURE_COPY_LOCATION dstLocation; int bpp; - if (textureData == NULL) { + if (!textureData) { return; } #if SDL_HAVE_YUV @@ -2071,7 +2090,7 @@ static void D3D12_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *textu { D3D12_TextureData *textureData = (D3D12_TextureData *)texture->driverdata; - if (textureData == NULL) { + if (!textureData) { return; } @@ -2083,7 +2102,7 @@ static int D3D12_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData = NULL; - if (texture == NULL) { + if (!texture) { if (rendererData->textureRenderTarget) { D3D12_TransitionResource(rendererData, rendererData->textureRenderTarget->mainTexture, @@ -2126,7 +2145,7 @@ static int D3D12_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, color.b = cmd->data.draw.b; color.a = cmd->data.draw.a; - if (verts == NULL) { + if (!verts) { return -1; } @@ -2153,7 +2172,7 @@ static int D3D12_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S int count = indices ? num_indices : num_vertices; VertexPositionColor *verts = (VertexPositionColor *)SDL_AllocateRenderVertices(renderer, count * sizeof(VertexPositionColor), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -2550,8 +2569,6 @@ static int D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *c /* Make sure each texture is in the correct state to be accessed by the pixel shader. */ D3D12_TransitionResource(rendererData, textureData->mainTexture, textureData->mainResourceState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); textureData->mainResourceState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; - D3D12_TransitionResource(rendererData, textureData->mainTextureNV, textureData->mainResourceStateNV, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - textureData->mainResourceStateNV = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; return D3D12_SetDrawState(renderer, cmd, shader, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, SDL_arraysize(shaderResources), shaderResources, textureSampler, matrix); @@ -2570,11 +2587,26 @@ static void D3D12_DrawPrimitives(SDL_Renderer *renderer, D3D12_PRIMITIVE_TOPOLOG D3D_CALL(rendererData->commandList, DrawInstanced, (UINT)vertexCount, 1, (UINT)vertexStart, 0); } +static void D3D12_InvalidateCachedState(SDL_Renderer *renderer) +{ + D3D12_RenderData *data = (D3D12_RenderData *)renderer->driverdata; + data->currentRenderTargetView.ptr = 0; + data->currentShaderResource.ptr = 0; + data->currentSampler.ptr = 0; + data->cliprectDirty = SDL_TRUE; + data->viewportDirty = SDL_TRUE; +} + static int D3D12_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; const int viewportRotation = D3D12_GetRotationForCurrentRenderTarget(renderer); + if (rendererData->pixelSizeChanged) { + D3D12_UpdateForWindowSizeChange(renderer); + rendererData->pixelSizeChanged = SDL_FALSE; + } + if (rendererData->currentViewportRotation != viewportRotation) { rendererData->currentViewportRotation = viewportRotation; rendererData->viewportDirty = SDL_TRUE; @@ -2948,21 +2980,20 @@ static int D3D12_SetVSync(SDL_Renderer *renderer, const int vsync) return 0; } -SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, Uint32 flags) +SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; D3D12_RenderData *data; renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } + renderer->magic = &SDL_renderer_magic; data = (D3D12_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); return NULL; } @@ -2985,6 +3016,7 @@ SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = D3D12_QueueDrawPoints; renderer->QueueDrawLines = D3D12_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = D3D12_QueueGeometry; + renderer->InvalidateCachedState = D3D12_InvalidateCachedState; renderer->RunCommandQueue = D3D12_RunCommandQueue; renderer->RenderReadPixels = D3D12_RenderReadPixels; renderer->RenderPresent = D3D12_RenderPresent; @@ -2993,8 +3025,9 @@ SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = D3D12_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + D3D12_InvalidateCachedState(renderer); - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } renderer->SetVSync = D3D12_SetVSync; @@ -3042,32 +3075,3 @@ SDL_RenderDriver D3D12_RenderDriver = { #endif #endif /* SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED */ - -#if defined(__WIN32__) || defined(__GDK__) -#ifdef __cplusplus -extern "C" -#endif - /* This function needs to always exist on Windows, for the Dynamic API. */ - ID3D12Device * - SDL_RenderGetD3D12Device(SDL_Renderer *renderer) -{ - ID3D12Device *device = NULL; - -#if defined(SDL_VIDEO_RENDER_D3D12) && !defined(SDL_RENDER_DISABLED) - D3D12_RenderData *data = (D3D12_RenderData *)renderer->driverdata; - - /* Make sure that this is a D3D renderer */ - if (renderer->DestroyRenderer != D3D12_DestroyRenderer) { - SDL_SetError("Renderer is not a D3D12 renderer"); - return NULL; - } - - device = (ID3D12Device *)data->d3dDevice; - if (device) { - D3D_CALL(device, AddRef); - } -#endif /* SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED */ - - return device; -} -#endif /* defined(__WIN32__) || defined(__GDK__) */ diff --git a/src/render/direct3d12/SDL_render_d3d12_xbox.cpp b/src/render/direct3d12/SDL_render_d3d12_xbox.cpp index 48a6df99..0c460556 100644 --- a/src/render/direct3d12/SDL_render_d3d12_xbox.cpp +++ b/src/render/direct3d12/SDL_render_d3d12_xbox.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -19,9 +19,156 @@ 3. This notice may not be removed or altered from any source distribution. */ -#include "SDL_internal.h" -#if defined(SDL_VIDEO_RENDER_D3D12) && !defined(SDL_RENDER_DISABLED) && (defined(__XBOXONE__) || defined(__XBOXSERIES__)) +#include "../../SDL_internal.h" +#if SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED && (defined(__XBOXONE__) || defined(__XBOXSERIES__)) #include "SDL_render_d3d12_xbox.h" +#include "../../core/windows/SDL_windows.h" +#include + +#if defined(_MSC_VER) && !defined(__clang__) +#define SDL_COMPOSE_ERROR(str) __FUNCTION__ ", " str +#else +#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str +#endif + +static const GUID SDL_IID_ID3D12Device1 = { 0x77acce80, 0x638e, 0x4e65, { 0x88, 0x95, 0xc1, 0xf2, 0x33, 0x86, 0x86, 0x3e } }; +static const GUID SDL_IID_ID3D12Resource = { 0x696442be, 0xa72e, 0x4059, { 0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad } }; +static const GUID SDL_IID_IDXGIDevice1 = { 0x77db970f, 0x6276, 0x48ba, { 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, 0x2c } }; + +extern "C" HRESULT +D3D12_XBOX_CreateDevice(ID3D12Device **device, SDL_bool createDebug) +{ + HRESULT result; + D3D12XBOX_CREATE_DEVICE_PARAMETERS params; + IDXGIDevice1 *dxgiDevice; + IDXGIAdapter *dxgiAdapter; + IDXGIOutput *dxgiOutput; + SDL_zero(params); + + params.Version = D3D12_SDK_VERSION; + params.ProcessDebugFlags = createDebug ? D3D12_PROCESS_DEBUG_FLAG_DEBUG_LAYER_ENABLED : D3D12XBOX_PROCESS_DEBUG_FLAG_NONE; + params.GraphicsCommandQueueRingSizeBytes = D3D12XBOX_DEFAULT_SIZE_BYTES; + params.GraphicsScratchMemorySizeBytes = D3D12XBOX_DEFAULT_SIZE_BYTES; + params.ComputeScratchMemorySizeBytes = D3D12XBOX_DEFAULT_SIZE_BYTES; + + result = D3D12XboxCreateDevice(NULL, ¶ms, SDL_IID_ID3D12Device1, (void **) device); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] D3D12XboxCreateDevice"), result); + goto done; + } + + result = (*device)->QueryInterface(SDL_IID_IDXGIDevice1, (void **) &dxgiDevice); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] ID3D12Device to IDXGIDevice1"), result); + goto done; + } + + result = dxgiDevice->GetAdapter(&dxgiAdapter); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] dxgiDevice->GetAdapter"), result); + goto done; + } + + result = dxgiAdapter->EnumOutputs(0, &dxgiOutput); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] dxgiAdapter->EnumOutputs"), result); + goto done; + } + + /* Set frame interval */ + result = (*device)->SetFrameIntervalX(dxgiOutput, D3D12XBOX_FRAME_INTERVAL_60_HZ, 1, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] SetFrameIntervalX"), result); + goto done; + } + + result = (*device)->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0, NULL, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("[xbox] ScheduleFrameEventX"), result); + goto done; + } + +done: + return result; +} + +extern "C" HRESULT +D3D12_XBOX_CreateBackBufferTarget(ID3D12Device1 *device, int width, int height, void **resource) +{ + + D3D12_HEAP_PROPERTIES heapProps; + D3D12_RESOURCE_DESC resourceDesc; + + SDL_zero(heapProps); + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + SDL_zero(resourceDesc); + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Alignment = 0; + resourceDesc.Width = width; + resourceDesc.Height = height; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + resourceDesc.SampleDesc.Count = 1; + resourceDesc.SampleDesc.Quality = 0; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + return device->CreateCommittedResource(&heapProps, + D3D12_HEAP_FLAG_ALLOW_DISPLAY, + &resourceDesc, + D3D12_RESOURCE_STATE_PRESENT, + NULL, + SDL_IID_ID3D12Resource, + resource + ); +} + +extern "C" HRESULT +D3D12_XBOX_StartFrame(ID3D12Device1 *device, UINT64 *outToken) +{ + *outToken = D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL; + return device->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, NULL, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, outToken); +} + +extern "C" HRESULT +D3D12_XBOX_PresentFrame(ID3D12CommandQueue *commandQueue, UINT64 token, ID3D12Resource *renderTarget) +{ + D3D12XBOX_PRESENT_PLANE_PARAMETERS planeParameters; + SDL_zero(planeParameters); + planeParameters.Token = token; + planeParameters.ResourceCount = 1; + planeParameters.ppResources = &renderTarget; + return commandQueue->PresentX(1, &planeParameters, NULL); +} + +extern "C" void +D3D12_XBOX_GetResolution(Uint32 *width, Uint32 *height) +{ + switch (XSystemGetDeviceType()) { + case XSystemDeviceType::XboxScarlettLockhart: + *width = 2560; + *height = 1440; + break; + + case XSystemDeviceType::XboxOneX: + case XSystemDeviceType::XboxScarlettAnaconda: + case XSystemDeviceType::XboxOneXDevkit: + case XSystemDeviceType::XboxScarlettDevkit: + *width = 3840; + *height = 2160; + break; + + default: + *width = 1920; + *height = 1080; + break; + } +} -#error "This is a placeholder Xbox file, as the real one is under NDA. See README-gdk.md for more info." #endif diff --git a/src/render/direct3d12/SDL_render_d3d12_xbox.h b/src/render/direct3d12/SDL_render_d3d12_xbox.h index fd725d9f..6db955a4 100644 --- a/src/render/direct3d12/SDL_render_d3d12_xbox.h +++ b/src/render/direct3d12/SDL_render_d3d12_xbox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -19,4 +19,31 @@ 3. This notice may not be removed or altered from any source distribution. */ -#error "This is a placeholder Xbox file, as the real one is under NDA. See README-gdk.md for more info." +#ifndef SDL_render_d3d12_xbox_h_ +#define SDL_render_d3d12_xbox_h_ + +#include "../../SDL_internal.h" + +#if defined(__XBOXONE__) +#include +#else /* __XBOXSERIES__ */ +#include +#endif + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +extern HRESULT D3D12_XBOX_CreateDevice(ID3D12Device **device, SDL_bool createDebug); +extern HRESULT D3D12_XBOX_CreateBackBufferTarget(ID3D12Device1 *device, int width, int height, void **resource); +extern HRESULT D3D12_XBOX_StartFrame(ID3D12Device1 *device, UINT64 *outToken); +extern HRESULT D3D12_XBOX_PresentFrame(ID3D12CommandQueue *commandQueue, UINT64 token, ID3D12Resource *renderTarget); +extern void D3D12_XBOX_GetResolution(Uint32 *width, Uint32 *height); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/render/direct3d12/SDL_shaders_d3d12.c b/src/render/direct3d12/SDL_shaders_d3d12.c index 36bedf52..ea5ef319 100644 --- a/src/render/direct3d12/SDL_shaders_d3d12.c +++ b/src/render/direct3d12/SDL_shaders_d3d12.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d12/SDL_shaders_d3d12.h b/src/render/direct3d12/SDL_shaders_d3d12.h index 76861ada..e672f0d2 100644 --- a/src/render/direct3d12/SDL_shaders_d3d12.h +++ b/src/render/direct3d12/SDL_shaders_d3d12.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/direct3d12/SDL_shaders_d3d12_xboxone.cpp b/src/render/direct3d12/SDL_shaders_d3d12_xboxone.cpp index 6dcb07ba..e224ffc2 100644 --- a/src/render/direct3d12/SDL_shaders_d3d12_xboxone.cpp +++ b/src/render/direct3d12/SDL_shaders_d3d12_xboxone.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,10 +18,127 @@ 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_internal.h" -#if defined(SDL_VIDEO_RENDER_D3D12) && !defined(SDL_RENDER_DISABLED) && defined(__XBOXONE__) +#if SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED && defined(__XBOXONE__) -#error "This is a placeholder Xbox file, as the real one is under NDA. See README-gdk.md for more info." +#include + +#include "../../core/windows/SDL_windows.h" +#include + +#include "SDL_shaders_d3d12.h" + +#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str + +/* Shader blob headers are generated with a pre-build step using buildshaders.bat */ +#include "../VisualC-GDK/shaders/D3D12_PixelShader_Colors_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_Textures_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709_One.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG_One.h" + +#include "../VisualC-GDK/shaders/D3D12_VertexShader_Color_One.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_NV_One.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_Texture_One.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_YUV_One.h" + +#include "../VisualC-GDK/shaders/D3D12_RootSig_Color_One.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_NV_One.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_Texture_One.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_YUV_One.h" + +static struct +{ + const void *ps_shader_data; + SIZE_T ps_shader_size; + const void *vs_shader_data; + SIZE_T vs_shader_size; + D3D12_RootSignature root_sig; +} D3D12_shaders[NUM_SHADERS] = { + { D3D12_PixelShader_Colors, sizeof(D3D12_PixelShader_Colors), + D3D12_VertexShader_Color, sizeof(D3D12_VertexShader_Color), + ROOTSIG_COLOR }, + { D3D12_PixelShader_Textures, sizeof(D3D12_PixelShader_Textures), + D3D12_VertexShader_Texture, sizeof(D3D12_VertexShader_Texture), + ROOTSIG_TEXTURE }, +#if SDL_HAVE_YUV + { D3D12_PixelShader_YUV_JPEG, sizeof(D3D12_PixelShader_YUV_JPEG), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_YUV_BT601, sizeof(D3D12_PixelShader_YUV_BT601), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_YUV_BT709, sizeof(D3D12_PixelShader_YUV_BT709), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_NV12_JPEG, sizeof(D3D12_PixelShader_NV12_JPEG), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV12_BT601, sizeof(D3D12_PixelShader_NV12_BT601), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV12_BT709, sizeof(D3D12_PixelShader_NV12_BT709), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_JPEG, sizeof(D3D12_PixelShader_NV21_JPEG), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_BT601, sizeof(D3D12_PixelShader_NV21_BT601), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_BT709, sizeof(D3D12_PixelShader_NV21_BT709), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, +#endif +}; + +static struct +{ + const void *rs_shader_data; + SIZE_T rs_shader_size; +} D3D12_rootsigs[NUM_ROOTSIGS] = { + { D3D12_RootSig_Color, sizeof(D3D12_RootSig_Color) }, + { D3D12_RootSig_Texture, sizeof(D3D12_RootSig_Texture) }, +#if SDL_HAVE_YUV + { D3D12_RootSig_YUV, sizeof(D3D12_RootSig_YUV) }, + { D3D12_RootSig_NV, sizeof(D3D12_RootSig_NV) }, +#endif +}; + +extern "C" void +D3D12_GetVertexShader(D3D12_Shader shader, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_shaders[shader].vs_shader_data; + outBytecode->BytecodeLength = D3D12_shaders[shader].vs_shader_size; +} + +extern "C" void +D3D12_GetPixelShader(D3D12_Shader shader, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_shaders[shader].ps_shader_data; + outBytecode->BytecodeLength = D3D12_shaders[shader].ps_shader_size; +} + +extern "C" D3D12_RootSignature +D3D12_GetRootSignatureType(D3D12_Shader shader) +{ + return D3D12_shaders[shader].root_sig; +} + +extern "C" void +D3D12_GetRootSignatureData(D3D12_RootSignature rootSig, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_rootsigs[rootSig].rs_shader_data; + outBytecode->BytecodeLength = D3D12_rootsigs[rootSig].rs_shader_size; +} #endif /* SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED && defined(__XBOXONE__) */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/render/direct3d12/SDL_shaders_d3d12_xboxseries.cpp b/src/render/direct3d12/SDL_shaders_d3d12_xboxseries.cpp index bfcfc471..29c20cd6 100644 --- a/src/render/direct3d12/SDL_shaders_d3d12_xboxseries.cpp +++ b/src/render/direct3d12/SDL_shaders_d3d12_xboxseries.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,10 +18,127 @@ 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_internal.h" -#if defined(SDL_VIDEO_RENDER_D3D12) && !defined(SDL_RENDER_DISABLED) && defined(__XBOXSERIES__) +#if SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED && defined(__XBOXSERIES__) -#error "This is a placeholder Xbox file, as the real one is under NDA. See README-gdk.md for more info." +#include + +#include "../../core/windows/SDL_windows.h" +#include + +#include "SDL_shaders_d3d12.h" + +#define SDL_COMPOSE_ERROR(str) SDL_STRINGIFY_ARG(__FUNCTION__) ", " str + +/* Shader blob headers are generated with a pre-build step using buildshaders.bat */ +#include "../VisualC-GDK/shaders/D3D12_PixelShader_Colors_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_Textures_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT601_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_BT709_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV12_JPEG_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT601_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_BT709_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_NV21_JPEG_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT601_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_BT709_Series.h" +#include "../VisualC-GDK/shaders/D3D12_PixelShader_YUV_JPEG_Series.h" + +#include "../VisualC-GDK/shaders/D3D12_VertexShader_Color_Series.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_Texture_Series.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_NV_Series.h" +#include "../VisualC-GDK/shaders/D3D12_VertexShader_YUV_Series.h" + +#include "../VisualC-GDK/shaders/D3D12_RootSig_Color_Series.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_Texture_Series.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_YUV_Series.h" +#include "../VisualC-GDK/shaders/D3D12_RootSig_NV_Series.h" + +static struct +{ + const void *ps_shader_data; + SIZE_T ps_shader_size; + const void *vs_shader_data; + SIZE_T vs_shader_size; + D3D12_RootSignature root_sig; +} D3D12_shaders[NUM_SHADERS] = { + { D3D12_PixelShader_Colors, sizeof(D3D12_PixelShader_Colors), + D3D12_VertexShader_Color, sizeof(D3D12_VertexShader_Color), + ROOTSIG_COLOR }, + { D3D12_PixelShader_Textures, sizeof(D3D12_PixelShader_Textures), + D3D12_VertexShader_Texture, sizeof(D3D12_VertexShader_Texture), + ROOTSIG_TEXTURE }, +#if SDL_HAVE_YUV + { D3D12_PixelShader_YUV_JPEG, sizeof(D3D12_PixelShader_YUV_JPEG), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_YUV_BT601, sizeof(D3D12_PixelShader_YUV_BT601), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_YUV_BT709, sizeof(D3D12_PixelShader_YUV_BT709), + D3D12_VertexShader_YUV, sizeof(D3D12_VertexShader_YUV), + ROOTSIG_YUV }, + { D3D12_PixelShader_NV12_JPEG, sizeof(D3D12_PixelShader_NV12_JPEG), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV12_BT601, sizeof(D3D12_PixelShader_NV12_BT601), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV12_BT709, sizeof(D3D12_PixelShader_NV12_BT709), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_JPEG, sizeof(D3D12_PixelShader_NV21_JPEG), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_BT601, sizeof(D3D12_PixelShader_NV21_BT601), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, + { D3D12_PixelShader_NV21_BT709, sizeof(D3D12_PixelShader_NV21_BT709), + D3D12_VertexShader_NV, sizeof(D3D12_VertexShader_NV), + ROOTSIG_NV }, +#endif +}; + +static struct +{ + const void *rs_shader_data; + SIZE_T rs_shader_size; +} D3D12_rootsigs[NUM_ROOTSIGS] = { + { D3D12_RootSig_Color, sizeof(D3D12_RootSig_Color) }, + { D3D12_RootSig_Texture, sizeof(D3D12_RootSig_Texture) }, +#if SDL_HAVE_YUV + { D3D12_RootSig_YUV, sizeof(D3D12_RootSig_YUV) }, + { D3D12_RootSig_NV, sizeof(D3D12_RootSig_NV) }, +#endif +}; + +extern "C" void +D3D12_GetVertexShader(D3D12_Shader shader, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_shaders[shader].vs_shader_data; + outBytecode->BytecodeLength = D3D12_shaders[shader].vs_shader_size; +} + +extern "C" void +D3D12_GetPixelShader(D3D12_Shader shader, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_shaders[shader].ps_shader_data; + outBytecode->BytecodeLength = D3D12_shaders[shader].ps_shader_size; +} + +extern "C" D3D12_RootSignature +D3D12_GetRootSignatureType(D3D12_Shader shader) +{ + return D3D12_shaders[shader].root_sig; +} + +extern "C" void +D3D12_GetRootSignatureData(D3D12_RootSignature rootSig, D3D12_SHADER_BYTECODE *outBytecode) +{ + outBytecode->pShaderBytecode = D3D12_rootsigs[rootSig].rs_shader_data; + outBytecode->BytecodeLength = D3D12_rootsigs[rootSig].rs_shader_size; +} #endif /* SDL_VIDEO_RENDER_D3D12 && !SDL_RENDER_DISABLED && defined(__XBOXSERIES__) */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m index 3f038f27..b471cfba 100644 --- a/src/render/metal/SDL_render_metal.m +++ b/src/render/metal/SDL_render_metal.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,7 +35,6 @@ #ifdef SDL_VIDEO_DRIVER_UIKIT #import #endif -#include /* Regenerate these with build-metal-shaders.sh */ #ifdef __MACOS__ @@ -160,20 +159,16 @@ typedef struct METAL_ShaderPipelines @implementation METAL_TextureData @end -static int IsMetalAvailable(const SDL_SysWMinfo *syswm) +static SDL_bool IsMetalAvailable() { - if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) { - return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment."); - } - - // this checks a weak symbol. #if (defined(__MACOS__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100)) + // this checks a weak symbol. if (MTLCreateSystemDefaultDevice == NULL) { // probably on 10.10 or lower. - return SDL_SetError("Metal framework not available on this system"); + SDL_SetError("Metal framework not available on this system"); + return SDL_FALSE; } #endif - - return 0; + return SDL_TRUE; } static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF; @@ -344,7 +339,6 @@ static id MakePipelineState(METAL_RenderData *data, META return (__bridge id)pipeline.pipe; } else { CFBridgingRelease(pipeline.pipe); - SDL_OutOfMemory(); return NULL; } } @@ -406,7 +400,6 @@ static METAL_ShaderPipelines *ChooseShaderPipelines(METAL_RenderData *data, MTLP allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines)); if (allpipelines == NULL) { - SDL_OutOfMemory(); return NULL; } @@ -549,7 +542,7 @@ static SDL_bool METAL_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode bl return SDL_TRUE; } -static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { @autoreleasepool { METAL_RenderData *data = (__bridge METAL_RenderData *)renderer->driverdata; @@ -1315,6 +1308,11 @@ static SDL_bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cm return SDL_TRUE; } +static void METAL_InvalidateCachedState(SDL_Renderer *renderer) +{ + // METAL_DrawStateCache only exists during a run of METAL_RunCommandQueue, so there's nothing to invalidate! +} + static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { @autoreleasepool { @@ -1507,7 +1505,7 @@ static int METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, temp_pitch = rect->w * 4UL; temp_pixels = SDL_malloc(temp_pitch * rect->h); if (!temp_pixels) { - return SDL_OutOfMemory(); + return -1; } [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0]; @@ -1527,9 +1525,10 @@ static int METAL_RenderPresent(SDL_Renderer *renderer) // If we don't have a command buffer, we can't present, so activate to get one. if (data.mtlcmdencoder == nil) { - // We haven't even gotten a backbuffer yet? Clear it to black. Otherwise, load the existing data. + // We haven't even gotten a backbuffer yet? Load and clear it. Otherwise, load the existing data. if (data.mtlbackbuffer == nil) { - MTLClearColor color = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f); + float alpha = (SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT) ? 0.0f : 1.0f; + MTLClearColor color = MTLClearColorMake(0.0f, 0.0f, 0.0f, alpha); ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color, nil); } else { ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); @@ -1630,33 +1629,35 @@ static int METAL_SetVSync(SDL_Renderer *renderer, const int vsync) static SDL_MetalView GetWindowView(SDL_Window *window) { - SDL_SysWMinfo info; - - if (SDL_GetWindowWMInfo(window, &info, SDL_SYSWM_CURRENT_VERSION) == 0) { -#ifdef SDL_ENABLE_SYSWM_COCOA - if (info.subsystem == SDL_SYSWM_COCOA) { - NSView *view = info.info.cocoa.window.contentView; - if (view.subviews.count > 0) { - view = view.subviews[0]; - if (view.tag == SDL_METALVIEW_TAG) { - return (SDL_MetalView)CFBridgingRetain(view); - } - } - } -#endif -#ifdef SDL_ENABLE_SYSWM_UIKIT - if (info.subsystem == SDL_SYSWM_UIKIT) { - UIView *view = info.info.uikit.window.rootViewController.view; - if (view.tag == SDL_METALVIEW_TAG) { +#ifdef SDL_VIDEO_DRIVER_COCOA + NSWindow *nswindow = (__bridge NSWindow *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_COCOA_WINDOW_POINTER, NULL); + NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, 0); + if (nswindow && tag) { + NSView *view = nswindow.contentView; + if (view.subviews.count > 0) { + view = view.subviews[0]; + if (view.tag == tag) { return (SDL_MetalView)CFBridgingRetain(view); } } -#endif } +#endif + +#ifdef SDL_VIDEO_DRIVER_UIKIT + UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_UIKIT_WINDOW_POINTER, NULL); + NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, 0); + if (uiwindow && tag) { + UIView *view = uiwindow.rootViewController.view; + if (view.tag == tag) { + return (SDL_MetalView)CFBridgingRetain(view); + } + } +#endif + return nil; } -static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) +static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { @autoreleasepool { SDL_Renderer *renderer = NULL; @@ -1664,7 +1665,6 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) id mtldevice = nil; SDL_MetalView view = NULL; CAMetalLayer *layer = nil; - SDL_SysWMinfo syswm; NSError *err = nil; dispatch_data_t mtllibdata; char *constantdata; @@ -1740,17 +1740,12 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */ }; - if (SDL_GetWindowWMInfo(window, &syswm, SDL_SYSWM_CURRENT_VERSION) < 0) { - return NULL; - } - - if (IsMetalAvailable(&syswm) == -1) { + if (!IsMetalAvailable()) { return NULL; } renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); if (!renderer) { - SDL_OutOfMemory(); return NULL; } @@ -1801,6 +1796,7 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) } renderer->driverdata = (void *)CFBridgingRetain(data); + METAL_InvalidateCachedState(renderer); renderer->window = window; data.mtlview = view; @@ -1913,6 +1909,7 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = METAL_QueueDrawPoints; renderer->QueueDrawLines = METAL_QueueDrawLines; renderer->QueueGeometry = METAL_QueueGeometry; + renderer->InvalidateCachedState = METAL_InvalidateCachedState; renderer->RunCommandQueue = METAL_RunCommandQueue; renderer->RenderReadPixels = METAL_RenderReadPixels; renderer->RenderPresent = METAL_RenderPresent; @@ -1925,11 +1922,9 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = METAL_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; - renderer->always_batch = SDL_TRUE; - #if (defined(__MACOS__) && defined(MAC_OS_X_VERSION_10_13)) || TARGET_OS_MACCATALYST if (@available(macOS 10.13, *)) { - data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0; + data.mtllayer.displaySyncEnabled = SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE); if (data.mtllayer.displaySyncEnabled) { renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } diff --git a/src/render/opengl/SDL_glfuncs.h b/src/render/opengl/SDL_glfuncs.h index f32b368c..3c0f098d 100644 --- a/src/render/opengl/SDL_glfuncs.h +++ b/src/render/opengl/SDL_glfuncs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 2d7da746..9e57f550 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -75,10 +75,13 @@ typedef struct SDL_bool cliprect_dirty; SDL_Rect cliprect; SDL_bool texturing; + SDL_bool texturing_dirty; SDL_bool vertex_array; SDL_bool color_array; SDL_bool texture_array; + SDL_bool color_dirty; Uint32 color; + SDL_bool clear_color_dirty; Uint32 clear_color; } GL_DrawStateCache; @@ -125,6 +128,7 @@ typedef struct typedef struct { GLuint texture; + SDL_bool texture_external; GLfloat texw; GLfloat texh; GLenum format; @@ -139,7 +143,9 @@ typedef struct SDL_bool yuv; SDL_bool nv12; GLuint utexture; + SDL_bool utexture_external; GLuint vtexture; + SDL_bool vtexture_external; #endif GL_FBOList *fbo; @@ -183,7 +189,7 @@ static void GL_ClearErrors(SDL_Renderer *renderer) data->errors = 0; data->error_messages = NULL; } - } else if (data->glGetError != NULL) { + } else if (data->glGetError) { while (data->glGetError() != GL_NO_ERROR) { /* continue; */ } @@ -302,7 +308,7 @@ static GL_FBOList *GL_GetFBO(GL_RenderData *data, Uint32 w, Uint32 h) result = result->next; } - if (result == NULL) { + if (!result) { result = SDL_malloc(sizeof(GL_FBOList)); if (result) { result->w = w; @@ -434,7 +440,7 @@ static SDL_bool convert_format(GL_RenderData *renderdata, Uint32 pixel_format, return SDL_TRUE; } -static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { GL_RenderData *renderdata = (GL_RenderData *)renderer->driverdata; const GLenum textype = renderdata->textype; @@ -447,6 +453,7 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) GL_ActivateRenderer(renderer); renderdata->drawstate.texture = NULL; /* we trash this state. */ + renderdata->drawstate.texturing_dirty = SDL_TRUE; /* we trash this state. */ if (texture->access == SDL_TEXTUREACCESS_TARGET && !renderdata->GL_EXT_framebuffer_object_supported) { @@ -460,8 +467,8 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) } data = (GL_TextureData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } if (texture->access == SDL_TEXTUREACCESS_STREAMING) { @@ -481,7 +488,7 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->pixels = SDL_calloc(1, size); if (!data->pixels) { SDL_free(data); - return SDL_OutOfMemory(); + return -1; } } @@ -491,14 +498,19 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->fbo = NULL; } - GL_CheckError("", renderer); - renderdata->glGenTextures(1, &data->texture); - if (GL_CheckError("glGenTextures()", renderer) < 0) { - if (data->pixels) { - SDL_free(data->pixels); + data->texture = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER, 0); + if (data->texture) { + data->texture_external = SDL_TRUE; + } else { + GL_CheckError("", renderer); + renderdata->glGenTextures(1, &data->texture); + if (GL_CheckError("glGenTextures()", renderer) < 0) { + if (data->pixels) { + SDL_free(data->pixels); + } + SDL_free(data); + return -1; } - SDL_free(data); - return -1; } texture->driverdata = data; @@ -518,6 +530,11 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->texw = (GLfloat)(texture->w) / texture_w; data->texh = (GLfloat)texture->h / texture_h; } + SDL_PropertiesID props = SDL_GetTextureProperties(texture); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_NUMBER, data->texture); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_TARGET, (Sint64) textype); + SDL_SetFloatProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEX_W_FLOAT, data->texw); + SDL_SetFloatProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEX_H_FLOAT, data->texh); data->format = format; data->formattype = type; @@ -576,8 +593,18 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) texture->format == SDL_PIXELFORMAT_IYUV) { data->yuv = SDL_TRUE; - renderdata->glGenTextures(1, &data->utexture); - renderdata->glGenTextures(1, &data->vtexture); + data->utexture = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER, 0); + if (data->utexture) { + data->utexture_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->utexture); + } + data->vtexture = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER, 0); + if (data->vtexture) { + data->vtexture_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->vtexture); + } renderdata->glBindTexture(textype, data->utexture); renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, @@ -590,6 +617,7 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) GL_CLAMP_TO_EDGE); renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, format, type, NULL); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_U_NUMBER, data->utexture); renderdata->glBindTexture(textype, data->vtexture); renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, @@ -602,13 +630,19 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) GL_CLAMP_TO_EDGE); renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, format, type, NULL); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_V_NUMBER, data->vtexture); } if (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21) { data->nv12 = SDL_TRUE; - renderdata->glGenTextures(1, &data->utexture); + data->utexture = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER, 0); + if (data->utexture) { + data->utexture_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->utexture); + } renderdata->glBindTexture(textype, data->utexture); renderdata->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, scaleMode); @@ -620,6 +654,7 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) GL_CLAMP_TO_EDGE); renderdata->glTexImage2D(textype, 0, GL_LUMINANCE_ALPHA, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL); + SDL_SetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGL_TEXTURE_UV_NUMBER, data->utexture); } #endif @@ -877,7 +912,7 @@ static int GL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) data->drawstate.viewport_dirty = SDL_TRUE; - if (texture == NULL) { + if (!texture) { data->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); return 0; } @@ -907,7 +942,7 @@ static int GL_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, co GLfloat *verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(GLfloat), 0, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -927,7 +962,7 @@ static int GL_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, con const size_t vertlen = (sizeof(GLfloat) * 2) * count; GLfloat *verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } cmd->data.draw.count = count; @@ -972,7 +1007,7 @@ static int GL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_ size_t sz = 2 * sizeof(GLfloat) + 4 * sizeof(Uint8) + (texture ? 2 : 0) * sizeof(GLfloat); verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, count * sz, 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -1077,14 +1112,15 @@ static int SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, const data->drawstate.shader = shader; } - if ((cmd->data.draw.texture != NULL) != data->drawstate.texturing) { - if (cmd->data.draw.texture == NULL) { + if (data->drawstate.texturing_dirty || ((cmd->data.draw.texture != NULL) != data->drawstate.texturing)) { + if (!cmd->data.draw.texture) { data->glDisable(data->textype); data->drawstate.texturing = SDL_FALSE; } else { data->glEnable(data->textype); data->drawstate.texturing = SDL_TRUE; } + data->drawstate.texturing_dirty = SDL_FALSE; } vertex_array = cmd->command == SDL_RENDERCMD_DRAW_POINTS || cmd->command == SDL_RENDERCMD_DRAW_LINES || cmd->command == SDL_RENDERCMD_GEOMETRY; @@ -1110,7 +1146,7 @@ static int SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, const } /* This is a little awkward but should avoid texcoord arrays getting into - a bad state if SDL_GL_BindTexture/UnbindTexture are called. */ + a bad state if the application is manually binding textures */ if (texture_array != data->drawstate.texture_array) { if (texture_array) { data->glEnableClientState(GL_TEXTURE_COORD_ARRAY); @@ -1162,6 +1198,25 @@ static int SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd) return 0; } +static void GL_InvalidateCachedState(SDL_Renderer *renderer) +{ + GL_DrawStateCache *cache = &((GL_RenderData *)renderer->driverdata)->drawstate; + cache->viewport_dirty = SDL_TRUE; + cache->texture = NULL; + cache->drawablew = 0; + cache->drawableh = 0; + cache->blend = SDL_BLENDMODE_INVALID; + cache->shader = SHADER_INVALID; + cache->cliprect_enabled_dirty = SDL_TRUE; + cache->cliprect_dirty = SDL_TRUE; + cache->texturing_dirty = SDL_TRUE; + cache->vertex_array = SDL_FALSE; /* !!! FIXME: this resets to false at the end of GL_RunCommandQueue, but we could cache this more aggressively. */ + cache->color_array = SDL_FALSE; /* !!! FIXME: this resets to false at the end of GL_RunCommandQueue, but we could cache this more aggressively. */ + cache->texture_array = SDL_FALSE; /* !!! FIXME: this resets to false at the end of GL_RunCommandQueue, but we could cache this more aggressively. */ + cache->color_dirty = SDL_TRUE; + cache->clear_color_dirty = SDL_TRUE; +} + static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { /* !!! FIXME: it'd be nice to use a vertex buffer instead of immediate mode... */ @@ -1199,9 +1254,10 @@ static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo const Uint8 b = cmd->data.color.b; const Uint8 a = cmd->data.color.a; const Uint32 color = (((Uint32)a << 24) | (r << 16) | (g << 8) | b); - if (color != data->drawstate.color) { + if ((data->drawstate.color_dirty) || (color != data->drawstate.color)) { data->glColor4ub((GLubyte)r, (GLubyte)g, (GLubyte)b, (GLubyte)a); data->drawstate.color = color; + data->drawstate.color_dirty = SDL_FALSE; } break; } @@ -1238,13 +1294,14 @@ static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo const Uint8 b = cmd->data.color.b; const Uint8 a = cmd->data.color.a; const Uint32 color = (((Uint32)a << 24) | (r << 16) | (g << 8) | b); - if (color != data->drawstate.clear_color) { + if ((data->drawstate.clear_color_dirty) || (color != data->drawstate.clear_color)) { const GLfloat fr = ((GLfloat)r) * inv255f; const GLfloat fg = ((GLfloat)g) * inv255f; const GLfloat fb = ((GLfloat)b) * inv255f; const GLfloat fa = ((GLfloat)a) * inv255f; data->glClearColor(fr, fg, fb, fa); data->drawstate.clear_color = color; + data->drawstate.clear_color_dirty = SDL_FALSE; } if (data->drawstate.cliprect_enabled || data->drawstate.cliprect_enabled_dirty) { @@ -1283,7 +1340,7 @@ static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SDL_RenderCommand *nextcmd = cmd->next; SDL_BlendMode thisblend = cmd->data.draw.blend; - while (nextcmd != NULL) { + while (nextcmd) { const SDL_RenderCommandType nextcmdtype = nextcmd->command; if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { break; /* can't go any further on this draw call, different render command up next. */ @@ -1317,7 +1374,7 @@ static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SDL_RenderCommand *nextcmd = cmd->next; size_t count = cmd->data.draw.count; int ret; - while (nextcmd != NULL) { + while (nextcmd) { const SDL_RenderCommandType nextcmdtype = nextcmd->command; if (nextcmdtype != thiscmdtype) { break; /* can't go any further on this draw call, different render command up next. */ @@ -1426,8 +1483,8 @@ static int GL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, temp_pitch = rect->w * SDL_BYTESPERPIXEL(temp_format); temp_pixels = SDL_malloc((size_t)rect->h * temp_pitch); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_GetCurrentRenderOutputSize(renderer, &w, &h); @@ -1491,16 +1548,25 @@ static void GL_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) renderdata->drawstate.target = NULL; } - if (data == NULL) { + if (!data) { return; } - if (data->texture) { + if (data->texture && !data->texture_external) { renderdata->glDeleteTextures(1, &data->texture); } #if SDL_HAVE_YUV if (data->yuv) { - renderdata->glDeleteTextures(1, &data->utexture); - renderdata->glDeleteTextures(1, &data->vtexture); + if (!data->utexture_external) { + renderdata->glDeleteTextures(1, &data->utexture); + } + if (!data->vtexture_external) { + renderdata->glDeleteTextures(1, &data->vtexture); + } + } + if (data->nv12) { + if (!data->utexture_external) { + renderdata->glDeleteTextures(1, &data->utexture); + } } #endif SDL_free(data->pixels); @@ -1513,7 +1579,7 @@ static void GL_DestroyRenderer(SDL_Renderer *renderer) GL_RenderData *data = (GL_RenderData *)renderer->driverdata; if (data) { - if (data->context != NULL) { + if (data->context) { /* make sure we delete the right resources! */ GL_ActivateRenderer(renderer); } @@ -1545,105 +1611,6 @@ static void GL_DestroyRenderer(SDL_Renderer *renderer) SDL_free(renderer); } -static int GL_BindTexture(SDL_Renderer *renderer, SDL_Texture *texture, float *texw, float *texh) -{ - GL_RenderData *data = (GL_RenderData *)renderer->driverdata; - GL_TextureData *texturedata = (GL_TextureData *)texture->driverdata; - const GLenum textype = data->textype; - - GL_ActivateRenderer(renderer); - - data->glEnable(textype); -#if SDL_HAVE_YUV - if (texturedata->yuv) { - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE2_ARB); - } - data->glBindTexture(textype, texturedata->vtexture); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } - data->glBindTexture(textype, texturedata->utexture); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE0_ARB); - } - } - if (texturedata->nv12) { - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } - data->glBindTexture(textype, texturedata->utexture); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE0_ARB); - } - } -#endif - data->glBindTexture(textype, texturedata->texture); - - data->drawstate.texturing = SDL_TRUE; - data->drawstate.texture = texture; - - if (texw) { - *texw = (float)texturedata->texw; - } - if (texh) { - *texh = (float)texturedata->texh; - } - return 0; -} - -static int GL_UnbindTexture(SDL_Renderer *renderer, SDL_Texture *texture) -{ - GL_RenderData *data = (GL_RenderData *)renderer->driverdata; - const GLenum textype = data->textype; -#if SDL_HAVE_YUV - GL_TextureData *texturedata = (GL_TextureData *)texture->driverdata; -#endif - - GL_ActivateRenderer(renderer); - -#if SDL_HAVE_YUV - if (texturedata->yuv) { - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE2_ARB); - } - data->glBindTexture(textype, 0); - data->glDisable(textype); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } - data->glBindTexture(textype, 0); - data->glDisable(textype); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE0_ARB); - } - } - if (texturedata->nv12) { - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE1_ARB); - } - data->glBindTexture(textype, 0); - data->glDisable(textype); - - if (data->GL_ARB_multitexture_supported) { - data->glActiveTextureARB(GL_TEXTURE0_ARB); - } - } -#endif - data->glBindTexture(textype, 0); - data->glDisable(textype); - - data->drawstate.texturing = SDL_FALSE; - data->drawstate.texture = NULL; - - return 0; -} - static int GL_SetVSync(SDL_Renderer *renderer, const int vsync) { int retval; @@ -1662,7 +1629,7 @@ static int GL_SetVSync(SDL_Renderer *renderer, const int vsync) return retval; } - if (interval > 0) { + if (interval != 0) { renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } else { renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC; @@ -1696,7 +1663,7 @@ static SDL_bool GL_IsProbablyAccelerated(const GL_RenderData *data) return SDL_TRUE; } -static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) +static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; GL_RenderData *data; @@ -1712,6 +1679,7 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); #ifndef SDL_VIDEO_VITA_PVR_OGL + SDL_SyncWindow(window); window_flags = SDL_GetWindowFlags(window); if (!(window_flags & SDL_WINDOW_OPENGL) || profile_mask == SDL_GL_CONTEXT_PROFILE_ES || major != RENDERER_CONTEXT_MAJOR || minor != RENDERER_CONTEXT_MINOR) { @@ -1728,15 +1696,13 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) #endif renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { goto error; } data = (GL_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); goto error; } @@ -1757,17 +1723,17 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = GL_QueueDrawPoints; renderer->QueueDrawLines = GL_QueueDrawLines; renderer->QueueGeometry = GL_QueueGeometry; + renderer->InvalidateCachedState = GL_InvalidateCachedState; renderer->RunCommandQueue = GL_RunCommandQueue; renderer->RenderReadPixels = GL_RenderReadPixels; renderer->RenderPresent = GL_RenderPresent; renderer->DestroyTexture = GL_DestroyTexture; renderer->DestroyRenderer = GL_DestroyRenderer; renderer->SetVSync = GL_SetVSync; - renderer->GL_BindTexture = GL_BindTexture; - renderer->GL_UnbindTexture = GL_UnbindTexture; renderer->info = GL_RenderDriver.info; renderer->info.flags = 0; /* will set some flags below. */ renderer->driverdata = data; + GL_InvalidateCachedState(renderer); renderer->window = window; data->context = SDL_GL_CreateContext(window); @@ -1801,7 +1767,7 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) */ #endif - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { SDL_GL_SetSwapInterval(1); } else { SDL_GL_SetSwapInterval(0); @@ -1811,10 +1777,8 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) int interval = 0; if (SDL_GL_GetSwapInterval(&interval) < 0) { /* Error */ - } else { - if (interval > 0) { - renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; - } + } else if (interval != 0) { + renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } } @@ -1836,7 +1800,7 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) } hint = SDL_getenv("GL_ARB_texture_non_power_of_two"); - if (hint == NULL || *hint != '0') { + if (!hint || *hint != '0') { SDL_bool isGL2 = SDL_FALSE; const char *verstr = (const char *)data->glGetString(GL_VERSION); if (verstr) { @@ -1946,8 +1910,6 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, Uint32 flags) /* This ended up causing video discrepancies between OpenGL and Direct3D */ /* data->glEnable(GL_LINE_SMOOTH); */ - data->drawstate.blend = SDL_BLENDMODE_INVALID; - data->drawstate.shader = SHADER_INVALID; data->drawstate.color = 0xFFFFFFFF; data->drawstate.clear_color = 0xFFFFFFFF; diff --git a/src/render/opengl/SDL_shaders_gl.c b/src/render/opengl/SDL_shaders_gl.c index 4a35e1e7..3431e32e 100644 --- a/src/render/opengl/SDL_shaders_gl.c +++ b/src/render/opengl/SDL_shaders_gl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -495,7 +495,7 @@ GL_ShaderContext *GL_CreateShaderContext(void) int i; ctx = (GL_ShaderContext *)SDL_calloc(1, sizeof(*ctx)); - if (ctx == NULL) { + if (!ctx) { return NULL; } diff --git a/src/render/opengl/SDL_shaders_gl.h b/src/render/opengl/SDL_shaders_gl.h index 22022501..c0a4c424 100644 --- a/src/render/opengl/SDL_shaders_gl.h +++ b/src/render/opengl/SDL_shaders_gl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/opengles2/SDL_gles2funcs.h b/src/render/opengles2/SDL_gles2funcs.h index 59910214..125d64d8 100644 --- a/src/render/opengles2/SDL_gles2funcs.h +++ b/src/render/opengles2/SDL_gles2funcs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index 7216a614..5e63a421 100644 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -61,6 +61,7 @@ struct GLES2_FBOList typedef struct GLES2_TextureData { GLuint texture; + SDL_bool texture_external; GLenum texture_type; GLenum pixel_format; GLenum pixel_type; @@ -71,7 +72,9 @@ typedef struct GLES2_TextureData SDL_bool yuv; SDL_bool nv12; GLuint texture_v; + GLuint texture_v_external; GLuint texture_u; + GLuint texture_u_external; #endif GLES2_FBOList *fbo; } GLES2_TextureData; @@ -135,7 +138,9 @@ typedef struct SDL_bool cliprect_dirty; SDL_Rect cliprect; SDL_bool texturing; + SDL_bool texturing_dirty; Uint32 clear_color; + SDL_bool clear_color_dirty; int drawablew; int drawableh; GLES2_ProgramCacheEntry *program; @@ -216,7 +221,7 @@ static int GL_CheckAllErrors(const char *prefix, SDL_Renderer *renderer, const c for (;;) { GLenum error = data->glGetError(); if (error != GL_NO_ERROR) { - if (prefix == NULL || prefix[0] == '\0') { + if (!prefix || prefix[0] == '\0') { prefix = "generic"; } SDL_SetError("%s: %s (%d): %s %s (0x%X)", prefix, file, line, function, GL_TranslateError(error), error); @@ -269,7 +274,7 @@ static GLES2_FBOList *GLES2_GetFBO(GLES2_RenderData *data, Uint32 w, Uint32 h) while ((result) && ((result->w != w) || (result->h != h))) { result = result->next; } - if (result == NULL) { + if (!result) { result = SDL_malloc(sizeof(GLES2_FBOList)); result->w = w; result->h = h; @@ -415,8 +420,7 @@ static GLES2_ProgramCacheEntry *GLES2_CacheProgram(GLES2_RenderData *data, GLuin /* Create a program cache entry */ entry = (GLES2_ProgramCacheEntry *)SDL_calloc(1, sizeof(GLES2_ProgramCacheEntry)); - if (entry == NULL) { - SDL_OutOfMemory(); + if (!entry) { return NULL; } entry->vertex_shader = vertex; @@ -476,7 +480,7 @@ static GLES2_ProgramCacheEntry *GLES2_CacheProgram(GLES2_RenderData *data, GLuin if (data->program_cache.count > GLES2_MAX_CACHED_PROGRAMS) { data->glDeleteProgram(data->program_cache.tail->id); data->program_cache.tail = data->program_cache.tail->prev; - if (data->program_cache.tail != NULL) { + if (data->program_cache.tail) { SDL_free(data->program_cache.tail->next); data->program_cache.tail->next = NULL; } @@ -493,7 +497,7 @@ static GLuint GLES2_CacheShader(GLES2_RenderData *data, GLES2_ShaderType type, G const GLchar *shader_src_list[3]; const GLchar *shader_body = GLES2_GetShader(type); - if (shader_body == NULL) { + if (!shader_body) { SDL_SetError("No shader body src"); return 0; } @@ -539,20 +543,19 @@ static GLuint GLES2_CacheShader(GLES2_RenderData *data, GLES2_ShaderType type, G } if (!compileSuccessful) { - SDL_bool isstack = SDL_FALSE; char *info = NULL; int length = 0; data->glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length); if (length > 0) { - info = SDL_small_alloc(char, length, &isstack); + info = (char *)SDL_malloc(length); if (info) { data->glGetShaderInfoLog(id, length, &length, info); } } if (info) { SDL_SetError("Failed to load the shader %d: %s", type, info); - SDL_small_free(info, isstack); + SDL_free(info); } else { SDL_SetError("Failed to load the shader %d", type); } @@ -703,7 +706,7 @@ static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source, /* Generate a matching program */ program = GLES2_CacheProgram(data, vertex, fragment); - if (program == NULL) { + if (!program) { goto fault; } @@ -727,7 +730,7 @@ static int GLES2_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd static int GLES2_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) { - const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 || renderer->target->format == SDL_PIXELFORMAT_XRGB8888)); + const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_BGRA32 || renderer->target->format == SDL_PIXELFORMAT_BGRX32)); SDL_VertexSolid *verts = (SDL_VertexSolid *)SDL_AllocateRenderVertices(renderer, count * sizeof(*verts), 0, &cmd->data.draw.first); int i; SDL_Color color; @@ -736,7 +739,7 @@ static int GLES2_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, color.b = cmd->data.draw.b; color.a = cmd->data.draw.a; - if (verts == NULL) { + if (!verts) { return -1; } @@ -759,7 +762,7 @@ static int GLES2_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, static int GLES2_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) { - const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 || renderer->target->format == SDL_PIXELFORMAT_XRGB8888)); + const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_BGRA32 || renderer->target->format == SDL_PIXELFORMAT_BGRX32)); int i; GLfloat prevx, prevy; SDL_VertexSolid *verts = (SDL_VertexSolid *)SDL_AllocateRenderVertices(renderer, count * sizeof(*verts), 0, &cmd->data.draw.first); @@ -769,7 +772,7 @@ static int GLES2_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, color.b = cmd->data.draw.b; color.a = cmd->data.draw.a; - if (verts == NULL) { + if (!verts) { return -1; } @@ -819,7 +822,7 @@ static int GLES2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S float scale_x, float scale_y) { int i; - const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 || renderer->target->format == SDL_PIXELFORMAT_XRGB8888)); + const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_BGRA32 || renderer->target->format == SDL_PIXELFORMAT_BGRX32)); int count = indices ? num_indices : num_vertices; cmd->data.draw.count = count; @@ -827,7 +830,7 @@ static int GLES2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S if (texture) { SDL_Vertex *verts = (SDL_Vertex *)SDL_AllocateRenderVertices(renderer, count * sizeof(*verts), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -867,7 +870,7 @@ static int GLES2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S } else { SDL_VertexSolid *verts = (SDL_VertexSolid *)SDL_AllocateRenderVertices(renderer, count * sizeof(*verts), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -946,14 +949,15 @@ static int SetDrawState(GLES2_RenderData *data, const SDL_RenderCommand *cmd, co data->drawstate.cliprect_dirty = SDL_FALSE; } - if ((texture != NULL) != data->drawstate.texturing) { - if (texture == NULL) { + if (data->drawstate.texturing_dirty || ((texture != NULL) != data->drawstate.texturing)) { + if (!texture) { data->glDisableVertexAttribArray((GLenum)GLES2_ATTRIBUTE_TEXCOORD); data->drawstate.texturing = SDL_FALSE; } else { data->glEnableVertexAttribArray((GLenum)GLES2_ATTRIBUTE_TEXCOORD); data->drawstate.texturing = SDL_TRUE; } + data->drawstate.texturing_dirty = SDL_FALSE; } if (texture) { @@ -1017,50 +1021,50 @@ static int SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, vo /* Check if we need to do color mapping between the source and render target textures */ if (renderer->target->format != texture->format) { switch (texture->format) { - case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_BGRA32: switch (renderer->target->format) { - case SDL_PIXELFORMAT_ABGR8888: - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_RGBA32: + case SDL_PIXELFORMAT_RGBX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; - case SDL_PIXELFORMAT_XRGB8888: + case SDL_PIXELFORMAT_BGRX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR; break; } break; - case SDL_PIXELFORMAT_ABGR8888: + case SDL_PIXELFORMAT_RGBA32: switch (renderer->target->format) { - case SDL_PIXELFORMAT_ARGB8888: - case SDL_PIXELFORMAT_XRGB8888: + case SDL_PIXELFORMAT_BGRA32: + case SDL_PIXELFORMAT_BGRX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_RGBX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR; break; } break; - case SDL_PIXELFORMAT_XRGB8888: + case SDL_PIXELFORMAT_BGRX32: switch (renderer->target->format) { - case SDL_PIXELFORMAT_ABGR8888: + case SDL_PIXELFORMAT_RGBA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; - case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_BGRA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR; break; - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_RGBX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; } break; - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_RGBX32: switch (renderer->target->format) { - case SDL_PIXELFORMAT_ABGR8888: + case SDL_PIXELFORMAT_RGBA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR; break; - case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_BGRA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB; break; - case SDL_PIXELFORMAT_XRGB8888: + case SDL_PIXELFORMAT_BGRX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; } @@ -1088,16 +1092,16 @@ static int SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, vo } } else { switch (texture->format) { - case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_BGRA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB; break; - case SDL_PIXELFORMAT_ABGR8888: + case SDL_PIXELFORMAT_RGBA32: sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR; break; - case SDL_PIXELFORMAT_XRGB8888: + case SDL_PIXELFORMAT_BGRX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB; break; - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_RGBX32: sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR; break; #if SDL_HAVE_YUV @@ -1147,10 +1151,25 @@ static int SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, vo return ret; } +static void GLES2_InvalidateCachedState(SDL_Renderer *renderer) +{ + GLES2_DrawStateCache *cache = &((GLES2_RenderData *)renderer->driverdata)->drawstate; + cache->viewport_dirty = SDL_TRUE; + cache->texture = NULL; + cache->blend = SDL_BLENDMODE_INVALID; + cache->cliprect_enabled_dirty = SDL_TRUE; + cache->cliprect_dirty = SDL_TRUE; + cache->texturing_dirty = SDL_TRUE; + cache->clear_color_dirty = SDL_TRUE; + cache->drawablew = 0; + cache->drawableh = 0; + cache->program = NULL; +} + static int GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata; - const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 || renderer->target->format == SDL_PIXELFORMAT_XRGB8888)); + const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_BGRA32 || renderer->target->format == SDL_PIXELFORMAT_BGRX32)); #if USE_VERTEX_BUFFER_OBJECTS const int vboidx = data->current_vertex_buffer; @@ -1230,13 +1249,14 @@ static int GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const Uint8 b = colorswap ? cmd->data.color.r : cmd->data.color.b; const Uint8 a = cmd->data.color.a; const Uint32 color = (((Uint32)a << 24) | (r << 16) | (g << 8) | b); - if (color != data->drawstate.clear_color) { + if (data->drawstate.clear_color_dirty || (color != data->drawstate.clear_color)) { const GLfloat fr = ((GLfloat)r) * inv255f; const GLfloat fg = ((GLfloat)g) * inv255f; const GLfloat fb = ((GLfloat)b) * inv255f; const GLfloat fa = ((GLfloat)a) * inv255f; data->glClearColor(fr, fg, fb, fa); data->drawstate.clear_color = color; + data->drawstate.clear_color_dirty = SDL_FALSE; } if (data->drawstate.cliprect_enabled || data->drawstate.cliprect_enabled_dirty) { @@ -1270,7 +1290,7 @@ static int GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_RenderCommand *nextcmd = cmd->next; SDL_BlendMode thisblend = cmd->data.draw.blend; - while (nextcmd != NULL) { + while (nextcmd) { const SDL_RenderCommandType nextcmdtype = nextcmd->command; if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { break; /* can't go any further on this draw call, different render command up next. */ @@ -1304,7 +1324,7 @@ static int GLES2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_RenderCommand *nextcmd = cmd->next; size_t count = cmd->data.draw.count; int ret; - while (nextcmd != NULL) { + while (nextcmd) { const SDL_RenderCommandType nextcmdtype = nextcmd->command; if (nextcmdtype != thiscmdtype) { break; /* can't go any further on this draw call, different render command up next. */ @@ -1396,7 +1416,7 @@ static void GLES2_DestroyRenderer(SDL_Renderer *renderer) SDL_free(renderer); } -static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { GLES2_RenderData *renderdata = (GLES2_RenderData *)renderer->driverdata; GLES2_TextureData *data; @@ -1410,10 +1430,10 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) /* Determine the corresponding GLES texture format params */ switch (texture->format) { - case SDL_PIXELFORMAT_ARGB8888: - case SDL_PIXELFORMAT_ABGR8888: - case SDL_PIXELFORMAT_XRGB8888: - case SDL_PIXELFORMAT_XBGR8888: + case SDL_PIXELFORMAT_BGRA32: + case SDL_PIXELFORMAT_RGBA32: + case SDL_PIXELFORMAT_BGRX32: + case SDL_PIXELFORMAT_RGBX32: format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; @@ -1443,8 +1463,8 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) /* Allocate a texture struct */ data = (GLES2_TextureData *)SDL_calloc(1, sizeof(GLES2_TextureData)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } data->texture = 0; #ifdef GL_TEXTURE_EXTERNAL_OES @@ -1479,7 +1499,7 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) data->pixel_data = SDL_calloc(1, size); if (!data->pixel_data) { SDL_free(data); - return SDL_OutOfMemory(); + return -1; } } @@ -1488,9 +1508,14 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) #if SDL_HAVE_YUV if (data->yuv) { - renderdata->glGenTextures(1, &data->texture_v); - if (GL_CheckError("glGenTexures()", renderer) < 0) { - return -1; + data->texture_v = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER, 0); + if (data->texture_v) { + data->texture_v_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->texture_v); + if (GL_CheckError("glGenTexures()", renderer) < 0) { + return -1; + } } renderdata->glActiveTexture(GL_TEXTURE2); renderdata->glBindTexture(data->texture_type, data->texture_v); @@ -1499,10 +1524,16 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL); + SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER, data->texture_v); - renderdata->glGenTextures(1, &data->texture_u); - if (GL_CheckError("glGenTexures()", renderer) < 0) { - return -1; + data->texture_u = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER, 0); + if (data->texture_u) { + data->texture_u_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->texture_u); + if (GL_CheckError("glGenTexures()", renderer) < 0) { + return -1; + } } renderdata->glActiveTexture(GL_TEXTURE1); renderdata->glBindTexture(data->texture_type, data->texture_u); @@ -1514,10 +1545,17 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) if (GL_CheckError("glTexImage2D()", renderer) < 0) { return -1; } + SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER, data->texture_u); + } else if (data->nv12) { - renderdata->glGenTextures(1, &data->texture_u); - if (GL_CheckError("glGenTexures()", renderer) < 0) { - return -1; + data->texture_u = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER, 0); + if (data->texture_u) { + data->texture_u_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->texture_u); + if (GL_CheckError("glGenTexures()", renderer) < 0) { + return -1; + } } renderdata->glActiveTexture(GL_TEXTURE1); renderdata->glBindTexture(data->texture_type, data->texture_u); @@ -1529,12 +1567,18 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) if (GL_CheckError("glTexImage2D()", renderer) < 0) { return -1; } + SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER, data->texture_u); } #endif - renderdata->glGenTextures(1, &data->texture); - if (GL_CheckError("glGenTexures()", renderer) < 0) { - return -1; + data->texture = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROPERTY_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER, 0); + if (data->texture) { + data->texture_external = SDL_TRUE; + } else { + renderdata->glGenTextures(1, &data->texture); + if (GL_CheckError("glGenTexures()", renderer) < 0) { + return -1; + } } texture->driverdata = data; renderdata->glActiveTexture(GL_TEXTURE0); @@ -1549,6 +1593,8 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) return -1; } } + SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_NUMBER, data->texture); + SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_TARGET, data->texture_type); if (texture->access == SDL_TEXTUREACCESS_TARGET) { data->fbo = GLES2_GetFBO(renderer->driverdata, texture->w, texture->h); @@ -1562,9 +1608,6 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) static int GLES2_TexSubImage2D(GLES2_RenderData *data, GLenum target, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels, GLint pitch, GLint bpp) { Uint8 *blob = NULL; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - Uint32 *blob2 = NULL; -#endif Uint8 *src; size_t src_pitch; int y; @@ -1578,8 +1621,8 @@ static int GLES2_TexSubImage2D(GLES2_RenderData *data, GLenum target, GLint xoff src = (Uint8 *)pixels; if ((size_t)pitch != src_pitch) { blob = (Uint8 *)SDL_malloc(src_pitch * height); - if (blob == NULL) { - return SDL_OutOfMemory(); + if (!blob) { + return -1; } src = blob; for (y = 0; y < height; ++y) { @@ -1590,33 +1633,10 @@ static int GLES2_TexSubImage2D(GLES2_RenderData *data, GLenum target, GLint xoff src = blob; } -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - if (format == GL_RGBA) { - int i; - Uint32 *src32 = (Uint32 *)src; - blob2 = (Uint32 *)SDL_malloc(src_pitch * height); - if (blob2 == NULL) { - if (blob) { - SDL_free(blob); - } - return SDL_OutOfMemory(); - } - for (i = 0; i < (src_pitch * height) / 4; i++) { - blob2[i] = SDL_Swap32(src32[i]); - } - src = (Uint8 *)blob2; - } -#endif - data->glTexSubImage2D(target, 0, xoffset, yoffset, width, height, format, type, src); if (blob) { SDL_free(blob); } -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - if (blob2) { - SDL_free(blob2); - } -#endif return 0; } @@ -1856,7 +1876,7 @@ static int GLES2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) data->drawstate.viewport_dirty = SDL_TRUE; - if (texture == NULL) { + if (!texture) { data->glBindFramebuffer(GL_FRAMEBUFFER, data->window_framebuffer); } else { texturedata = (GLES2_TextureData *)texture->driverdata; @@ -1888,12 +1908,14 @@ static void GLES2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) /* Destroy the texture */ if (tdata) { - data->glDeleteTextures(1, &tdata->texture); + if (tdata->texture && !tdata->texture_external) { + data->glDeleteTextures(1, &tdata->texture); + } #if SDL_HAVE_YUV - if (tdata->texture_v) { + if (tdata->texture_v && !tdata->texture_v_external) { data->glDeleteTextures(1, &tdata->texture_v); } - if (tdata->texture_u) { + if (tdata->texture_u && !tdata->texture_u_external) { data->glDeleteTextures(1, &tdata->texture_u); } #endif @@ -1907,7 +1929,7 @@ static int GLES2_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, Uint32 pixel_format, void *pixels, int pitch) { GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata; - Uint32 temp_format = renderer->target ? renderer->target->format : SDL_PIXELFORMAT_ABGR8888; + Uint32 temp_format = renderer->target ? renderer->target->format : SDL_PIXELFORMAT_RGBA32; size_t buflen; void *temp_pixels; int temp_pitch; @@ -1922,8 +1944,8 @@ static int GLES2_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, } temp_pixels = SDL_malloc(buflen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_GetCurrentRenderOutputSize(renderer, &w, &h); @@ -1984,7 +2006,7 @@ static int GLES2_SetVSync(SDL_Renderer *renderer, const int vsync) return retval; } - if (interval > 0) { + if (interval != 0) { renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } else { renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC; @@ -1992,65 +2014,11 @@ static int GLES2_SetVSync(SDL_Renderer *renderer, const int vsync) return retval; } -/************************************************************************************************* - * Bind/unbinding of textures - *************************************************************************************************/ -static int GLES2_BindTexture(SDL_Renderer *renderer, SDL_Texture *texture, float *texw, float *texh); -static int GLES2_UnbindTexture(SDL_Renderer *renderer, SDL_Texture *texture); - -static int GLES2_BindTexture(SDL_Renderer *renderer, SDL_Texture *texture, float *texw, float *texh) -{ - GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata; - GLES2_TextureData *texturedata = (GLES2_TextureData *)texture->driverdata; - GLES2_ActivateRenderer(renderer); - -#if SDL_HAVE_YUV - if (texturedata->yuv) { - data->glActiveTexture(GL_TEXTURE2); - data->glBindTexture(texturedata->texture_type, texturedata->texture_v); - - data->glActiveTexture(GL_TEXTURE1); - data->glBindTexture(texturedata->texture_type, texturedata->texture_u); - - data->glActiveTexture(GL_TEXTURE0); - } else if (texturedata->nv12) { - data->glActiveTexture(GL_TEXTURE1); - data->glBindTexture(texturedata->texture_type, texturedata->texture_u); - - data->glActiveTexture(GL_TEXTURE0); - } -#endif - - data->glBindTexture(texturedata->texture_type, texturedata->texture); - data->drawstate.texture = texture; - - if (texw) { - *texw = 1.0; - } - if (texh) { - *texh = 1.0; - } - - return 0; -} - -static int GLES2_UnbindTexture(SDL_Renderer *renderer, SDL_Texture *texture) -{ - GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata; - GLES2_TextureData *texturedata = (GLES2_TextureData *)texture->driverdata; - GLES2_ActivateRenderer(renderer); - - data->glBindTexture(texturedata->texture_type, 0); - data->drawstate.texture = NULL; - - return 0; -} - /************************************************************************************************* * Renderer instantiation * *************************************************************************************************/ -static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) +static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; GLES2_RenderData *data; @@ -2070,6 +2038,7 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) goto error; } + SDL_SyncWindow(window); window_flags = SDL_GetWindowFlags(window); /* OpenGL ES 3.0 is a superset of OpenGL ES 2.0 */ @@ -2088,20 +2057,19 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) /* Create the renderer struct */ renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(SDL_Renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { goto error; } data = (GLES2_RenderData *)SDL_calloc(1, sizeof(GLES2_RenderData)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); goto error; } renderer->info = GLES2_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + GLES2_InvalidateCachedState(renderer); renderer->window = window; /* Create an OpenGL ES 2.0 context */ @@ -2137,10 +2105,10 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) * is turned on. Not doing so will freeze the screen's contents to that * of the first drawn frame. */ - flags |= SDL_RENDERER_PRESENTVSYNC; + SDL_SetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_TRUE); #endif - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { SDL_GL_SetSwapInterval(1); } else { SDL_GL_SetSwapInterval(0); @@ -2150,10 +2118,8 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) int interval = 0; if (SDL_GL_GetSwapInterval(&interval) < 0) { /* Error */ - } else { - if (interval > 0) { - renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; - } + } else if (interval != 0) { + renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } } @@ -2197,14 +2163,13 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = GLES2_QueueDrawPoints; renderer->QueueDrawLines = GLES2_QueueDrawLines; renderer->QueueGeometry = GLES2_QueueGeometry; + renderer->InvalidateCachedState = GLES2_InvalidateCachedState; renderer->RunCommandQueue = GLES2_RunCommandQueue; renderer->RenderReadPixels = GLES2_RenderReadPixels; renderer->RenderPresent = GLES2_RenderPresent; renderer->DestroyTexture = GLES2_DestroyTexture; renderer->DestroyRenderer = GLES2_DestroyRenderer; renderer->SetVSync = GLES2_SetVSync; - renderer->GL_BindTexture = GLES2_BindTexture; - renderer->GL_UnbindTexture = GLES2_UnbindTexture; #if SDL_HAVE_YUV renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12; renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV; @@ -2239,7 +2204,6 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) data->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - data->drawstate.blend = SDL_BLENDMODE_INVALID; data->drawstate.clear_color = 0xFFFFFFFF; data->drawstate.projection[3][0] = -1.0f; data->drawstate.projection[3][3] = 1.0f; @@ -2264,10 +2228,10 @@ SDL_RenderDriver GLES2_RenderDriver = { { "opengles2", (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), 4, - { SDL_PIXELFORMAT_ARGB8888, - SDL_PIXELFORMAT_ABGR8888, - SDL_PIXELFORMAT_XRGB8888, - SDL_PIXELFORMAT_XBGR8888 }, + { SDL_PIXELFORMAT_RGBA32, + SDL_PIXELFORMAT_BGRA32, + SDL_PIXELFORMAT_BGRX32, + SDL_PIXELFORMAT_RGBX32 }, 0, 0 } }; diff --git a/src/render/opengles2/SDL_shaders_gles2.c b/src/render/opengles2/SDL_shaders_gles2.c index 4acfdea3..1502256a 100644 --- a/src/render/opengles2/SDL_shaders_gles2.c +++ b/src/render/opengles2/SDL_shaders_gles2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/opengles2/SDL_shaders_gles2.h b/src/render/opengles2/SDL_shaders_gles2.h index ac336480..b153d1dd 100644 --- a/src/render/opengles2/SDL_shaders_gles2.h +++ b/src/render/opengles2/SDL_shaders_gles2.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/ps2/SDL_render_ps2.c b/src/render/ps2/SDL_render_ps2.c index c1d748e3..acf177e0 100644 --- a/src/render/ps2/SDL_render_ps2.c +++ b/src/render/ps2/SDL_render_ps2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,10 +30,16 @@ #include #include +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#endif + #include + +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop +#endif /* turn black GS Screen */ #define GS_BLACK GS_SETREG_RGBA(0x00, 0x00, 0x00, 0x80) @@ -100,12 +106,12 @@ static void PS2_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event { } -static int PS2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int PS2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { GSTEXTURE *ps2_tex = (GSTEXTURE *)SDL_calloc(1, sizeof(GSTEXTURE)); - if (ps2_tex == NULL) { - return SDL_OutOfMemory(); + if (!ps2_tex) { + return -1; } ps2_tex->Width = texture->w; @@ -115,7 +121,7 @@ static int PS2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) if (!ps2_tex->Mem) { SDL_free(ps2_tex); - return SDL_OutOfMemory(); + return -1; } texture->driverdata = ps2_tex; @@ -201,7 +207,7 @@ static int PS2_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, c gs_rgbaq rgbaq; int i; - if (vertices == NULL) { + if (!vertices) { return -1; } @@ -236,7 +242,7 @@ static int PS2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL GSPRIMUVPOINT *vertices = (GSPRIMUVPOINT *) SDL_AllocateRenderVertices(renderer, count * sizeof(GSPRIMUVPOINT), 4, &cmd->data.draw.first); GSTEXTURE *ps2_tex = (GSTEXTURE *) texture->driverdata; - if (vertices == NULL) { + if (!vertices) { return -1; } @@ -269,7 +275,7 @@ static int PS2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL } else { GSPRIMPOINT *vertices = (GSPRIMPOINT *)SDL_AllocateRenderVertices(renderer, count * sizeof(GSPRIMPOINT), 4, &cmd->data.draw.first); - if (vertices == NULL) { + if (!vertices) { return -1; } @@ -440,6 +446,11 @@ int PS2_RenderPoints(SDL_Renderer *renderer, void *vertices, SDL_RenderCommand * return 0; } +static void PS2_InvalidateCachedState(SDL_Renderer *renderer) +{ + /* currently this doesn't do anything. If this needs to do something (and someone is mixing their own rendering calls in!), update this. */ +} + static int PS2_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { while (cmd) { @@ -530,11 +541,11 @@ static void PS2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) GSTEXTURE *ps2_texture = (GSTEXTURE *)texture->driverdata; PS2_RenderData *data = (PS2_RenderData *)renderer->driverdata; - if (data == NULL) { + if (!data) { return; } - if (ps2_texture == NULL) { + if (!ps2_texture) { return; } @@ -574,7 +585,7 @@ static int PS2_SetVSync(SDL_Renderer *renderer, const int vsync) return 0; } -static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, Uint32 flags) +static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; PS2_RenderData *data; @@ -583,15 +594,13 @@ static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, Uint32 flags) SDL_bool dynamicVsync; renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } data = (PS2_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { PS2_DestroyRenderer(renderer); - SDL_OutOfMemory(); return NULL; } @@ -634,7 +643,9 @@ static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, Uint32 flags) data->gsGlobal = gsGlobal; dynamicVsync = SDL_GetHintBoolean(SDL_HINT_PS2_DYNAMIC_VSYNC, SDL_FALSE); - data->vsync = flags & SDL_RENDERER_PRESENTVSYNC ? (dynamicVsync ? 2 : 1) : 0; + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { + data->vsync = (dynamicVsync ? 2 : 1); + } renderer->WindowEvent = PS2_WindowEvent; renderer->CreateTexture = PS2_CreateTexture; @@ -648,6 +659,7 @@ static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = PS2_QueueDrawPoints; renderer->QueueDrawLines = PS2_QueueDrawPoints; renderer->QueueGeometry = PS2_QueueGeometry; + renderer->InvalidateCachedState = PS2_InvalidateCachedState; renderer->RunCommandQueue = PS2_RunCommandQueue; renderer->RenderReadPixels = PS2_RenderReadPixels; renderer->RenderPresent = PS2_RenderPresent; @@ -655,9 +667,14 @@ static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->DestroyRenderer = PS2_DestroyRenderer; renderer->SetVSync = PS2_SetVSync; renderer->info = PS2_RenderDriver.info; + renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + PS2_InvalidateCachedState(renderer); renderer->window = window; + if (data->vsync) { + renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; + } return renderer; } diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c index 82b0a6b7..aa6434fd 100644 --- a/src/render/psp/SDL_render_psp.c +++ b/src/render/psp/SDL_render_psp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -281,12 +281,12 @@ static int TextureSwizzle(PSP_TextureData *psp_texture, void *dst) src = (unsigned int *)psp_texture->data; data = dst; - if (data == NULL) { + if (!data) { data = SDL_malloc(psp_texture->size); } - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } for (j = 0; j < height; j++, blockaddress += 16) { @@ -343,12 +343,12 @@ static int TextureUnswizzle(PSP_TextureData *psp_texture, void *dst) data = dst; - if (data == NULL) { + if (!data) { data = SDL_malloc(psp_texture->size); } - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } ydst = (unsigned char *)data; @@ -391,8 +391,8 @@ static int TextureSpillToSram(PSP_RenderData *data, PSP_TextureData *psp_texture if (psp_texture->swizzled) { // Texture was swizzled in vram, just copy to system memory void *sdata = SDL_malloc(psp_texture->size); - if (sdata == NULL) { - return SDL_OutOfMemory(); + if (!sdata) { + return -1; } SDL_memcpy(sdata, psp_texture->data, psp_texture->size); @@ -478,13 +478,13 @@ static void PSP_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event { } -static int PSP_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int PSP_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { PSP_RenderData *data = renderer->driverdata; PSP_TextureData *psp_texture = (PSP_TextureData *)SDL_calloc(1, sizeof(*psp_texture)); - if (psp_texture == NULL) { - return SDL_OutOfMemory(); + if (!psp_texture) { + return -1; } psp_texture->swizzled = SDL_FALSE; @@ -527,7 +527,7 @@ static int PSP_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) if (!psp_texture->data) { SDL_free(psp_texture); - return SDL_OutOfMemory(); + return -1; } texture->driverdata = psp_texture; @@ -629,7 +629,7 @@ static int PSP_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, c VertV *verts = (VertV *)SDL_AllocateRenderVertices(renderer, count * sizeof(VertV), 4, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -655,10 +655,10 @@ static int PSP_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL cmd->data.draw.count = count; size_indices = indices ? size_indices : 0; - if (texture == NULL) { + if (!texture) { VertCV *verts; verts = (VertCV *)SDL_AllocateRenderVertices(renderer, count * sizeof(VertCV), 4, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -691,7 +691,7 @@ static int PSP_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL PSP_TextureData *psp_texture = (PSP_TextureData *)texture->driverdata; VertTCV *verts; verts = (VertTCV *)SDL_AllocateRenderVertices(renderer, count * sizeof(VertTCV), 4, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -736,7 +736,7 @@ static int PSP_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, co VertV *verts = (VertV *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(VertV), 4, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -772,7 +772,7 @@ static int PSP_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Tex if ((MathAbs(u1) - MathAbs(u0)) < 64.0f) { verts = (VertTV *)SDL_AllocateRenderVertices(renderer, 2 * sizeof(VertTV), 4, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -808,7 +808,7 @@ static int PSP_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Tex cmd->data.draw.count = count; verts = (VertTV *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(VertTV), 4, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -859,7 +859,7 @@ static int PSP_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_T float u1 = srcrect->x + srcrect->w; float v1 = srcrect->y + srcrect->h; - if (verts == NULL) { + if (!verts) { return -1; } @@ -1010,7 +1010,7 @@ static void PSP_SetBlendState(PSP_RenderData *data, PSP_BlendState *state) } if (state->texture != current->texture) { - if (state->texture != NULL) { + if (state->texture) { TextureActivate(state->texture); sceGuEnable(GU_TEXTURE_2D); } else { @@ -1021,6 +1021,11 @@ static void PSP_SetBlendState(PSP_RenderData *data, PSP_BlendState *state) *current = *state; } +static void PSP_InvalidateCachedState(SDL_Renderer *renderer) +{ + /* currently this doesn't do anything. If this needs to do something (and someone is mixing their own rendering calls in!), update this. */ +} + static int PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { PSP_RenderData *data = (PSP_RenderData *)renderer->driverdata; @@ -1034,7 +1039,7 @@ static int PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v rendering backends report a reasonable maximum, so the higher level can flush if we appear to be exceeding that. */ gpumem = (Uint8 *)sceGuGetMemory(vertsize); - if (gpumem == NULL) { + if (!gpumem) { return SDL_SetError("Couldn't obtain a %d-byte vertex buffer!", (int)vertsize); } SDL_memcpy(gpumem, vertices, vertsize); @@ -1176,7 +1181,7 @@ static int PSP_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, v case SDL_RENDERCMD_GEOMETRY: { const size_t count = cmd->data.draw.count; - if (cmd->data.draw.texture == NULL) { + if (!cmd->data.draw.texture) { const VertCV *verts = (VertCV *)(gpumem + cmd->data.draw.first); sceGuDisable(GU_TEXTURE_2D); /* In GU_SMOOTH mode */ @@ -1244,11 +1249,11 @@ static void PSP_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) PSP_RenderData *renderdata = (PSP_RenderData *)renderer->driverdata; PSP_TextureData *psp_texture = (PSP_TextureData *)texture->driverdata; - if (renderdata == NULL) { + if (!renderdata) { return; } - if (psp_texture == NULL) { + if (!psp_texture) { return; } @@ -1290,24 +1295,21 @@ static int PSP_SetVSync(SDL_Renderer *renderer, const int vsync) return 0; } -SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, Uint32 flags) +SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { - SDL_Renderer *renderer; PSP_RenderData *data; int pixelformat; void *doublebuffer = NULL; renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } data = (PSP_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { PSP_DestroyRenderer(renderer); - SDL_OutOfMemory(); return NULL; } @@ -1326,6 +1328,7 @@ SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueFillRects = PSP_QueueFillRects; renderer->QueueCopy = PSP_QueueCopy; renderer->QueueCopyEx = PSP_QueueCopyEx; + renderer->InvalidateCachedState = PSP_InvalidateCachedState; renderer->RunCommandQueue = PSP_RunCommandQueue; renderer->RenderReadPixels = PSP_RenderReadPixels; renderer->RenderPresent = PSP_RenderPresent; @@ -1335,13 +1338,14 @@ SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = PSP_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + PSP_InvalidateCachedState(renderer); renderer->window = window; data->initialized = SDL_TRUE; data->most_recent_target = NULL; data->least_recent_target = NULL; - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { data->vsync = SDL_TRUE; } else { data->vsync = SDL_FALSE; @@ -1400,6 +1404,9 @@ SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, Uint32 flags) sceKernelRegisterSubIntrHandler(PSP_VBLANK_INT, 0, psp_on_vblank, data); sceKernelEnableSubIntr(PSP_VBLANK_INT, 0); + if (data->vsync) { + renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; + } return renderer; } diff --git a/src/render/software/SDL_blendfillrect.c b/src/render/software/SDL_blendfillrect.c index 72a01d7a..f5e3f235 100644 --- a/src/render/software/SDL_blendfillrect.c +++ b/src/render/software/SDL_blendfillrect.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -211,7 +211,7 @@ int SDL_BlendFillRect(SDL_Surface *dst, const SDL_Rect *rect, { SDL_Rect clipped; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_BlendFillRect(): dst"); } @@ -281,7 +281,7 @@ int SDL_BlendFillRects(SDL_Surface *dst, const SDL_Rect *rects, int count, SDL_BlendMode blendMode, Uint8 r, Uint8 g, Uint8 b, Uint8 a) = NULL; int status = 0; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_BlendFillRects(): dst"); } @@ -325,7 +325,7 @@ int SDL_BlendFillRects(SDL_Surface *dst, const SDL_Rect *rects, int count, break; } - if (func == NULL) { + if (!func) { if (!dst->format->Amask) { func = SDL_BlendFillRect_RGB; } else { diff --git a/src/render/software/SDL_blendfillrect.h b/src/render/software/SDL_blendfillrect.h index 65bf5235..8ab67d2e 100644 --- a/src/render/software/SDL_blendfillrect.h +++ b/src/render/software/SDL_blendfillrect.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_blendline.c b/src/render/software/SDL_blendline.c index 1bacf945..e52acd7a 100644 --- a/src/render/software/SDL_blendline.c +++ b/src/render/software/SDL_blendline.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -798,12 +798,12 @@ int SDL_BlendLine(SDL_Surface *dst, int x1, int y1, int x2, int y2, { BlendLineFunc func; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_BlendLine(): dst"); } func = SDL_CalculateBlendLineFunc(dst->format); - if (func == NULL) { + if (!func) { return SDL_SetError("SDL_BlendLine(): Unsupported surface format"); } @@ -826,12 +826,12 @@ int SDL_BlendLines(SDL_Surface *dst, const SDL_Point *points, int count, SDL_bool draw_end; BlendLineFunc func; - if (dst == NULL) { + if (!dst) { return SDL_SetError("SDL_BlendLines(): Passed NULL destination surface"); } func = SDL_CalculateBlendLineFunc(dst->format); - if (func == NULL) { + if (!func) { return SDL_SetError("SDL_BlendLines(): Unsupported surface format"); } diff --git a/src/render/software/SDL_blendline.h b/src/render/software/SDL_blendline.h index d694129b..ad079545 100644 --- a/src/render/software/SDL_blendline.h +++ b/src/render/software/SDL_blendline.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_blendpoint.c b/src/render/software/SDL_blendpoint.c index 34ed5999..16072c9a 100644 --- a/src/render/software/SDL_blendpoint.c +++ b/src/render/software/SDL_blendpoint.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -209,7 +209,7 @@ static int SDL_BlendPoint_RGBA(SDL_Surface *dst, int x, int y, SDL_BlendMode ble int SDL_BlendPoint(SDL_Surface *dst, int x, int y, SDL_BlendMode blendMode, Uint8 r, Uint8 g, Uint8 b, Uint8 a) { - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_BlendPoint(): dst"); } @@ -277,7 +277,7 @@ int SDL_BlendPoints(SDL_Surface *dst, const SDL_Point *points, int count, SDL_BlendMode blendMode, Uint8 r, Uint8 g, Uint8 b, Uint8 a) = NULL; int status = 0; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_BlendPoints(): dst"); } @@ -323,7 +323,7 @@ int SDL_BlendPoints(SDL_Surface *dst, const SDL_Point *points, int count, break; } - if (func == NULL) { + if (!func) { if (!dst->format->Amask) { func = SDL_BlendPoint_RGB; } else { diff --git a/src/render/software/SDL_blendpoint.h b/src/render/software/SDL_blendpoint.h index 563e15dd..7b4b679b 100644 --- a/src/render/software/SDL_blendpoint.h +++ b/src/render/software/SDL_blendpoint.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_draw.h b/src/render/software/SDL_draw.h index b8f3fc64..15511618 100644 --- a/src/render/software/SDL_draw.h +++ b/src/render/software/SDL_draw.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_drawline.c b/src/render/software/SDL_drawline.c index 55680574..c588e2e5 100644 --- a/src/render/software/SDL_drawline.c +++ b/src/render/software/SDL_drawline.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -137,12 +137,12 @@ int SDL_DrawLine(SDL_Surface *dst, int x1, int y1, int x2, int y2, Uint32 color) { DrawLineFunc func; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_DrawLine(): dst"); } func = SDL_CalculateDrawLineFunc(dst->format); - if (func == NULL) { + if (!func) { return SDL_SetError("SDL_DrawLine(): Unsupported surface format"); } @@ -165,12 +165,12 @@ int SDL_DrawLines(SDL_Surface *dst, const SDL_Point *points, int count, SDL_bool draw_end; DrawLineFunc func; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_DrawLines(): dst"); } func = SDL_CalculateDrawLineFunc(dst->format); - if (func == NULL) { + if (!func) { return SDL_SetError("SDL_DrawLines(): Unsupported surface format"); } diff --git a/src/render/software/SDL_drawline.h b/src/render/software/SDL_drawline.h index 90599389..1bbb827f 100644 --- a/src/render/software/SDL_drawline.h +++ b/src/render/software/SDL_drawline.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_drawpoint.c b/src/render/software/SDL_drawpoint.c index 7f3c2a80..6152c4da 100644 --- a/src/render/software/SDL_drawpoint.c +++ b/src/render/software/SDL_drawpoint.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,7 +27,7 @@ int SDL_DrawPoint(SDL_Surface *dst, int x, int y, Uint32 color) { - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_DrawPoint(): dst"); } @@ -67,7 +67,7 @@ int SDL_DrawPoints(SDL_Surface *dst, const SDL_Point *points, int count, int i; int x, y; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_DrawPoints(): dst"); } diff --git a/src/render/software/SDL_drawpoint.h b/src/render/software/SDL_drawpoint.h index de6d5973..6766f9d9 100644 --- a/src/render/software/SDL_drawpoint.h +++ b/src/render/software/SDL_drawpoint.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_render_sw.c b/src/render/software/SDL_render_sw.c index 762680f2..18c8b36e 100644 --- a/src/render/software/SDL_render_sw.c +++ b/src/render/software/SDL_render_sw.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -97,11 +97,11 @@ static int SW_GetOutputSize(SDL_Renderer *renderer, int *w, int *h) return SDL_SetError("Software renderer doesn't have an output surface"); } -static int SW_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int SW_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { SDL_Surface *surface = SDL_CreateSurface(texture->w, texture->h, texture->format); - if (surface == NULL) { + if (!surface) { return SDL_SetError("Cannot create surface"); } texture->driverdata = surface; @@ -191,7 +191,7 @@ static int SW_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, co SDL_Point *verts = (SDL_Point *)SDL_AllocateRenderVertices(renderer, count * sizeof(SDL_Point), 0, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -210,7 +210,7 @@ static int SW_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, con SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, count * sizeof(SDL_Rect), 0, &cmd->data.draw.first); int i; - if (verts == NULL) { + if (!verts) { return -1; } @@ -231,7 +231,7 @@ static int SW_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Text { SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, 2 * sizeof(SDL_Rect), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -268,7 +268,7 @@ static int SW_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Te { CopyExData *verts = (CopyExData *)SDL_AllocateRenderVertices(renderer, sizeof(CopyExData), 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -302,7 +302,7 @@ static int Blit_to_Screen(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *surf r.y = (int)((float)dstrect->y * scale_y); r.w = (int)((float)dstrect->w * scale_x); r.h = (int)((float)dstrect->h * scale_y); - retval = SDL_PrivateBlitSurfaceScaled(src, srcrect, surface, &r, scaleMode); + retval = SDL_BlitSurfaceScaled(src, srcrect, surface, &r, scaleMode); } else { retval = SDL_BlitSurface(src, srcrect, surface, dstrect); } @@ -324,7 +324,7 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex int blitRequired = SDL_FALSE; int isOpaque = SDL_FALSE; - if (surface == NULL) { + if (!surface) { return -1; } @@ -344,7 +344,7 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex * The original source surface must be treated as read-only. */ src_clone = SDL_CreateSurfaceFrom(src->pixels, src->w, src->h, src->pitch, src->format->format); - if (src_clone == NULL) { + if (!src_clone) { if (SDL_MUSTLOCK(src)) { SDL_UnlockSurface(src); } @@ -387,7 +387,7 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex */ if (blendmode == SDL_BLENDMODE_NONE && !isOpaque) { mask = SDL_CreateSurface(final_rect->w, final_rect->h, SDL_PIXELFORMAT_ARGB8888); - if (mask == NULL) { + if (!mask) { retval = -1; } else { SDL_SetSurfaceBlendMode(mask, SDL_BLENDMODE_MOD); @@ -400,11 +400,11 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex if (!retval && (blitRequired || applyModulation)) { SDL_Rect scale_rect = tmp_rect; src_scaled = SDL_CreateSurface(final_rect->w, final_rect->h, SDL_PIXELFORMAT_ARGB8888); - if (src_scaled == NULL) { + if (!src_scaled) { retval = -1; } else { SDL_SetSurfaceBlendMode(src_clone, SDL_BLENDMODE_NONE); - retval = SDL_PrivateBlitSurfaceScaled(src_clone, srcrect, src_scaled, &scale_rect, texture->scaleMode); + retval = SDL_BlitSurfaceScaled(src_clone, srcrect, src_scaled, &scale_rect, texture->scaleMode); SDL_DestroySurface(src_clone); src_clone = src_scaled; src_scaled = NULL; @@ -423,15 +423,15 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex src_rotated = SDLgfx_rotateSurface(src_clone, angle, (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? 0 : 1, flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, &rect_dest, cangle, sangle, center); - if (src_rotated == NULL) { + if (!src_rotated) { retval = -1; } - if (!retval && mask != NULL) { + if (!retval && mask) { /* The mask needed for the NONE blend mode gets rotated with the same parameters. */ mask_rotated = SDLgfx_rotateSurface(mask, angle, SDL_FALSE, 0, 0, &rect_dest, cangle, sangle, center); - if (mask_rotated == NULL) { + if (!mask_rotated) { retval = -1; } } @@ -487,7 +487,7 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex src_rotated_rgb = SDL_CreateSurfaceFrom(src_rotated->pixels, src_rotated->w, src_rotated->h, src_rotated->pitch, f); - if (src_rotated_rgb == NULL) { + if (!src_rotated_rgb) { retval = -1; } else { SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD); @@ -499,7 +499,7 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex } SDL_DestroySurface(mask_rotated); } - if (src_rotated != NULL) { + if (src_rotated) { SDL_DestroySurface(src_rotated); } } @@ -508,10 +508,10 @@ static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Tex if (SDL_MUSTLOCK(src)) { SDL_UnlockSurface(src); } - if (mask != NULL) { + if (mask) { SDL_DestroySurface(mask); } - if (src_clone != NULL) { + if (src_clone) { SDL_DestroySurface(src_clone); } return retval; @@ -538,10 +538,10 @@ static int SW_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_ int i; int count = indices ? num_indices : num_vertices; void *verts; - size_t sz = texture != NULL ? sizeof(GeometryCopyData) : sizeof(GeometryFillData); + size_t sz = texture ? sizeof(GeometryCopyData) : sizeof(GeometryFillData); verts = SDL_AllocateRenderVertices(renderer, count * sz, 0, &cmd->data.draw.first); - if (verts == NULL) { + if (!verts) { return -1; } @@ -643,7 +643,7 @@ static void SetDrawState(SDL_Surface *surface, SW_DrawStateCache *drawstate) const SDL_Rect *cliprect = drawstate->cliprect; SDL_assert_release(viewport != NULL); /* the higher level should have forced a SDL_RENDERCMD_SETVIEWPORT */ - if (cliprect != NULL) { + if (cliprect && viewport) { SDL_Rect clip_rect; clip_rect.x = cliprect->x + viewport->x; clip_rect.y = cliprect->y + viewport->y; @@ -658,12 +658,18 @@ static void SetDrawState(SDL_Surface *surface, SW_DrawStateCache *drawstate) } } +static void SW_InvalidateCachedState(SDL_Renderer *renderer) +{ + /* SW_DrawStateCache only lives during SW_RunCommandQueue, so nothing to do here! */ +} + + static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { SDL_Surface *surface = SW_ActivateRenderer(renderer); SW_DrawStateCache drawstate; - if (surface == NULL) { + if (!surface) { return -1; } @@ -717,7 +723,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SetDrawState(surface, &drawstate); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { int i; for (i = 0; i < count; i++) { verts[i].x += drawstate.viewport->x; @@ -745,7 +751,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SetDrawState(surface, &drawstate); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { int i; for (i = 0; i < count; i++) { verts[i].x += drawstate.viewport->x; @@ -773,7 +779,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SetDrawState(surface, &drawstate); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { int i; for (i = 0; i < count; i++) { verts[i].x += drawstate.viewport->x; @@ -802,7 +808,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo PrepTextureForCopy(cmd); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { dstrect->x += drawstate.viewport->x; dstrect->y += drawstate.viewport->y; } @@ -837,7 +843,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo SDL_SetSurfaceColorMod(src, 255, 255, 255); SDL_SetSurfaceAlphaMod(src, 255); - SDL_PrivateBlitSurfaceScaled(src, srcrect, tmp, &r, texture->scaleMode); + SDL_BlitSurfaceScaled(src, srcrect, tmp, &r, texture->scaleMode); SDL_SetSurfaceColorMod(tmp, rMod, gMod, bMod); SDL_SetSurfaceAlphaMod(tmp, alphaMod); @@ -848,7 +854,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo /* No need to set back r/g/b/a/blendmode to 'src' since it's done in PrepTextureForCopy() */ } } else { - SDL_PrivateBlitSurfaceScaled(src, srcrect, surface, dstrect, texture->scaleMode); + SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, texture->scaleMode); } } break; @@ -861,7 +867,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo PrepTextureForCopy(cmd); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { copydata->dstrect.x += drawstate.viewport->x; copydata->dstrect.y += drawstate.viewport->y; } @@ -890,7 +896,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo PrepTextureForCopy(cmd); /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { SDL_Point vp; vp.x = drawstate.viewport->x; vp.y = drawstate.viewport->y; @@ -913,7 +919,7 @@ static int SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, vo GeometryFillData *ptr = (GeometryFillData *)verts; /* Apply viewport */ - if (drawstate.viewport != NULL && (drawstate.viewport->x || drawstate.viewport->y)) { + if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) { SDL_Point vp; vp.x = drawstate.viewport->x; vp.y = drawstate.viewport->y; @@ -948,7 +954,7 @@ static int SW_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, Uint32 src_format; void *src_pixels; - if (surface == NULL) { + if (!surface) { return -1; } @@ -975,7 +981,7 @@ static int SW_RenderPresent(SDL_Renderer *renderer) { SDL_Window *window = renderer->window; - if (window == NULL) { + if (!window) { return -1; } return SDL_UpdateWindowSurface(window); @@ -990,8 +996,12 @@ static void SW_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) static void SW_DestroyRenderer(SDL_Renderer *renderer) { + SDL_Window *window = renderer->window; SW_RenderData *data = (SW_RenderData *)renderer->driverdata; + if (window) { + SDL_DestroyWindowSurface(window); + } SDL_free(data); SDL_free(renderer); } @@ -1094,21 +1104,19 @@ SDL_Renderer *SW_CreateRendererForSurface(SDL_Surface *surface) SDL_Renderer *renderer; SW_RenderData *data; - if (surface == NULL) { + if (!surface) { SDL_InvalidParamError("surface"); return NULL; } renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } data = (SW_RenderData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SW_DestroyRenderer(renderer); - SDL_OutOfMemory(); return NULL; } data->surface = surface; @@ -1130,6 +1138,7 @@ SDL_Renderer *SW_CreateRendererForSurface(SDL_Surface *surface) renderer->QueueCopy = SW_QueueCopy; renderer->QueueCopyEx = SW_QueueCopyEx; renderer->QueueGeometry = SW_QueueGeometry; + renderer->InvalidateCachedState = SW_InvalidateCachedState; renderer->RunCommandQueue = SW_RunCommandQueue; renderer->RenderReadPixels = SW_RenderReadPixels; renderer->RenderPresent = SW_RenderPresent; @@ -1137,15 +1146,14 @@ SDL_Renderer *SW_CreateRendererForSurface(SDL_Surface *surface) renderer->DestroyRenderer = SW_DestroyRenderer; renderer->info = SW_RenderDriver.info; renderer->driverdata = data; + SW_InvalidateCachedState(renderer); SW_SelectBestFormats(renderer, surface->format->format); - SW_ActivateRenderer(renderer); - return renderer; } -static SDL_Renderer *SW_CreateRenderer(SDL_Window *window, Uint32 flags) +static SDL_Renderer *SW_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { const char *hint; SDL_Surface *surface; @@ -1153,14 +1161,18 @@ static SDL_Renderer *SW_CreateRenderer(SDL_Window *window, Uint32 flags) /* Set the vsync hint based on our flags, if it's not already set */ hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC); - if (hint == NULL || !*hint) { + if (!hint || !*hint) { no_hint_set = SDL_TRUE; } else { no_hint_set = SDL_FALSE; } if (no_hint_set) { - SDL_SetHint(SDL_HINT_RENDER_VSYNC, (flags & SDL_RENDERER_PRESENTVSYNC) ? "1" : "0"); + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { + SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); + } else { + SDL_SetHint(SDL_HINT_RENDER_VSYNC, "0"); + } } surface = SDL_GetWindowSurface(window); @@ -1170,7 +1182,7 @@ static SDL_Renderer *SW_CreateRenderer(SDL_Window *window, Uint32 flags) SDL_SetHint(SDL_HINT_RENDER_VSYNC, ""); } - if (surface == NULL) { + if (!surface) { return NULL; } return SW_CreateRendererForSurface(surface); diff --git a/src/render/software/SDL_render_sw_c.h b/src/render/software/SDL_render_sw_c.h index 3bdffa65..fad327c0 100644 --- a/src/render/software/SDL_render_sw_c.h +++ b/src/render/software/SDL_render_sw_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_rotate.c b/src/render/software/SDL_rotate.c index 9dddd606..7689b68a 100644 --- a/src/render/software/SDL_rotate.c +++ b/src/render/software/SDL_rotate.c @@ -44,7 +44,7 @@ Andreas Schiffler -- aschiffler at ferzkopp dot net /* ---- Internally used structures */ /** -\brief A 32 bit RGBA pixel. +A 32 bit RGBA pixel. */ typedef struct tColorRGBA { @@ -55,7 +55,7 @@ typedef struct tColorRGBA } tColorRGBA; /** -\brief A 8bit Y/palette pixel. +A 8bit Y/palette pixel. */ typedef struct tColorY { @@ -63,7 +63,7 @@ typedef struct tColorY } tColorY; /** -\brief Number of guard rows added to destination surfaces. +Number of guard rows added to destination surfaces. This is a simple but effective workaround for observed issues. These rows allocate extra memory and are then hidden from the surface. @@ -75,7 +75,7 @@ to a situation where the program can segfault. #define GUARD_ROWS (2) /** -\brief Returns colorkey info for a surface +Returns colorkey info for a surface */ static Uint32 get_colorkey(SDL_Surface *src) { @@ -100,7 +100,7 @@ static void rotate(double sx, double sy, double sinangle, double cosangle, const } /** -\brief Internal target surface sizing function for rotations with trig result return. +Internal target surface sizing function for rotations with trig result return. \param width The source surface width. \param height The source surface height. @@ -249,7 +249,7 @@ static void transformSurfaceY90(SDL_Surface *src, SDL_Surface *dst, int angle, i #undef TRANSFORM_SURFACE_90 /** -\brief Internal 32 bit rotozoomer with optional anti-aliasing. +Internal 32 bit rotozoomer with optional anti-aliasing. Rotates and zooms 32 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control parameters by scanning the destination surface and applying optionally anti-aliasing @@ -296,8 +296,8 @@ static void transformSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int isin, i int y; for (y = 0; y < dst->h; y++) { int x; - double src_x = (rect_dest->x + 0 + 0.5 - center->x); - double src_y = (rect_dest->y + y + 0.5 - center->y); + double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x); + double src_y = ((double)rect_dest->y + y + 0.5 - center->y); int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half); int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half); for (x = 0; x < dst->w; x++) { @@ -364,8 +364,8 @@ static void transformSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int isin, i int y; for (y = 0; y < dst->h; y++) { int x; - double src_x = (rect_dest->x + 0 + 0.5 - center->x); - double src_y = (rect_dest->y + y + 0.5 - center->y); + double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x); + double src_y = ((double)rect_dest->y + y + 0.5 - center->y); int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half); int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half); for (x = 0; x < dst->w; x++) { @@ -391,7 +391,7 @@ static void transformSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int isin, i /** -\brief Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing. +Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing. Rotates and zooms 8 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control parameters by scanning the destination surface. @@ -437,8 +437,8 @@ static void transformSurfaceY(SDL_Surface *src, SDL_Surface *dst, int isin, int */ for (y = 0; y < dst->h; y++) { int x; - double src_x = (rect_dest->x + 0 + 0.5 - center->x); - double src_y = (rect_dest->y + y + 0.5 - center->y); + double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x); + double src_y = ((double)rect_dest->y + y + 0.5 - center->y); int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half); int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half); for (x = 0; x < dst->w; x++) { @@ -462,7 +462,7 @@ static void transformSurfaceY(SDL_Surface *src, SDL_Surface *dst, int isin, int } /** -\brief Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-aliasing. +Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-aliasing. Rotates a 32-bit or 8-bit 'src' surface to newly created 'dst' surface. 'angle' is the rotation in degrees, 'center' the rotation center. If 'smooth' is set @@ -498,7 +498,7 @@ SDL_Surface *SDLgfx_rotateSurface(SDL_Surface *src, double angle, int smooth, in double sangleinv, cangleinv; /* Sanity check */ - if (src == NULL) { + if (!src) { return NULL; } @@ -522,7 +522,7 @@ SDL_Surface *SDLgfx_rotateSurface(SDL_Surface *src, double angle, int smooth, in if (is8bit) { /* Target surface is 8 bit */ rz_dst = SDL_CreateSurface(rect_dest->w, rect_dest->h + GUARD_ROWS, src->format->format); - if (rz_dst != NULL) { + if (rz_dst) { if (src->format->palette) { for (i = 0; i < src->format->palette->ncolors; i++) { rz_dst->format->palette->colors[i] = src->format->palette->colors[i]; @@ -536,7 +536,7 @@ SDL_Surface *SDLgfx_rotateSurface(SDL_Surface *src, double angle, int smooth, in } /* Check target */ - if (rz_dst == NULL) { + if (!rz_dst) { return NULL; } diff --git a/src/render/software/SDL_rotate.h b/src/render/software/SDL_rotate.h index 43e90e8a..9e08e53a 100644 --- a/src/render/software/SDL_rotate.h +++ b/src/render/software/SDL_rotate.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/software/SDL_triangle.c b/src/render/software/SDL_triangle.c index 3b6e37d0..5b14d37b 100644 --- a/src/render/software/SDL_triangle.c +++ b/src/render/software/SDL_triangle.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -86,9 +86,9 @@ int SDL_FillTriangle(SDL_Surface *dst, const SDL_Point points[3], Uint32 color) #endif /* cross product AB x AC */ -static int cross_product(const SDL_Point *a, const SDL_Point *b, int c_x, int c_y) +static Sint64 cross_product(const SDL_Point *a, const SDL_Point *b, int c_x, int c_y) { - return (b->x - a->x) * (c_y - a->y) - (b->y - a->y) * (c_x - a->x); + return ((Sint64)(b->x - a->x)) * ((Sint64)(c_y - a->y)) - ((Sint64)(b->y - a->y)) * ((Sint64)(c_x - a->x)); } /* check for top left rules */ @@ -112,10 +112,23 @@ static int is_top_left(const SDL_Point *a, const SDL_Point *b, int is_clockwise) return 0; } +/* x = (y << FP_BITS) */ +/* prevent runtime error: left shift of negative value */ +#define PRECOMP(x, y) \ + val = y; \ + if (val >= 0) { \ + x = val << FP_BITS; \ + } else { \ + val *= -1; \ + x = val << FP_BITS; \ + x *= -1; \ + } + void trianglepoint_2_fixedpoint(SDL_Point *a) { - a->x <<= FP_BITS; - a->y <<= FP_BITS; + int val; + PRECOMP(a->x, a->x); + PRECOMP(a->y, a->y); } /* bounding rect of three points (in fixed point) */ @@ -157,9 +170,9 @@ static void bounding_rect(const SDL_Point *a, const SDL_Point *b, const SDL_Poin int x, y; \ for (y = 0; y < dstrect.h; y++) { \ /* y start */ \ - int w0 = w0_row; \ - int w1 = w1_row; \ - int w2 = w2_row; \ + Sint64 w0 = w0_row; \ + Sint64 w1 = w1_row; \ + Sint64 w2 = w2_row; \ for (x = 0; x < dstrect.w; x++) { \ /* In triangle */ \ if (w0 + bias_w0 >= 0 && w1 + bias_w1 >= 0 && w2 + bias_w2 >= 0) { \ @@ -212,14 +225,14 @@ int SDL_SW_FillTriangle(SDL_Surface *dst, SDL_Point *d0, SDL_Point *d1, SDL_Poin int area, is_clockwise; int d2d1_y, d1d2_x, d0d2_y, d2d0_x, d1d0_y, d0d1_x; - int w0_row, w1_row, w2_row; + Sint64 w0_row, w1_row, w2_row; int bias_w0, bias_w1, bias_w2; int is_uniform; SDL_Surface *tmp = NULL; - if (dst == NULL) { + if (!dst) { return -1; } @@ -271,7 +284,7 @@ int SDL_SW_FillTriangle(SDL_Surface *dst, SDL_Point *d0, SDL_Point *d1, SDL_Poin /* Use an intermediate surface */ tmp = SDL_CreateSurface(dstrect.w, dstrect.h, format); - if (tmp == NULL) { + if (!tmp) { ret = -1; goto end; } @@ -297,12 +310,15 @@ int SDL_SW_FillTriangle(SDL_Surface *dst, SDL_Point *d0, SDL_Point *d1, SDL_Poin is_clockwise = area > 0; area = SDL_abs(area); - d2d1_y = (d1->y - d2->y) << FP_BITS; - d0d2_y = (d2->y - d0->y) << FP_BITS; - d1d0_y = (d0->y - d1->y) << FP_BITS; - d1d2_x = (d2->x - d1->x) << FP_BITS; - d2d0_x = (d0->x - d2->x) << FP_BITS; - d0d1_x = (d1->x - d0->x) << FP_BITS; + { + int val; + PRECOMP(d2d1_y, d1->y - d2->y) + PRECOMP(d0d2_y, d2->y - d0->y) + PRECOMP(d1d0_y, d0->y - d1->y) + PRECOMP(d1d2_x, d2->x - d1->x) + PRECOMP(d2d0_x, d0->x - d2->x) + PRECOMP(d0d1_x, d1->x - d0->x) + } /* Starting point for rendering, at the middle of a pixel */ { @@ -338,12 +354,7 @@ int SDL_SW_FillTriangle(SDL_Surface *dst, SDL_Point *d0, SDL_Point *d1, SDL_Poin if (is_uniform) { Uint32 color; if (tmp) { - if (dst->format->Amask) { - color = SDL_MapRGBA(tmp->format, c0.r, c0.g, c0.b, c0.a); - } else { - // color = SDL_MapRGB(tmp->format, c0.r, c0.g, c0.b); - color = SDL_MapRGBA(tmp->format, c0.r, c0.g, c0.b, c0.a); - } + color = SDL_MapRGBA(tmp->format, c0.r, c0.g, c0.b, c0.a); } else { color = SDL_MapRGBA(dst->format, c0.r, c0.g, c0.b, c0.a); } @@ -457,14 +468,14 @@ int SDL_SW_BlitTriangle( int d2d1_y, d1d2_x, d0d2_y, d2d0_x, d1d0_y, d0d1_x; int s2s0_x, s2s1_x, s2s0_y, s2s1_y; - int w0_row, w1_row, w2_row; + Sint64 w0_row, w1_row, w2_row; int bias_w0, bias_w1, bias_w2; int is_uniform; int has_modulation; - if (src == NULL || dst == NULL) { + if (!src || !dst) { return -1; } @@ -569,13 +580,15 @@ int SDL_SW_BlitTriangle( is_clockwise = area > 0; area = SDL_abs(area); - d2d1_y = (d1->y - d2->y) << FP_BITS; - d0d2_y = (d2->y - d0->y) << FP_BITS; - d1d0_y = (d0->y - d1->y) << FP_BITS; - - d1d2_x = (d2->x - d1->x) << FP_BITS; - d2d0_x = (d0->x - d2->x) << FP_BITS; - d0d1_x = (d1->x - d0->x) << FP_BITS; + { + int val; + PRECOMP(d2d1_y, d1->y - d2->y) + PRECOMP(d0d2_y, d2->y - d0->y) + PRECOMP(d1d0_y, d0->y - d1->y) + PRECOMP(d1d2_x, d2->x - d1->x) + PRECOMP(d2d0_x, d0->x - d2->x) + PRECOMP(d0d1_x, d1->x - d0->x) + } s2s0_x = s0->x - s2->x; s2s1_x = s1->x - s2->x; @@ -788,15 +801,20 @@ static void SDL_BlitTriangle_Slow(SDL_BlitInfo *info, continue; } } - if (FORMAT_HAS_ALPHA(dstfmt_val)) { - DISEMBLE_RGBA(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB, dstA); - } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) { - DISEMBLE_RGB(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB); - dstA = 0xFF; + if ((flags & (SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL))) { + if (FORMAT_HAS_ALPHA(dstfmt_val)) { + DISEMBLE_RGBA(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB, dstA); + } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) { + DISEMBLE_RGB(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB); + dstA = 0xFF; + } else { + /* SDL_PIXELFORMAT_ARGB2101010 */ + dstpixel = *((Uint32 *) (dst)); + RGBA_FROM_ARGB2101010(dstpixel, dstR, dstG, dstB, dstA); + } } else { - /* SDL_PIXELFORMAT_ARGB2101010 */ - dstpixel = *((Uint32 *)(dst)); - RGBA_FROM_ARGB2101010(dstpixel, dstR, dstG, dstB, dstA); + /* don't care */ + dstR = dstG = dstB = dstA = 0; } if (!is_uniform) { diff --git a/src/render/software/SDL_triangle.h b/src/render/software/SDL_triangle.h index 9f983439..f5af0aad 100644 --- a/src/render/software/SDL_triangle.h +++ b/src/render/software/SDL_triangle.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/vitagxm/SDL_render_vita_gxm.c b/src/render/vitagxm/SDL_render_vita_gxm.c index de0fa7e1..5b902b27 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm.c +++ b/src/render/vitagxm/SDL_render_vita_gxm.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,13 +42,13 @@ #include #endif -static SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags); +static SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props); static void VITA_GXM_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event); static SDL_bool VITA_GXM_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode); -static int VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture); +static int VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props); static int VITA_GXM_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch); @@ -89,6 +89,8 @@ static int VITA_GXM_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd static int VITA_GXM_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd); +static void VITA_GXM_InvalidateCachedState(SDL_Renderer *renderer); + static int VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize); static int VITA_GXM_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, @@ -167,7 +169,7 @@ void StartDrawing(SDL_Renderer *renderer) // data->colorFragmentProgram = in->color; // data->textureFragmentProgram = in->texture; - if (renderer->target == NULL) { + if (!renderer->target) { sceGxmBeginScene( data->gxm_context, 0, @@ -209,21 +211,19 @@ static int VITA_GXM_SetVSync(SDL_Renderer *renderer, const int vsync) return 0; } -SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags) +SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_props) { SDL_Renderer *renderer; VITA_GXM_RenderData *data; renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer)); - if (renderer == NULL) { - SDL_OutOfMemory(); + if (!renderer) { return NULL; } data = (VITA_GXM_RenderData *)SDL_calloc(1, sizeof(VITA_GXM_RenderData)); - if (data == NULL) { + if (!data) { SDL_free(renderer); - SDL_OutOfMemory(); return NULL; } @@ -244,6 +244,7 @@ SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueDrawPoints = VITA_GXM_QueueDrawPoints; renderer->QueueDrawLines = VITA_GXM_QueueDrawLines; renderer->QueueGeometry = VITA_GXM_QueueGeometry; + renderer->InvalidateCachedState = VITA_GXM_InvalidateCachedState; renderer->RunCommandQueue = VITA_GXM_RunCommandQueue; renderer->RenderReadPixels = VITA_GXM_RenderReadPixels; renderer->RenderPresent = VITA_GXM_RenderPresent; @@ -254,12 +255,14 @@ SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->info = VITA_GXM_RenderDriver.info; renderer->info.flags = SDL_RENDERER_ACCELERATED; renderer->driverdata = data; + VITA_GXM_InvalidateCachedState(renderer); renderer->window = window; data->initialized = SDL_TRUE; - if (flags & SDL_RENDERER_PRESENTVSYNC) { + if (SDL_GetBooleanProperty(create_props, SDL_PROPERTY_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN, SDL_FALSE)) { data->displayData.wait_vblank = SDL_TRUE; + renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; } else { data->displayData.wait_vblank = SDL_FALSE; } @@ -288,13 +291,13 @@ static SDL_bool VITA_GXM_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode return SDL_FALSE; } -static int VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) +static int VITA_GXM_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->driverdata; VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)SDL_calloc(1, sizeof(VITA_GXM_TextureData)); - if (vita_texture == NULL) { - return SDL_OutOfMemory(); + if (!vita_texture) { + return -1; } vita_texture->tex = create_gxm_texture( @@ -581,7 +584,7 @@ static int VITA_GXM_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, *pitch = vita_texture->pitch; // make sure that rendering is finished on render target textures - if (vita_texture->tex->gxm_rendertarget != NULL) { + if (vita_texture->tex->gxm_rendertarget) { sceGxmFinish(data->gxm_context); } @@ -731,7 +734,7 @@ static int VITA_GXM_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd data, count * sizeof(texture_vertex)); - if (vertices == NULL) { + if (!vertices) { return -1; } @@ -770,7 +773,7 @@ static int VITA_GXM_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd data, count * sizeof(color_vertex)); - if (vertices == NULL) { + if (!vertices) { return -1; } @@ -928,6 +931,11 @@ static int SetDrawState(VITA_GXM_RenderData *data, const SDL_RenderCommand *cmd) return 0; } +static void VITA_GXM_InvalidateCachedState(SDL_Renderer *renderer) +{ + /* currently this doesn't do anything. If this needs to do something (and someone is mixing their own rendering calls in!), update this. */ +} + static int VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) { VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->driverdata; @@ -1004,7 +1012,7 @@ static int VITA_GXM_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *c SDL_RenderCommand *nextcmd = cmd->next; size_t count = cmd->data.draw.count; int ret; - while (nextcmd != NULL) { + while (nextcmd) { const SDL_RenderCommandType nextcmdtype = nextcmd->command; if (nextcmdtype != thiscmdtype) { break; /* can't go any further on this draw call, different render command up next. */ @@ -1101,8 +1109,8 @@ static int VITA_GXM_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rec } temp_pixels = SDL_malloc(buflen); - if (temp_pixels == NULL) { - return SDL_OutOfMemory(); + if (!temp_pixels) { + return -1; } SDL_GetCurrentRenderOutputSize(renderer, &w, &h); @@ -1184,15 +1192,15 @@ static void VITA_GXM_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->driverdata; VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->driverdata; - if (data == NULL) { + if (!data) { return; } - if (vita_texture == NULL) { + if (!vita_texture) { return; } - if (vita_texture->tex == NULL) { + if (!vita_texture->tex) { return; } diff --git a/src/render/vitagxm/SDL_render_vita_gxm_memory.c b/src/render/vitagxm/SDL_render_vita_gxm_memory.c index 0c29c76c..4eef2bb4 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_memory.c +++ b/src/render/vitagxm/SDL_render_vita_gxm_memory.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -66,7 +66,7 @@ void *vita_gpu_mem_alloc(VITA_GXM_RenderData *data, unsigned int size) { void *mem; - if (data->texturePool == NULL) { + if (!data->texturePool) { int poolsize; int ret; SceKernelFreeMemorySizeInfo info; @@ -88,7 +88,7 @@ void *vita_gpu_mem_alloc(VITA_GXM_RenderData *data, unsigned int size) } data->texturePool = sceClibMspaceCreate(mem, poolsize); - if (data->texturePool == NULL) { + if (!data->texturePool) { return NULL; } ret = sceGxmMapMemory(mem, poolsize, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE); @@ -101,7 +101,7 @@ void *vita_gpu_mem_alloc(VITA_GXM_RenderData *data, unsigned int size) void vita_gpu_mem_free(VITA_GXM_RenderData *data, void *ptr) { - if (data->texturePool != NULL) { + if (data->texturePool) { sceClibMspaceFree(data->texturePool, ptr); } } @@ -109,7 +109,7 @@ void vita_gpu_mem_free(VITA_GXM_RenderData *data, void *ptr) void vita_gpu_mem_destroy(VITA_GXM_RenderData *data) { void *mem = NULL; - if (data->texturePool != NULL) { + if (data->texturePool) { sceClibMspaceDestroy(data->texturePool); data->texturePool = NULL; if (sceKernelGetMemBlockBase(data->texturePoolUID, &mem) < 0) { diff --git a/src/render/vitagxm/SDL_render_vita_gxm_memory.h b/src/render/vitagxm/SDL_render_vita_gxm_memory.h index 6be61cf1..9cd9f889 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_memory.h +++ b/src/render/vitagxm/SDL_render_vita_gxm_memory.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/vitagxm/SDL_render_vita_gxm_shaders.h b/src/render/vitagxm/SDL_render_vita_gxm_shaders.h index 98d86054..ed3e3caa 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_shaders.h +++ b/src/render/vitagxm/SDL_render_vita_gxm_shaders.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/vitagxm/SDL_render_vita_gxm_tools.c b/src/render/vitagxm/SDL_render_vita_gxm_tools.c index 7a8b59f4..82fc7516 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_tools.c +++ b/src/render/vitagxm/SDL_render_vita_gxm_tools.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -1001,7 +1001,7 @@ gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsig tex_size += (((aligned_w + 1) / 2) * ((h + 1) / 2)) * 2; } - if (texture == NULL) { + if (!texture) { return NULL; } @@ -1015,7 +1015,7 @@ gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsig tex_size); /* Try SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE in case we're out of VRAM */ - if (texture_data == NULL) { + if (!texture_data) { SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "CDRAM texture allocation failed\n"); texture_data = vita_mem_alloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, @@ -1028,7 +1028,7 @@ gxm_texture *create_gxm_texture(VITA_GXM_RenderData *data, unsigned int w, unsig texture->cdram = 1; } - if (texture_data == NULL) { + if (!texture_data) { SDL_free(texture); return NULL; } diff --git a/src/render/vitagxm/SDL_render_vita_gxm_tools.h b/src/render/vitagxm/SDL_render_vita_gxm_tools.h index 1f4d5034..067edc2d 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_tools.h +++ b/src/render/vitagxm/SDL_render_vita_gxm_tools.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/render/vitagxm/SDL_render_vita_gxm_types.h b/src/render/vitagxm/SDL_render_vita_gxm_types.h index 2bdec06f..0ae20924 100644 --- a/src/render/vitagxm/SDL_render_vita_gxm_types.h +++ b/src/render/vitagxm/SDL_render_vita_gxm_types.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/SDL_sensor.c b/src/sensor/SDL_sensor.c index 26eed898..c2806f98 100644 --- a/src/sensor/SDL_sensor.c +++ b/src/sensor/SDL_sensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -58,7 +58,6 @@ static SDL_AtomicInt SDL_sensor_lock_pending; static int SDL_sensors_locked; static SDL_bool SDL_sensors_initialized; static SDL_Sensor *SDL_sensors SDL_GUARDED_BY(SDL_sensor_lock) = NULL; -static SDL_AtomicInt SDL_last_sensor_instance_id SDL_GUARDED_BY(SDL_sensor_lock); static char SDL_sensor_magic; #define CHECK_SENSOR_MAGIC(sensor, retval) \ @@ -116,7 +115,7 @@ void SDL_UnlockSensors(void) SDL_bool SDL_SensorsLocked(void) { - return (SDL_sensors_locked > 0) ? SDL_TRUE : SDL_FALSE; + return (SDL_sensors_locked > 0); } void SDL_AssertSensorsLocked(void) @@ -209,8 +208,6 @@ SDL_SensorID *SDL_GetSensors(int *count) if (count) { *count = 0; } - - SDL_OutOfMemory(); } } SDL_UnlockSensors(); @@ -218,15 +215,6 @@ SDL_SensorID *SDL_GetSensors(int *count) return sensors; } -/* - * Return the next available sensor instance ID - * This may be called by drivers from multiple threads, unprotected by any locks - */ -SDL_SensorID SDL_GetNextSensorInstanceID(void) -{ - return SDL_AtomicIncRef(&SDL_last_sensor_instance_id) + 1; -} - /* * Get the driver and device index for a sensor instance ID * This should be called while the sensor lock is held, to prevent another thread from updating the list @@ -248,7 +236,7 @@ static SDL_bool SDL_GetDriverAndSensorIndex(SDL_SensorID instance_id, SDL_Sensor } } } - SDL_SetError("Sensor %" SDL_PRIs32 " not found", instance_id); + SDL_SetError("Sensor %" SDL_PRIu32 " not found", instance_id); return SDL_FALSE; } @@ -339,8 +327,7 @@ SDL_Sensor *SDL_OpenSensor(SDL_SensorID instance_id) /* Create and initialize the sensor */ sensor = (SDL_Sensor *)SDL_calloc(sizeof(*sensor), 1); - if (sensor == NULL) { - SDL_OutOfMemory(); + if (!sensor) { SDL_UnlockSensors(); return NULL; } @@ -393,6 +380,27 @@ SDL_Sensor *SDL_GetSensorFromInstanceID(SDL_SensorID instance_id) return sensor; } +/* + * Get the properties associated with a sensor. + */ +SDL_PropertiesID SDL_GetSensorProperties(SDL_Sensor *sensor) +{ + SDL_PropertiesID retval; + + SDL_LockSensors(); + { + CHECK_SENSOR_MAGIC(sensor, 0); + + if (sensor->props == 0) { + sensor->props = SDL_CreateProperties(); + } + retval = sensor->props; + } + SDL_UnlockSensors(); + + return retval; +} + /* * Get the friendly name of this sensor */ @@ -500,6 +508,8 @@ void SDL_CloseSensor(SDL_Sensor *sensor) return; } + SDL_DestroyProperties(sensor->props); + sensor->driver->Close(sensor); sensor->hwdata = NULL; diff --git a/src/sensor/SDL_sensor_c.h b/src/sensor/SDL_sensor_c.h index ece9ec50..fce48968 100644 --- a/src/sensor/SDL_sensor_c.h +++ b/src/sensor/SDL_sensor_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,9 +31,6 @@ struct SDL_SensorDriver; /* Useful functions and variables from SDL_sensor.c */ -/* Function to get the next available sensor instance ID */ -extern SDL_SensorID SDL_GetNextSensorInstanceID(void); - /* Initialization and shutdown functions */ extern int SDL_InitSensors(void); extern void SDL_QuitSensors(void); diff --git a/src/sensor/SDL_syssensor.h b/src/sensor/SDL_syssensor.h index 9376be6b..65fb1ad3 100644 --- a/src/sensor/SDL_syssensor.h +++ b/src/sensor/SDL_syssensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -45,6 +45,8 @@ struct SDL_Sensor struct sensor_hwdata *hwdata _guarded; /* Driver dependent information */ + SDL_PropertiesID props _guarded; + int ref_count _guarded; /* Reference count for multiple opens */ struct SDL_Sensor *next _guarded; /* pointer to next sensor we have allocated */ diff --git a/src/sensor/android/SDL_androidsensor.c b/src/sensor/android/SDL_androidsensor.c index 856f64b3..eb46dd5e 100644 --- a/src/sensor/android/SDL_androidsensor.c +++ b/src/sensor/android/SDL_androidsensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -138,7 +138,7 @@ static int SDL_ANDROID_SensorInit(void) ASensorList sensors; SDL_sensor_manager = ASensorManager_getInstance(); - if (SDL_sensor_manager == NULL) { + if (!SDL_sensor_manager) { return SDL_SetError("Couldn't create sensor manager"); } @@ -146,13 +146,13 @@ static int SDL_ANDROID_SensorInit(void) sensors_count = ASensorManager_getSensorList(SDL_sensor_manager, &sensors); if (sensors_count > 0) { SDL_sensors = (SDL_AndroidSensor *)SDL_calloc(sensors_count, sizeof(*SDL_sensors)); - if (SDL_sensors == NULL) { - return SDL_OutOfMemory(); + if (!SDL_sensors) { + return -1; } for (i = 0; i < sensors_count; ++i) { SDL_sensors[i].asensor = sensors[i]; - SDL_sensors[i].instance_id = SDL_GetNextSensorInstanceID(); + SDL_sensors[i].instance_id = SDL_GetNextObjectID(); } SDL_sensors_count = sensors_count; } diff --git a/src/sensor/android/SDL_androidsensor.h b/src/sensor/android/SDL_androidsensor.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/sensor/android/SDL_androidsensor.h +++ b/src/sensor/android/SDL_androidsensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/coremotion/SDL_coremotionsensor.h b/src/sensor/coremotion/SDL_coremotionsensor.h index cbbec898..2e65230e 100644 --- a/src/sensor/coremotion/SDL_coremotionsensor.h +++ b/src/sensor/coremotion/SDL_coremotionsensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/coremotion/SDL_coremotionsensor.m b/src/sensor/coremotion/SDL_coremotionsensor.m index 6afe47c8..3f9c3646 100644 --- a/src/sensor/coremotion/SDL_coremotionsensor.m +++ b/src/sensor/coremotion/SDL_coremotionsensor.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,18 +57,18 @@ static int SDL_COREMOTION_SensorInit(void) if (sensors_count > 0) { SDL_sensors = (SDL_CoreMotionSensor *)SDL_calloc(sensors_count, sizeof(*SDL_sensors)); if (!SDL_sensors) { - return SDL_OutOfMemory(); + return -1; } i = 0; if (SDL_motion_manager.accelerometerAvailable) { SDL_sensors[i].type = SDL_SENSOR_ACCEL; - SDL_sensors[i].instance_id = SDL_GetNextSensorInstanceID(); + SDL_sensors[i].instance_id = SDL_GetNextObjectID(); ++i; } if (SDL_motion_manager.gyroAvailable) { SDL_sensors[i].type = SDL_SENSOR_GYRO; - SDL_sensors[i].instance_id = SDL_GetNextSensorInstanceID(); + SDL_sensors[i].instance_id = SDL_GetNextObjectID(); ++i; } SDL_sensors_count = sensors_count; @@ -118,7 +118,7 @@ static int SDL_COREMOTION_SensorOpen(SDL_Sensor *sensor, int device_index) hwdata = (struct sensor_hwdata *)SDL_calloc(1, sizeof(*hwdata)); if (hwdata == NULL) { - return SDL_OutOfMemory(); + return -1; } sensor->hwdata = hwdata; diff --git a/src/sensor/dummy/SDL_dummysensor.c b/src/sensor/dummy/SDL_dummysensor.c index 6e8f933b..c9041d81 100644 --- a/src/sensor/dummy/SDL_dummysensor.c +++ b/src/sensor/dummy/SDL_dummysensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/dummy/SDL_dummysensor.h b/src/sensor/dummy/SDL_dummysensor.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/sensor/dummy/SDL_dummysensor.h +++ b/src/sensor/dummy/SDL_dummysensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/n3ds/SDL_n3dssensor.c b/src/sensor/n3ds/SDL_n3dssensor.c index 21ccb441..5b44d656 100644 --- a/src/sensor/n3ds/SDL_n3dssensor.c +++ b/src/sensor/n3ds/SDL_n3dssensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -54,9 +54,9 @@ static int N3DS_SensorInit(void) } N3DS_sensors[0].type = SDL_SENSOR_ACCEL; - N3DS_sensors[0].instance_id = SDL_GetNextSensorInstanceID(); + N3DS_sensors[0].instance_id = SDL_GetNextObjectID(); N3DS_sensors[1].type = SDL_SENSOR_GYRO; - N3DS_sensors[1].instance_id = SDL_GetNextSensorInstanceID(); + N3DS_sensors[1].instance_id = SDL_GetNextObjectID(); return 0; } diff --git a/src/sensor/vita/SDL_vitasensor.c b/src/sensor/vita/SDL_vitasensor.c index 1f203103..7c0cd8b6 100644 --- a/src/sensor/vita/SDL_vitasensor.c +++ b/src/sensor/vita/SDL_vitasensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,14 +51,14 @@ static int SDL_VITA_SensorInit(void) SDL_sensors_count = 2; SDL_sensors = (SDL_VitaSensor *)SDL_calloc(SDL_sensors_count, sizeof(*SDL_sensors)); - if (SDL_sensors == NULL) { - return SDL_OutOfMemory(); + if (!SDL_sensors) { + return -1; } SDL_sensors[0].type = SDL_SENSOR_ACCEL; - SDL_sensors[0].instance_id = SDL_GetNextSensorInstanceID(); + SDL_sensors[0].instance_id = SDL_GetNextObjectID(); SDL_sensors[1].type = SDL_SENSOR_GYRO; - SDL_sensors[1].instance_id = SDL_GetNextSensorInstanceID(); + SDL_sensors[1].instance_id = SDL_GetNextObjectID(); return 0; } @@ -118,8 +118,8 @@ static int SDL_VITA_SensorOpen(SDL_Sensor *sensor, int device_index) struct sensor_hwdata *hwdata; hwdata = (struct sensor_hwdata *)SDL_calloc(1, sizeof(*hwdata)); - if (hwdata == NULL) { - return SDL_OutOfMemory(); + if (!hwdata) { + return -1; } sensor->hwdata = hwdata; diff --git a/src/sensor/vita/SDL_vitasensor.h b/src/sensor/vita/SDL_vitasensor.h index 0bd3f3ba..92518df7 100644 --- a/src/sensor/vita/SDL_vitasensor.h +++ b/src/sensor/vita/SDL_vitasensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/sensor/windows/SDL_windowssensor.c b/src/sensor/windows/SDL_windowssensor.c index 3d209dc3..d7c8813f 100644 --- a/src/sensor/windows/SDL_windowssensor.c +++ b/src/sensor/windows/SDL_windowssensor.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -62,7 +62,7 @@ static int DisconnectSensor(ISensor *sensor); static HRESULT STDMETHODCALLTYPE ISensorManagerEventsVtbl_QueryInterface(ISensorManagerEvents *This, REFIID riid, void **ppvObject) { - if (ppvObject == NULL) { + if (!ppvObject) { return E_INVALIDARG; } @@ -102,7 +102,7 @@ static ISensorManagerEvents sensor_manager_events = { static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_QueryInterface(ISensorEvents *This, REFIID riid, void **ppvObject) { - if (ppvObject == NULL) { + if (!ppvObject) { return E_INVALIDARG; } @@ -150,7 +150,7 @@ static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnDataUpdated(ISensorEvents * if (pSensor == SDL_sensors[i].sensor) { if (SDL_sensors[i].sensor_opened) { HRESULT hrX, hrY, hrZ; - PROPVARIANT valueX, valueY, valueZ; + PROPVARIANT valueX = { 0 }, valueY = { 0 }, valueZ = { 0 }; SYSTEMTIME sensor_systemtime; FILETIME sensor_filetime; Uint64 sensor_timestamp; @@ -295,16 +295,16 @@ static int ConnectSensor(ISensor *sensor) if (bstr_name != NULL) { SysFreeString(bstr_name); } - if (name == NULL) { - return SDL_OutOfMemory(); + if (!name) { + return -1; } SDL_LockSensors(); new_sensors = (SDL_Windows_Sensor *)SDL_realloc(SDL_sensors, (SDL_num_sensors + 1) * sizeof(SDL_Windows_Sensor)); - if (new_sensors == NULL) { + if (!new_sensors) { SDL_UnlockSensors(); SDL_free(name); - return SDL_OutOfMemory(); + return -1; } ISensor_AddRef(sensor); @@ -315,7 +315,7 @@ static int ConnectSensor(ISensor *sensor) ++SDL_num_sensors; SDL_zerop(new_sensor); - new_sensor->id = SDL_GetNextSensorInstanceID(); + new_sensor->id = SDL_GetNextObjectID(); new_sensor->sensor = sensor; new_sensor->type = type; new_sensor->name = name; diff --git a/src/sensor/windows/SDL_windowssensor.h b/src/sensor/windows/SDL_windowssensor.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/sensor/windows/SDL_windowssensor.h +++ b/src/sensor/windows/SDL_windowssensor.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_crc16.c b/src/stdlib/SDL_crc16.c index 9847f983..58830d69 100644 --- a/src/stdlib/SDL_crc16.c +++ b/src/stdlib/SDL_crc16.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_crc32.c b/src/stdlib/SDL_crc32.c index 2eb90174..f61ae543 100644 --- a/src/stdlib/SDL_crc32.c +++ b/src/stdlib/SDL_crc32.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_getenv.c b/src/stdlib/SDL_getenv.c index 7d84cb12..b8bceaba 100644 --- a/src/stdlib/SDL_getenv.c +++ b/src/stdlib/SDL_getenv.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,7 +40,7 @@ static size_t SDL_envmemlen = 0; int SDL_setenv(const char *name, const char *value, int overwrite) { /* Input validation */ - if (name == NULL || *name == '\0' || SDL_strchr(name, '=') != NULL || value == NULL) { + if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { return -1; } @@ -50,7 +50,7 @@ int SDL_setenv(const char *name, const char *value, int overwrite) int SDL_setenv(const char *name, const char *value, int overwrite) { /* Input validation */ - if (name == NULL || *name == '\0' || SDL_strchr(name, '=') != NULL || value == NULL) { + if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { return -1; } @@ -72,7 +72,7 @@ int SDL_setenv(const char *name, const char *value, int overwrite) char *new_variable; /* Input validation */ - if (name == NULL || *name == '\0' || SDL_strchr(name, '=') != NULL || value == NULL) { + if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { return -1; } @@ -87,7 +87,7 @@ int SDL_setenv(const char *name, const char *value, int overwrite) /* This leaks. Sorry. Get a better OS so we don't have to do this. */ len = SDL_strlen(name) + SDL_strlen(value) + 2; new_variable = (char *)SDL_malloc(len); - if (new_variable == NULL) { + if (!new_variable) { return -1; } @@ -104,7 +104,7 @@ int SDL_setenv(const char *name, const char *value, int overwrite) char *new_variable; /* Input validation */ - if (name == NULL || *name == '\0' || SDL_strchr(name, '=') != NULL || value == NULL) { + if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { return -1; } @@ -116,7 +116,7 @@ int SDL_setenv(const char *name, const char *value, int overwrite) /* Allocate memory for the variable */ len = SDL_strlen(name) + SDL_strlen(value) + 2; new_variable = (char *)SDL_malloc(len); - if (new_variable == NULL) { + if (!new_variable) { return -1; } @@ -169,7 +169,7 @@ char *SDL_getenv(const char *name) #endif /* Input validation */ - if (name == NULL || *name == '\0') { + if (!name || *name == '\0') { return NULL; } @@ -181,7 +181,7 @@ char *SDL_getenv(const char *name) size_t bufferlen; /* Input validation */ - if (name == NULL || *name == '\0') { + if (!name || *name == '\0') { return NULL; } @@ -192,7 +192,7 @@ char *SDL_getenv(const char *name) } if (bufferlen > SDL_envmemlen) { char *newmem = (char *)SDL_realloc(SDL_envmem, bufferlen); - if (newmem == NULL) { + if (!newmem) { return NULL; } SDL_envmem = newmem; @@ -208,14 +208,14 @@ char *SDL_getenv(const char *name) char *value; /* Input validation */ - if (name == NULL || *name == '\0') { + if (!name || *name == '\0') { return NULL; } value = (char *)0; if (SDL_env) { len = SDL_strlen(name); - for (i = 0; SDL_env[i] && value == NULL; ++i) { + for (i = 0; SDL_env[i] && !value; ++i) { if ((SDL_strncmp(SDL_env[i], name, len) == 0) && (SDL_env[i][len] == '=')) { value = &SDL_env[i][len + 1]; diff --git a/src/stdlib/SDL_iconv.c b/src/stdlib/SDL_iconv.c index e5a51f65..b4c444bd 100644 --- a/src/stdlib/SDL_iconv.c +++ b/src/stdlib/SDL_iconv.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,7 +23,7 @@ /* This file contains portable iconv functions for SDL */ #if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) -#ifdef __FreeBSD__ +#ifndef SDL_USE_LIBICONV /* Define LIBICONV_PLUG to use iconv from the base instead of ports and avoid linker errors. */ #define LIBICONV_PLUG 1 #endif @@ -158,28 +158,28 @@ static const char *getlocale(char *buffer, size_t bufsize) char *ptr; lang = SDL_getenv("LC_ALL"); - if (lang == NULL) { + if (!lang) { lang = SDL_getenv("LC_CTYPE"); } - if (lang == NULL) { + if (!lang) { lang = SDL_getenv("LC_MESSAGES"); } - if (lang == NULL) { + if (!lang) { lang = SDL_getenv("LANG"); } - if (lang == NULL || !*lang || SDL_strcmp(lang, "C") == 0) { + if (!lang || !*lang || SDL_strcmp(lang, "C") == 0) { lang = "ASCII"; } /* We need to trim down strings like "en_US.UTF-8@blah" to "UTF-8" */ ptr = SDL_strchr(lang, '.'); - if (ptr != NULL) { + if (ptr) { lang = ptr + 1; } SDL_strlcpy(buffer, lang, bufsize); ptr = SDL_strchr(buffer, '@'); - if (ptr != NULL) { + if (ptr) { *ptr = '\0'; /* chop end of string. */ } @@ -194,10 +194,10 @@ SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode) char fromcode_buffer[64]; char tocode_buffer[64]; - if (fromcode == NULL || !*fromcode) { + if (!fromcode || !*fromcode) { fromcode = getlocale(fromcode_buffer, sizeof(fromcode_buffer)); } - if (tocode == NULL || !*tocode) { + if (!tocode || !*tocode) { tocode = getlocale(tocode_buffer, sizeof(tocode_buffer)); } for (i = 0; i < SDL_arraysize(encodings); ++i) { @@ -236,11 +236,11 @@ size_t SDL_iconv(SDL_iconv_t cd, Uint32 ch = 0; size_t total; - if (inbuf == NULL || !*inbuf) { + if (!inbuf || !*inbuf) { /* Reset the context */ return 0; } - if (outbuf == NULL || !*outbuf || outbytesleft == NULL || !*outbytesleft) { + if (!outbuf || !*outbuf || !outbytesleft || !*outbytesleft) { return SDL_ICONV_E2BIG; } src = *inbuf; @@ -786,24 +786,20 @@ char *SDL_iconv_string(const char *tocode, const char *fromcode, const char *inb size_t outbytesleft; size_t retCode = 0; - cd = SDL_iconv_open(tocode, fromcode); - if (cd == (SDL_iconv_t)-1) { - /* See if we can recover here (fixes iconv on Solaris 11) */ - if (tocode == NULL || !*tocode) { - tocode = "UTF-8"; - } - if (fromcode == NULL || !*fromcode) { - fromcode = "UTF-8"; - } - cd = SDL_iconv_open(tocode, fromcode); + if (!tocode || !*tocode) { + tocode = "UTF-8"; } + if (!fromcode || !*fromcode) { + fromcode = "UTF-8"; + } + cd = SDL_iconv_open(tocode, fromcode); if (cd == (SDL_iconv_t)-1) { return NULL; } stringsize = inbytesleft; string = (char *)SDL_malloc(stringsize + sizeof(Uint32)); - if (string == NULL) { + if (!string) { SDL_iconv_close(cd); return NULL; } @@ -820,7 +816,7 @@ char *SDL_iconv_string(const char *tocode, const char *fromcode, const char *inb char *oldstring = string; stringsize *= 2; string = (char *)SDL_realloc(string, stringsize + sizeof(Uint32)); - if (string == NULL) { + if (!string) { SDL_free(oldstring); SDL_iconv_close(cd); return NULL; diff --git a/src/stdlib/SDL_malloc.c b/src/stdlib/SDL_malloc.c index c9ffd8e7..3f6a8240 100644 --- a/src/stdlib/SDL_malloc.c +++ b/src/stdlib/SDL_malloc.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -5287,7 +5287,10 @@ void *SDL_malloc(size_t size) mem = s_mem.malloc_func(size); if (mem) { SDL_AtomicIncRef(&s_mem.num_allocations); + } else { + SDL_OutOfMemory(); } + return mem; } @@ -5303,7 +5306,10 @@ void *SDL_calloc(size_t nmemb, size_t size) mem = s_mem.calloc_func(nmemb, size); if (mem) { SDL_AtomicIncRef(&s_mem.num_allocations); + } else { + SDL_OutOfMemory(); } + return mem; } @@ -5318,7 +5324,10 @@ void *SDL_realloc(void *ptr, size_t size) mem = s_mem.realloc_func(ptr, size); if (mem && !ptr) { SDL_AtomicIncRef(&s_mem.num_allocations); + } else if (!mem) { + SDL_OutOfMemory(); } + return mem; } diff --git a/src/stdlib/SDL_mslibc.c b/src/stdlib/SDL_mslibc.c index 11184275..eac162dc 100644 --- a/src/stdlib/SDL_mslibc.c +++ b/src/stdlib/SDL_mslibc.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -696,8 +696,80 @@ RETZERO: /* *INDENT-ON* */ } +void __declspec(naked) _chkstk(void) +{ + __asm { + push ecx + mov ecx,esp ; lea ecx,dword ptr [esp]+4 + add ecx,4 + sub ecx,eax + sbb eax,eax + not eax + and ecx,eax + mov eax,esp + and eax,0xfffff000 +L1: + cmp ecx,eax + jb short L2 + mov eax,ecx + pop ecx + xchg esp,eax + mov eax,dword ptr [eax] + mov dword ptr [esp],eax + ret +L2: + sub eax,0x1000 + test dword ptr [eax],eax + jmp short L1 + } +} + +void __declspec(naked) _alloca_probe_8(void) +{ + /* *INDENT-OFF* */ + __asm { + push ecx + mov ecx,esp ; lea ecx,dword ptr [esp]+8 + add ecx,8 + sub ecx,eax + and ecx,0x7 + add eax,ecx + sbb ecx,ecx + or eax,ecx + pop ecx + jmp _chkstk + } + /* *INDENT-ON* */ +} + +void __declspec(naked) _alloca_probe_16(void) +{ + /* *INDENT-OFF* */ + __asm { + push ecx + mov ecx,esp ; lea ecx,dword ptr [esp]+8 + add ecx,8 + sub ecx,eax + and ecx,0xf + add eax,ecx + sbb ecx,ecx + or eax,ecx + pop ecx + jmp _chkstk + } + /* *INDENT-ON* */ +} + #endif /* _M_IX86 */ +#ifdef _M_ARM64 + +void __chkstk(void); +void __chkstk() { +} + +#endif + #endif /* MSC_VER */ #ifdef __ICL diff --git a/src/stdlib/SDL_mslibc_x64.masm b/src/stdlib/SDL_mslibc_x64.masm new file mode 100644 index 00000000..1590d88a --- /dev/null +++ b/src/stdlib/SDL_mslibc_x64.masm @@ -0,0 +1,29 @@ +include ksamd64.inc + +text SEGMENT EXECUTE + +public __chkstk + +__chkstk: + sub rsp,010h + mov QWORD PTR [rsp],r10 + mov QWORD PTR [rsp+08h],r11 + xor r11,r11 + lea r10,[rsp+018h] + sub r10,rax + cmovb r10,r11 + mov r11,QWORD PTR gs:[TeStackLimit] + cmp r10,r11 + jae chkstk_finish + and r10w,0f000h +chkstk_loop: + lea r11,[r11-PAGE_SIZE] + mov BYTE PTR [r11],0h + cmp r10,r11 + jne chkstk_loop +chkstk_finish: + mov r10,QWORD PTR [rsp] + mov r11,QWORD PTR [rsp+08h] + add rsp,010h + ret +end diff --git a/src/stdlib/SDL_qsort.c b/src/stdlib/SDL_qsort.c index 9e44f89d..6454b2ca 100644 --- a/src/stdlib/SDL_qsort.c +++ b/src/stdlib/SDL_qsort.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_stdlib.c b/src/stdlib/SDL_stdlib.c index 789c8eb9..97cab4c5 100644 --- a/src/stdlib/SDL_stdlib.c +++ b/src/stdlib/SDL_stdlib.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index ca660013..0a88b4ed 100644 --- a/src/stdlib/SDL_string.c +++ b/src/stdlib/SDL_string.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -468,19 +468,28 @@ wchar_t *SDL_wcsdup(const wchar_t *string) return newstr; } +wchar_t *SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen) +{ + size_t length = SDL_wcslen(needle); + if (length == 0) { + return (wchar_t *)haystack; + } + while (maxlen >= length && *haystack) { + if (maxlen >= length && SDL_wcsncmp(haystack, needle, length) == 0) { + return (wchar_t *)haystack; + } + ++haystack; + --maxlen; + } + return NULL; +} + wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle) { #ifdef HAVE_WCSSTR return SDL_const_cast(wchar_t *, wcsstr(haystack, needle)); #else - size_t length = SDL_wcslen(needle); - while (*haystack) { - if (SDL_wcsncmp(haystack, needle, length) == 0) { - return (wchar_t *)haystack; - } - ++haystack; - } - return NULL; + return SDL_wcsnstr(haystack, needle, SDL_wcslen(haystack)); #endif /* HAVE_WCSSTR */ } @@ -821,19 +830,32 @@ char *SDL_strrchr(const char *string, int c) #endif /* HAVE_STRRCHR */ } +char *SDL_strnstr(const char *haystack, const char *needle, size_t maxlen) +{ +#ifdef HAVE_STRNSTR + return SDL_const_cast(char *, strnstr(haystack, needle, maxlen)); +#else + size_t length = SDL_strlen(needle); + if (length == 0) { + return (char *)haystack; + } + while (maxlen >= length && *haystack) { + if (SDL_strncmp(haystack, needle, length) == 0) { + return (char *)haystack; + } + ++haystack; + --maxlen; + } + return NULL; +#endif /* HAVE_STRSTR */ +} + char *SDL_strstr(const char *haystack, const char *needle) { #ifdef HAVE_STRSTR return SDL_const_cast(char *, strstr(haystack, needle)); #else - size_t length = SDL_strlen(needle); - while (*haystack) { - if (SDL_strncmp(haystack, needle, length) == 0) { - return (char *)haystack; - } - ++haystack; - } - return NULL; + return SDL_strnstr(haystack, needle, SDL_strlen(haystack)); #endif /* HAVE_STRSTR */ } @@ -1199,12 +1221,45 @@ int SDL_vsscanf(const char *text, const char *fmt, va_list ap) return vsscanf(text, fmt, ap); } #else +static SDL_bool CharacterMatchesSet(char c, const char *set, size_t set_len) +{ + SDL_bool invert = SDL_FALSE; + SDL_bool result = SDL_FALSE; + + if (*set == '^') { + invert = SDL_TRUE; + ++set; + --set_len; + } + while (set_len > 0 && !result) { + if (set_len >= 3 && set[1] == '-') { + char low_char = SDL_min(set[0], set[2]); + char high_char = SDL_max(set[0], set[2]); + if (c >= low_char && c <= high_char) { + result = SDL_TRUE; + } + set += 3; + set_len -= 3; + } else { + if (c == *set) { + result = SDL_TRUE; + } + ++set; + --set_len; + } + } + if (invert) { + result = !result; + } + return result; +} + /* NOLINTNEXTLINE(readability-non-const-parameter) */ -int SDL_vsscanf(const char *text, const char *fmt, va_list ap) +int SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_list ap) { int retval = 0; - if (text == NULL || !*text) { + if (!text || !*text) { return -1; } @@ -1472,6 +1527,44 @@ int SDL_vsscanf(const char *text, const char *fmt, va_list ap) } done = SDL_TRUE; break; + case '[': + { + const char *set = fmt + 1; + while (*fmt && *fmt != ']') { + ++fmt; + } + if (*fmt) { + size_t set_len = (fmt - set); + if (suppress) { + while (CharacterMatchesSet(*text, set, set_len)) { + ++text; + if (count) { + if (--count == 0) { + break; + } + } + } + } else { + SDL_bool had_match = SDL_FALSE; + char *valuep = va_arg(ap, char *); + while (CharacterMatchesSet(*text, set, set_len)) { + had_match = SDL_TRUE; + *valuep++ = *text++; + if (count) { + if (--count == 0) { + break; + } + } + } + *valuep = '\0'; + if (had_match) { + ++retval; + } + } + } + } + done = SDL_TRUE; + break; default: done = SDL_TRUE; break; @@ -1570,7 +1663,7 @@ static size_t SDL_PrintString(char *text, size_t maxlen, SDL_FormatInfo *info, c size_t length = 0; size_t slen, sz; - if (string == NULL) { + if (!string) { string = "(null)"; } @@ -1630,7 +1723,7 @@ static void SDL_IntPrecisionAdjust(char *num, size_t maxlen, SDL_FormatInfo *inf { /* left-pad num with zeroes. */ size_t sz, pad, have_sign; - if (info == NULL) { + if (!info) { return; } @@ -1829,7 +1922,7 @@ static size_t SDL_PrintPointer(char *text, size_t maxlen, SDL_FormatInfo *info, } /* NOLINTNEXTLINE(readability-non-const-parameter) */ -int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap) +int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) { size_t length = 0; @@ -2053,7 +2146,7 @@ int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *f #undef TEXT_AND_LEN_ARGS #endif /* HAVE_VSNPRINTF */ -int SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, va_list ap) +int SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, const wchar_t *fmt, va_list ap) { char *text_utf8 = NULL, *fmt_utf8 = NULL; int retval; @@ -2108,7 +2201,7 @@ int SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) return retval; } -int SDL_vasprintf(char **strp, const char *fmt, va_list ap) +int SDL_vasprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) { int retval; int size = 100; /* Guess we need no more than 100 bytes */ @@ -2118,7 +2211,7 @@ int SDL_vasprintf(char **strp, const char *fmt, va_list ap) *strp = NULL; p = (char *)SDL_malloc(size); - if (p == NULL) { + if (!p) { return -1; } @@ -2130,6 +2223,7 @@ int SDL_vasprintf(char **strp, const char *fmt, va_list ap) /* Check error code */ if (retval < 0) { + SDL_free(p); return retval; } @@ -2143,7 +2237,7 @@ int SDL_vasprintf(char **strp, const char *fmt, va_list ap) size = retval + 1; /* Precisely what is needed */ np = (char *)SDL_realloc(p, size); - if (np == NULL) { + if (!np) { SDL_free(p); return -1; } else { diff --git a/src/stdlib/SDL_strtokr.c b/src/stdlib/SDL_strtokr.c index e67c0ed4..53026184 100644 --- a/src/stdlib/SDL_strtokr.c +++ b/src/stdlib/SDL_strtokr.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/stdlib/SDL_vacopy.h b/src/stdlib/SDL_vacopy.h index e1df5c6b..e557b489 100644 --- a/src/stdlib/SDL_vacopy.h +++ b/src/stdlib/SDL_vacopy.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/test/SDL_test_assert.c b/src/test/SDL_test_assert.c index e817dbe9..ad7b63a1 100644 --- a/src/test/SDL_test_assert.c +++ b/src/test/SDL_test_assert.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -48,10 +48,10 @@ #define SDLTEST_ASSERT_SUMMARY_FORMAT "Assert Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_RED "Failed=%d" COLOR_END #define SDLTEST_ASSERT_SUMMARY_FORMAT_OK "Assert Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_GREEN "Failed=%d" COLOR_END -/* ! \brief counts the failed asserts */ +/* ! counts the failed asserts */ static int SDLTest_AssertsFailed = 0; -/* ! \brief counts the passed asserts */ +/* ! counts the passed asserts */ static int SDLTest_AssertsPassed = 0; /* diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 57b65ea6..ffca4030 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -102,8 +102,7 @@ SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, Uint32 flags) } state = (SDLTest_CommonState *)SDL_calloc(1, sizeof(*state)); - if (state == NULL) { - SDL_OutOfMemory(); + if (!state) { return NULL; } @@ -228,7 +227,6 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) } state->renderdriver = argv[index]; SDL_SetHint(SDL_HINT_RENDER_DRIVER, state->renderdriver); - SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1"); return 2; } if (SDL_strcasecmp(argv[index], "--gldebug") == 0) { @@ -783,8 +781,8 @@ static void SDLTest_PrintWindowFlag(char *text, size_t maxlen, Uint32 flag) case SDL_WINDOW_MOUSE_FOCUS: SDL_snprintfcat(text, maxlen, "MOUSE_FOCUS"); break; - case SDL_WINDOW_FOREIGN: - SDL_snprintfcat(text, maxlen, "FOREIGN"); + case SDL_WINDOW_EXTERNAL: + SDL_snprintfcat(text, maxlen, "EXTERNAL"); break; case SDL_WINDOW_HIGH_PIXEL_DENSITY: SDL_snprintfcat(text, maxlen, "HIGH_PIXEL_DENSITY"); @@ -835,7 +833,7 @@ static void SDLTest_PrintWindowFlags(char *text, size_t maxlen, Uint32 flags) SDL_WINDOW_MOUSE_GRABBED, SDL_WINDOW_INPUT_FOCUS, SDL_WINDOW_MOUSE_FOCUS, - SDL_WINDOW_FOREIGN, + SDL_WINDOW_EXTERNAL, SDL_WINDOW_HIGH_PIXEL_DENSITY, SDL_WINDOW_MOUSE_CAPTURE, SDL_WINDOW_ALWAYS_ON_TOP, @@ -862,6 +860,82 @@ static void SDLTest_PrintWindowFlags(char *text, size_t maxlen, Uint32 flags) } } +static void SDLTest_PrintModStateFlag(char *text, size_t maxlen, SDL_Keymod flag) +{ + switch (flag) { + case SDL_KMOD_LSHIFT: + SDL_snprintfcat(text, maxlen, "LSHIFT"); + break; + case SDL_KMOD_RSHIFT: + SDL_snprintfcat(text, maxlen, "RSHIFT"); + break; + case SDL_KMOD_LCTRL: + SDL_snprintfcat(text, maxlen, "LCTRL"); + break; + case SDL_KMOD_RCTRL: + SDL_snprintfcat(text, maxlen, "RCTRL"); + break; + case SDL_KMOD_LALT: + SDL_snprintfcat(text, maxlen, "LALT"); + break; + case SDL_KMOD_RALT: + SDL_snprintfcat(text, maxlen, "RALT"); + break; + case SDL_KMOD_LGUI: + SDL_snprintfcat(text, maxlen, "LGUI"); + break; + case SDL_KMOD_RGUI: + SDL_snprintfcat(text, maxlen, "RGUI"); + break; + case SDL_KMOD_NUM: + SDL_snprintfcat(text, maxlen, "NUM"); + break; + case SDL_KMOD_CAPS: + SDL_snprintfcat(text, maxlen, "CAPS"); + break; + case SDL_KMOD_MODE: + SDL_snprintfcat(text, maxlen, "MODE"); + break; + case SDL_KMOD_SCROLL: + SDL_snprintfcat(text, maxlen, "SCROLL"); + break; + default: + SDL_snprintfcat(text, maxlen, "0x%8.8x", (unsigned int) flag); + break; + } +} + +static void SDLTest_PrintModState(char *text, size_t maxlen, SDL_Keymod keymod) +{ + const SDL_Keymod kmod_flags[] = { + SDL_KMOD_LSHIFT, + SDL_KMOD_RSHIFT, + SDL_KMOD_LCTRL, + SDL_KMOD_RCTRL, + SDL_KMOD_LALT, + SDL_KMOD_RALT, + SDL_KMOD_LGUI, + SDL_KMOD_RGUI, + SDL_KMOD_NUM, + SDL_KMOD_CAPS, + SDL_KMOD_MODE, + SDL_KMOD_SCROLL + }; + + int i; + int count = 0; + for (i = 0; i < SDL_arraysize(kmod_flags); ++i) { + const SDL_Keymod flag = kmod_flags[i]; + if ((keymod & flag) == flag) { + if (count > 0) { + SDL_snprintfcat(text, maxlen, " | "); + } + SDLTest_PrintModStateFlag(text, maxlen, flag); + ++count; + } + } +} + static void SDLTest_PrintButtonMask(char *text, size_t maxlen, Uint32 flags) { int i; @@ -908,6 +982,12 @@ static void SDLTest_PrintPixelFormat(char *text, size_t maxlen, Uint32 format) case SDL_PIXELFORMAT_INDEX1MSB: SDL_snprintfcat(text, maxlen, "Index1MSB"); break; + case SDL_PIXELFORMAT_INDEX2LSB: + SDL_snprintfcat(text, maxlen, "Index2LSB"); + break; + case SDL_PIXELFORMAT_INDEX2MSB: + SDL_snprintfcat(text, maxlen, "Index2MSB"); + break; case SDL_PIXELFORMAT_INDEX4LSB: SDL_snprintfcat(text, maxlen, "Index4LSB"); break; @@ -1090,7 +1170,7 @@ static SDL_Surface *SDLTest_LoadIcon(const char *file) /* Load the icon surface */ icon = SDL_LoadBMP(file); - if (icon == NULL) { + if (!icon) { SDL_Log("Couldn't load %s: %s\n", file, SDL_GetError()); return NULL; } @@ -1347,6 +1427,7 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) for (i = 0; i < state->num_windows; ++i) { char title[1024]; SDL_Rect r; + SDL_PropertiesID props; r.x = state->window_x; r.y = state->window_y; @@ -1369,7 +1450,15 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) } else { SDL_strlcpy(title, state->window_title, SDL_arraysize(title)); } - state->windows[i] = SDL_CreateWindowWithPosition(title, r.x, r.y, r.w, r.h, state->window_flags); + props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, title); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_X_NUMBER, r.x); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER, r.y); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, r.w); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, r.h); + SDL_SetNumberProperty(props, "flags", state->window_flags); + state->windows[i] = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); if (!state->windows[i]) { SDL_Log("Couldn't create window: %s\n", SDL_GetError()); @@ -1539,10 +1628,10 @@ static const char *GamepadButtonName(const SDL_GamepadButton button) case SDL_GAMEPAD_BUTTON_##btn: \ return #btn BUTTON_CASE(INVALID); - BUTTON_CASE(A); - BUTTON_CASE(B); - BUTTON_CASE(X); - BUTTON_CASE(Y); + BUTTON_CASE(SOUTH); + BUTTON_CASE(EAST); + BUTTON_CASE(WEST); + BUTTON_CASE(NORTH); BUTTON_CASE(BACK); BUTTON_CASE(GUIDE); BUTTON_CASE(START); @@ -1560,14 +1649,14 @@ static const char *GamepadButtonName(const SDL_GamepadButton button) } } -static void SDLTest_PrintEvent(SDL_Event *event) +static void SDLTest_PrintEvent(const SDL_Event *event) { switch (event->type) { case SDL_EVENT_SYSTEM_THEME_CHANGED: SDL_Log("SDL EVENT: System theme changed to %s\n", SystemThemeName()); break; - case SDL_EVENT_DISPLAY_CONNECTED: - SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " connected", + case SDL_EVENT_DISPLAY_ADDED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " attached", event->display.displayID); break; case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: @@ -1585,8 +1674,8 @@ static void SDLTest_PrintEvent(SDL_Event *event) SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed orientation to %s", event->display.displayID, DisplayOrientationName(event->display.data1)); break; - case SDL_EVENT_DISPLAY_DISCONNECTED: - SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " disconnected", + case SDL_EVENT_DISPLAY_REMOVED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " removed", event->display.displayID); break; case SDL_EVENT_WINDOW_SHOWN: @@ -1652,20 +1741,37 @@ static void SDLTest_PrintEvent(SDL_Event *event) case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " display scale changed to %d%%", event->window.windowID, (int)(SDL_GetWindowDisplayScale(SDL_GetWindowFromID(event->window.windowID)) * 100.0f)); break; + case SDL_EVENT_WINDOW_OCCLUDED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " occluded", event->window.windowID); + break; + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " entered fullscreen", event->window.windowID); + break; + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " left fullscreen", event->window.windowID); + break; + case SDL_EVENT_WINDOW_DESTROYED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " destroyed", event->window.windowID); + break; case SDL_EVENT_KEY_DOWN: - SDL_Log("SDL EVENT: Keyboard: key pressed in window %" SDL_PRIu32 ": scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s", + case SDL_EVENT_KEY_UP: { + char modstr[64]; + if (event->key.keysym.mod) { + modstr[0] = '\0'; + SDLTest_PrintModState(modstr, sizeof (modstr), event->key.keysym.mod); + } else { + SDL_strlcpy(modstr, "NONE", sizeof (modstr)); + } + + SDL_Log("SDL EVENT: Keyboard: key %s in window %" SDL_PRIu32 ": scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s, mods = %s", + (event->type == SDL_EVENT_KEY_DOWN) ? "pressed" : "released", event->key.windowID, event->key.keysym.scancode, SDL_GetScancodeName(event->key.keysym.scancode), - event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym)); - break; - case SDL_EVENT_KEY_UP: - SDL_Log("SDL EVENT: Keyboard: key released in window %" SDL_PRIu32 ": scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s", - event->key.windowID, - event->key.keysym.scancode, - SDL_GetScancodeName(event->key.keysym.scancode), - event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym)); + event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym), + modstr); break; + } case SDL_EVENT_TEXT_EDITING: SDL_Log("SDL EVENT: Keyboard: text editing \"%s\" in window %" SDL_PRIu32, event->edit.text, event->edit.windowID); @@ -1820,13 +1926,16 @@ static void SDLTest_PrintEvent(SDL_Event *event) SDL_Log("SDL EVENT: App entered the foreground"); break; case SDL_EVENT_DROP_BEGIN: - SDL_Log("SDL EVENT: Drag and drop beginning"); + SDL_Log("SDL EVENT: Drag and drop beginning in window %" SDL_PRIu32 "", event->drop.windowID); + break; + case SDL_EVENT_DROP_POSITION: + SDL_Log("SDL EVENT: Drag and drop moving in window %" SDL_PRIu32 ": %g,%g", event->drop.windowID, event->drop.x, event->drop.y); break; case SDL_EVENT_DROP_FILE: - SDL_Log("SDL EVENT: Drag and drop file: '%s'", event->drop.file); + SDL_Log("SDL EVENT: Drag and drop file in window %" SDL_PRIu32 ": '%s'", event->drop.windowID, event->drop.data); break; case SDL_EVENT_DROP_TEXT: - SDL_Log("SDL EVENT: Drag and drop text: '%s'", event->drop.file); + SDL_Log("SDL EVENT: Drag and drop text in window %" SDL_PRIu32 ": '%s'", event->drop.windowID, event->drop.data); break; case SDL_EVENT_DROP_COMPLETE: SDL_Log("SDL EVENT: Drag and drop ending"); @@ -1916,7 +2025,7 @@ static void SDLTest_CopyScreenShot(SDL_Renderer *renderer) }; SDLTest_ClipboardData *clipboard_data; - if (renderer == NULL) { + if (!renderer) { return; } @@ -1924,7 +2033,7 @@ static void SDLTest_CopyScreenShot(SDL_Renderer *renderer) surface = SDL_CreateSurface(viewport.w, viewport.h, SDL_PIXELFORMAT_BGR24); - if (surface == NULL) { + if (!surface) { SDL_Log("Couldn't create surface: %s\n", SDL_GetError()); return; } @@ -2029,7 +2138,7 @@ static void FullscreenTo(SDLTest_CommonState *state, int index, int windowId) SDL_free(displays); } -void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done) +int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event) { int i; @@ -2273,7 +2382,7 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done /* Ctrl-G toggle mouse grab */ SDL_Window *window = SDL_GetWindowFromID(event->key.windowID); if (window) { - SDL_SetWindowGrab(window, !SDL_GetWindowGrab(window) ? SDL_TRUE : SDL_FALSE); + SDL_SetWindowGrab(window, !SDL_GetWindowGrab(window)); } } break; @@ -2282,7 +2391,7 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done /* Ctrl-K toggle keyboard grab */ SDL_Window *window = SDL_GetWindowFromID(event->key.windowID); if (window) { - SDL_SetWindowKeyboardGrab(window, !SDL_GetWindowKeyboardGrab(window) ? SDL_TRUE : SDL_FALSE); + SDL_SetWindowKeyboardGrab(window, !SDL_GetWindowKeyboardGrab(window)); } } break; @@ -2311,7 +2420,7 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done case SDLK_r: if (withControl) { /* Ctrl-R toggle mouse relative mode */ - SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode() ? SDL_TRUE : SDL_FALSE); + SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode()); } break; case SDLK_t: @@ -2408,22 +2517,22 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done } break; case SDLK_ESCAPE: - *done = 1; - break; + return 1; default: break; } break; } case SDL_EVENT_QUIT: - *done = 1; - break; - - case SDL_EVENT_DROP_FILE: - case SDL_EVENT_DROP_TEXT: - SDL_free(event->drop.file); - break; + return 1; } + + return 0; /* keep going */ +} + +void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done) +{ + *done = SDLTest_CommonEventMainCallbacks(state, event) ? 1 : 0; } void SDLTest_CommonQuit(SDLTest_CommonState *state) @@ -2643,6 +2752,19 @@ void SDLTest_CommonDrawWindowInfo(SDL_Renderer *renderer, SDL_Window *window, fl SDLTest_DrawString(renderer, 0.0f, textY, text); textY += lineHeight; + /* Keyboard */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0, textY, "-- Keyboard --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetModState: "); + SDLTest_PrintModState(text, sizeof(text), SDL_GetModState()); + SDLTest_DrawString(renderer, 0, textY, text); + textY += lineHeight; + if (usedHeight) { *usedHeight = textY; } diff --git a/src/test/SDL_test_compare.c b/src/test/SDL_test_compare.c index 247751cf..d7adb22d 100644 --- a/src/test/SDL_test_compare.c +++ b/src/test/SDL_test_compare.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,32 +28,57 @@ */ #include +#include "../video/SDL_surface_pixel_impl.h" + #define FILENAME_SIZE 128 /* Counter for _CompareSurface calls; used for filename creation when comparisons fail */ static int _CompareSurfaceCount = 0; +int +SDLTest_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a) +{ + return SDL_ReadSurfacePixel_impl(surface, x, y, r, g, b, a); +} + +static void +LogErrorFormat(const char *name, const SDL_PixelFormat *format) +{ + SDLTest_LogError("%s: %08" SDL_PRIx32 " %s, %u bits/%u bytes per pixel", name, format->format, SDL_GetPixelFormatName(format->format), format->BitsPerPixel, format->BytesPerPixel); + SDLTest_LogError("%s: R mask %08" SDL_PRIx32 ", loss %u, shift %u", name, format->Rmask, format->Rloss, format->Rshift); + SDLTest_LogError("%s: G mask %08" SDL_PRIx32 ", loss %u, shift %u", name, format->Gmask, format->Gloss, format->Gshift); + SDLTest_LogError("%s: B mask %08" SDL_PRIx32 ", loss %u, shift %u", name, format->Bmask, format->Bloss, format->Bshift); + SDLTest_LogError("%s: A mask %08" SDL_PRIx32 ", loss %u, shift %u", name, format->Amask, format->Aloss, format->Ashift); +} + /* Compare surfaces */ int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error) { int ret; int i, j; - int bpp, bpp_reference; - Uint8 *p, *p_reference; int dist; int sampleErrorX = 0, sampleErrorY = 0, sampleDist = 0; + SDL_Color sampleReference = { 0, 0, 0, 0 }; + SDL_Color sampleActual = { 0, 0, 0, 0 }; Uint8 R, G, B, A; Uint8 Rd, Gd, Bd, Ad; char imageFilename[FILENAME_SIZE]; char referenceFilename[FILENAME_SIZE]; /* Validate input surfaces */ - if (surface == NULL || referenceSurface == NULL) { + if (!surface) { + SDLTest_LogError("Cannot compare NULL surface"); + return -1; + } + + if (!referenceSurface) { + SDLTest_LogError("Cannot compare NULL reference surface"); return -1; } /* Make sure surface size is the same. */ if ((surface->w != referenceSurface->w) || (surface->h != referenceSurface->h)) { + SDLTest_LogError("Expected %dx%d surface, got %dx%d", referenceSurface->w, referenceSurface->h, surface->w, surface->h); return -2; } @@ -66,16 +91,24 @@ int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, SDL_LockSurface(referenceSurface); ret = 0; - bpp = surface->format->BytesPerPixel; - bpp_reference = referenceSurface->format->BytesPerPixel; /* Compare image - should be same format. */ for (j = 0; j < surface->h; j++) { for (i = 0; i < surface->w; i++) { - p = (Uint8 *)surface->pixels + j * surface->pitch + i * bpp; - p_reference = (Uint8 *)referenceSurface->pixels + j * referenceSurface->pitch + i * bpp_reference; + int temp; - SDL_GetRGBA(*(Uint32 *)p, surface->format, &R, &G, &B, &A); - SDL_GetRGBA(*(Uint32 *)p_reference, referenceSurface->format, &Rd, &Gd, &Bd, &Ad); + temp = SDLTest_ReadSurfacePixel(surface, i, j, &R, &G, &B, &A); + if (temp != 0) { + SDLTest_LogError("Failed to retrieve pixel (%d,%d): %s", i, j, SDL_GetError()); + ret++; + continue; + } + + temp = SDLTest_ReadSurfacePixel(referenceSurface, i, j, &Rd, &Gd, &Bd, &Ad); + if (temp != 0) { + SDLTest_LogError("Failed to retrieve reference pixel (%d,%d): %s", i, j, SDL_GetError()); + ret++; + continue; + } dist = 0; dist += (R - Rd) * (R - Rd); @@ -89,6 +122,14 @@ int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, sampleErrorX = i; sampleErrorY = j; sampleDist = dist; + sampleReference.r = Rd; + sampleReference.g = Gd; + sampleReference.b = Bd; + sampleReference.a = Ad; + sampleActual.r = R; + sampleActual.g = G; + sampleActual.b = B; + sampleActual.a = A; } } } @@ -101,7 +142,11 @@ int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, _CompareSurfaceCount++; if (ret != 0) { SDLTest_LogError("Comparison of pixels with allowable error of %i failed %i times.", allowable_error, ret); + LogErrorFormat("Reference surface format", referenceSurface->format); + LogErrorFormat("Actual surface format ", surface->format); SDLTest_LogError("First detected occurrence at position %i,%i with a squared RGB-difference of %i.", sampleErrorX, sampleErrorY, sampleDist); + SDLTest_LogError("Reference pixel: R=%u G=%u B=%u A=%u", sampleReference.r, sampleReference.g, sampleReference.b, sampleReference.a); + SDLTest_LogError("Actual pixel : R=%u G=%u B=%u A=%u", sampleActual.r, sampleActual.g, sampleActual.b, sampleActual.a); (void)SDL_snprintf(imageFilename, FILENAME_SIZE - 1, "CompareSurfaces%04d_TestOutput.bmp", _CompareSurfaceCount); SDL_SaveBMP(surface, imageFilename); (void)SDL_snprintf(referenceFilename, FILENAME_SIZE - 1, "CompareSurfaces%04d_Reference.bmp", _CompareSurfaceCount); diff --git a/src/test/SDL_test_crc32.c b/src/test/SDL_test_crc32.c index ef400034..b079daed 100644 --- a/src/test/SDL_test_crc32.c +++ b/src/test/SDL_test_crc32.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,7 +33,7 @@ int SDLTest_Crc32Init(SDLTest_Crc32Context *crcContext) CrcUint32 c; /* Sanity check context pointer */ - if (crcContext == NULL) { + if (!crcContext) { return -1; } @@ -87,7 +87,7 @@ int SDLTest_Crc32Calc(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, CrcUint int SDLTest_Crc32CalcStart(SDLTest_Crc32Context *crcContext, CrcUint32 *crc32) { /* Sanity check pointers */ - if (crcContext == NULL) { + if (!crcContext) { *crc32 = 0; return -1; } @@ -105,7 +105,7 @@ int SDLTest_Crc32CalcStart(SDLTest_Crc32Context *crcContext, CrcUint32 *crc32) int SDLTest_Crc32CalcEnd(SDLTest_Crc32Context *crcContext, CrcUint32 *crc32) { /* Sanity check pointers */ - if (crcContext == NULL) { + if (!crcContext) { *crc32 = 0; return -1; } @@ -125,12 +125,12 @@ int SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, C CrcUint8 *p; register CrcUint32 crc; - if (crcContext == NULL) { + if (!crcContext) { *crc32 = 0; return -1; } - if (inBuf == NULL) { + if (!inBuf) { return -1; } @@ -152,7 +152,7 @@ int SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, C int SDLTest_Crc32Done(SDLTest_Crc32Context *crcContext) { - if (crcContext == NULL) { + if (!crcContext) { return -1; } diff --git a/src/test/SDL_test_font.c b/src/test/SDL_test_font.c index 59205580..67e2c17b 100644 --- a/src/test/SDL_test_font.c +++ b/src/test/SDL_test_font.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -3122,7 +3122,7 @@ struct SDLTest_CharTextureCache }; /*! -\brief List of per-renderer caches for 8x8 pixel font textures created at runtime. +List of per-renderer caches for 8x8 pixel font textures created at runtime. */ static struct SDLTest_CharTextureCache *SDLTest_CharTextureCacheList; @@ -3165,14 +3165,14 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) ci = c; /* Search for this renderer's cache */ - for (cache = SDLTest_CharTextureCacheList; cache != NULL; cache = cache->next) { + for (cache = SDLTest_CharTextureCacheList; cache; cache = cache->next) { if (cache->renderer == renderer) { break; } } /* Allocate a new cache for this renderer if needed */ - if (cache == NULL) { + if (!cache) { cache = (struct SDLTest_CharTextureCache *)SDL_calloc(1, sizeof(struct SDLTest_CharTextureCache)); cache->renderer = renderer; cache->next = SDLTest_CharTextureCacheList; @@ -3187,7 +3187,7 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) * Redraw character into surface */ character = SDL_CreateSurface(charWidth, charHeight, SDL_PIXELFORMAT_RGBA8888); - if (character == NULL) { + if (!character) { return -1; } @@ -3357,7 +3357,7 @@ SDLTest_TextWindow *SDLTest_TextWindowCreate(float x, float y, float w, float h) { SDLTest_TextWindow *textwin = (SDLTest_TextWindow *)SDL_malloc(sizeof(*textwin)); - if (textwin == NULL) { + if (!textwin) { return NULL; } diff --git a/src/test/SDL_test_fuzzer.c b/src/test/SDL_test_fuzzer.c index 0941c5f3..7e45f6c0 100644 --- a/src/test/SDL_test_fuzzer.c +++ b/src/test/SDL_test_fuzzer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -472,7 +472,7 @@ char *SDLTest_RandomAsciiStringOfSize(int size) } string = (char *)SDL_malloc((size + 1) * sizeof(char)); - if (string == NULL) { + if (!string) { return NULL; } diff --git a/src/test/SDL_test_harness.c b/src/test/SDL_test_harness.c index ffdc593a..73e23016 100644 --- a/src/test/SDL_test_harness.c +++ b/src/test/SDL_test_harness.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -47,7 +47,7 @@ /* Final result message format */ #define SDLTEST_FINAL_RESULT_FORMAT COLOR_YELLOW ">>> %s '%s':" COLOR_END " %s\n" -/* ! \brief Timeout for single test case execution */ +/* ! Timeout for single test case execution */ static Uint32 SDLTest_TestCaseTimeout = 3600; /** @@ -74,7 +74,7 @@ char *SDLTest_GenerateRunSeed(const int length) /* Allocate output buffer */ seed = (char *)SDL_malloc((length + 1) * sizeof(char)); - if (seed == NULL) { + if (!seed) { SDLTest_LogError("SDL_malloc for run seed output buffer failed."); SDL_Error(SDL_ENOMEM); return NULL; @@ -118,17 +118,17 @@ static Uint64 SDLTest_GenerateExecKey(const char *runSeed, const char *suiteName size_t entireStringLength; char *buffer; - if (runSeed == NULL || runSeed[0] == '\0') { + if (!runSeed || runSeed[0] == '\0') { SDLTest_LogError("Invalid runSeed string."); return -1; } - if (suiteName == NULL || suiteName[0] == '\0') { + if (!suiteName || suiteName[0] == '\0') { SDLTest_LogError("Invalid suiteName string."); return -1; } - if (testName == NULL || testName[0] == '\0') { + if (!testName || testName[0] == '\0') { SDLTest_LogError("Invalid testName string."); return -1; } @@ -149,7 +149,7 @@ static Uint64 SDLTest_GenerateExecKey(const char *runSeed, const char *suiteName iterationStringLength = SDL_strlen(iterationString); entireStringLength = runSeedLength + suiteNameLength + testNameLength + iterationStringLength + 1; buffer = (char *)SDL_malloc(entireStringLength); - if (buffer == NULL) { + if (!buffer) { SDLTest_LogError("Failed to allocate buffer for execKey generation."); SDL_Error(SDL_ENOMEM); return 0; @@ -167,7 +167,7 @@ static Uint64 SDLTest_GenerateExecKey(const char *runSeed, const char *suiteName } /** - * \brief Set timeout handler for test. + * Set timeout handler for test. * * Note: SDL_Init(SDL_INIT_TIMER) will be called if it wasn't done so before. * @@ -181,7 +181,7 @@ static SDL_TimerID SDLTest_SetTestTimeout(int timeout, void(SDLCALL *callback)(v Uint32 timeoutInMilliseconds; SDL_TimerID timerID; - if (callback == NULL) { + if (!callback) { SDLTest_LogError("Timeout callback can't be NULL"); return -1; } @@ -211,7 +211,7 @@ static SDL_TimerID SDLTest_SetTestTimeout(int timeout, void(SDLCALL *callback)(v } /** - * \brief Timeout handler. Aborts test run and exits harness process. + * Timeout handler. Aborts test run and exits harness process. */ #ifdef __WATCOMC__ #pragma aux SDLTest_BailOut aborts; @@ -223,7 +223,7 @@ static SDL_NORETURN void SDLCALL SDLTest_BailOut(void) } /** - * \brief Execute a test using the given execution key. + * Execute a test using the given execution key. * * \param testSuite Suite containing the test case. * \param testCase Case to execute. @@ -239,7 +239,7 @@ static int SDLTest_RunTest(SDLTest_TestSuiteReference *testSuite, const SDLTest_ int testResult = 0; int fuzzerCount; - if (testSuite == NULL || testCase == NULL || testSuite->name == NULL || testCase->name == NULL) { + if (!testSuite || !testCase || !testSuite->name || !testCase->name) { SDLTest_LogError("Setup failure: testSuite or testCase references NULL"); return TEST_RESULT_SETUP_FAILURE; } @@ -356,7 +356,7 @@ static float GetClock(void) } /** - * \brief Execute a test suite using the given run seed and execution key. + * Execute a test suite using the given run seed and execution key. * * The filter string is matched to the suite name (full comparison) to select a single suite, * or if no suite matches, it is matched to the test names (full comparison) to select a single test. @@ -412,9 +412,9 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user } /* Generate run see if we don't have one already */ - if (userRunSeed == NULL || userRunSeed[0] == '\0') { + if (!userRunSeed || userRunSeed[0] == '\0') { char *tmp = SDLTest_GenerateRunSeed(16); - if (tmp == NULL) { + if (!tmp) { SDLTest_LogError("Generating a random seed failed"); return 2; } @@ -455,20 +455,20 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user /* Pre-allocate an array for tracking failed tests (potentially all test cases) */ failedTests = (const SDLTest_TestCaseReference **)SDL_malloc(totalNumberOfTests * sizeof(SDLTest_TestCaseReference *)); - if (failedTests == NULL) { + if (!failedTests) { SDLTest_LogError("Unable to allocate cache for failed tests"); SDL_Error(SDL_ENOMEM); return -1; } /* Initialize filtering */ - if (filter != NULL && filter[0] != '\0') { + if (filter && filter[0] != '\0') { /* Loop over all suites to check if we have a filter match */ suiteCounter = 0; while (testSuites[suiteCounter] && suiteFilter == 0) { testSuite = testSuites[suiteCounter]; suiteCounter++; - if (testSuite->name != NULL && SDL_strcasecmp(filter, testSuite->name) == 0) { + if (testSuite->name && SDL_strcasecmp(filter, testSuite->name) == 0) { /* Matched a suite name */ suiteFilter = 1; suiteFilterName = testSuite->name; @@ -481,7 +481,7 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user while (testSuite->testCases[testCounter] && testFilter == 0) { testCase = testSuite->testCases[testCounter]; testCounter++; - if (testCase->name != NULL && SDL_strcasecmp(filter, testCase->name) == 0) { + if (testCase->name && SDL_strcasecmp(filter, testCase->name) == 0) { /* Matched a test name */ suiteFilter = 1; suiteFilterName = testSuite->name; @@ -497,14 +497,14 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user SDLTest_LogError("Filter '%s' did not match any test suite/case.", filter); for (suiteCounter = 0; testSuites[suiteCounter]; ++suiteCounter) { testSuite = testSuites[suiteCounter]; - if (testSuite->name != NULL) { + if (testSuite->name) { SDLTest_Log("Test suite: %s", testSuite->name); } /* Within each suite, loop over all test cases to check if we have a filter match */ for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) { testCase = testSuite->testCases[testCounter]; - SDLTest_Log(" test: %s", testCase->name); + SDLTest_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)"); } } SDLTest_Log("Exit code: 2"); @@ -521,7 +521,7 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user suiteCounter++; /* Filter suite if flag set and we have a name */ - if (suiteFilter == 1 && suiteFilterName != NULL && testSuite->name != NULL && + if (suiteFilter == 1 && suiteFilterName && testSuite->name && SDL_strcasecmp(suiteFilterName, testSuite->name) != 0) { /* Skip suite */ SDLTest_Log("===== Test Suite %i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n", @@ -550,7 +550,7 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user testCounter++; /* Filter tests if flag set and we have a name */ - if (testFilter == 1 && testFilterName != NULL && testCase->name != NULL && + if (testFilter == 1 && testFilterName && testCase->name && SDL_strcasecmp(testFilterName, testCase->name) != 0) { /* Skip test */ SDLTest_Log("===== Test Case %i.%i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n", @@ -572,7 +572,7 @@ int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *user suiteCounter, testCounter, currentTestName); - if (testCase->description != NULL && testCase->description[0] != '\0') { + if (testCase->description && testCase->description[0] != '\0') { SDLTest_Log("Test Description: '%s'", (testCase->description) ? testCase->description : SDLTEST_INVALID_NAME_FORMAT); } diff --git a/src/test/SDL_test_log.c b/src/test/SDL_test_log.c index 54261f59..e3532dc0 100644 --- a/src/test/SDL_test_log.c +++ b/src/test/SDL_test_log.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/test/SDL_test_md5.c b/src/test/SDL_test_md5.c index 9f7448e4..b14dd83a 100644 --- a/src/test/SDL_test_md5.c +++ b/src/test/SDL_test_md5.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -110,7 +110,7 @@ static unsigned char MD5PADDING[64] = { void SDLTest_Md5Init(SDLTest_Md5Context *mdContext) { - if (mdContext == NULL) { + if (!mdContext) { return; } @@ -138,10 +138,10 @@ void SDLTest_Md5Update(SDLTest_Md5Context *mdContext, unsigned char *inBuf, int mdi; unsigned int i, ii; - if (mdContext == NULL) { + if (!mdContext) { return; } - if (inBuf == NULL || inLen < 1) { + if (!inBuf || inLen < 1) { return; } @@ -190,7 +190,7 @@ void SDLTest_Md5Final(SDLTest_Md5Context *mdContext) unsigned int i, ii; unsigned int padLen; - if (mdContext == NULL) { + if (!mdContext) { return; } diff --git a/src/test/SDL_test_memory.c b/src/test/SDL_test_memory.c index 526686ad..f8c8d7b3 100644 --- a/src/test/SDL_test_memory.c +++ b/src/test/SDL_test_memory.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,6 +43,10 @@ typedef BOOL (__stdcall *dbghelp_SymGetLineFromAddr_fn)(HANDLE hProcess, DWORD q #endif static dbghelp_SymGetLineFromAddr_fn dbghelp_SymGetLineFromAddr; +/* older SDKs might not have this: */ +__declspec(dllimport) USHORT WINAPI RtlCaptureStackBackTrace(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash); +#define CaptureStackBackTrace RtlCaptureStackBackTrace + #endif /* This is a simple tracking allocator to demonstrate the use of SDL's @@ -114,7 +118,7 @@ static void SDL_TrackAllocation(void *mem, size_t size) return; } entry = (SDL_tracked_allocation *)SDL_malloc_orig(sizeof(*entry)); - if (entry == NULL) { + if (!entry) { return; } entry->mem = mem; @@ -179,7 +183,7 @@ static void SDL_TrackAllocation(void *mem, size_t size) line.LineNumber = 0; } - SDL_snprintf(entry->stack_names[i], sizeof(entry->stack_names[i]), "%s+0x%llx %s:%u", pSymbol->Name, (unsigned long long)dwDisplacement, line.FileName, (Uint32)line.LineNumber); + SDL_snprintf(entry->stack_names[i], sizeof(entry->stack_names[i]), "%s+0x%I64x %s:%u", pSymbol->Name, dwDisplacement, line.FileName, (Uint32)line.LineNumber); } } } @@ -268,7 +272,7 @@ static void *SDLCALL SDLTest_TrackedRealloc(void *ptr, size_t size) static void SDLCALL SDLTest_TrackedFree(void *ptr) { - if (ptr == NULL) { + if (!ptr) { return; } @@ -303,7 +307,7 @@ void SDLTest_TrackAllocations(void) #else dbghelp_SymGetLineFromAddr = (dbghelp_SymGetLineFromAddr_fn)SDL_LoadFunction(s_dbghelp, "SymGetLineFromAddr"); #endif - if (!dbghelp_SymFromAddr || !dbghelp_SymFromAddr || !dbghelp_SymGetLineFromAddr) { + if (!dbghelp_SymInitialize || !dbghelp_SymFromAddr || !dbghelp_SymGetLineFromAddr) { SDL_UnloadObject(s_dbghelp); s_dbghelp = NULL; } else { diff --git a/src/test/SDL_test_random.c b/src/test/SDL_test_random.c index c5633cec..72eceadc 100644 --- a/src/test/SDL_test_random.c +++ b/src/test/SDL_test_random.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,7 +36,7 @@ void SDLTest_RandomInit(SDLTest_RandomContext *rndContext, unsigned int xi, unsigned int ci) { - if (rndContext == NULL) { + if (!rndContext) { return; } @@ -64,7 +64,7 @@ void SDLTest_RandomInitTime(SDLTest_RandomContext *rndContext) { int a, b; - if (rndContext == NULL) { + if (!rndContext) { return; } @@ -81,7 +81,7 @@ unsigned int SDLTest_Random(SDLTest_RandomContext *rndContext) { unsigned int xh, xl; - if (rndContext == NULL) { + if (!rndContext) { return -1; } diff --git a/src/thread/SDL_systhread.h b/src/thread/SDL_systhread.h index fd9c966e..70df324c 100644 --- a/src/thread/SDL_systhread.h +++ b/src/thread/SDL_systhread.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/SDL_thread.c b/src/thread/SDL_thread.c index e062a96f..ae9a1fda 100644 --- a/src/thread/SDL_thread.c +++ b/src/thread/SDL_thread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -37,7 +37,7 @@ void *SDL_GetTLS(SDL_TLSID id) SDL_TLSData *storage; storage = SDL_SYS_GetTLSData(); - if (storage == NULL || id == 0 || id > storage->limit) { + if (!storage || id == 0 || id > storage->limit) { return NULL; } return storage->array[id - 1].data; @@ -52,15 +52,17 @@ int SDL_SetTLS(SDL_TLSID id, const void *value, void(SDLCALL *destructor)(void * } storage = SDL_SYS_GetTLSData(); - if (storage == NULL || (id > storage->limit)) { + if (!storage || (id > storage->limit)) { unsigned int i, oldlimit, newlimit; + SDL_TLSData *new_storage; oldlimit = storage ? storage->limit : 0; newlimit = (id + TLS_ALLOC_CHUNKSIZE); - storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage) + (newlimit - 1) * sizeof(storage->array[0])); - if (storage == NULL) { - return SDL_OutOfMemory(); + new_storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage) + (newlimit - 1) * sizeof(storage->array[0])); + if (!new_storage) { + return -1; } + storage = new_storage; storage->limit = newlimit; for (i = oldlimit; i < newlimit; ++i) { storage->array[i].data = NULL; @@ -118,14 +120,14 @@ SDL_TLSData *SDL_Generic_GetTLSData(void) SDL_TLSData *storage = NULL; #ifndef SDL_THREADS_DISABLED - if (SDL_generic_TLS_mutex == NULL) { + if (!SDL_generic_TLS_mutex) { static SDL_SpinLock tls_lock; SDL_AtomicLock(&tls_lock); - if (SDL_generic_TLS_mutex == NULL) { + if (!SDL_generic_TLS_mutex) { SDL_Mutex *mutex = SDL_CreateMutex(); SDL_MemoryBarrierRelease(); SDL_generic_TLS_mutex = mutex; - if (SDL_generic_TLS_mutex == NULL) { + if (!SDL_generic_TLS_mutex) { SDL_AtomicUnlock(&tls_lock); return NULL; } @@ -159,10 +161,10 @@ int SDL_Generic_SetTLSData(SDL_TLSData *data) prev = NULL; for (entry = SDL_generic_TLS; entry; entry = entry->next) { if (entry->thread == thread) { - if (data != NULL) { + if (data) { entry->storage = data; } else { - if (prev != NULL) { + if (prev) { prev->next = entry->next; } else { SDL_generic_TLS = entry->next; @@ -173,7 +175,7 @@ int SDL_Generic_SetTLSData(SDL_TLSData *data) } prev = entry; } - if (entry == NULL) { + if (!entry) { entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry)); if (entry) { entry->thread = thread; @@ -184,10 +186,7 @@ int SDL_Generic_SetTLSData(SDL_TLSData *data) } SDL_UnlockMutex(SDL_generic_TLS_mutex); - if (entry == NULL) { - return SDL_OutOfMemory(); - } - return 0; + return entry ? 0 : -1; } /* Non-thread-safe global error variable */ @@ -213,7 +212,7 @@ static void SDLCALL SDL_FreeErrBuf(void *data) #endif /* Routine to get the thread-specific error variable */ -SDL_error *SDL_GetErrBuf(void) +SDL_error *SDL_GetErrBuf(SDL_bool create) { #ifdef SDL_THREADS_DISABLED return SDL_GetStaticErrBuf(); @@ -224,6 +223,10 @@ SDL_error *SDL_GetErrBuf(void) const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1; SDL_error *errbuf; + if (!tls_errbuf && !create) { + return NULL; + } + /* tls_being_created is there simply to prevent recursion if SDL_CreateTLS() fails. It also means it's possible for another thread to also use SDL_global_errbuf, but that's very unlikely and hopefully won't cause issues. @@ -249,7 +252,7 @@ SDL_error *SDL_GetErrBuf(void) if (errbuf == ALLOCATION_IN_PROGRESS) { return SDL_GetStaticErrBuf(); } - if (errbuf == NULL) { + if (!errbuf) { /* Get the original memory functions for this allocation because the lifetime * of the error buffer may span calls to SDL_SetMemoryFunctions() by the app */ @@ -260,7 +263,7 @@ SDL_error *SDL_GetErrBuf(void) /* Mark that we're in the middle of allocating our buffer */ SDL_SetTLS(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL); errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf)); - if (errbuf == NULL) { + if (!errbuf) { SDL_SetTLS(tls_errbuf, NULL, NULL); return SDL_GetStaticErrBuf(); } @@ -328,18 +331,16 @@ SDL_Thread *SDL_CreateThreadWithStackSize(int(SDLCALL *fn)(void *), /* Allocate memory for the thread info structure */ thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread)); - if (thread == NULL) { - SDL_OutOfMemory(); + if (!thread) { return NULL; } thread->status = -1; SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE); /* Set up the arguments for the thread */ - if (name != NULL) { + if (name) { thread->name = SDL_strdup(name); - if (thread->name == NULL) { - SDL_OutOfMemory(); + if (!thread->name) { SDL_free(thread); return NULL; } @@ -381,7 +382,7 @@ DECLSPEC SDL_Thread *SDLCALL SDL_CreateThread(int(SDLCALL *fn)(void *), size_t stacksize = 0; /* If the SDL_HINT_THREAD_STACK_SIZE exists, use it */ - if (stackhint != NULL) { + if (stackhint) { char *endp = NULL; const Sint64 hintval = SDL_strtoll(stackhint, &endp, 10); if ((*stackhint != '\0') && (*endp == '\0')) { /* a valid number? */ @@ -450,7 +451,7 @@ void SDL_WaitThread(SDL_Thread *thread, int *status) void SDL_DetachThread(SDL_Thread *thread) { - if (thread == NULL) { + if (!thread) { return; } @@ -472,7 +473,7 @@ void SDL_DetachThread(SDL_Thread *thread) int SDL_WaitSemaphore(SDL_Semaphore *sem) { - return SDL_WaitSemaphoreTimeoutNS(sem, SDL_MUTEX_MAXWAIT); + return SDL_WaitSemaphoreTimeoutNS(sem, -1); } int SDL_TryWaitSemaphore(SDL_Semaphore *sem) @@ -494,7 +495,7 @@ int SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS) int SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex) { - return SDL_WaitConditionTimeoutNS(cond, mutex, SDL_MUTEX_MAXWAIT); + return SDL_WaitConditionTimeoutNS(cond, mutex, -1); } int SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS) diff --git a/src/thread/SDL_thread_c.h b/src/thread/SDL_thread_c.h index 30961e03..7d4e245a 100644 --- a/src/thread/SDL_thread_c.h +++ b/src/thread/SDL_thread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/generic/SDL_syscond.c b/src/thread/generic/SDL_syscond.c index 6535008a..291c21f6 100644 --- a/src/thread/generic/SDL_syscond.c +++ b/src/thread/generic/SDL_syscond.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -64,8 +64,6 @@ SDL_Condition *SDL_CreateCondition_generic(void) SDL_DestroyCondition_generic((SDL_Condition *)cond); cond = NULL; } - } else { - SDL_OutOfMemory(); } return (SDL_Condition *)cond; } @@ -92,7 +90,7 @@ void SDL_DestroyCondition_generic(SDL_Condition *_cond) int SDL_SignalCondition_generic(SDL_Condition *_cond) { SDL_cond_generic *cond = (SDL_cond_generic *)_cond; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -116,7 +114,7 @@ int SDL_SignalCondition_generic(SDL_Condition *_cond) int SDL_BroadcastCondition_generic(SDL_Condition *_cond) { SDL_cond_generic *cond = (SDL_cond_generic *)_cond; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -172,7 +170,7 @@ int SDL_WaitConditionTimeoutNS_generic(SDL_Condition *_cond, SDL_Mutex *mutex, S SDL_cond_generic *cond = (SDL_cond_generic *)_cond; int retval; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } diff --git a/src/thread/generic/SDL_syscond_c.h b/src/thread/generic/SDL_syscond_c.h index 93a828c0..8e360780 100644 --- a/src/thread/generic/SDL_syscond_c.h +++ b/src/thread/generic/SDL_syscond_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/generic/SDL_sysmutex.c b/src/thread/generic/SDL_sysmutex.c index a735db97..a5642703 100644 --- a/src/thread/generic/SDL_sysmutex.c +++ b/src/thread/generic/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -/* An implementation of mutexes using semaphores */ +// An implementation of mutexes using semaphores #include "SDL_systhread_c.h" @@ -31,13 +31,9 @@ struct SDL_Mutex SDL_Semaphore *sem; }; -/* Create a mutex */ SDL_Mutex *SDL_CreateMutex(void) { - SDL_Mutex *mutex; - - /* Allocate mutex memory */ - mutex = (SDL_Mutex *)SDL_calloc(1, sizeof(*mutex)); + SDL_Mutex *mutex = (SDL_Mutex *)SDL_calloc(1, sizeof(*mutex)); #ifndef SDL_THREADS_DISABLED if (mutex) { @@ -49,15 +45,12 @@ SDL_Mutex *SDL_CreateMutex(void) SDL_free(mutex); mutex = NULL; } - } else { - SDL_OutOfMemory(); } -#endif /* !SDL_THREADS_DISABLED */ +#endif // !SDL_THREADS_DISABLED return mutex; } -/* Free the mutex */ void SDL_DestroyMutex(SDL_Mutex *mutex) { if (mutex) { @@ -68,94 +61,72 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } } -/* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SDL_threadID this_thread; - - if (mutex == NULL) { - return 0; - } - - this_thread = SDL_ThreadID(); - if (mutex->owner == this_thread) { - ++mutex->recursive; - } else { - /* The order of operations is important. - We set the locking thread id after we obtain the lock - so unlocks from other threads will fail. - */ - SDL_WaitSemaphore(mutex->sem); - mutex->owner = this_thread; - mutex->recursive = 0; - } - - return 0; -#endif /* SDL_THREADS_DISABLED */ -} - -/* try Lock the mutex */ -int SDL_TryLockMutex(SDL_Mutex *mutex) -{ -#ifdef SDL_THREADS_DISABLED - return 0; -#else - int retval = 0; - SDL_threadID this_thread; - - if (mutex == NULL) { - return 0; - } - - this_thread = SDL_ThreadID(); - if (mutex->owner == this_thread) { - ++mutex->recursive; - } else { - /* The order of operations is important. - We set the locking thread id after we obtain the lock - so unlocks from other threads will fail. - */ - retval = SDL_WaitSemaphore(mutex->sem); - if (retval == 0) { +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + SDL_threadID this_thread = SDL_ThreadID(); + if (mutex->owner == this_thread) { + ++mutex->recursive; + } else { + /* The order of operations is important. + We set the locking thread id after we obtain the lock + so unlocks from other threads will fail. + */ + SDL_WaitSemaphore(mutex->sem); mutex->owner = this_thread; mutex->recursive = 0; } } - - return retval; #endif /* SDL_THREADS_DISABLED */ } -/* Unlock the mutex */ -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +int SDL_TryLockMutex(SDL_Mutex *mutex) { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - if (mutex == NULL) { - return 0; + int retval = 0; +#ifndef SDL_THREADS_DISABLED + if (mutex) { + SDL_threadID this_thread = SDL_ThreadID(); + if (mutex->owner == this_thread) { + ++mutex->recursive; + } else { + /* The order of operations is important. + We set the locking thread id after we obtain the lock + so unlocks from other threads will fail. + */ + retval = SDL_TryWaitSemaphore(mutex->sem); + if (retval == 0) { + mutex->owner = this_thread; + mutex->recursive = 0; + } + } } - - /* If we don't own the mutex, we can't unlock it */ - if (SDL_ThreadID() != mutex->owner) { - return SDL_SetError("mutex not owned by this thread"); - } - - if (mutex->recursive) { - --mutex->recursive; - } else { - /* The order of operations is important. - First reset the owner so another thread doesn't lock - the mutex and set the ownership before we reset it, - then release the lock semaphore. - */ - mutex->owner = 0; - SDL_PostSemaphore(mutex->sem); - } - return 0; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED + return retval; +} + +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes +{ +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + // If we don't own the mutex, we can't unlock it + if (SDL_ThreadID() != mutex->owner) { + SDL_assert(!"Tried to unlock a mutex we don't own!"); + return; // (undefined behavior!) SDL_SetError("mutex not owned by this thread"); + } + + if (mutex->recursive) { + --mutex->recursive; + } else { + /* The order of operations is important. + First reset the owner so another thread doesn't lock + the mutex and set the ownership before we reset it, + then release the lock semaphore. + */ + mutex->owner = 0; + SDL_PostSemaphore(mutex->sem); + } + } +#endif // SDL_THREADS_DISABLED } diff --git a/src/thread/generic/SDL_sysmutex_c.h b/src/thread/generic/SDL_sysmutex_c.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/thread/generic/SDL_sysmutex_c.h +++ b/src/thread/generic/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/generic/SDL_sysrwlock.c b/src/thread/generic/SDL_sysrwlock.c index d88bee15..f41c28d7 100644 --- a/src/thread/generic/SDL_sysrwlock.c +++ b/src/thread/generic/SDL_sysrwlock.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -/* An implementation of rwlocks using mutexes, condition variables, and atomics. */ +// An implementation of rwlocks using mutexes, condition variables, and atomics. #include "SDL_systhread_c.h" @@ -30,7 +30,7 @@ * will be chosen at runtime), the function names need to be * suffixed */ -/* !!! FIXME: this is quite a tapdance with macros and the build system, maybe we can simplify how we do this. --ryan. */ +// !!! FIXME: this is quite a tapdance with macros and the build system, maybe we can simplify how we do this. --ryan. #ifndef SDL_THREAD_GENERIC_RWLOCK_SUFFIX #define SDL_CreateRWLock_generic SDL_CreateRWLock #define SDL_DestroyRWLock_generic SDL_DestroyRWLock @@ -59,7 +59,6 @@ SDL_RWLock *SDL_CreateRWLock_generic(void) SDL_RWLock *rwlock = (SDL_RWLock *) SDL_calloc(1, sizeof (*rwlock)); if (!rwlock) { - SDL_OutOfMemory(); return NULL; } @@ -95,63 +94,48 @@ void SDL_DestroyRWLock_generic(SDL_RWLock *rwlock) } } -int SDL_LockRWLockForReading_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForReading_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { #ifndef SDL_THREADS_DISABLED - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } else if (SDL_LockMutex(rwlock->lock) == -1) { - return -1; + if (rwlock) { + // !!! FIXME: these don't have to be atomic, we always gate them behind a mutex. + SDL_LockMutex(rwlock->lock); + SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); // shouldn't be able to grab lock if there's a writer! + SDL_AtomicAdd(&rwlock->reader_count, 1); + SDL_UnlockMutex(rwlock->lock); // other readers can attempt to share the lock. } - - SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */ - - SDL_AtomicAdd(&rwlock->reader_count, 1); - SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */ #endif - - return 0; } -int SDL_LockRWLockForWriting_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForWriting_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { #ifndef SDL_THREADS_DISABLED - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } else if (SDL_LockMutex(rwlock->lock) == -1) { - return -1; - } + if (rwlock) { + SDL_LockMutex(rwlock->lock); + while (SDL_AtomicGet(&rwlock->reader_count) > 0) { // while something is holding the shared lock, keep waiting. + SDL_WaitCondition(rwlock->condition, rwlock->lock); // release the lock and wait for readers holding the shared lock to release it, regrab the lock. + } - while (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* while something is holding the shared lock, keep waiting. */ - SDL_WaitCondition(rwlock->condition, rwlock->lock); /* release the lock and wait for readers holding the shared lock to release it, regrab the lock. */ + // we hold the lock! + SDL_AtomicAdd(&rwlock->writer_count, 1); // we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! } - - /* we hold the lock! */ - SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */ #endif - - return 0; } int SDL_TryLockRWLockForReading_generic(SDL_RWLock *rwlock) { #ifndef SDL_THREADS_DISABLED - int rc; + if (rwlock) { + const int rc = SDL_TryLockMutex(rwlock->lock); + if (rc != 0) { + // !!! FIXME: there is a small window where a reader has to lock the mutex, and if we hit that, we will return SDL_RWLOCK_TIMEDOUT even though we could have shared the lock. + return rc; + } - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); + SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); // shouldn't be able to grab lock if there's a writer! + SDL_AtomicAdd(&rwlock->reader_count, 1); + SDL_UnlockMutex(rwlock->lock); // other readers can attempt to share the lock. } - - rc = SDL_TryLockMutex(rwlock->lock); - if (rc != 0) { - /* !!! FIXME: there is a small window where a reader has to lock the mutex, and if we hit that, we will return SDL_RWLOCK_TIMEDOUT even though we could have shared the lock. */ - return rc; - } - - SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */ - - SDL_AtomicAdd(&rwlock->reader_count, 1); - SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */ #endif return 0; @@ -160,46 +144,41 @@ int SDL_TryLockRWLockForReading_generic(SDL_RWLock *rwlock) int SDL_TryLockRWLockForWriting_generic(SDL_RWLock *rwlock) { #ifndef SDL_THREADS_DISABLED - int rc; + if (rwlock) { + const int rc = SDL_TryLockMutex(rwlock->lock); + if (rc != 0) { + return rc; + } - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } else if ((rc = SDL_TryLockMutex(rwlock->lock)) != 0) { - return rc; + if (SDL_AtomicGet(&rwlock->reader_count) > 0) { // a reader is using the shared lock, treat it as unavailable. + SDL_UnlockMutex(rwlock->lock); + return SDL_RWLOCK_TIMEDOUT; + } + + // we hold the lock! + SDL_AtomicAdd(&rwlock->writer_count, 1); // we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! } - - if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* a reader is using the shared lock, treat it as unavailable. */ - SDL_UnlockMutex(rwlock->lock); - return SDL_RWLOCK_TIMEDOUT; - } - - /* we hold the lock! */ - SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */ #endif return 0; } -int SDL_UnlockRWLock_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockRWLock_generic(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { #ifndef SDL_THREADS_DISABLED - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); + if (rwlock) { + SDL_LockMutex(rwlock->lock); // recursive lock for writers, readers grab lock to make sure things are sane. + + if (SDL_AtomicGet(&rwlock->reader_count) > 0) { // we're a reader + SDL_AtomicAdd(&rwlock->reader_count, -1); + SDL_BroadcastCondition(rwlock->condition); // alert any pending writers to attempt to try to grab the lock again. + } else if (SDL_AtomicGet(&rwlock->writer_count) > 0) { // we're a writer + SDL_AtomicAdd(&rwlock->writer_count, -1); + SDL_UnlockMutex(rwlock->lock); // recursive unlock. + } + + SDL_UnlockMutex(rwlock->lock); } - - SDL_LockMutex(rwlock->lock); /* recursive lock for writers, readers grab lock to make sure things are sane. */ - - if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* we're a reader */ - SDL_AtomicAdd(&rwlock->reader_count, -1); - SDL_BroadcastCondition(rwlock->condition); /* alert any pending writers to attempt to try to grab the lock again. */ - } else if (SDL_AtomicGet(&rwlock->writer_count) > 0) { /* we're a writer */ - SDL_AtomicAdd(&rwlock->writer_count, -1); - SDL_UnlockMutex(rwlock->lock); /* recursive unlock. */ - } - - SDL_UnlockMutex(rwlock->lock); #endif - - return 0; } diff --git a/src/thread/generic/SDL_sysrwlock_c.h b/src/thread/generic/SDL_sysrwlock_c.h index 8247f8eb..8264fa89 100644 --- a/src/thread/generic/SDL_sysrwlock_c.h +++ b/src/thread/generic/SDL_sysrwlock_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,12 +27,12 @@ SDL_RWLock *SDL_CreateRWLock_generic(void); void SDL_DestroyRWLock_generic(SDL_RWLock *rwlock); -int SDL_LockRWLockForReading_generic(SDL_RWLock *rwlock); -int SDL_LockRWLockForWriting_generic(SDL_RWLock *rwlock); +void SDL_LockRWLockForReading_generic(SDL_RWLock *rwlock); +void SDL_LockRWLockForWriting_generic(SDL_RWLock *rwlock); int SDL_TryLockRWLockForReading_generic(SDL_RWLock *rwlock); int SDL_TryLockRWLockForWriting_generic(SDL_RWLock *rwlock); -int SDL_UnlockRWLock_generic(SDL_RWLock *rwlock); +void SDL_UnlockRWLock_generic(SDL_RWLock *rwlock); -#endif /* SDL_THREAD_GENERIC_RWLOCK_SUFFIX */ +#endif // SDL_THREAD_GENERIC_RWLOCK_SUFFIX -#endif /* SDL_sysrwlock_c_h_ */ +#endif // SDL_sysrwlock_c_h_ diff --git a/src/thread/generic/SDL_syssem.c b/src/thread/generic/SDL_syssem.c index 3dae50be..8ae53243 100644 --- a/src/thread/generic/SDL_syssem.c +++ b/src/thread/generic/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -66,8 +66,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_Semaphore *sem; sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem)); - if (sem == NULL) { - SDL_OutOfMemory(); + if (!sem) { return NULL; } sem->count = initial_value; @@ -108,7 +107,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) { int retval; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -156,7 +155,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) int SDL_PostSemaphore(SDL_Semaphore *sem) { - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } diff --git a/src/thread/generic/SDL_systhread.c b/src/thread/generic/SDL_systhread.c index ce2c8df2..3e455850 100644 --- a/src/thread/generic/SDL_systhread.c +++ b/src/thread/generic/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/generic/SDL_systhread_c.h b/src/thread/generic/SDL_systhread_c.h index 0dc26b36..352961be 100644 --- a/src/thread/generic/SDL_systhread_c.h +++ b/src/thread/generic/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/generic/SDL_systls.c b/src/thread/generic/SDL_systls.c index 7ccfd48b..e439f4d4 100644 --- a/src/thread/generic/SDL_systls.c +++ b/src/thread/generic/SDL_systls.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/n3ds/SDL_syscond.c b/src/thread/n3ds/SDL_syscond.c index e170ab55..ff82627f 100644 --- a/src/thread/n3ds/SDL_syscond.c +++ b/src/thread/n3ds/SDL_syscond.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -37,8 +37,6 @@ SDL_Condition *SDL_CreateCondition(void) SDL_Condition *cond = (SDL_Condition *)SDL_malloc(sizeof(SDL_Condition)); if (cond) { CondVar_Init(&cond->cond_variable); - } else { - SDL_OutOfMemory(); } return cond; } @@ -54,7 +52,7 @@ void SDL_DestroyCondition(SDL_Condition *cond) /* Restart one of the threads that are waiting on the condition variable */ int SDL_SignalCondition(SDL_Condition *cond) { - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -65,7 +63,7 @@ int SDL_SignalCondition(SDL_Condition *cond) /* Restart all threads that are waiting on the condition variable */ int SDL_BroadcastCondition(SDL_Condition *cond) { - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -98,10 +96,10 @@ int SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 tim { Result res; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } - if (mutex == NULL) { + if (!mutex) { return SDL_InvalidParamError("mutex"); } diff --git a/src/thread/n3ds/SDL_sysmutex.c b/src/thread/n3ds/SDL_sysmutex.c index 0995ad11..0ca67a58 100644 --- a/src/thread/n3ds/SDL_sysmutex.c +++ b/src/thread/n3ds/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,26 +22,19 @@ #ifdef SDL_THREAD_N3DS -/* An implementation of mutexes using libctru's RecursiveLock */ +// An implementation of mutexes using libctru's RecursiveLock #include "SDL_sysmutex_c.h" -/* Create a mutex */ SDL_Mutex *SDL_CreateMutex(void) { - SDL_Mutex *mutex; - - /* Allocate mutex memory */ - mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); + SDL_Mutex *mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); if (mutex) { RecursiveLock_Init(&mutex->lock); - } else { - SDL_OutOfMemory(); } return mutex; } -/* Free the mutex */ void SDL_DestroyMutex(SDL_Mutex *mutex) { if (mutex) { @@ -49,38 +42,23 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } } -/* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (mutex == NULL) { - return 0; + if (mutex != NULL) { + RecursiveLock_Lock(&mutex->lock); } - - RecursiveLock_Lock(&mutex->lock); - - return 0; } -/* try Lock the mutex */ int SDL_TryLockMutex(SDL_Mutex *mutex) { - if (mutex == NULL) { - return 0; - } - - return RecursiveLock_TryLock(&mutex->lock); + return (!mutex) ? 0 : RecursiveLock_TryLock(&mutex->lock); } -/* Unlock the mutex */ -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (mutex == NULL) { - return 0; + if (mutex != NULL) { + RecursiveLock_Unlock(&mutex->lock); } - - RecursiveLock_Unlock(&mutex->lock); - - return 0; } -#endif /* SDL_THREAD_N3DS */ +#endif // SDL_THREAD_N3DS diff --git a/src/thread/n3ds/SDL_sysmutex_c.h b/src/thread/n3ds/SDL_sysmutex_c.h index aeb34198..1b225663 100644 --- a/src/thread/n3ds/SDL_sysmutex_c.h +++ b/src/thread/n3ds/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/n3ds/SDL_syssem.c b/src/thread/n3ds/SDL_syssem.c index 1c197f55..025aec3b 100644 --- a/src/thread/n3ds/SDL_syssem.c +++ b/src/thread/n3ds/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -43,8 +43,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) } sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem)); - if (sem == NULL) { - SDL_OutOfMemory(); + if (!sem) { return NULL; } @@ -63,11 +62,11 @@ void SDL_DestroySemaphore(SDL_Semaphore *sem) int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) { - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } - if (timeoutNS == SDL_MUTEX_MAXWAIT) { + if (timeoutNS == -1) { // -1 == wait indefinitely. LightSemaphore_Acquire(&sem->semaphore, 1); return 0; } @@ -99,7 +98,7 @@ int WaitOnSemaphoreFor(SDL_Semaphore *sem, Sint64 timeout) Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) { - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -108,7 +107,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) int SDL_PostSemaphore(SDL_Semaphore *sem) { - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } LightSemaphore_Release(&sem->semaphore, 1); diff --git a/src/thread/n3ds/SDL_systhread.c b/src/thread/n3ds/SDL_systhread.c index 1f74ae37..d99fca5a 100644 --- a/src/thread/n3ds/SDL_systhread.c +++ b/src/thread/n3ds/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,9 +26,8 @@ #include "../SDL_systhread.h" -/* N3DS has very limited RAM (128MB), so we put a limit on thread stack size. */ -#define N3DS_THREAD_STACK_SIZE_MAX (16 * 1024) -#define N3DS_THREAD_STACK_SIZE_DEFAULT (4 * 1024) +/* N3DS has very limited RAM (128MB), so we set a low default thread stack size. */ +#define N3DS_THREAD_STACK_SIZE_DEFAULT (80 * 1024) #define N3DS_THREAD_PRIORITY_LOW 0x3F /**< Minimum priority */ #define N3DS_THREAD_PRIORITY_MEDIUM 0x2F /**< Slightly higher than main thread (0x30) */ @@ -49,18 +48,25 @@ static void ThreadEntry(void *arg) int SDL_SYS_CreateThread(SDL_Thread *thread) { - s32 priority; + s32 priority = 0x30; + int cpu = -1; size_t stack_size = GetStackSize(thread->stacksize); + svcGetThreadPriority(&priority, CUR_THREAD_HANDLE); + /* prefer putting audio thread on system core */ + if (thread->name && (SDL_strncmp(thread->name, "SDLAudioP", 9) == 0) && R_SUCCEEDED(APT_SetAppCpuTimeLimit(30))) { + cpu = 1; + } + thread->handle = threadCreate(ThreadEntry, thread, stack_size, priority, - -1, + cpu, false); - if (thread->handle == NULL) { + if (!thread->handle) { return SDL_SetError("Couldn't create thread"); } @@ -73,14 +79,6 @@ static size_t GetStackSize(size_t requested_size) return N3DS_THREAD_STACK_SIZE_DEFAULT; } - if (requested_size > N3DS_THREAD_STACK_SIZE_MAX) { - SDL_LogWarn(SDL_LOG_CATEGORY_SYSTEM, - "Requested a thread size of %zu," - " falling back to the maximum supported of %zu\n", - requested_size, - N3DS_THREAD_STACK_SIZE_MAX); - requested_size = N3DS_THREAD_STACK_SIZE_MAX; - } return requested_size; } diff --git a/src/thread/n3ds/SDL_systhread_c.h b/src/thread/n3ds/SDL_systhread_c.h index e0fc6811..195c8cff 100644 --- a/src/thread/n3ds/SDL_systhread_c.h +++ b/src/thread/n3ds/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/ngage/SDL_sysmutex.cpp b/src/thread/ngage/SDL_sysmutex.cpp index 9cc47d42..5bf1fd73 100644 --- a/src/thread/ngage/SDL_sysmutex.cpp +++ b/src/thread/ngage/SDL_sysmutex.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -67,17 +67,13 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } /* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ { - if (mutex == NULL) { - return 0; + if (mutex != NULL) { + RMutex rmutex; + rmutex.SetHandle(mutex->handle); + rmutex.Wait(); } - - RMutex rmutex; - rmutex.SetHandle(mutex->handle); - rmutex.Wait(); - - return 0; } /* Try to lock the mutex */ @@ -95,16 +91,12 @@ int SDL_TryLockMutex(SDL_Mutex *mutex) #endif /* Unlock the mutex */ -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ { - if (mutex == NULL) { - return 0; + if (mutex != NULL) { + RMutex rmutex; + rmutex.SetHandle(mutex->handle); + rmutex.Signal(); } - - RMutex rmutex; - rmutex.SetHandle(mutex->handle); - rmutex.Signal(); - - return 0; } diff --git a/src/thread/ngage/SDL_syssem.cpp b/src/thread/ngage/SDL_syssem.cpp index 8b15be46..081d2787 100644 --- a/src/thread/ngage/SDL_syssem.cpp +++ b/src/thread/ngage/SDL_syssem.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -90,7 +90,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) void SDL_DestroySemaphore(SDL_Semaphore *sem) { - if (sem != NULL) { + if (sem) { RSemaphore sema; sema.SetHandle(sem->handle); sema.Signal(sema.Count()); @@ -102,7 +102,7 @@ void SDL_DestroySemaphore(SDL_Semaphore *sem) int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) { - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -114,7 +114,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) return SDL_MUTEX_TIMEOUT; } - if (timeoutNS == SDL_MUTEX_MAXWAIT) { + if (timeoutNS == -1) { // -1 == wait indefinitely. WaitAll(sem); return 0; } @@ -140,7 +140,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) { - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -149,7 +149,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) int SDL_PostSemaphore(SDL_Semaphore *sem) { - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } sem->count++; diff --git a/src/thread/ngage/SDL_systhread.cpp b/src/thread/ngage/SDL_systhread.cpp index 6ce6fdab..e57ff6f2 100644 --- a/src/thread/ngage/SDL_systhread.cpp +++ b/src/thread/ngage/SDL_systhread.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/ngage/SDL_systhread_c.h b/src/thread/ngage/SDL_systhread_c.h index 8fd96b5b..c0b69728 100644 --- a/src/thread/ngage/SDL_systhread_c.h +++ b/src/thread/ngage/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/ps2/SDL_syssem.c b/src/thread/ps2/SDL_syssem.c index 3878d09e..2cd93973 100644 --- a/src/thread/ps2/SDL_syssem.c +++ b/src/thread/ps2/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,7 +26,7 @@ #include #include -#include +#include #include @@ -35,11 +35,6 @@ struct SDL_Semaphore s32 semid; }; -static void usercb(struct timer_alarm_t *alarm, void *arg) -{ - iReleaseWaitThread((int)arg); -} - /* Create a semaphore */ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) { @@ -47,7 +42,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) ee_sema_t sema; sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem)); - if (sem != NULL) { + if (sem) { /* TODO: Figure out the limit on the maximum value. */ sema.init_count = initial_value; sema.max_count = 255; @@ -59,8 +54,6 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_free(sem); sem = NULL; } - } else { - SDL_OutOfMemory(); } return sem; @@ -69,7 +62,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) /* Free the semaphore */ void SDL_DestroySemaphore(SDL_Semaphore *sem) { - if (sem != NULL) { + if (sem) { if (sem->semid > 0) { DeleteSema(sem->semid); sem->semid = 0; @@ -82,10 +75,10 @@ void SDL_DestroySemaphore(SDL_Semaphore *sem) int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) { int ret; - struct timer_alarm_t alarm; - InitializeTimerAlarm(&alarm); + u64 timeout_usec; + u64 *timeout_ptr; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -96,12 +89,14 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) return 0; } - if (timeoutNS != SDL_MUTEX_MAXWAIT) { - SetTimerAlarm(&alarm, MSec2TimerBusClock(SDL_NS_TO_MS(timeoutNS)), &usercb, (void *)GetThreadId()); + timeout_ptr = NULL; + + if (timeoutNS != -1) { // -1 == wait indefinitely. + timeout_usec = SDL_NS_TO_US(timeoutNS); + timeout_ptr = &timeout_usec; } - ret = WaitSema(sem->semid); - StopTimerAlarm(&alarm); + ret = WaitSemaEx(sem->semid, 1, timeout_ptr); if (ret < 0) { return SDL_MUTEX_TIMEDOUT; @@ -114,7 +109,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) { ee_sema_t info; - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -130,7 +125,7 @@ int SDL_PostSemaphore(SDL_Semaphore *sem) { int res; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } diff --git a/src/thread/ps2/SDL_systhread.c b/src/thread/ps2/SDL_systhread.c index b40b0be0..c6e4f681 100644 --- a/src/thread/ps2/SDL_systhread.c +++ b/src/thread/ps2/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/ps2/SDL_systhread_c.h b/src/thread/ps2/SDL_systhread_c.h index b9757045..afab3838 100644 --- a/src/thread/ps2/SDL_systhread_c.h +++ b/src/thread/ps2/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/psp/SDL_syscond.c b/src/thread/psp/SDL_syscond.c deleted file mode 100644 index a5389444..00000000 --- a/src/thread/psp/SDL_syscond.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_THREAD_PSP - -/* An implementation of condition variables using semaphores and mutexes */ -/* - This implementation borrows heavily from the BeOS condition variable - implementation, written by Christopher Tate and Owen Smith. Thanks! - */ - -struct SDL_Condition -{ - SDL_Mutex *lock; - int waiting; - int signals; - SDL_Semaphore *wait_sem; - SDL_Semaphore *wait_done; -}; - -/* Create a condition variable */ -SDL_Condition *SDL_CreateCondition(void) -{ - SDL_Condition *cond; - - cond = (SDL_Condition *)SDL_malloc(sizeof(SDL_Condition)); - if (cond) { - cond->lock = SDL_CreateMutex(); - cond->wait_sem = SDL_CreateSemaphore(0); - cond->wait_done = SDL_CreateSemaphore(0); - cond->waiting = cond->signals = 0; - if (!cond->lock || !cond->wait_sem || !cond->wait_done) { - SDL_DestroyCondition(cond); - cond = NULL; - } - } else { - SDL_OutOfMemory(); - } - return cond; -} - -/* Destroy a condition variable */ -void SDL_DestroyCondition(SDL_Condition *cond) -{ - if (cond) { - if (cond->wait_sem) { - SDL_DestroySemaphore(cond->wait_sem); - } - if (cond->wait_done) { - SDL_DestroySemaphore(cond->wait_done); - } - if (cond->lock) { - SDL_DestroyMutex(cond->lock); - } - SDL_free(cond); - } -} - -/* Restart one of the threads that are waiting on the condition variable */ -int SDL_SignalCondition(SDL_Condition *cond) -{ - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - SDL_LockMutex(cond->lock); - if (cond->waiting > cond->signals) { - ++cond->signals; - SDL_PostSemaphore(cond->wait_sem); - SDL_UnlockMutex(cond->lock); - SDL_WaitSemaphore(cond->wait_done); - } else { - SDL_UnlockMutex(cond->lock); - } - - return 0; -} - -/* Restart all threads that are waiting on the condition variable */ -int SDL_BroadcastCondition(SDL_Condition *cond) -{ - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - SDL_LockMutex(cond->lock); - if (cond->waiting > cond->signals) { - int i, num_waiting; - - num_waiting = (cond->waiting - cond->signals); - cond->signals = cond->waiting; - for (i = 0; i < num_waiting; ++i) { - SDL_PostSemaphore(cond->wait_sem); - } - /* Now all released threads are blocked here, waiting for us. - Collect them all (and win fabulous prizes!) :-) - */ - SDL_UnlockMutex(cond->lock); - for (i = 0; i < num_waiting; ++i) { - SDL_WaitSemaphore(cond->wait_done); - } - } else { - SDL_UnlockMutex(cond->lock); - } - - return 0; -} - -/* Wait on the condition variable for at most 'timeoutNS' nanoseconds. - The mutex must be locked before entering this function! - The mutex is unlocked during the wait, and locked again after the wait. - -Typical use: - -Thread A: - SDL_LockMutex(lock); - while ( ! condition ) { - SDL_WaitCondition(cond, lock); - } - SDL_UnlockMutex(lock); - -Thread B: - SDL_LockMutex(lock); - ... - condition = true; - ... - SDL_SignalCondition(cond); - SDL_UnlockMutex(lock); - */ -int SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS) -{ - int retval; - - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* Obtain the protection mutex, and increment the number of waiters. - This allows the signal mechanism to only perform a signal if there - are waiting threads. - */ - SDL_LockMutex(cond->lock); - ++cond->waiting; - SDL_UnlockMutex(cond->lock); - - /* Unlock the mutex, as is required by condition variable semantics */ - SDL_UnlockMutex(mutex); - - /* Wait for a signal */ - retval = SDL_WaitSemaphoreTimeout(cond->wait_sem, timeoutNS); - - /* Let the signaler know we have completed the wait, otherwise - the signaler can race ahead and get the condition semaphore - if we are stopped between the mutex unlock and semaphore wait, - giving a deadlock. See the following URL for details: - http://www-classic.be.com/aboutbe/benewsletter/volume_III/Issue40.html - */ - SDL_LockMutex(cond->lock); - if (cond->signals > 0) { - /* If we timed out, we need to eat a condition signal */ - if (retval > 0) { - SDL_WaitSemaphore(cond->wait_sem); - } - /* We always notify the signal thread that we are done */ - SDL_PostSemaphore(cond->wait_done); - - /* Signal handshake complete */ - --cond->signals; - } - --cond->waiting; - SDL_UnlockMutex(cond->lock); - - /* Lock the mutex, as is required by condition variable semantics */ - SDL_LockMutex(mutex); - - return retval; -} - -#endif /* SDL_THREAD_PSP */ diff --git a/src/thread/psp/SDL_sysmutex.c b/src/thread/psp/SDL_sysmutex.c index 8aaa9622..259f46a2 100644 --- a/src/thread/psp/SDL_sysmutex.c +++ b/src/thread/psp/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ #ifdef SDL_THREAD_PSP -/* An implementation of mutexes using semaphores */ +// An implementation of mutexes using semaphores #include "SDL_systhread_c.h" @@ -36,17 +36,11 @@ struct SDL_Mutex SceLwMutexWorkarea lock; }; -/* Create a mutex */ SDL_Mutex *SDL_CreateMutex(void) { - SDL_Mutex *mutex = NULL; - SceInt32 res = 0; - - /* Allocate mutex memory */ - mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); + SDL_Mutex *mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); if (mutex) { - - res = sceKernelCreateLwMutex( + const SceInt32 res = sceKernelCreateLwMutex( &mutex->lock, "SDL mutex", SCE_KERNEL_MUTEX_ATTR_RECURSIVE, @@ -54,15 +48,14 @@ SDL_Mutex *SDL_CreateMutex(void) NULL); if (res < 0) { + SDL_free(mutex); + mutex = NULL; SDL_SetError("Error trying to create mutex: %lx", res); } - } else { - SDL_OutOfMemory(); } return mutex; } -/* Free the mutex */ void SDL_DestroyMutex(SDL_Mutex *mutex) { if (mutex) { @@ -71,75 +64,43 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } } -/* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SceInt32 res = 0; - - if (mutex == NULL) { - return 0; +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + const SceInt32 res = sceKernelLockLwMutex(&mutex->lock, 1, NULL); + SDL_assert(res == SCE_KERNEL_ERROR_OK); // assume we're in a lot of trouble if this assert fails. } - - res = sceKernelLockLwMutex(&mutex->lock, 1, NULL); - if (res != SCE_KERNEL_ERROR_OK) { - return SDL_SetError("Error trying to lock mutex: %lx", res); - } - - return 0; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED } -/* Try to lock the mutex */ int SDL_TryLockMutex(SDL_Mutex *mutex) { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SceInt32 res = 0; - - if (mutex == NULL) { - return 0; + int retval = 0; +#ifndef SDL_THREADS_DISABLED + if (mutex) { + const SceInt32 res = sceKernelTryLockLwMutex(&mutex->lock, 1); + if (res == SCE_KERNEL_ERROR_OK) { + retval = 0; + } else if (res == SCE_KERNEL_ERROR_WAIT_TIMEOUT) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + SDL_assert(!"Error trying to lock mutex"); // assume we're in a lot of trouble if this assert fails. + retval = SDL_MUTEX_TIMEDOUT; + } } - - res = sceKernelTryLockLwMutex(&mutex->lock, 1); - switch (res) { - case SCE_KERNEL_ERROR_OK: - return 0; - break; - case SCE_KERNEL_ERROR_WAIT_TIMEOUT: - return SDL_MUTEX_TIMEDOUT; - break; - default: - return SDL_SetError("Error trying to lock mutex: %lx", res); - break; - } - - return -1; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED + return retval; } -/* Unlock the mutex */ -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SceInt32 res = 0; - - if (mutex == NULL) { - return 0; +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + const SceInt32 res = sceKernelUnlockLwMutex(&mutex->lock, 1); + SDL_assert(res == 0); // assume we're in a lot of trouble if this assert fails. } - - res = sceKernelUnlockLwMutex(&mutex->lock, 1); - if (res != 0) { - return SDL_SetError("Error trying to unlock mutex: %lx", res); - } - - return 0; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED } -#endif /* SDL_THREAD_PSP */ +#endif // SDL_THREAD_PSP diff --git a/src/thread/psp/SDL_sysmutex_c.h b/src/thread/psp/SDL_sysmutex_c.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/thread/psp/SDL_sysmutex_c.h +++ b/src/thread/psp/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/psp/SDL_syssem.c b/src/thread/psp/SDL_syssem.c index d65c9f1a..9d606530 100644 --- a/src/thread/psp/SDL_syssem.c +++ b/src/thread/psp/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,7 +41,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_Semaphore *sem; sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem)); - if (sem != NULL) { + if (sem) { /* TODO: Figure out the limit on the maximum value. */ sem->semid = sceKernelCreateSema("SDL sema", 0, initial_value, 255, NULL); if (sem->semid < 0) { @@ -49,8 +49,6 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_free(sem); sem = NULL; } - } else { - SDL_OutOfMemory(); } return sem; @@ -59,7 +57,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) /* Free the semaphore */ void SDL_DestroySemaphore(SDL_Semaphore *sem) { - if (sem != NULL) { + if (sem) { if (sem->semid > 0) { sceKernelDeleteSema(sem->semid); sem->semid = 0; @@ -70,7 +68,7 @@ void SDL_DestroySemaphore(SDL_Semaphore *sem) } /* TODO: This routine is a bit overloaded. - * If the timeout is 0 then just poll the semaphore; if it's SDL_MUTEX_MAXWAIT, pass + * If the timeout is 0 then just poll the semaphore; if it's -1, pass * NULL to sceKernelWaitSema() so that it waits indefinitely; and if the timeout * is specified, convert it to microseconds. */ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) @@ -79,7 +77,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) SceUInt *pTimeout; int res; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -114,7 +112,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) { SceKernelSemaInfo info; - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -130,7 +128,7 @@ int SDL_PostSemaphore(SDL_Semaphore *sem) { int res; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } diff --git a/src/thread/psp/SDL_systhread.c b/src/thread/psp/SDL_systhread.c index 9f3e1a48..a6f6bf72 100644 --- a/src/thread/psp/SDL_systhread.c +++ b/src/thread/psp/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/psp/SDL_systhread_c.h b/src/thread/psp/SDL_systhread_c.h index ef3fbccf..ffa84492 100644 --- a/src/thread/psp/SDL_systhread_c.h +++ b/src/thread/psp/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/pthread/SDL_syscond.c b/src/thread/pthread/SDL_syscond.c index f4160249..7583abbd 100644 --- a/src/thread/pthread/SDL_syscond.c +++ b/src/thread/pthread/SDL_syscond.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,7 +63,7 @@ int SDL_SignalCondition(SDL_Condition *cond) { int retval; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -79,7 +79,7 @@ int SDL_BroadcastCondition(SDL_Condition *cond) { int retval; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -98,7 +98,7 @@ int SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 tim #endif struct timespec abstime; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } diff --git a/src/thread/pthread/SDL_sysmutex.c b/src/thread/pthread/SDL_sysmutex.c index 5e3dd696..07d35cfb 100644 --- a/src/thread/pthread/SDL_sysmutex.c +++ b/src/thread/pthread/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,7 +30,7 @@ SDL_Mutex *SDL_CreateMutex(void) SDL_Mutex *mutex; pthread_mutexattr_t attr; - /* Allocate the structure */ + // Allocate the structure mutex = (SDL_Mutex *)SDL_calloc(1, sizeof(*mutex)); if (mutex) { pthread_mutexattr_init(&attr); @@ -39,15 +39,13 @@ SDL_Mutex *SDL_CreateMutex(void) #elif defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP) pthread_mutexattr_setkind_np(&attr, PTHREAD_MUTEX_RECURSIVE_NP); #else - /* No extra attributes necessary */ + // No extra attributes necessary #endif if (pthread_mutex_init(&mutex->id, &attr) != 0) { SDL_SetError("pthread_mutex_init() failed"); SDL_free(mutex); mutex = NULL; } - } else { - SDL_OutOfMemory(); } return mutex; } @@ -60,116 +58,96 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } } -/* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { + if (mutex != NULL) { #ifdef FAKE_RECURSIVE_MUTEX - pthread_t this_thread; -#endif - - if (mutex == NULL) { - return 0; - } - -#ifdef FAKE_RECURSIVE_MUTEX - this_thread = pthread_self(); - if (mutex->owner == this_thread) { - ++mutex->recursive; - } else { - /* The order of operations is important. - We set the locking thread id after we obtain the lock - so unlocks from other threads will fail. - */ - if (pthread_mutex_lock(&mutex->id) == 0) { + pthread_t this_thread = pthread_self(); + if (mutex->owner == this_thread) { + ++mutex->recursive; + } else { + /* The order of operations is important. + We set the locking thread id after we obtain the lock + so unlocks from other threads will fail. + */ + const int rc = pthread_mutex_lock(&mutex->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. mutex->owner = this_thread; mutex->recursive = 0; - } else { - return SDL_SetError("pthread_mutex_lock() failed"); } - } #else - if (pthread_mutex_lock(&mutex->id) != 0) { - return SDL_SetError("pthread_mutex_lock() failed"); - } + const int rc = pthread_mutex_lock(&mutex->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. #endif - return 0; + } } int SDL_TryLockMutex(SDL_Mutex *mutex) { - int retval; - int result; -#ifdef FAKE_RECURSIVE_MUTEX - pthread_t this_thread; -#endif + int retval = 0; - if (mutex == NULL) { - return 0; - } - - retval = 0; + if (mutex) { #ifdef FAKE_RECURSIVE_MUTEX - this_thread = pthread_self(); - if (mutex->owner == this_thread) { - ++mutex->recursive; - } else { - /* The order of operations is important. - We set the locking thread id after we obtain the lock - so unlocks from other threads will fail. - */ - result = pthread_mutex_trylock(&mutex->id); - if (result == 0) { - mutex->owner = this_thread; - mutex->recursive = 0; - } else if (result == EBUSY) { - retval = SDL_MUTEX_TIMEDOUT; + pthread_t this_thread = pthread_self(); + if (mutex->owner == this_thread) { + ++mutex->recursive; } else { - retval = SDL_SetError("pthread_mutex_trylock() failed"); + /* The order of operations is important. + We set the locking thread id after we obtain the lock + so unlocks from other threads will fail. + */ + const int result = pthread_mutex_trylock(&mutex->id); + if (result == 0) { + mutex->owner = this_thread; + mutex->recursive = 0; + } else if (result == EBUSY) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + SDL_assert(!"Error trying to lock mutex"); // assume we're in a lot of trouble if this assert fails. + retval = SDL_MUTEX_TIMEDOUT; + } } - } #else - result = pthread_mutex_trylock(&mutex->id); - if (result != 0) { - if (result == EBUSY) { - retval = SDL_MUTEX_TIMEDOUT; - } else { - retval = SDL_SetError("pthread_mutex_trylock() failed"); + const int result = pthread_mutex_trylock(&mutex->id); + if (result != 0) { + if (result == EBUSY) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + SDL_assert(!"Error trying to lock mutex"); // assume we're in a lot of trouble if this assert fails. + retval = SDL_MUTEX_TIMEDOUT; + } } - } #endif + } + return retval; } -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (mutex == NULL) { - return 0; - } - + if (mutex != NULL) { #ifdef FAKE_RECURSIVE_MUTEX - /* We can only unlock the mutex if we own it */ - if (pthread_self() == mutex->owner) { - if (mutex->recursive) { - --mutex->recursive; + // We can only unlock the mutex if we own it + if (pthread_self() == mutex->owner) { + if (mutex->recursive) { + --mutex->recursive; + } else { + /* The order of operations is important. + First reset the owner so another thread doesn't lock + the mutex and set the ownership before we reset it, + then release the lock semaphore. + */ + mutex->owner = 0; + pthread_mutex_unlock(&mutex->id); + } } else { - /* The order of operations is important. - First reset the owner so another thread doesn't lock - the mutex and set the ownership before we reset it, - then release the lock semaphore. - */ - mutex->owner = 0; - pthread_mutex_unlock(&mutex->id); + return SDL_SetError("mutex not owned by this thread"); } - } else { - return SDL_SetError("mutex not owned by this thread"); - } #else - if (pthread_mutex_unlock(&mutex->id) != 0) { - return SDL_SetError("pthread_mutex_unlock() failed"); + const int rc = pthread_mutex_unlock(&mutex->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. +#endif // FAKE_RECURSIVE_MUTEX } -#endif /* FAKE_RECURSIVE_MUTEX */ - - return 0; } diff --git a/src/thread/pthread/SDL_sysmutex_c.h b/src/thread/pthread/SDL_sysmutex_c.h index 3e365f6e..bbb5aa87 100644 --- a/src/thread/pthread/SDL_sysmutex_c.h +++ b/src/thread/pthread/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/pthread/SDL_sysrwlock.c b/src/thread/pthread/SDL_sysrwlock.c index 3624d369..8e486dc4 100644 --- a/src/thread/pthread/SDL_sysrwlock.c +++ b/src/thread/pthread/SDL_sysrwlock.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,8 +41,6 @@ SDL_RWLock *SDL_CreateRWLock(void) SDL_free(rwlock); rwlock = NULL; } - } else { - SDL_OutOfMemory(); } return rwlock; } @@ -55,42 +53,36 @@ void SDL_DestroyRWLock(SDL_RWLock *rwlock) } } -int SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); - } else if (pthread_rwlock_rdlock(&rwlock->id) != 0) { - return SDL_SetError("pthread_rwlock_rdlock() failed"); + if (rwlock) { + const int rc = pthread_rwlock_rdlock(&rwlock->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. } - return 0; } -int SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); - } else if (pthread_rwlock_wrlock(&rwlock->id) != 0) { - return SDL_SetError("pthread_rwlock_wrlock() failed"); + if (rwlock) { + const int rc = pthread_rwlock_wrlock(&rwlock->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. } - return 0; } int SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) { int retval = 0; - if (rwlock == NULL) { - retval = SDL_InvalidParamError("rwlock"); - } else { + if (rwlock) { const int result = pthread_rwlock_tryrdlock(&rwlock->id); if (result != 0) { - if (result == EBUSY) { - retval = SDL_RWLOCK_TIMEDOUT; - } else { - retval = SDL_SetError("pthread_rwlock_tryrdlock() failed"); + retval = SDL_RWLOCK_TIMEDOUT; + if (result != EBUSY) { + SDL_assert(!"Error trying to lock rwlock for reading"); // assume we're in a lot of trouble if this assert fails. } } } + return retval; } @@ -98,15 +90,12 @@ int SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) { int retval = 0; - if (rwlock == NULL) { - retval = SDL_InvalidParamError("rwlock"); - } else { + if (rwlock) { const int result = pthread_rwlock_trywrlock(&rwlock->id); if (result != 0) { - if (result == EBUSY) { - retval = SDL_RWLOCK_TIMEDOUT; - } else { - retval = SDL_SetError("pthread_rwlock_tryrdlock() failed"); + retval = SDL_RWLOCK_TIMEDOUT; + if (result != EBUSY) { + SDL_assert(!"Error trying to lock rwlock for writing"); // assume we're in a lot of trouble if this assert fails. } } } @@ -114,13 +103,11 @@ int SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) return retval; } -int SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); - } else if (pthread_rwlock_unlock(&rwlock->id) != 0) { - return SDL_SetError("pthread_rwlock_unlock() failed"); + if (rwlock) { + const int rc = pthread_rwlock_unlock(&rwlock->id); + SDL_assert(rc == 0); // assume we're in a lot of trouble if this assert fails. } - return 0; } diff --git a/src/thread/pthread/SDL_syssem.c b/src/thread/pthread/SDL_syssem.c index b1b38fa5..d4137727 100644 --- a/src/thread/pthread/SDL_syssem.c +++ b/src/thread/pthread/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,21 +42,19 @@ struct SDL_Semaphore SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) { SDL_Semaphore *sem = (SDL_Semaphore *)SDL_malloc(sizeof(SDL_Semaphore)); - if (sem != NULL) { + if (sem) { if (sem_init(&sem->sem, 0, initial_value) < 0) { SDL_SetError("sem_init() failed"); SDL_free(sem); sem = NULL; } - } else { - SDL_OutOfMemory(); } return sem; } void SDL_DestroySemaphore(SDL_Semaphore *sem) { - if (sem != NULL) { + if (sem) { sem_destroy(&sem->sem); SDL_free(sem); } @@ -74,7 +72,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) Uint64 end; #endif - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -152,7 +150,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) { int ret = 0; - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -168,7 +166,7 @@ int SDL_PostSemaphore(SDL_Semaphore *sem) { int retval; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } diff --git a/src/thread/pthread/SDL_systhread.c b/src/thread/pthread/SDL_systhread.c index b4b6193a..4a8b84b9 100644 --- a/src/thread/pthread/SDL_systhread.c +++ b/src/thread/pthread/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -118,10 +118,10 @@ void SDL_SYS_SetupThread(const char *name) int i; sigset_t mask; - if (name != NULL) { + if (name) { #if (defined(__MACOS__) || defined(__IOS__) || defined(__LINUX__)) && defined(HAVE_DLOPEN) SDL_assert(checked_setname); - if (ppthread_setname_np != NULL) { + if (ppthread_setname_np) { #if defined(__MACOS__) || defined(__IOS__) ppthread_setname_np(name); #elif defined(__LINUX__) diff --git a/src/thread/pthread/SDL_systhread_c.h b/src/thread/pthread/SDL_systhread_c.h index 8a8c07ad..e8593c46 100644 --- a/src/thread/pthread/SDL_systhread_c.h +++ b/src/thread/pthread/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/pthread/SDL_systls.c b/src/thread/pthread/SDL_systls.c index 17b3b64b..9c44d575 100644 --- a/src/thread/pthread/SDL_systls.c +++ b/src/thread/pthread/SDL_systls.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/stdcpp/SDL_syscond.cpp b/src/thread/stdcpp/SDL_syscond.cpp index 90c8a88a..02c1ddd2 100644 --- a/src/thread/stdcpp/SDL_syscond.cpp +++ b/src/thread/stdcpp/SDL_syscond.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -56,7 +56,7 @@ SDL_CreateCondition(void) extern "C" void SDL_DestroyCondition(SDL_Condition *cond) { - if (cond != NULL) { + if (cond) { delete cond; } } @@ -65,7 +65,7 @@ SDL_DestroyCondition(SDL_Condition *cond) extern "C" int SDL_SignalCondition(SDL_Condition *cond) { - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -77,7 +77,7 @@ SDL_SignalCondition(SDL_Condition *cond) extern "C" int SDL_BroadcastCondition(SDL_Condition *cond) { - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } diff --git a/src/thread/stdcpp/SDL_sysmutex.cpp b/src/thread/stdcpp/SDL_sysmutex.cpp index 01181f09..48be2f96 100644 --- a/src/thread/stdcpp/SDL_sysmutex.cpp +++ b/src/thread/stdcpp/SDL_sysmutex.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,74 +27,51 @@ extern "C" { #include #include "SDL_sysmutex_c.h" -#include +#include -/* Create a mutex */ -extern "C" SDL_Mutex * -SDL_CreateMutex(void) +extern "C" SDL_Mutex * SDL_CreateMutex(void) { - /* Allocate and initialize the mutex */ + // Allocate and initialize the mutex try { SDL_Mutex *mutex = new SDL_Mutex; return mutex; } catch (std::system_error &ex) { SDL_SetError("unable to create a C++ mutex: code=%d; %s", ex.code(), ex.what()); - return NULL; } catch (std::bad_alloc &) { SDL_OutOfMemory(); - return NULL; } + return NULL; } -/* Free the mutex */ -extern "C" void -SDL_DestroyMutex(SDL_Mutex *mutex) +extern "C" void SDL_DestroyMutex(SDL_Mutex *mutex) { - if (mutex != NULL) { + if (mutex) { delete mutex; } } -/* Lock the mutex */ -extern "C" int -SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +extern "C" void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (mutex == NULL) { - return 0; - } - - try { - mutex->cpp_mutex.lock(); - return 0; - } catch (std::system_error &ex) { - return SDL_SetError("unable to lock a C++ mutex: code=%d; %s", ex.code(), ex.what()); + if (mutex != NULL) { + try { + mutex->cpp_mutex.lock(); + } catch (std::system_error &ex) { + SDL_assert(!"Error trying to lock mutex"); // assume we're in a lot of trouble if this assert fails. + //return SDL_SetError("unable to lock a C++ mutex: code=%d; %s", ex.code(), ex.what()); + } } } -/* TryLock the mutex */ -int SDL_TryLockMutex(SDL_Mutex *mutex) +extern "C" int SDL_TryLockMutex(SDL_Mutex *mutex) { - int retval = 0; - - if (mutex == NULL) { - return 0; - } - - if (mutex->cpp_mutex.try_lock() == false) { - retval = SDL_MUTEX_TIMEDOUT; - } - return retval; + return ((!mutex) || mutex->cpp_mutex.try_lock()) ? 0 : SDL_MUTEX_TIMEDOUT; } /* Unlock the mutex */ -extern "C" int -SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +extern "C" void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (mutex == NULL) { - return 0; + if (mutex != NULL) { + mutex->cpp_mutex.unlock(); } - - mutex->cpp_mutex.unlock(); - return 0; } diff --git a/src/thread/stdcpp/SDL_sysmutex_c.h b/src/thread/stdcpp/SDL_sysmutex_c.h index f70e4c69..c18f9874 100644 --- a/src/thread/stdcpp/SDL_sysmutex_c.h +++ b/src/thread/stdcpp/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/stdcpp/SDL_sysrwlock.cpp b/src/thread/stdcpp/SDL_sysrwlock.cpp index 72673cab..9bb7c1d7 100644 --- a/src/thread/stdcpp/SDL_sysrwlock.cpp +++ b/src/thread/stdcpp/SDL_sysrwlock.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,10 +30,8 @@ struct SDL_RWLock SDL_threadID write_owner; }; -/* Create a rwlock */ extern "C" SDL_RWLock *SDL_CreateRWLock(void) { - /* Allocate and initialize the rwlock */ try { SDL_RWLock *rwlock = new SDL_RWLock; return rwlock; @@ -46,85 +44,71 @@ extern "C" SDL_RWLock *SDL_CreateRWLock(void) } } -/* Free the rwlock */ extern "C" void SDL_DestroyRWLock(SDL_RWLock *rwlock) { - if (rwlock != NULL) { + if (rwlock) { delete rwlock; } } -/* Lock the rwlock */ -extern "C" int SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +extern "C" void SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } - - try { - rwlock->cpp_mutex.lock_shared(); - return 0; - } catch (std::system_error &ex) { - return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + if (rwlock) { + try { + rwlock->cpp_mutex.lock_shared(); + } catch (std::system_error &ex) { + SDL_assert(!"Error trying to lock rwlock for reading"); // assume we're in a lot of trouble if this assert fails. + //return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + } } } -/* Lock the rwlock for writing */ -extern "C" int SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +extern "C" void SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } - - try { - rwlock->cpp_mutex.lock(); - rwlock->write_owner = SDL_ThreadID(); - return 0; - } catch (std::system_error &ex) { - return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + if (rwlock) { + try { + rwlock->cpp_mutex.lock(); + rwlock->write_owner = SDL_ThreadID(); + } catch (std::system_error &ex) { + SDL_assert(!"Error trying to lock rwlock for writing"); // assume we're in a lot of trouble if this assert fails. + //return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + } } } -/* TryLock the rwlock for reading */ -int SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) +extern "C" int SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) { int retval = 0; - - if (!rwlock) { - retval = SDL_InvalidParamError("rwlock"); - } else if (rwlock->cpp_mutex.try_lock_shared() == false) { - retval = SDL_RWLOCK_TIMEDOUT; + if (rwlock) { + if (rwlock->cpp_mutex.try_lock_shared() == false) { + retval = SDL_RWLOCK_TIMEDOUT; + } } return retval; } -/* TryLock the rwlock for writing */ -int SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) +extern "C" int SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) { int retval = 0; - - if (!rwlock) { - retval = SDL_InvalidParamError("rwlock"); - } else if (rwlock->cpp_mutex.try_lock() == false) { - retval = SDL_RWLOCK_TIMEDOUT; - } else { - rwlock->write_owner = SDL_ThreadID(); + if (rwlock) { + if (rwlock->cpp_mutex.try_lock() == false) { + retval = SDL_RWLOCK_TIMEDOUT; + } else { + rwlock->write_owner = SDL_ThreadID(); + } } return retval; } -/* Unlock the rwlock */ -extern "C" int -SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +extern "C" void SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - if (!rwlock) { - return SDL_InvalidParamError("rwlock"); - } else if (rwlock->write_owner == SDL_ThreadID()) { - rwlock->write_owner = 0; - rwlock->cpp_mutex.unlock(); - } else { - rwlock->cpp_mutex.unlock_shared(); + if (rwlock) { + if (rwlock->write_owner == SDL_ThreadID()) { + rwlock->write_owner = 0; + rwlock->cpp_mutex.unlock(); + } else { + rwlock->cpp_mutex.unlock_shared(); + } } - return 0; } diff --git a/src/thread/stdcpp/SDL_systhread.cpp b/src/thread/stdcpp/SDL_systhread.cpp index a1d1fd17..29004ad2 100644 --- a/src/thread/stdcpp/SDL_systhread.cpp +++ b/src/thread/stdcpp/SDL_systhread.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/stdcpp/SDL_systhread_c.h b/src/thread/stdcpp/SDL_systhread_c.h index 160e50ad..49aa4e85 100644 --- a/src/thread/stdcpp/SDL_systhread_c.h +++ b/src/thread/stdcpp/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/vita/SDL_syscond.c b/src/thread/vita/SDL_syscond.c deleted file mode 100644 index dd32da5d..00000000 --- a/src/thread/vita/SDL_syscond.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_THREAD_VITA - -/* An implementation of condition variables using semaphores and mutexes */ -/* - This implementation borrows heavily from the BeOS condition variable - implementation, written by Christopher Tate and Owen Smith. Thanks! - */ - -struct SDL_Condition -{ - SDL_Mutex *lock; - int waiting; - int signals; - SDL_Semaphore *wait_sem; - SDL_Semaphore *wait_done; -}; - -/* Create a condition variable */ -SDL_Condition *SDL_CreateCondition(void) -{ - SDL_Condition *cond; - - cond = (SDL_Condition *)SDL_malloc(sizeof(SDL_Condition)); - if (cond != NULL) { - cond->lock = SDL_CreateMutex(); - cond->wait_sem = SDL_CreateSemaphore(0); - cond->wait_done = SDL_CreateSemaphore(0); - cond->waiting = cond->signals = 0; - if (!cond->lock || !cond->wait_sem || !cond->wait_done) { - SDL_DestroyCondition(cond); - cond = NULL; - } - } else { - SDL_OutOfMemory(); - } - return cond; -} - -/* Destroy a condition variable */ -void SDL_DestroyCondition(SDL_Condition *cond) -{ - if (cond != NULL) { - if (cond->wait_sem) { - SDL_DestroySemaphore(cond->wait_sem); - } - if (cond->wait_done) { - SDL_DestroySemaphore(cond->wait_done); - } - if (cond->lock) { - SDL_DestroyMutex(cond->lock); - } - SDL_free(cond); - } -} - -/* Restart one of the threads that are waiting on the condition variable */ -int SDL_SignalCondition(SDL_Condition *cond) -{ - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - SDL_LockMutex(cond->lock); - if (cond->waiting > cond->signals) { - ++cond->signals; - SDL_PostSemaphore(cond->wait_sem); - SDL_UnlockMutex(cond->lock); - SDL_WaitSemaphore(cond->wait_done); - } else { - SDL_UnlockMutex(cond->lock); - } - - return 0; -} - -/* Restart all threads that are waiting on the condition variable */ -int SDL_BroadcastCondition(SDL_Condition *cond) -{ - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - SDL_LockMutex(cond->lock); - if (cond->waiting > cond->signals) { - int i, num_waiting; - - num_waiting = (cond->waiting - cond->signals); - cond->signals = cond->waiting; - for (i = 0; i < num_waiting; ++i) { - SDL_PostSemaphore(cond->wait_sem); - } - /* Now all released threads are blocked here, waiting for us. - Collect them all (and win fabulous prizes!) :-) - */ - SDL_UnlockMutex(cond->lock); - for (i = 0; i < num_waiting; ++i) { - SDL_WaitSemaphore(cond->wait_done); - } - } else { - SDL_UnlockMutex(cond->lock); - } - - return 0; -} - -/* Wait on the condition variable for at most 'timeoutNS' nanoseconds. - The mutex must be locked before entering this function! - The mutex is unlocked during the wait, and locked again after the wait. - -Typical use: - -Thread A: - SDL_LockMutex(lock); - while ( ! condition ) { - SDL_WaitCondition(cond, lock); - } - SDL_UnlockMutex(lock); - -Thread B: - SDL_LockMutex(lock); - ... - condition = true; - ... - SDL_SignalCondition(cond); - SDL_UnlockMutex(lock); - */ -int SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS) -{ - int retval; - - if (cond == NULL) { - return SDL_InvalidParamError("cond"); - } - - /* Obtain the protection mutex, and increment the number of waiters. - This allows the signal mechanism to only perform a signal if there - are waiting threads. - */ - SDL_LockMutex(cond->lock); - ++cond->waiting; - SDL_UnlockMutex(cond->lock); - - /* Unlock the mutex, as is required by condition variable semantics */ - SDL_UnlockMutex(mutex); - - /* Wait for a signal */ - retval = SDL_WaitSemaphoreTimeoutNS(cond->wait_sem, timeoutNS); - - /* Let the signaler know we have completed the wait, otherwise - the signaler can race ahead and get the condition semaphore - if we are stopped between the mutex unlock and semaphore wait, - giving a deadlock. See the following URL for details: - http://www-classic.be.com/aboutbe/benewsletter/volume_III/Issue40.html - */ - SDL_LockMutex(cond->lock); - if (cond->signals > 0) { - /* If we timed out, we need to eat a condition signal */ - if (retval > 0) { - SDL_WaitSemaphore(cond->wait_sem); - } - /* We always notify the signal thread that we are done */ - SDL_PostSemaphore(cond->wait_done); - - /* Signal handshake complete */ - --cond->signals; - } - --cond->waiting; - SDL_UnlockMutex(cond->lock); - - /* Lock the mutex, as is required by condition variable semantics */ - SDL_LockMutex(mutex); - - return retval; -} - -#endif /* SDL_THREAD_VITA */ diff --git a/src/thread/vita/SDL_sysmutex.c b/src/thread/vita/SDL_sysmutex.c index 5e5ee47f..c86508ff 100644 --- a/src/thread/vita/SDL_sysmutex.c +++ b/src/thread/vita/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,63 +32,44 @@ struct SDL_Mutex SceKernelLwMutexWork lock; }; -/* Create a mutex */ SDL_Mutex *SDL_CreateMutex(void) { - SDL_Mutex *mutex = NULL; - SceInt32 res = 0; - - /* Allocate mutex memory */ - mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); - if (mutex != NULL) { - - res = sceKernelCreateLwMutex( - &mutex->lock, - "SDL mutex", - SCE_KERNEL_MUTEX_ATTR_RECURSIVE, - 0, - NULL); + SDL_Mutex *mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex)); + if (mutex) { + const SceInt32 res = sceKernelCreateLwMutex( + &mutex->lock, + "SDL mutex", + SCE_KERNEL_MUTEX_ATTR_RECURSIVE, + 0, + NULL); if (res < 0) { + SDL_free(mutex); + mutex = NULL; SDL_SetError("Error trying to create mutex: %x", res); } - } else { - SDL_OutOfMemory(); } return mutex; } -/* Free the mutex */ void SDL_DestroyMutex(SDL_Mutex *mutex) { - if (mutex != NULL) { + if (mutex) { sceKernelDeleteLwMutex(&mutex->lock); SDL_free(mutex); } } -/* Lock the mutex */ -int SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SceInt32 res = 0; - - if (mutex == NULL) { - return 0; +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + const SceInt32 res = sceKernelLockLwMutex(&mutex->lock, 1, NULL); + SDL_assert(res == SCE_KERNEL_OK); // assume we're in a lot of trouble if this assert fails. } - - res = sceKernelLockLwMutex(&mutex->lock, 1, NULL); - if (res != SCE_KERNEL_OK) { - return SDL_SetError("Error trying to lock mutex: %x", res); - } - - return 0; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED } -/* Try to lock the mutex */ int SDL_TryLockMutex(SDL_Mutex *mutex) { #ifdef SDL_THREADS_DISABLED @@ -96,46 +77,30 @@ int SDL_TryLockMutex(SDL_Mutex *mutex) #else SceInt32 res = 0; - if (mutex == NULL) { + if (!mutex) { return 0; } res = sceKernelTryLockLwMutex(&mutex->lock, 1); switch (res) { - case SCE_KERNEL_OK: - return 0; - break; - case SCE_KERNEL_ERROR_MUTEX_FAILED_TO_OWN: - return SDL_MUTEX_TIMEDOUT; - break; - default: - return SDL_SetError("Error trying to lock mutex: %x", res); - break; + case SCE_KERNEL_OK: return 0; + case SCE_KERNEL_ERROR_MUTEX_FAILED_TO_OWN: return SDL_MUTEX_TIMEDOUT; + default: break; } - return -1; -#endif /* SDL_THREADS_DISABLED */ + SDL_assert(!"Error trying to lock mutex"); // assume we're in a lot of trouble if this assert fails. + return SDL_MUTEX_TIMEDOUT; +#endif // SDL_THREADS_DISABLED } -/* Unlock the mutex */ -int SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { -#ifdef SDL_THREADS_DISABLED - return 0; -#else - SceInt32 res = 0; - - if (mutex == NULL) { - return 0; +#ifndef SDL_THREADS_DISABLED + if (mutex != NULL) { + const SceInt32 res = sceKernelUnlockLwMutex(&mutex->lock, 1); + SDL_assert(res == SCE_KERNEL_OK); // assume we're in a lot of trouble if this assert fails. } - - res = sceKernelUnlockLwMutex(&mutex->lock, 1); - if (res != 0) { - return SDL_SetError("Error trying to unlock mutex: %x", res); - } - - return 0; -#endif /* SDL_THREADS_DISABLED */ +#endif // SDL_THREADS_DISABLED } -#endif /* SDL_THREAD_VITA */ +#endif // SDL_THREAD_VITA diff --git a/src/thread/vita/SDL_sysmutex_c.h b/src/thread/vita/SDL_sysmutex_c.h index 2a7ba0c1..7e84b1a4 100644 --- a/src/thread/vita/SDL_sysmutex_c.h +++ b/src/thread/vita/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/vita/SDL_syssem.c b/src/thread/vita/SDL_syssem.c index 784f88ed..1075c528 100644 --- a/src/thread/vita/SDL_syssem.c +++ b/src/thread/vita/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,7 +42,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_Semaphore *sem; sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem)); - if (sem != NULL) { + if (sem) { /* TODO: Figure out the limit on the maximum value. */ sem->semid = sceKernelCreateSema("SDL sema", 0, initial_value, 255, NULL); if (sem->semid < 0) { @@ -50,8 +50,6 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) SDL_free(sem); sem = NULL; } - } else { - SDL_OutOfMemory(); } return sem; @@ -60,7 +58,7 @@ SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) /* Free the semaphore */ void SDL_DestroySemaphore(SDL_Semaphore *sem) { - if (sem != NULL) { + if (sem) { if (sem->semid > 0) { sceKernelDeleteSema(sem->semid); sem->semid = 0; @@ -71,7 +69,7 @@ void SDL_DestroySemaphore(SDL_Semaphore *sem) } /* TODO: This routine is a bit overloaded. - * If the timeout is 0 then just poll the semaphore; if it's SDL_MUTEX_MAXWAIT, pass + * If the timeout is 0 then just poll the semaphore; if it's -1, pass * NULL to sceKernelWaitSema() so that it waits indefinitely; and if the timeout * is specified, convert it to microseconds. */ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) @@ -80,7 +78,7 @@ int SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS) SceUInt *pTimeout; int res; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -116,7 +114,7 @@ Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem) SceKernelSemaInfo info; info.size = sizeof(info); - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -132,7 +130,7 @@ int SDL_PostSemaphore(SDL_Semaphore *sem) { int res; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } diff --git a/src/thread/vita/SDL_systhread.c b/src/thread/vita/SDL_systhread.c index 013de113..fd6dad9d 100644 --- a/src/thread/vita/SDL_systhread.c +++ b/src/thread/vita/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/vita/SDL_systhread_c.h b/src/thread/vita/SDL_systhread_c.h index de7a0a60..eb5b1c73 100644 --- a/src/thread/vita/SDL_systhread_c.h +++ b/src/thread/vita/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/windows/SDL_syscond_cv.c b/src/thread/windows/SDL_syscond_cv.c index b0142d8b..4dd4d97a 100644 --- a/src/thread/windows/SDL_syscond_cv.c +++ b/src/thread/windows/SDL_syscond_cv.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -80,29 +80,20 @@ typedef struct SDL_cond_cv static SDL_Condition *SDL_CreateCondition_cv(void) { - SDL_cond_cv *cond; - /* Relies on CONDITION_VARIABLE_INIT == 0. */ - cond = (SDL_cond_cv *)SDL_calloc(1, sizeof(*cond)); - if (cond == NULL) { - SDL_OutOfMemory(); - } - - return (SDL_Condition *)cond; + return (SDL_Condition *)SDL_calloc(1, sizeof(SDL_cond_cv)); } static void SDL_DestroyCondition_cv(SDL_Condition *cond) { - if (cond != NULL) { - /* There are no kernel allocated resources */ - SDL_free(cond); - } + /* There are no kernel allocated resources */ + SDL_free(cond); } static int SDL_SignalCondition_cv(SDL_Condition *_cond) { SDL_cond_cv *cond = (SDL_cond_cv *)_cond; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -114,7 +105,7 @@ static int SDL_SignalCondition_cv(SDL_Condition *_cond) static int SDL_BroadcastCondition_cv(SDL_Condition *_cond) { SDL_cond_cv *cond = (SDL_cond_cv *)_cond; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } @@ -129,10 +120,10 @@ static int SDL_WaitConditionTimeoutNS_cv(SDL_Condition *_cond, SDL_Mutex *_mutex DWORD timeout; int ret; - if (cond == NULL) { + if (!cond) { return SDL_InvalidParamError("cond"); } - if (_mutex == NULL) { + if (!_mutex) { return SDL_InvalidParamError("mutex"); } @@ -208,13 +199,13 @@ static const SDL_cond_impl_t SDL_cond_impl_generic = { SDL_Condition *SDL_CreateCondition(void) { - if (SDL_cond_impl_active.Create == NULL) { + if (!SDL_cond_impl_active.Create) { const SDL_cond_impl_t *impl = NULL; if (SDL_mutex_impl_active.Type == SDL_MUTEX_INVALID) { /* The mutex implementation isn't decided yet, trigger it */ SDL_Mutex *mutex = SDL_CreateMutex(); - if (mutex == NULL) { + if (!mutex) { return NULL; } SDL_DestroyMutex(mutex); diff --git a/src/thread/windows/SDL_sysmutex.c b/src/thread/windows/SDL_sysmutex.c index 16cc81f9..7272b00e 100644 --- a/src/thread/windows/SDL_sysmutex.c +++ b/src/thread/windows/SDL_sysmutex.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -58,15 +58,10 @@ static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL; static SDL_Mutex *SDL_CreateMutex_srw(void) { - SDL_mutex_srw *mutex; - - mutex = (SDL_mutex_srw *)SDL_calloc(1, sizeof(*mutex)); - if (mutex == NULL) { - SDL_OutOfMemory(); + SDL_mutex_srw *mutex = (SDL_mutex_srw *)SDL_calloc(1, sizeof(*mutex)); + if (mutex) { + pInitializeSRWLock(&mutex->srw); } - - pInitializeSRWLock(&mutex->srw); - return (SDL_Mutex *)mutex; } @@ -76,12 +71,11 @@ static void SDL_DestroyMutex_srw(SDL_Mutex *mutex) SDL_free(mutex); } -static int SDL_LockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_LockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; - DWORD this_thread; + const DWORD this_thread = GetCurrentThreadId(); - this_thread = GetCurrentThreadId(); if (mutex->owner == this_thread) { ++mutex->count; } else { @@ -94,16 +88,14 @@ static int SDL_LockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* mutex->owner = this_thread; mutex->count = 1; } - return 0; } static int SDL_TryLockMutex_srw(SDL_Mutex *_mutex) { SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; - DWORD this_thread; + const DWORD this_thread = GetCurrentThreadId(); int retval = 0; - this_thread = GetCurrentThreadId(); if (mutex->owner == this_thread) { ++mutex->count; } else { @@ -118,7 +110,7 @@ static int SDL_TryLockMutex_srw(SDL_Mutex *_mutex) return retval; } -static int SDL_UnlockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_UnlockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; @@ -128,10 +120,8 @@ static int SDL_UnlockMutex_srw(SDL_Mutex *_mutex) SDL_NO_THREAD_SAFETY_ANALYSIS pReleaseSRWLockExclusive(&mutex->srw); } } else { - return SDL_SetError("mutex not owned by this thread"); + SDL_assert(!"mutex not owned by this thread"); // undefined behavior...! } - - return 0; } static const SDL_mutex_impl_t SDL_mutex_impl_srw = { @@ -147,64 +137,44 @@ static const SDL_mutex_impl_t SDL_mutex_impl_srw = { * Fallback Mutex implementation using Critical Sections (before Win 7) */ -/* Create a mutex */ static SDL_Mutex *SDL_CreateMutex_cs(void) { - SDL_mutex_cs *mutex; - - /* Allocate mutex memory */ - mutex = (SDL_mutex_cs *)SDL_malloc(sizeof(*mutex)); - if (mutex != NULL) { - /* Initialize */ - /* On SMP systems, a non-zero spin count generally helps performance */ + SDL_mutex_cs *mutex = (SDL_mutex_cs *)SDL_malloc(sizeof(*mutex)); + if (mutex) { + // Initialize + // On SMP systems, a non-zero spin count generally helps performance #ifdef __WINRT__ InitializeCriticalSectionEx(&mutex->cs, 2000, 0); #else InitializeCriticalSectionAndSpinCount(&mutex->cs, 2000); #endif - } else { - SDL_OutOfMemory(); } return (SDL_Mutex *)mutex; } -/* Free the mutex */ static void SDL_DestroyMutex_cs(SDL_Mutex *mutex_) { SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; - DeleteCriticalSection(&mutex->cs); SDL_free(mutex); } -/* Lock the mutex */ -static int SDL_LockMutex_cs(SDL_Mutex *mutex_) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_LockMutex_cs(SDL_Mutex *mutex_) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; - EnterCriticalSection(&mutex->cs); - return 0; } -/* TryLock the mutex */ static int SDL_TryLockMutex_cs(SDL_Mutex *mutex_) { SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; - int retval = 0; - - if (TryEnterCriticalSection(&mutex->cs) == 0) { - retval = SDL_MUTEX_TIMEDOUT; - } - return retval; + return (TryEnterCriticalSection(&mutex->cs) == 0) ? SDL_MUTEX_TIMEDOUT : 0; } -/* Unlock the mutex */ -static int SDL_UnlockMutex_cs(SDL_Mutex *mutex_) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_UnlockMutex_cs(SDL_Mutex *mutex_) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; - LeaveCriticalSection(&mutex->cs); - return 0; } static const SDL_mutex_impl_t SDL_mutex_impl_cs = { @@ -222,23 +192,23 @@ static const SDL_mutex_impl_t SDL_mutex_impl_cs = { SDL_Mutex *SDL_CreateMutex(void) { - if (SDL_mutex_impl_active.Create == NULL) { - /* Default to fallback implementation */ + if (!SDL_mutex_impl_active.Create) { + // Default to fallback implementation const SDL_mutex_impl_t *impl = &SDL_mutex_impl_cs; if (!SDL_GetHintBoolean(SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS, SDL_FALSE)) { #ifdef __WINRT__ - /* Link statically on this platform */ + // Link statically on this platform impl = &SDL_mutex_impl_srw; #else - /* Try faster implementation for Windows 7 and newer */ + // Try faster implementation for Windows 7 and newer HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll")); if (kernel32) { - /* Requires Vista: */ + // Requires Vista: pInitializeSRWLock = (pfnInitializeSRWLock)GetProcAddress(kernel32, "InitializeSRWLock"); pReleaseSRWLockExclusive = (pfnReleaseSRWLockExclusive)GetProcAddress(kernel32, "ReleaseSRWLockExclusive"); pAcquireSRWLockExclusive = (pfnAcquireSRWLockExclusive)GetProcAddress(kernel32, "AcquireSRWLockExclusive"); - /* Requires 7: */ + // Requires 7: pTryAcquireSRWLockExclusive = (pfnTryAcquireSRWLockExclusive)GetProcAddress(kernel32, "TryAcquireSRWLockExclusive"); if (pInitializeSRWLock && pReleaseSRWLockExclusive && pAcquireSRWLockExclusive && pTryAcquireSRWLockExclusive) { impl = &SDL_mutex_impl_srw; @@ -247,7 +217,7 @@ SDL_Mutex *SDL_CreateMutex(void) #endif } - /* Copy instead of using pointer to save one level of indirection */ + // Copy instead of using pointer to save one level of indirection SDL_copyp(&SDL_mutex_impl_active, impl); } return SDL_mutex_impl_active.Create(); @@ -260,31 +230,23 @@ void SDL_DestroyMutex(SDL_Mutex *mutex) } } -int SDL_LockMutex(SDL_Mutex *mutex) +void SDL_LockMutex(SDL_Mutex *mutex) { - if (mutex == NULL) { - return 0; + if (mutex) { + SDL_mutex_impl_active.Lock(mutex); } - - return SDL_mutex_impl_active.Lock(mutex); } int SDL_TryLockMutex(SDL_Mutex *mutex) { - if (mutex == NULL) { - return 0; - } - - return SDL_mutex_impl_active.TryLock(mutex); + return mutex ? SDL_mutex_impl_active.TryLock(mutex) : 0; } -int SDL_UnlockMutex(SDL_Mutex *mutex) +void SDL_UnlockMutex(SDL_Mutex *mutex) { - if (mutex == NULL) { - return 0; + if (mutex) { + SDL_mutex_impl_active.Unlock(mutex); } - - return SDL_mutex_impl_active.Unlock(mutex); } -#endif /* SDL_THREAD_WINDOWS */ +#endif // SDL_THREAD_WINDOWS diff --git a/src/thread/windows/SDL_sysmutex_c.h b/src/thread/windows/SDL_sysmutex_c.h index 5a04e143..1fe81ba0 100644 --- a/src/thread/windows/SDL_sysmutex_c.h +++ b/src/thread/windows/SDL_sysmutex_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,9 +23,9 @@ #include "../../core/windows/SDL_windows.h" typedef SDL_Mutex *(*pfnSDL_CreateMutex)(void); -typedef int (*pfnSDL_LockMutex)(SDL_Mutex *); +typedef void (*pfnSDL_LockMutex)(SDL_Mutex *); typedef int (*pfnSDL_TryLockMutex)(SDL_Mutex *); -typedef int (*pfnSDL_UnlockMutex)(SDL_Mutex *); +typedef void (*pfnSDL_UnlockMutex)(SDL_Mutex *); typedef void (*pfnSDL_DestroyMutex)(SDL_Mutex *); typedef enum diff --git a/src/thread/windows/SDL_sysrwlock_srw.c b/src/thread/windows/SDL_sysrwlock_srw.c index 489ea5cf..a02cfff2 100644 --- a/src/thread/windows/SDL_sysrwlock_srw.c +++ b/src/thread/windows/SDL_sysrwlock_srw.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,11 +57,11 @@ static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL; typedef SDL_RWLock *(*pfnSDL_CreateRWLock)(void); typedef void (*pfnSDL_DestroyRWLock)(SDL_RWLock *); -typedef int (*pfnSDL_LockRWLockForReading)(SDL_RWLock *); -typedef int (*pfnSDL_LockRWLockForWriting)(SDL_RWLock *); +typedef void (*pfnSDL_LockRWLockForReading)(SDL_RWLock *); +typedef void (*pfnSDL_LockRWLockForWriting)(SDL_RWLock *); typedef int (*pfnSDL_TryLockRWLockForReading)(SDL_RWLock *); typedef int (*pfnSDL_TryLockRWLockForWriting)(SDL_RWLock *); -typedef int (*pfnSDL_UnlockRWLock)(SDL_RWLock *); +typedef void (*pfnSDL_UnlockRWLock)(SDL_RWLock *); typedef struct SDL_rwlock_impl_t { @@ -88,10 +88,9 @@ typedef struct SDL_rwlock_srw static SDL_RWLock *SDL_CreateRWLock_srw(void) { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *)SDL_calloc(1, sizeof(*rwlock)); - if (rwlock == NULL) { - SDL_OutOfMemory(); + if (rwlock) { + pInitializeSRWLock(&rwlock->srw); } - pInitializeSRWLock(&rwlock->srw); return (SDL_RWLock *)rwlock; } @@ -104,35 +103,28 @@ static void SDL_DestroyRWLock_srw(SDL_RWLock *_rwlock) } } -static int SDL_LockRWLockForReading_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_LockRWLockForReading_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); + if (rwlock != NULL) { + pAcquireSRWLockShared(&rwlock->srw); } - pAcquireSRWLockShared(&rwlock->srw); - return 0; } -static int SDL_LockRWLockForWriting_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_LockRWLockForWriting_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); + if (rwlock != NULL) { + pAcquireSRWLockExclusive(&rwlock->srw); + rwlock->write_owner = SDL_ThreadID(); } - pAcquireSRWLockExclusive(&rwlock->srw); - rwlock->write_owner = SDL_ThreadID(); - return 0; } static int SDL_TryLockRWLockForReading_srw(SDL_RWLock *_rwlock) { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; int retval = 0; - - if (rwlock == NULL) { - retval = SDL_InvalidParamError("rwlock"); - } else { + if (rwlock) { retval = pTryAcquireSRWLockShared(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT; } return retval; @@ -142,27 +134,23 @@ static int SDL_TryLockRWLockForWriting_srw(SDL_RWLock *_rwlock) { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; int retval = 0; - - if (rwlock == NULL) { - retval = SDL_InvalidParamError("rwlock"); - } else { + if (rwlock) { retval = pTryAcquireSRWLockExclusive(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT; } return retval; } -static int SDL_UnlockRWLock_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +static void SDL_UnlockRWLock_srw(SDL_RWLock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; - if (rwlock == NULL) { - return SDL_InvalidParamError("rwlock"); - } else if (rwlock->write_owner == SDL_ThreadID()) { - rwlock->write_owner = 0; - pReleaseSRWLockExclusive(&rwlock->srw); - } else { - pReleaseSRWLockShared(&rwlock->srw); + if (rwlock != NULL) { + if (rwlock->write_owner == SDL_ThreadID()) { + rwlock->write_owner = 0; + pReleaseSRWLockExclusive(&rwlock->srw); + } else { + pReleaseSRWLockShared(&rwlock->srw); + } } - return 0; } static const SDL_rwlock_impl_t SDL_rwlock_impl_srw = { @@ -193,7 +181,7 @@ static const SDL_rwlock_impl_t SDL_rwlock_impl_generic = { SDL_RWLock *SDL_CreateRWLock(void) { - if (SDL_rwlock_impl_active.Create == NULL) { + if (!SDL_rwlock_impl_active.Create) { const SDL_rwlock_impl_t *impl; #ifdef __WINRT__ @@ -229,30 +217,39 @@ SDL_RWLock *SDL_CreateRWLock(void) void SDL_DestroyRWLock(SDL_RWLock *rwlock) { - SDL_rwlock_impl_active.Destroy(rwlock); + if (rwlock) { + SDL_rwlock_impl_active.Destroy(rwlock); + } } -int SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForReading(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - return SDL_rwlock_impl_active.LockForReading(rwlock); + if (rwlock != NULL) { + SDL_rwlock_impl_active.LockForReading(rwlock); + } } -int SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_LockRWLockForWriting(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - return SDL_rwlock_impl_active.LockForWriting(rwlock); + if (rwlock != NULL) { + SDL_rwlock_impl_active.LockForWriting(rwlock); + } } int SDL_TryLockRWLockForReading(SDL_RWLock *rwlock) { - return SDL_rwlock_impl_active.TryLockForReading(rwlock); + return rwlock ? SDL_rwlock_impl_active.TryLockForReading(rwlock) : 0; } int SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock) { - return SDL_rwlock_impl_active.TryLockForWriting(rwlock); + return rwlock ? SDL_rwlock_impl_active.TryLockForWriting(rwlock) : 0; } -int SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +void SDL_UnlockRWLock(SDL_RWLock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS // clang doesn't know about NULL mutexes { - return SDL_rwlock_impl_active.Unlock(rwlock); + if (rwlock != NULL) { + SDL_rwlock_impl_active.Unlock(rwlock); + } } + diff --git a/src/thread/windows/SDL_syssem.c b/src/thread/windows/SDL_syssem.c index b1097e31..7ad6f619 100644 --- a/src/thread/windows/SDL_syssem.c +++ b/src/thread/windows/SDL_syssem.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -83,19 +83,15 @@ static SDL_Semaphore *SDL_CreateSemaphore_atom(Uint32 initial_value) SDL_sem_atom *sem; sem = (SDL_sem_atom *)SDL_malloc(sizeof(*sem)); - if (sem != NULL) { + if (sem) { sem->count = initial_value; - } else { - SDL_OutOfMemory(); } return (SDL_Semaphore *)sem; } static void SDL_DestroySemaphore_atom(SDL_Semaphore *sem) { - if (sem != NULL) { - SDL_free(sem); - } + SDL_free(sem); } static int SDL_WaitSemaphoreTimeoutNS_atom(SDL_Semaphore *_sem, Sint64 timeoutNS) @@ -106,7 +102,7 @@ static int SDL_WaitSemaphoreTimeoutNS_atom(SDL_Semaphore *_sem, Sint64 timeoutNS Uint64 deadline; DWORD timeout_eff; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -176,7 +172,7 @@ static Uint32 SDL_GetSemaphoreValue_atom(SDL_Semaphore *_sem) { SDL_sem_atom *sem = (SDL_sem_atom *)_sem; - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -188,7 +184,7 @@ static int SDL_PostSemaphore_atom(SDL_Semaphore *_sem) { SDL_sem_atom *sem = (SDL_sem_atom *)_sem; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -224,8 +220,9 @@ static SDL_Semaphore *SDL_CreateSemaphore_kern(Uint32 initial_value) /* Allocate sem memory */ sem = (SDL_sem_kern *)SDL_malloc(sizeof(*sem)); - if (sem != NULL) { + if (sem) { /* Create the semaphore, with max value 32K */ +// !!! FIXME: CreateSemaphoreEx is available in Vista and later, so if XP support is dropped, we can lose this #ifdef. #ifdef __WINRT__ sem->id = CreateSemaphoreEx(NULL, initial_value, 32 * 1024, NULL, 0, SEMAPHORE_ALL_ACCESS); #else @@ -237,8 +234,6 @@ static SDL_Semaphore *SDL_CreateSemaphore_kern(Uint32 initial_value) SDL_free(sem); sem = NULL; } - } else { - SDL_OutOfMemory(); } return (SDL_Semaphore *)sem; } @@ -247,7 +242,7 @@ static SDL_Semaphore *SDL_CreateSemaphore_kern(Uint32 initial_value) static void SDL_DestroySemaphore_kern(SDL_Semaphore *_sem) { SDL_sem_kern *sem = (SDL_sem_kern *)_sem; - if (sem != NULL) { + if (sem) { if (sem->id) { CloseHandle(sem->id); sem->id = 0; @@ -262,7 +257,7 @@ static int SDL_WaitSemaphoreTimeoutNS_kern(SDL_Semaphore *_sem, Sint64 timeoutNS int retval; DWORD dwMilliseconds; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } @@ -290,7 +285,7 @@ static int SDL_WaitSemaphoreTimeoutNS_kern(SDL_Semaphore *_sem, Sint64 timeoutNS static Uint32 SDL_GetSemaphoreValue_kern(SDL_Semaphore *_sem) { SDL_sem_kern *sem = (SDL_sem_kern *)_sem; - if (sem == NULL) { + if (!sem) { SDL_InvalidParamError("sem"); return 0; } @@ -300,7 +295,7 @@ static Uint32 SDL_GetSemaphoreValue_kern(SDL_Semaphore *_sem) static int SDL_PostSemaphore_kern(SDL_Semaphore *_sem) { SDL_sem_kern *sem = (SDL_sem_kern *)_sem; - if (sem == NULL) { + if (!sem) { return SDL_InvalidParamError("sem"); } /* Increase the counter in the first place, because @@ -330,7 +325,7 @@ static const SDL_sem_impl_t SDL_sem_impl_kern = { SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value) { - if (SDL_sem_impl_active.Create == NULL) { + if (!SDL_sem_impl_active.Create) { /* Default to fallback implementation */ const SDL_sem_impl_t *impl = &SDL_sem_impl_kern; diff --git a/src/thread/windows/SDL_systhread.c b/src/thread/windows/SDL_systhread.c index 8755f67c..693700be 100644 --- a/src/thread/windows/SDL_systhread.c +++ b/src/thread/windows/SDL_systhread.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -47,7 +47,7 @@ static DWORD RunThread(void *data) SDL_Thread *thread = (SDL_Thread *)data; pfnSDL_CurrentEndThread pfnEndThread = (pfnSDL_CurrentEndThread)thread->endfunc; SDL_RunThread(thread); - if (pfnEndThread != NULL) { + if (pfnEndThread) { pfnEndThread(0); } return 0; @@ -96,7 +96,7 @@ int SDL_SYS_CreateThread(SDL_Thread *thread) RunThreadViaCreateThread, thread, flags, &threadid); } - if (thread->handle == NULL) { + if (!thread->handle) { return SDL_SetError("Not enough resources to create thread"); } return 0; @@ -116,7 +116,7 @@ typedef HRESULT(WINAPI *pfnSetThreadDescription)(HANDLE, PCWSTR); void SDL_SYS_SetupThread(const char *name) { - if (name != NULL) { + if (name) { #ifndef __WINRT__ /* !!! FIXME: There's no LoadLibrary() in WinRT; don't know if SetThreadDescription is available there at all at the moment. */ static pfnSetThreadDescription pSetThreadDescription = NULL; static HMODULE kernel32 = NULL; @@ -128,7 +128,7 @@ void SDL_SYS_SetupThread(const char *name) } } - if (pSetThreadDescription != NULL) { + if (pSetThreadDescription) { WCHAR *strw = WIN_UTF8ToStringW(name); if (strw) { pSetThreadDescription(GetCurrentThread(), strw); diff --git a/src/thread/windows/SDL_systhread_c.h b/src/thread/windows/SDL_systhread_c.h index 180b82ea..568551d6 100644 --- a/src/thread/windows/SDL_systhread_c.h +++ b/src/thread/windows/SDL_systhread_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/thread/windows/SDL_systls.c b/src/thread/windows/SDL_systls.c index 3a0dcc97..4eb2d53d 100644 --- a/src/thread/windows/SDL_systls.c +++ b/src/thread/windows/SDL_systls.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/SDL_timer.c b/src/timer/SDL_timer.c index 0052ff60..d3501155 100644 --- a/src/timer/SDL_timer.c +++ b/src/timer/SDL_timer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -141,7 +141,7 @@ static int SDLCALL SDL_TimerThread(void *_data) } /* Initial delay if there are no timers */ - delay = (Uint64)SDL_MUTEX_MAXWAIT; + delay = (Uint64)-1; tick = SDL_GetTicksNS(); @@ -171,7 +171,7 @@ static int SDLCALL SDL_TimerThread(void *_data) current->scheduled = tick + interval; SDL_AddTimerInternal(data, current); } else { - if (freelist_head == NULL) { + if (!freelist_head) { freelist_head = current; } if (freelist_tail) { @@ -296,8 +296,7 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *para SDL_RemoveTimer(timer->timerID); } else { timer = (SDL_Timer *)SDL_malloc(sizeof(*timer)); - if (timer == NULL) { - SDL_OutOfMemory(); + if (!timer) { return 0; } } @@ -309,9 +308,8 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *para SDL_AtomicSet(&timer->canceled, 0); entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry)); - if (entry == NULL) { + if (!entry) { SDL_free(timer); - SDL_OutOfMemory(); return 0; } entry->timer = timer; @@ -422,8 +420,7 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *para SDL_TimerMap *entry; entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry)); - if (entry == NULL) { - SDL_OutOfMemory(); + if (!entry) { return 0; } entry->timerID = ++data->nextID; diff --git a/src/timer/SDL_timer_c.h b/src/timer/SDL_timer_c.h index dd4e0268..161fb914 100644 --- a/src/timer/SDL_timer_c.h +++ b/src/timer/SDL_timer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/haiku/SDL_systimer.c b/src/timer/haiku/SDL_systimer.c index 6bc51e71..726e4150 100644 --- a/src/timer/haiku/SDL_systimer.c +++ b/src/timer/haiku/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/n3ds/SDL_systimer.c b/src/timer/n3ds/SDL_systimer.c index fc8fdc30..99389a4c 100644 --- a/src/timer/n3ds/SDL_systimer.c +++ b/src/timer/n3ds/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/ngage/SDL_systimer.cpp b/src/timer/ngage/SDL_systimer.cpp index 41037a86..f7f11ea3 100644 --- a/src/timer/ngage/SDL_systimer.cpp +++ b/src/timer/ngage/SDL_systimer.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/ps2/SDL_systimer.c b/src/timer/ps2/SDL_systimer.c index 2ba22807..7070e17a 100644 --- a/src/timer/ps2/SDL_systimer.c +++ b/src/timer/ps2/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/psp/SDL_systimer.c b/src/timer/psp/SDL_systimer.c index 7ccf73fe..9df7c11a 100644 --- a/src/timer/psp/SDL_systimer.c +++ b/src/timer/psp/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/unix/SDL_systimer.c b/src/timer/unix/SDL_systimer.c index 3691a08d..a9c6ccbb 100644 --- a/src/timer/unix/SDL_systimer.c +++ b/src/timer/unix/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/vita/SDL_systimer.c b/src/timer/vita/SDL_systimer.c index f7523248..a99b8e1c 100644 --- a/src/timer/vita/SDL_systimer.c +++ b/src/timer/vita/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/timer/windows/SDL_systimer.c b/src/timer/windows/SDL_systimer.c index 19ab9a5c..54f56de1 100644 --- a/src/timer/windows/SDL_systimer.c +++ b/src/timer/windows/SDL_systimer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_RLEaccel.c b/src/video/SDL_RLEaccel.c index 5c478a58..cc475743 100644 --- a/src/video/SDL_RLEaccel.c +++ b/src/video/SDL_RLEaccel.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -1023,7 +1023,7 @@ static int RLEAlphaSurface(SDL_Surface *surface) SDL_PixelFormat *, SDL_PixelFormat *); dest = surface->map->dst; - if (dest == NULL) { + if (!dest) { return -1; } df = dest->format; @@ -1080,8 +1080,8 @@ static int RLEAlphaSurface(SDL_Surface *surface) maxsize += sizeof(RLEDestFormat); rlebuf = (Uint8 *)SDL_malloc(maxsize); - if (rlebuf == NULL) { - return SDL_OutOfMemory(); + if (!rlebuf) { + return -1; } { /* save the destination format so we can undo the encoding later */ @@ -1226,7 +1226,7 @@ static int RLEAlphaSurface(SDL_Surface *surface) /* reallocate the buffer to release unused memory */ { Uint8 *p = SDL_realloc(rlebuf, dst - rlebuf); - if (p == NULL) { + if (!p) { p = rlebuf; } surface->map->data = p; @@ -1299,8 +1299,8 @@ static int RLEColorkeySurface(SDL_Surface *surface) } rlebuf = (Uint8 *)SDL_malloc(maxsize); - if (rlebuf == NULL) { - return SDL_OutOfMemory(); + if (!rlebuf) { + return -1; } /* Set up the conversion */ @@ -1394,7 +1394,7 @@ static int RLEColorkeySurface(SDL_Surface *surface) { /* If SDL_realloc returns NULL, the original block is left intact */ Uint8 *p = SDL_realloc(rlebuf, dst - rlebuf); - if (p == NULL) { + if (!p) { p = rlebuf; } surface->map->data = p; @@ -1496,7 +1496,7 @@ static SDL_bool UnRLEAlpha(SDL_Surface *surface) } surface->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), size); - if (surface->pixels == NULL) { + if (!surface->pixels) { return SDL_FALSE; } surface->flags |= SDL_SIMD_ALIGNED; @@ -1568,7 +1568,7 @@ void SDL_UnRLESurface(SDL_Surface *surface, int recode) return; } surface->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), size); - if (surface->pixels == NULL) { + if (!surface->pixels) { /* Oh crap... */ surface->flags |= SDL_RLEACCEL; return; diff --git a/src/video/SDL_RLEaccel_c.h b/src/video/SDL_RLEaccel_c.h index ac37c116..cbf4c490 100644 --- a/src/video/SDL_RLEaccel_c.h +++ b/src/video/SDL_RLEaccel_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_blit.c b/src/video/SDL_blit.c index cb3f25a1..fb82675b 100644 --- a/src/video/SDL_blit.c +++ b/src/video/SDL_blit.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -126,7 +126,7 @@ static SDL_BlitFunc SDL_ChooseBlitFunc(Uint32 src_format, Uint32 dst_format, int SDL_BlitFuncEntry *entries) { int i, flagcheck = (flags & (SDL_COPY_MODULATE_COLOR | SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL | SDL_COPY_COLORKEY | SDL_COPY_NEAREST)); - static int features = 0x7fffffff; + static unsigned int features = 0x7fffffff; /* Get the available CPU features */ if (features == 0x7fffffff) { @@ -247,7 +247,7 @@ int SDL_CalculateBlit(SDL_Surface *surface) } #endif #if SDL_HAVE_BLIT_AUTO - if (blit == NULL) { + if (!blit) { Uint32 src_format = surface->format->format; Uint32 dst_format = dst->format->format; @@ -258,7 +258,7 @@ int SDL_CalculateBlit(SDL_Surface *surface) #endif #ifndef TEST_SLOW_BLIT - if (blit == NULL) + if (!blit) #endif { Uint32 src_format = surface->format->format; @@ -274,7 +274,7 @@ int SDL_CalculateBlit(SDL_Surface *surface) map->data = blit; /* Make sure we have a blit function */ - if (blit == NULL) { + if (!blit) { SDL_InvalidateMap(map); return SDL_SetError("Blit combination not supported"); } diff --git a/src/video/SDL_blit.h b/src/video/SDL_blit.h index 0568dce3..be30fa64 100644 --- a/src/video/SDL_blit.h +++ b/src/video/SDL_blit.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -79,7 +79,7 @@ typedef struct Uint32 src_format; Uint32 dst_format; int flags; - int cpu; + unsigned int cpu; SDL_BlitFunc func; } SDL_BlitFuncEntry; @@ -260,6 +260,14 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface); a = (a * 3) / 255; \ Pixel = (a << 30) | (r << 20) | (g << 10) | b; \ } +#define ABGR2101010_FROM_RGBA(Pixel, r, g, b, a) \ + { \ + r = r ? ((r << 2) | 0x3) : 0; \ + g = g ? ((g << 2) | 0x3) : 0; \ + b = b ? ((b << 2) | 0x3) : 0; \ + a = (a * 3) / 255; \ + Pixel = (a << 30) | (b << 20) | (g << 10) | r; \ + } #define ASSEMBLE_RGB(buf, bpp, fmt, r, g, b) \ { \ switch (bpp) { \ @@ -352,6 +360,13 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface); b = ((Pixel >> 2) & 0xFF); \ a = SDL_expand_byte[6][(Pixel >> 30)]; \ } +#define RGBA_FROM_ABGR2101010(Pixel, r, g, b, a) \ + { \ + r = ((Pixel >> 2) & 0xFF); \ + g = ((Pixel >> 12) & 0xFF); \ + b = ((Pixel >> 22) & 0xFF); \ + a = SDL_expand_byte[6][(Pixel >> 30)]; \ + } #define DISEMBLE_RGBA(buf, bpp, fmt, Pixel, r, g, b, a) \ do { \ switch (bpp) { \ diff --git a/src/video/SDL_blit_0.c b/src/video/SDL_blit_0.c index dd859636..6fc4356e 100644 --- a/src/video/SDL_blit_0.c +++ b/src/video/SDL_blit_0.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,8 +26,11 @@ /* Functions to blit from bitmaps to other surfaces */ -static void BlitBto1(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto1(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int c; int width, height; Uint8 *src, *map, *dst; @@ -41,22 +44,28 @@ static void BlitBto1(SDL_BlitInfo *info) dst = info->dst; dstskip = info->dst_skip; map = info->table; - srcskip += width - (width + 7) / 8; + + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; if (map) { - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { *dst = map[bit]; } dst++; - byte >>= 1; + byte >>= srcbpp; } src += srcskip; dst += dstskip; @@ -65,34 +74,34 @@ static void BlitBto1(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { *dst = map[bit]; } dst++; - byte <<= 1; + byte <<= srcbpp; } src += srcskip; dst += dstskip; } } } else { - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { *dst = bit; } dst++; - byte >>= 1; + byte >>= srcbpp; } src += srcskip; dst += dstskip; @@ -101,15 +110,15 @@ static void BlitBto1(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { *dst = bit; } dst++; - byte <<= 1; + byte <<= srcbpp; } src += srcskip; dst += dstskip; @@ -118,8 +127,11 @@ static void BlitBto1(SDL_BlitInfo *info) } } -static void BlitBto2(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto2(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int c; int width, height; Uint8 *src; @@ -134,20 +146,26 @@ static void BlitBto2(SDL_BlitInfo *info) dst = (Uint16 *)info->dst; dstskip = info->dst_skip / 2; map = (Uint16 *)info->table; - srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; + + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { *dst = map[bit]; } - byte >>= 1; + byte >>= srcbpp; dst++; } src += srcskip; @@ -157,14 +175,14 @@ static void BlitBto2(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { *dst = map[bit]; } - byte <<= 1; + byte <<= srcbpp; dst++; } src += srcskip; @@ -173,8 +191,11 @@ static void BlitBto2(SDL_BlitInfo *info) } } -static void BlitBto3(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto3(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int c, o; int width, height; Uint8 *src, *map, *dst; @@ -188,23 +209,29 @@ static void BlitBto3(SDL_BlitInfo *info) dst = info->dst; dstskip = info->dst_skip; map = info->table; - srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; + + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { o = bit * 4; dst[0] = map[o++]; dst[1] = map[o++]; dst[2] = map[o++]; } - byte >>= 1; + byte >>= srcbpp; dst += 3; } src += srcskip; @@ -214,17 +241,17 @@ static void BlitBto3(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { o = bit * 4; dst[0] = map[o++]; dst[1] = map[o++]; dst[2] = map[o++]; } - byte <<= 1; + byte <<= srcbpp; dst += 3; } src += srcskip; @@ -233,8 +260,11 @@ static void BlitBto3(SDL_BlitInfo *info) } } -static void BlitBto4(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto4(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width, height; Uint8 *src; Uint32 *map, *dst; @@ -249,20 +279,26 @@ static void BlitBto4(SDL_BlitInfo *info) dst = (Uint32 *)info->dst; dstskip = info->dst_skip / 4; map = (Uint32 *)info->table; - srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; + + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { *dst = map[bit]; } - byte >>= 1; + byte >>= srcbpp; dst++; } src += srcskip; @@ -272,14 +308,14 @@ static void BlitBto4(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { *dst = map[bit]; } - byte <<= 1; + byte <<= srcbpp; dst++; } src += srcskip; @@ -288,8 +324,11 @@ static void BlitBto4(SDL_BlitInfo *info) } } -static void BlitBto1Key(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto1Key(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -301,22 +340,27 @@ static void BlitBto1Key(SDL_BlitInfo *info) int c; /* Set up some basic variables */ - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; if (palmap) { - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { *dst = palmap[bit]; } dst++; - byte >>= 1; + byte >>= srcbpp; } src += srcskip; dst += dstskip; @@ -325,34 +369,34 @@ static void BlitBto1Key(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { *dst = palmap[bit]; } dst++; - byte <<= 1; + byte <<= srcbpp; } src += srcskip; dst += dstskip; } } } else { - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { *dst = bit; } dst++; - byte >>= 1; + byte >>= srcbpp; } src += srcskip; dst += dstskip; @@ -361,15 +405,15 @@ static void BlitBto1Key(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { *dst = bit; } dst++; - byte <<= 1; + byte <<= srcbpp; } src += srcskip; dst += dstskip; @@ -378,8 +422,11 @@ static void BlitBto1Key(SDL_BlitInfo *info) } } -static void BlitBto2Key(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto2Key(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -391,21 +438,26 @@ static void BlitBto2Key(SDL_BlitInfo *info) int c; /* Set up some basic variables */ - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; dstskip /= 2; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { *dstp = ((Uint16 *)palmap)[bit]; } - byte >>= 1; + byte >>= srcbpp; dstp++; } src += srcskip; @@ -415,14 +467,14 @@ static void BlitBto2Key(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { *dstp = ((Uint16 *)palmap)[bit]; } - byte <<= 1; + byte <<= srcbpp; dstp++; } src += srcskip; @@ -431,8 +483,11 @@ static void BlitBto2Key(SDL_BlitInfo *info) } } -static void BlitBto3Key(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto3Key(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -444,20 +499,25 @@ static void BlitBto3Key(SDL_BlitInfo *info) int c; /* Set up some basic variables */ - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { SDL_memcpy(dst, &palmap[bit * 4], 3); } - byte >>= 1; + byte >>= srcbpp; dst += 3; } src += srcskip; @@ -467,14 +527,14 @@ static void BlitBto3Key(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { SDL_memcpy(dst, &palmap[bit * 4], 3); } - byte <<= 1; + byte <<= srcbpp; dst += 3; } src += srcskip; @@ -483,8 +543,11 @@ static void BlitBto3Key(SDL_BlitInfo *info) } } -static void BlitBto4Key(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBto4Key(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -496,21 +559,26 @@ static void BlitBto4Key(SDL_BlitInfo *info) int c; /* Set up some basic variables */ - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; dstskip /= 4; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { *dstp = ((Uint32 *)palmap)[bit]; } - byte >>= 1; + byte >>= srcbpp; dstp++; } src += srcskip; @@ -520,14 +588,14 @@ static void BlitBto4Key(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { *dstp = ((Uint32 *)palmap)[bit]; } - byte <<= 1; + byte <<= srcbpp; dstp++; } src += srcskip; @@ -536,8 +604,11 @@ static void BlitBto4Key(SDL_BlitInfo *info) } } -static void BlitBtoNAlpha(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBtoNAlpha(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -555,16 +626,21 @@ static void BlitBtoNAlpha(SDL_BlitInfo *info) /* Set up some basic variables */ dstbpp = dstfmt->BytesPerPixel; - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (1) { sR = srcpal[bit].r; sG = srcpal[bit].g; @@ -573,7 +649,7 @@ static void BlitBtoNAlpha(SDL_BlitInfo *info) ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); } - byte >>= 1; + byte >>= srcbpp; dst += dstbpp; } src += srcskip; @@ -583,10 +659,10 @@ static void BlitBtoNAlpha(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (1) { sR = srcpal[bit].r; sG = srcpal[bit].g; @@ -595,7 +671,7 @@ static void BlitBtoNAlpha(SDL_BlitInfo *info) ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); } - byte <<= 1; + byte <<= srcbpp; dst += dstbpp; } src += srcskip; @@ -604,8 +680,11 @@ static void BlitBtoNAlpha(SDL_BlitInfo *info) } } -static void BlitBtoNAlphaKey(SDL_BlitInfo *info) +SDL_FORCE_INLINE void BlitBtoNAlphaKey(SDL_BlitInfo *info, const Uint32 srcbpp) { + const Uint32 mask = (1 << srcbpp) - 1; + const Uint32 align = (8 / srcbpp) - 1; + int width = info->dst_w; int height = info->dst_h; Uint8 *src = info->src; @@ -625,16 +704,21 @@ static void BlitBtoNAlphaKey(SDL_BlitInfo *info) /* Set up some basic variables */ dstbpp = dstfmt->BytesPerPixel; - srcskip += width - (width + 7) / 8; + if (srcbpp == 4) + srcskip += width - (width + 1) / 2; + else if (srcbpp == 2) + srcskip += width - (width + 3) / 4; + else if (srcbpp == 1) + srcskip += width - (width + 7) / 8; - if (info->src_fmt->format == SDL_PIXELFORMAT_INDEX1LSB) { + if (SDL_PIXELORDER(info->src_fmt->format) == SDL_BITMAPORDER_4321) { while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x01); + bit = (byte & mask); if (bit != ckey) { sR = srcpal[bit].r; sG = srcpal[bit].g; @@ -643,7 +727,7 @@ static void BlitBtoNAlphaKey(SDL_BlitInfo *info) ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); } - byte >>= 1; + byte >>= srcbpp; dst += dstbpp; } src += srcskip; @@ -653,10 +737,10 @@ static void BlitBtoNAlphaKey(SDL_BlitInfo *info) while (height--) { Uint8 byte = 0, bit; for (c = 0; c < width; ++c) { - if (!(c & 7)) { + if (!(c & align)) { byte = *src++; } - bit = (byte & 0x80) >> 7; + bit = (byte >> (8 - srcbpp)) & mask; if (bit != ckey) { sR = srcpal[bit].r; sG = srcpal[bit].g; @@ -665,7 +749,7 @@ static void BlitBtoNAlphaKey(SDL_BlitInfo *info) ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); } - byte <<= 1; + byte <<= srcbpp; dst += dstbpp; } src += srcskip; @@ -674,115 +758,221 @@ static void BlitBtoNAlphaKey(SDL_BlitInfo *info) } } -static const SDL_BlitFunc bitmap_blit[] = { - (SDL_BlitFunc)NULL, BlitBto1, BlitBto2, BlitBto3, BlitBto4 -}; -static const SDL_BlitFunc colorkey_blit[] = { - (SDL_BlitFunc)NULL, BlitBto1Key, BlitBto2Key, BlitBto3Key, BlitBto4Key -}; -static void Blit4bto4(SDL_BlitInfo *info) -{ - int width = info->dst_w; - int height = info->dst_h; - Uint8 *src = info->src; - Uint32 *dst = (Uint32 *)info->dst; - int srcskip = info->src_skip; - int dstskip = info->dst_skip; - Uint32 *map = (Uint32 *)info->table; - int c; - - /* Set up some basic variables */ - srcskip += width - (width + 1) / 2; - - while (height--) { - Uint8 byte = 0, bit; - for (c = 0; c < width; ++c) { - if (!(c & 0x1)) { - byte = *src++; - } - bit = (byte & 0xF0) >> 4; - if (1) { - *dst = map[bit]; - } - byte <<= 4; - dst++; - } - src += srcskip; - dst = (Uint32 *)((Uint8 *)dst + dstskip); - } +static void Blit1bto1(SDL_BlitInfo *info) { + BlitBto1(info, 1); } -static void Blit4bto4Key(SDL_BlitInfo *info) -{ - int width = info->dst_w; - int height = info->dst_h; - Uint8 *src = info->src; - Uint32 *dst = (Uint32 *)info->dst; - int srcskip = info->src_skip; - int dstskip = info->dst_skip; - Uint32 ckey = info->colorkey; - Uint32 *map = (Uint32 *)info->table; - int c; - - /* Set up some basic variables */ - srcskip += width - (width + 1) / 2; - - while (height--) { - Uint8 byte = 0, bit; - for (c = 0; c < width; ++c) { - if (!(c & 0x1)) { - byte = *src++; - } - bit = (byte & 0xF0) >> 4; - if (bit != ckey) { - *dst = map[bit]; - } - byte <<= 4; - dst++; - } - src += srcskip; - dst = (Uint32 *)((Uint8 *)dst + dstskip); - } +static void Blit1bto2(SDL_BlitInfo *info) { + BlitBto2(info, 1); } +static void Blit1bto3(SDL_BlitInfo *info) { + BlitBto3(info, 1); +} + +static void Blit1bto4(SDL_BlitInfo *info) { + BlitBto4(info, 1); +} + +static const SDL_BlitFunc bitmap_blit_1b[] = { + (SDL_BlitFunc)NULL, Blit1bto1, Blit1bto2, Blit1bto3, Blit1bto4 +}; + +static void Blit1bto1Key(SDL_BlitInfo *info) { + BlitBto1Key(info, 1); +} + +static void Blit1bto2Key(SDL_BlitInfo *info) { + BlitBto2Key(info, 1); +} + +static void Blit1bto3Key(SDL_BlitInfo *info) { + BlitBto3Key(info, 1); +} + +static void Blit1bto4Key(SDL_BlitInfo *info) { + BlitBto4Key(info, 1); +} + +static const SDL_BlitFunc colorkey_blit_1b[] = { + (SDL_BlitFunc)NULL, Blit1bto1Key, Blit1bto2Key, Blit1bto3Key, Blit1bto4Key +}; + +static void Blit1btoNAlpha(SDL_BlitInfo *info) +{ + BlitBtoNAlpha(info, 1); +} + +static void Blit1btoNAlphaKey(SDL_BlitInfo *info) +{ + BlitBtoNAlphaKey(info, 1); +} + + + +static void Blit2bto1(SDL_BlitInfo *info) { + BlitBto1(info, 2); +} + +static void Blit2bto2(SDL_BlitInfo *info) { + BlitBto2(info, 2); +} + +static void Blit2bto3(SDL_BlitInfo *info) { + BlitBto3(info, 2); +} + +static void Blit2bto4(SDL_BlitInfo *info) { + BlitBto4(info, 2); +} + +static const SDL_BlitFunc bitmap_blit_2b[] = { + (SDL_BlitFunc)NULL, Blit2bto1, Blit2bto2, Blit2bto3, Blit2bto4 +}; + +static void Blit2bto1Key(SDL_BlitInfo *info) { + BlitBto1Key(info, 2); +} + +static void Blit2bto2Key(SDL_BlitInfo *info) { + BlitBto2Key(info, 2); +} + +static void Blit2bto3Key(SDL_BlitInfo *info) { + BlitBto3Key(info, 2); +} + +static void Blit2bto4Key(SDL_BlitInfo *info) { + BlitBto4Key(info, 2); +} + +static const SDL_BlitFunc colorkey_blit_2b[] = { + (SDL_BlitFunc)NULL, Blit2bto1Key, Blit2bto2Key, Blit2bto3Key, Blit2bto4Key +}; + +static void Blit2btoNAlpha(SDL_BlitInfo *info) +{ + BlitBtoNAlpha(info, 2); +} + +static void Blit2btoNAlphaKey(SDL_BlitInfo *info) +{ + BlitBtoNAlphaKey(info, 2); +} + + + +static void Blit4bto1(SDL_BlitInfo *info) { + BlitBto1(info, 4); +} + +static void Blit4bto2(SDL_BlitInfo *info) { + BlitBto2(info, 4); +} + +static void Blit4bto3(SDL_BlitInfo *info) { + BlitBto3(info, 4); +} + +static void Blit4bto4(SDL_BlitInfo *info) { + BlitBto4(info, 4); +} + +static const SDL_BlitFunc bitmap_blit_4b[] = { + (SDL_BlitFunc)NULL, Blit4bto1, Blit4bto2, Blit4bto3, Blit4bto4 +}; + +static void Blit4bto1Key(SDL_BlitInfo *info) { + BlitBto1Key(info, 4); +} + +static void Blit4bto2Key(SDL_BlitInfo *info) { + BlitBto2Key(info, 4); +} + +static void Blit4bto3Key(SDL_BlitInfo *info) { + BlitBto3Key(info, 4); +} + +static void Blit4bto4Key(SDL_BlitInfo *info) { + BlitBto4Key(info, 4); +} + +static const SDL_BlitFunc colorkey_blit_4b[] = { + (SDL_BlitFunc)NULL, Blit4bto1Key, Blit4bto2Key, Blit4bto3Key, Blit4bto4Key +}; + +static void Blit4btoNAlpha(SDL_BlitInfo *info) +{ + BlitBtoNAlpha(info, 4); +} + +static void Blit4btoNAlphaKey(SDL_BlitInfo *info) +{ + BlitBtoNAlphaKey(info, 4); +} + + + SDL_BlitFunc SDL_CalculateBlit0(SDL_Surface *surface) { int which; - /* 4bits to 32bits */ - if (surface->format->format == SDL_PIXELFORMAT_INDEX4MSB) { - if (surface->map->dst->format->BytesPerPixel == 4) { - switch (surface->map->info.flags & ~SDL_COPY_RLE_MASK) { - case 0: - return Blit4bto4; + if (surface->map->dst->format->BitsPerPixel < 8) { + which = 0; + } else { + which = surface->map->dst->format->BytesPerPixel; + } - case SDL_COPY_COLORKEY: - return Blit4bto4Key; - } + if (SDL_PIXELTYPE(surface->format->format) == SDL_PIXELTYPE_INDEX1) { + switch (surface->map->info.flags & ~SDL_COPY_RLE_MASK) { + case 0: + return bitmap_blit_1b[which]; + + case SDL_COPY_COLORKEY: + return colorkey_blit_1b[which]; + + case SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: + return which >= 2 ? Blit1btoNAlpha : (SDL_BlitFunc)NULL; + + case SDL_COPY_COLORKEY | SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: + return which >= 2 ? Blit1btoNAlphaKey : (SDL_BlitFunc)NULL; } return NULL; } - if (surface->format->format == SDL_PIXELFORMAT_INDEX1MSB) { - if (surface->map->dst->format->BitsPerPixel < 8) { - which = 0; - } else { - which = surface->map->dst->format->BytesPerPixel; - } + if (SDL_PIXELTYPE(surface->format->format) == SDL_PIXELTYPE_INDEX2) { switch (surface->map->info.flags & ~SDL_COPY_RLE_MASK) { case 0: - return bitmap_blit[which]; + return bitmap_blit_2b[which]; case SDL_COPY_COLORKEY: - return colorkey_blit[which]; + return colorkey_blit_2b[which]; case SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: - return which >= 2 ? BlitBtoNAlpha : (SDL_BlitFunc)NULL; + return which >= 2 ? Blit2btoNAlpha : (SDL_BlitFunc)NULL; case SDL_COPY_COLORKEY | SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: - return which >= 2 ? BlitBtoNAlphaKey : (SDL_BlitFunc)NULL; + return which >= 2 ? Blit2btoNAlphaKey : (SDL_BlitFunc)NULL; + } + return NULL; + } + + if (SDL_PIXELTYPE(surface->format->format) == SDL_PIXELTYPE_INDEX4) { + switch (surface->map->info.flags & ~SDL_COPY_RLE_MASK) { + case 0: + return bitmap_blit_4b[which]; + + case SDL_COPY_COLORKEY: + return colorkey_blit_4b[which]; + + case SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: + return which >= 2 ? Blit4btoNAlpha : (SDL_BlitFunc)NULL; + + case SDL_COPY_COLORKEY | SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: + return which >= 2 ? Blit4btoNAlphaKey : (SDL_BlitFunc)NULL; } return NULL; } diff --git a/src/video/SDL_blit_1.c b/src/video/SDL_blit_1.c index 2c54a2e8..5f5a3a81 100644 --- a/src/video/SDL_blit_1.c +++ b/src/video/SDL_blit_1.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -435,7 +435,7 @@ static void Blit1toNAlpha(SDL_BlitInfo *info) const SDL_Color *srcpal = info->src_fmt->palette->colors; int dstbpp; Uint32 pixel; - unsigned sR, sG, sB; + unsigned sR, sG, sB, sA; unsigned dR, dG, dB, dA; const unsigned A = info->a; @@ -449,8 +449,9 @@ static void Blit1toNAlpha(SDL_BlitInfo *info) sR = srcpal[*src].r; sG = srcpal[*src].g; sB = srcpal[*src].b; + sA = (srcpal[*src].a * A) / 255; DISEMBLE_RGBA(dst, dstbpp, dstfmt, pixel, dR, dG, dB, dA); - ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); + ALPHA_BLEND_RGBA(sR, sG, sB, sA, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); src++; dst += dstbpp; @@ -475,7 +476,7 @@ static void Blit1toNAlphaKey(SDL_BlitInfo *info) Uint32 ckey = info->colorkey; int dstbpp; Uint32 pixel; - unsigned sR, sG, sB; + unsigned sR, sG, sB, sA; unsigned dR, dG, dB, dA; const unsigned A = info->a; @@ -490,8 +491,9 @@ static void Blit1toNAlphaKey(SDL_BlitInfo *info) sR = srcpal[*src].r; sG = srcpal[*src].g; sB = srcpal[*src].b; + sA = (srcpal[*src].a * A) / 255; DISEMBLE_RGBA(dst, dstbpp, dstfmt, pixel, dR, dG, dB, dA); - ALPHA_BLEND_RGBA(sR, sG, sB, A, dR, dG, dB, dA); + ALPHA_BLEND_RGBA(sR, sG, sB, sA, dR, dG, dB, dA); ASSEMBLE_RGBA(dst, dstbpp, dstfmt, dR, dG, dB, dA); } src++; @@ -531,8 +533,10 @@ SDL_BlitFunc SDL_CalculateBlit1(SDL_Surface *surface) return one_blitkey[which]; case SDL_COPY_COLORKEY | SDL_COPY_BLEND: /* this is not super-robust but handles a specific case we found sdl12-compat. */ - return (surface->map->info.a == 255) ? one_blitkey[which] : (SDL_BlitFunc)NULL; + return (surface->map->info.a == 255) ? one_blitkey[which] : + which >= 2 ? Blit1toNAlphaKey : (SDL_BlitFunc)NULL; + case SDL_COPY_BLEND: case SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND: /* Supporting 8bpp->8bpp alpha is doable but requires lots of tables which consume space and takes time to precompute, diff --git a/src/video/SDL_blit_A.c b/src/video/SDL_blit_A.c index ef0b8de4..1c5e9ceb 100644 --- a/src/video/SDL_blit_A.c +++ b/src/video/SDL_blit_A.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -345,7 +345,7 @@ static void SDL_TARGETING("mmx") BlitRGBtoRGBPixelAlphaMMX(SDL_BlitInfo *info) } multmask = 0x00FF; - multmask <<= (ashift * 2); + multmask <<= ((Uint64)ashift * 2); multmask2 = 0x00FF00FF00FF00FFULL; while (height--) { @@ -1326,7 +1326,7 @@ SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface) /* Per-pixel alpha blits */ switch (df->BytesPerPixel) { case 1: - if (df->palette != NULL) { + if (df->palette) { return BlitNto1PixelAlpha; } else { /* RGB332 has no palette ! */ @@ -1397,7 +1397,7 @@ SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface) /* Per-surface alpha blits */ switch (df->BytesPerPixel) { case 1: - if (df->palette != NULL) { + if (df->palette) { return BlitNto1SurfaceAlpha; } else { /* RGB332 has no palette ! */ @@ -1452,7 +1452,7 @@ SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface) if (sf->Amask == 0) { if (df->BytesPerPixel == 1) { - if (df->palette != NULL) { + if (df->palette) { return BlitNto1SurfaceAlphaKey; } else { /* RGB332 has no palette ! */ diff --git a/src/video/SDL_blit_N.c b/src/video/SDL_blit_N.c index bf7c9107..49d86a74 100644 --- a/src/video/SDL_blit_N.c +++ b/src/video/SDL_blit_N.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -971,7 +971,7 @@ static void Blit_XRGB8888_index8(SDL_BlitInfo *info) dstskip = info->dst_skip; map = info->table; - if (map == NULL) { + if (!map) { while (height--) { #ifdef USE_DUFFS_LOOP /* *INDENT-OFF* */ /* clang-format off */ @@ -1085,7 +1085,7 @@ static void Blit_RGB101010_index8(SDL_BlitInfo *info) dstskip = info->dst_skip; map = info->table; - if (map == NULL) { + if (!map) { while (height--) { #ifdef USE_DUFFS_LOOP /* *INDENT-OFF* */ /* clang-format off */ @@ -2109,7 +2109,7 @@ static void BlitNto1(SDL_BlitInfo *info) srcfmt = info->src_fmt; srcbpp = srcfmt->BytesPerPixel; - if (map == NULL) { + if (!map) { while (height--) { #ifdef USE_DUFFS_LOOP /* *INDENT-OFF* */ /* clang-format off */ @@ -2327,8 +2327,8 @@ static void BlitNtoN(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 4->4 */ if (srcbpp == 4 && dstbpp == 4 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format) && + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { /* Find the appropriate permutation */ int alpha_channel, p0, p1, p2, p3; @@ -2356,7 +2356,7 @@ static void BlitNtoN(SDL_BlitInfo *info) /* Blit with permutation: 4->3 */ if (srcbpp == 4 && dstbpp == 3 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format)) { /* Find the appropriate permutation */ int p0, p1, p2, p3; @@ -2382,7 +2382,7 @@ static void BlitNtoN(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 3->4 */ if (srcbpp == 3 && dstbpp == 4 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { /* Find the appropriate permutation */ int alpha_channel, p0, p1, p2, p3; @@ -2445,8 +2445,8 @@ static void BlitNtoNCopyAlpha(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 4->4 */ if (srcbpp == 4 && dstbpp == 4 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format) && + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { /* Find the appropriate permutation */ int p0, p1, p2, p3; @@ -2505,7 +2505,7 @@ static void BlitNto1Key(SDL_BlitInfo *info) srcbpp = srcfmt->BytesPerPixel; ckey &= rgbmask; - if (palmap == NULL) { + if (!palmap) { while (height--) { /* *INDENT-OFF* */ /* clang-format off */ DUFFS_LOOP( @@ -2651,8 +2651,8 @@ static void BlitNtoNKey(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 4->4 */ if (srcbpp == 4 && dstbpp == 4 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format) && + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { /* Find the appropriate permutation */ int alpha_channel, p0, p1, p2, p3; @@ -2760,7 +2760,7 @@ static void BlitNtoNKey(SDL_BlitInfo *info) /* Blit with permutation: 4->3 */ if (srcbpp == 4 && dstbpp == 3 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format)) { /* Find the appropriate permutation */ int p0, p1, p2, p3; @@ -2789,7 +2789,7 @@ static void BlitNtoNKey(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 3->4 */ if (srcbpp == 3 && dstbpp == 4 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { #if SDL_BYTEORDER == SDL_LIL_ENDIAN Uint8 k0 = ckey & 0xFF; @@ -2909,8 +2909,8 @@ static void BlitNtoNKeyCopyAlpha(SDL_BlitInfo *info) #if HAVE_FAST_WRITE_INT8 /* Blit with permutation: 4->4 */ if (srcbpp == 4 && dstbpp == 4 && - srcfmt->format != SDL_PIXELFORMAT_ARGB2101010 && - dstfmt->format != SDL_PIXELFORMAT_ARGB2101010) { + !SDL_ISPIXELFORMAT_10BIT(srcfmt->format) && + !SDL_ISPIXELFORMAT_10BIT(dstfmt->format)) { /* Find the appropriate permutation */ int p0, p1, p2, p3; diff --git a/src/video/SDL_blit_auto.c b/src/video/SDL_blit_auto.c index 9cfb5bc9..5bd6f135 100644 --- a/src/video/SDL_blit_auto.c +++ b/src/video/SDL_blit_auto.c @@ -1,7 +1,7 @@ /* DO NOT EDIT! This file is generated by sdlgenblit.pl */ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_blit_auto.h b/src/video/SDL_blit_auto.h index c927b62b..15614504 100644 --- a/src/video/SDL_blit_auto.h +++ b/src/video/SDL_blit_auto.h @@ -1,7 +1,7 @@ /* DO NOT EDIT! This file is generated by sdlgenblit.pl */ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_blit_copy.c b/src/video/SDL_blit_copy.c index 8dc3511e..36edfc25 100644 --- a/src/video/SDL_blit_copy.c +++ b/src/video/SDL_blit_copy.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_blit_copy.h b/src/video/SDL_blit_copy.h index a2967b52..de931153 100644 --- a/src/video/SDL_blit_copy.h +++ b/src/video/SDL_blit_copy.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_blit_slow.c b/src/video/SDL_blit_slow.c index 6348fbf2..621f47c6 100644 --- a/src/video/SDL_blit_slow.c +++ b/src/video/SDL_blit_slow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,7 +30,7 @@ #define FORMAT_HAS_NO_ALPHA(format) format < 0 static int detect_format(SDL_PixelFormat *pf) { - if (pf->format == SDL_PIXELFORMAT_ARGB2101010) { + if (SDL_ISPIXELFORMAT_10BIT(pf->format)) { return FORMAT_2101010; } else if (pf->Amask) { return FORMAT_ALPHA; @@ -88,9 +88,27 @@ void SDL_Blit_Slow(SDL_BlitInfo *info) DISEMBLE_RGB(src, srcbpp, src_fmt, srcpixel, srcR, srcG, srcB); srcA = 0xFF; } else { - /* SDL_PIXELFORMAT_ARGB2101010 */ + /* 10-bit pixel format */ srcpixel = *((Uint32 *)(src)); - RGBA_FROM_ARGB2101010(srcpixel, srcR, srcG, srcB, srcA); + switch (src_fmt->format) { + case SDL_PIXELFORMAT_XRGB2101010: + RGBA_FROM_ARGB2101010(srcpixel, srcR, srcG, srcB, srcA); + srcA = 0xFF; + break; + case SDL_PIXELFORMAT_XBGR2101010: + RGBA_FROM_ABGR2101010(srcpixel, srcR, srcG, srcB, srcA); + srcA = 0xFF; + break; + case SDL_PIXELFORMAT_ARGB2101010: + RGBA_FROM_ARGB2101010(srcpixel, srcR, srcG, srcB, srcA); + break; + case SDL_PIXELFORMAT_ABGR2101010: + RGBA_FROM_ABGR2101010(srcpixel, srcR, srcG, srcB, srcA); + break; + default: + srcR = srcG = srcB = srcA = 0; + break; + } } if (flags & SDL_COPY_COLORKEY) { @@ -105,15 +123,20 @@ void SDL_Blit_Slow(SDL_BlitInfo *info) continue; } } - if (FORMAT_HAS_ALPHA(dstfmt_val)) { - DISEMBLE_RGBA(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB, dstA); - } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) { - DISEMBLE_RGB(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB); - dstA = 0xFF; + if ((flags & (SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL))) { + if (FORMAT_HAS_ALPHA(dstfmt_val)) { + DISEMBLE_RGBA(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB, dstA); + } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) { + DISEMBLE_RGB(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB); + dstA = 0xFF; + } else { + /* SDL_PIXELFORMAT_ARGB2101010 */ + dstpixel = *((Uint32 *) (dst)); + RGBA_FROM_ARGB2101010(dstpixel, dstR, dstG, dstB, dstA); + } } else { - /* SDL_PIXELFORMAT_ARGB2101010 */ - dstpixel = *((Uint32 *)(dst)); - RGBA_FROM_ARGB2101010(dstpixel, dstR, dstG, dstB, dstA); + /* don't care */ + dstR = dstG = dstB = dstA = 0; } if (flags & SDL_COPY_MODULATE_COLOR) { @@ -184,9 +207,25 @@ void SDL_Blit_Slow(SDL_BlitInfo *info) } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) { ASSEMBLE_RGB(dst, dstbpp, dst_fmt, dstR, dstG, dstB); } else { - /* SDL_PIXELFORMAT_ARGB2101010 */ + /* 10-bit pixel format */ Uint32 pixel; - ARGB2101010_FROM_RGBA(pixel, dstR, dstG, dstB, dstA); + switch (dst_fmt->format) { + case SDL_PIXELFORMAT_XRGB2101010: + dstA = 0xFF; + SDL_FALLTHROUGH; + case SDL_PIXELFORMAT_ARGB2101010: + ARGB2101010_FROM_RGBA(pixel, dstR, dstG, dstB, dstA); + break; + case SDL_PIXELFORMAT_XBGR2101010: + dstA = 0xFF; + SDL_FALLTHROUGH; + case SDL_PIXELFORMAT_ABGR2101010: + ABGR2101010_FROM_RGBA(pixel, dstR, dstG, dstB, dstA); + break; + default: + pixel = 0; + break; + } *(Uint32 *)dst = pixel; } posx += incx; diff --git a/src/video/SDL_blit_slow.h b/src/video/SDL_blit_slow.h index 3fabc78b..40e1a44c 100644 --- a/src/video/SDL_blit_slow.h +++ b/src/video/SDL_blit_slow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_bmp.c b/src/video/SDL_bmp.c index 998bee1f..ba7c50da 100644 --- a/src/video/SDL_bmp.c +++ b/src/video/SDL_bmp.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -197,7 +197,6 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) { SDL_bool was_error = SDL_TRUE; Sint64 fp_offset = 0; - int bmpPitch; int i, pad; SDL_Surface *surface; Uint32 Rmask = 0; @@ -208,7 +207,6 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) Uint8 *bits; Uint8 *top, *end; SDL_bool topDown; - int ExpandBMP; SDL_bool haveRGBMasks = SDL_FALSE; SDL_bool haveAlphaMask = SDL_FALSE; SDL_bool correctAlpha = SDL_FALSE; @@ -235,7 +233,7 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) /* Make sure we are passed a valid data source */ surface = NULL; - if (src == NULL) { + if (!src) { SDL_InvalidParamError("src"); goto done; } @@ -365,23 +363,16 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) goto done; } - /* Expand 1, 2 and 4 bit bitmaps to 8 bits per pixel */ + /* Reject invalid bit depths */ switch (biBitCount) { - case 1: - case 2: - case 4: - ExpandBMP = biBitCount; - biBitCount = 8; - break; case 0: case 3: case 5: case 6: case 7: - SDL_SetError("%d-bpp BMP images are not supported", biBitCount); + SDL_SetError("%u bpp BMP images are not supported", biBitCount); goto done; default: - ExpandBMP = 0; break; } @@ -442,7 +433,7 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) format = SDL_GetPixelFormatEnumForMasks(biBitCount, Rmask, Gmask, Bmask, Amask); surface = SDL_CreateSurface(biWidth, biHeight, format); - if (surface == NULL) { + if (!surface) { goto done; } } @@ -514,89 +505,49 @@ SDL_Surface *SDL_LoadBMP_RW(SDL_RWops *src, SDL_bool freesrc) } top = (Uint8 *)surface->pixels; end = (Uint8 *)surface->pixels + (surface->h * surface->pitch); - switch (ExpandBMP) { - case 1: - bmpPitch = (biWidth + 7) >> 3; - pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); - break; - case 2: - bmpPitch = (biWidth + 3) >> 2; - pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); - break; - case 4: - bmpPitch = (biWidth + 1) >> 1; - pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); - break; - default: - pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0); - break; - } + pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0); if (topDown) { bits = top; } else { bits = end - surface->pitch; } while (bits >= top && bits < end) { - switch (ExpandBMP) { - case 1: - case 2: - case 4: - { - Uint8 pixel = 0; - int shift = (8 - ExpandBMP); + if (SDL_RWread(src, bits, surface->pitch) != (size_t)surface->pitch) { + goto done; + } + if (biBitCount == 8 && palette && biClrUsed < (1u << biBitCount)) { for (i = 0; i < surface->w; ++i) { - if (i % (8 / ExpandBMP) == 0) { - if (!SDL_ReadU8(src, &pixel)) { - goto done; - } - } - bits[i] = (pixel >> shift); if (bits[i] >= biClrUsed) { SDL_SetError("A BMP image contains a pixel with a color out of the palette"); goto done; } - pixel <<= ExpandBMP; - } - } break; - - default: - if (SDL_RWread(src, bits, surface->pitch) != (size_t)surface->pitch) { - goto done; - } - if (biBitCount == 8 && palette && biClrUsed < (1u << biBitCount)) { - for (i = 0; i < surface->w; ++i) { - if (bits[i] >= biClrUsed) { - SDL_SetError("A BMP image contains a pixel with a color out of the palette"); - goto done; - } - } } + } #if SDL_BYTEORDER == SDL_BIG_ENDIAN - /* Byte-swap the pixels if needed. Note that the 24bpp - case has already been taken care of above. */ - switch (biBitCount) { - case 15: - case 16: - { - Uint16 *pix = (Uint16 *)bits; - for (i = 0; i < surface->w; i++) { - pix[i] = SDL_Swap16(pix[i]); - } - break; + /* Byte-swap the pixels if needed. Note that the 24bpp + case has already been taken care of above. */ + switch (biBitCount) { + case 15: + case 16: + { + Uint16 *pix = (Uint16 *)bits; + for (i = 0; i < surface->w; i++) { + pix[i] = SDL_Swap16(pix[i]); } - - case 32: - { - Uint32 *pix = (Uint32 *)bits; - for (i = 0; i < surface->w; i++) { - pix[i] = SDL_Swap32(pix[i]); - } - break; - } - } -#endif break; } + + case 32: + { + Uint32 *pix = (Uint32 *)bits; + for (i = 0; i < surface->w; i++) { + pix[i] = SDL_Swap32(pix[i]); + } + break; + } + } +#endif + /* Skip padding bytes, ugh */ if (pad) { Uint8 padbyte; @@ -695,11 +646,11 @@ int SDL_SaveBMP_RW(SDL_Surface *surface, SDL_RWops *dst, SDL_bool freedst) } #endif /* SAVE_32BIT_BMP */ - if (surface->format->palette != NULL && !save32bit) { + if (surface->format->palette && !save32bit) { if (surface->format->BitsPerPixel == 8) { intermediate_surface = surface; } else { - SDL_SetError("%d bpp BMP files not supported", + SDL_SetError("%u bpp BMP files not supported", surface->format->BitsPerPixel); goto done; } @@ -726,7 +677,7 @@ int SDL_SaveBMP_RW(SDL_Surface *surface, SDL_RWops *dst, SDL_bool freedst) pixel_format = SDL_PIXELFORMAT_BGR24; } intermediate_surface = SDL_ConvertSurfaceFormat(surface, pixel_format); - if (intermediate_surface == NULL) { + if (!intermediate_surface) { SDL_SetError("Couldn't convert image to %d bpp", (int)SDL_BITSPERPIXEL(pixel_format)); goto done; diff --git a/src/video/SDL_clipboard.c b/src/video/SDL_clipboard.c index a2396bd4..dbfbe513 100644 --- a/src/video/SDL_clipboard.c +++ b/src/video/SDL_clipboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -58,7 +58,7 @@ int SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanu SDL_VideoDevice *_this = SDL_GetVideoDevice(); size_t i; - if (_this == NULL) { + if (!_this) { return SDL_SetError("Video subsystem must be initialized to set clipboard text"); } @@ -97,7 +97,7 @@ int SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanu } if (num_allocated < num_mime_types) { SDL_ClearClipboardData(); - return SDL_OutOfMemory(); + return -1; } _this->num_clipboard_mime_types = num_mime_types; } @@ -156,8 +156,6 @@ void *SDL_GetInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type if (data) { SDL_memcpy(data, provided_data, *size); SDL_memset((Uint8 *)data + *size, 0, sizeof(Uint32)); - } else { - SDL_OutOfMemory(); } } } @@ -168,7 +166,7 @@ void *SDL_GetClipboardData(const char *mime_type, size_t *size) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to get clipboard data"); return NULL; } @@ -215,7 +213,7 @@ SDL_bool SDL_HasClipboardData(const char *mime_type) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to check clipboard data"); return SDL_FALSE; } @@ -272,7 +270,7 @@ int SDL_SetClipboardText(const char *text) size_t num_mime_types; const char **text_mime_types; - if (_this == NULL) { + if (!_this) { return SDL_SetError("Video subsystem must be initialized to set clipboard text"); } @@ -293,7 +291,7 @@ char *SDL_GetClipboardText(void) size_t length; char *text = NULL; - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to get clipboard text"); return SDL_strdup(""); } @@ -318,7 +316,7 @@ SDL_bool SDL_HasClipboardText(void) size_t i, num_mime_types; const char **text_mime_types; - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to check clipboard text"); return SDL_FALSE; } @@ -338,11 +336,11 @@ int SDL_SetPrimarySelectionText(const char *text) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (_this == NULL) { + if (!_this) { return SDL_SetError("Video subsystem must be initialized to set primary selection text"); } - if (text == NULL) { + if (!text) { text = ""; } if (_this->SetPrimarySelectionText) { @@ -362,7 +360,7 @@ char *SDL_GetPrimarySelectionText(void) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to get primary selection text"); return SDL_strdup(""); } @@ -371,7 +369,7 @@ char *SDL_GetPrimarySelectionText(void) return _this->GetPrimarySelectionText(_this); } else { const char *text = _this->primary_selection_text; - if (text == NULL) { + if (!text) { text = ""; } return SDL_strdup(text); @@ -382,7 +380,7 @@ SDL_bool SDL_HasPrimarySelectionText(void) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (_this == NULL) { + if (!_this) { SDL_SetError("Video subsystem must be initialized to check primary selection text"); return SDL_FALSE; } diff --git a/src/video/SDL_clipboard_c.h b/src/video/SDL_clipboard_c.h index 50a245bd..98e204a2 100644 --- a/src/video/SDL_clipboard_c.h +++ b/src/video/SDL_clipboard_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_egl.c b/src/video/SDL_egl.c index 19b80428..0adf1d77 100644 --- a/src/video/SDL_egl.c +++ b/src/video/SDL_egl.c @@ -1,6 +1,6 @@ /* * Simple DirectMedia Layer - * Copyright (C) 1997-2023 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 @@ -184,7 +184,7 @@ SDL_bool SDL_EGL_HasExtension(SDL_VideoDevice *_this, SDL_EGL_ExtensionType type const char *ext_start; /* Invalid extensions can be rejected early */ - if (ext == NULL || *ext == 0 || SDL_strchr(ext, ' ') != NULL) { + if (!ext || *ext == 0 || SDL_strchr(ext, ' ') != NULL) { /* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "SDL_EGL_HasExtension: Invalid EGL extension"); */ return SDL_FALSE; } @@ -197,7 +197,7 @@ SDL_bool SDL_EGL_HasExtension(SDL_VideoDevice *_this, SDL_EGL_ExtensionType type * 1 If set, the client extension is masked and not present to SDL. */ ext_override = SDL_getenv(ext); - if (ext_override != NULL) { + if (ext_override) { int disable_ext = SDL_atoi(ext_override); if (disable_ext & 0x01 && type == SDL_EGL_DISPLAY_EXTENSION) { return SDL_FALSE; @@ -223,12 +223,12 @@ SDL_bool SDL_EGL_HasExtension(SDL_VideoDevice *_this, SDL_EGL_ExtensionType type return SDL_FALSE; } - if (egl_extstr != NULL) { + if (egl_extstr) { ext_start = egl_extstr; while (*ext_start) { ext_start = SDL_strstr(ext_start, ext); - if (ext_start == NULL) { + if (!ext_start) { return SDL_FALSE; } /* Check if the match is not just a substring of one of the extensions */ @@ -251,24 +251,24 @@ SDL_bool SDL_EGL_HasExtension(SDL_VideoDevice *_this, SDL_EGL_ExtensionType type SDL_FunctionPointer SDL_EGL_GetProcAddressInternal(SDL_VideoDevice *_this, const char *proc) { SDL_FunctionPointer retval = NULL; - if (_this->egl_data != NULL) { + if (_this->egl_data) { const Uint32 eglver = (((Uint32)_this->egl_data->egl_version_major) << 16) | ((Uint32)_this->egl_data->egl_version_minor); const SDL_bool is_egl_15_or_later = eglver >= ((((Uint32)1) << 16) | 5); /* EGL 1.5 can use eglGetProcAddress() for any symbol. 1.4 and earlier can't use it for core entry points. */ - if (retval == NULL && is_egl_15_or_later && _this->egl_data->eglGetProcAddress) { + if (!retval && is_egl_15_or_later && _this->egl_data->eglGetProcAddress) { retval = _this->egl_data->eglGetProcAddress(proc); } #if !defined(__EMSCRIPTEN__) && !defined(SDL_VIDEO_DRIVER_VITA) /* LoadFunction isn't needed on Emscripten and will call dlsym(), causing other problems. */ /* Try SDL_LoadFunction() first for EGL <= 1.4, or as a fallback for >= 1.5. */ - if (retval == NULL) { + if (!retval) { retval = SDL_LoadFunction(_this->egl_data->opengl_dll_handle, proc); } #endif /* Try eglGetProcAddress if we're on <= 1.4 and still searching... */ - if (retval == NULL && !is_egl_15_or_later && _this->egl_data->eglGetProcAddress) { + if (!retval && !is_egl_15_or_later && _this->egl_data->eglGetProcAddress) { retval = _this->egl_data->eglGetProcAddress(proc); } } @@ -342,17 +342,17 @@ static int SDL_EGL_LoadLibraryInternal(SDL_VideoDevice *_this, const char *egl_p #if !defined(SDL_VIDEO_STATIC_ANGLE) && !defined(SDL_VIDEO_DRIVER_VITA) /* A funny thing, loading EGL.so first does not work on the Raspberry, so we load libGL* first */ path = SDL_getenv("SDL_VIDEO_GL_DRIVER"); - if (path != NULL) { + if (path) { opengl_dll_handle = SDL_LoadObject(path); } - if (opengl_dll_handle == NULL) { + if (!opengl_dll_handle) { if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) { if (_this->gl_config.major_version > 1) { path = DEFAULT_OGL_ES2; opengl_dll_handle = SDL_LoadObject(path); #ifdef ALT_OGL_ES2 - if (opengl_dll_handle == NULL && !vc4) { + if (!opengl_dll_handle && !vc4) { path = ALT_OGL_ES2; opengl_dll_handle = SDL_LoadObject(path); } @@ -361,12 +361,12 @@ static int SDL_EGL_LoadLibraryInternal(SDL_VideoDevice *_this, const char *egl_p } else { path = DEFAULT_OGL_ES; opengl_dll_handle = SDL_LoadObject(path); - if (opengl_dll_handle == NULL) { + if (!opengl_dll_handle) { path = DEFAULT_OGL_ES_PVR; opengl_dll_handle = SDL_LoadObject(path); } #ifdef ALT_OGL_ES2 - if (opengl_dll_handle == NULL && !vc4) { + if (!opengl_dll_handle && !vc4) { path = ALT_OGL_ES2; opengl_dll_handle = SDL_LoadObject(path); } @@ -378,7 +378,7 @@ static int SDL_EGL_LoadLibraryInternal(SDL_VideoDevice *_this, const char *egl_p path = DEFAULT_OGL; opengl_dll_handle = SDL_LoadObject(path); #ifdef ALT_OGL - if (opengl_dll_handle == NULL) { + if (!opengl_dll_handle) { path = ALT_OGL; opengl_dll_handle = SDL_LoadObject(path); } @@ -388,34 +388,34 @@ static int SDL_EGL_LoadLibraryInternal(SDL_VideoDevice *_this, const char *egl_p } _this->egl_data->opengl_dll_handle = opengl_dll_handle; - if (opengl_dll_handle == NULL) { + if (!opengl_dll_handle) { return SDL_SetError("Could not initialize OpenGL / GLES library"); } /* Loading libGL* in the previous step took care of loading libEGL.so, but we future proof by double checking */ - if (egl_path != NULL) { + if (egl_path) { egl_dll_handle = SDL_LoadObject(egl_path); } /* Try loading a EGL symbol, if it does not work try the default library paths */ - if (egl_dll_handle == NULL || SDL_LoadFunction(egl_dll_handle, "eglChooseConfig") == NULL) { - if (egl_dll_handle != NULL) { + if (!egl_dll_handle || SDL_LoadFunction(egl_dll_handle, "eglChooseConfig") == NULL) { + if (egl_dll_handle) { SDL_UnloadObject(egl_dll_handle); } path = SDL_getenv("SDL_VIDEO_EGL_DRIVER"); - if (path == NULL) { + if (!path) { path = DEFAULT_EGL; } egl_dll_handle = SDL_LoadObject(path); #ifdef ALT_EGL - if (egl_dll_handle == NULL && !vc4) { + if (!egl_dll_handle && !vc4) { path = ALT_EGL; egl_dll_handle = SDL_LoadObject(path); } #endif - if (egl_dll_handle == NULL || SDL_LoadFunction(egl_dll_handle, "eglChooseConfig") == NULL) { - if (egl_dll_handle != NULL) { + if (!egl_dll_handle || SDL_LoadFunction(egl_dll_handle, "eglChooseConfig") == NULL) { + if (egl_dll_handle) { SDL_UnloadObject(egl_dll_handle); } return SDL_SetError("Could not load EGL library"); @@ -476,7 +476,7 @@ int SDL_EGL_LoadLibraryOnly(SDL_VideoDevice *_this, const char *egl_path) _this->egl_data = (struct SDL_EGL_VideoData *)SDL_calloc(1, sizeof(SDL_EGL_VideoData)); if (!_this->egl_data) { - return SDL_OutOfMemory(); + return -1; } if (SDL_EGL_LoadLibraryInternal(_this, egl_path) < 0) { @@ -550,7 +550,7 @@ int SDL_EGL_LoadLibrary(SDL_VideoDevice *_this, const char *egl_path, NativeDisp #endif /* Try the implementation-specific eglGetDisplay even if eglGetPlatformDisplay fails */ if ((_this->egl_data->egl_display == EGL_NO_DISPLAY) && - (_this->egl_data->eglGetDisplay != NULL) && + (_this->egl_data->eglGetDisplay) && SDL_GetHintBoolean(SDL_HINT_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK, SDL_TRUE)) { _this->egl_data->egl_display = _this->egl_data->eglGetDisplay(native_display); } @@ -594,11 +594,11 @@ int SDL_EGL_InitializeOffscreen(SDL_VideoDevice *_this, int device) } /* Check for all extensions that are optional until used and fail if any is missing */ - if (_this->egl_data->eglQueryDevicesEXT == NULL) { + if (!_this->egl_data->eglQueryDevicesEXT) { return SDL_SetError("eglQueryDevicesEXT is missing (EXT_device_enumeration not supported by the drivers?)"); } - if (_this->egl_data->eglGetPlatformDisplayEXT == NULL) { + if (!_this->egl_data->eglGetPlatformDisplayEXT) { return SDL_SetError("eglGetPlatformDisplayEXT is missing (EXT_platform_base not supported by the drivers?)"); } diff --git a/src/video/SDL_egl_c.h b/src/video/SDL_egl_c.h index 1916e12e..fed546a6 100644 --- a/src/video/SDL_egl_c.h +++ b/src/video/SDL_egl_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_fillrect.c b/src/video/SDL_fillrect.c index 1e0eac85..070e0733 100644 --- a/src/video/SDL_fillrect.c +++ b/src/video/SDL_fillrect.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -231,12 +231,12 @@ static void SDL_FillSurfaceRect4(Uint8 *pixels, int pitch, Uint32 color, int w, */ int SDL_FillSurfaceRect(SDL_Surface *dst, const SDL_Rect *rect, Uint32 color) { - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_FillSurfaceRect(): dst"); } /* If 'rect' == NULL, then fill the whole surface */ - if (rect == NULL) { + if (!rect) { rect = &dst->clip_rect; /* Don't attempt to fill if the surface's clip_rect is empty */ if (SDL_RectEmpty(rect)) { @@ -304,7 +304,7 @@ int SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, void (*fill_function)(Uint8 * pixels, int pitch, Uint32 color, int w, int h) = NULL; int i; - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("SDL_FillSurfaceRects(): dst"); } @@ -318,7 +318,7 @@ int SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, return SDL_SetError("SDL_FillSurfaceRects(): You must lock the surface"); } - if (rects == NULL) { + if (!rects) { return SDL_InvalidParamError("SDL_FillSurfaceRects(): rects"); } @@ -340,7 +340,7 @@ int SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, } #ifdef SDL_ARM_NEON_BLITTERS - if (SDL_HasNEON() && dst->format->BytesPerPixel != 3 && fill_function == NULL) { + if (SDL_HasNEON() && dst->format->BytesPerPixel != 3 && !fill_function) { switch (dst->format->BytesPerPixel) { case 1: fill_function = fill_8_neon; @@ -355,7 +355,7 @@ int SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, } #endif #ifdef SDL_ARM_SIMD_BLITTERS - if (SDL_HasARMSIMD() && dst->format->BytesPerPixel != 3 && fill_function == NULL) { + if (SDL_HasARMSIMD() && dst->format->BytesPerPixel != 3 && !fill_function) { switch (dst->format->BytesPerPixel) { case 1: fill_function = fill_8_simd; @@ -370,7 +370,7 @@ int SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, } #endif - if (fill_function == NULL) { + if (!fill_function) { switch (dst->format->BytesPerPixel) { case 1: { diff --git a/src/video/SDL_pixels.c b/src/video/SDL_pixels.c index 7c31a24b..72def3a1 100644 --- a/src/video/SDL_pixels.c +++ b/src/video/SDL_pixels.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -89,6 +89,8 @@ const char *SDL_GetPixelFormatName(Uint32 format) CASE(SDL_PIXELFORMAT_INDEX1LSB) CASE(SDL_PIXELFORMAT_INDEX1MSB) + CASE(SDL_PIXELFORMAT_INDEX2LSB) + CASE(SDL_PIXELFORMAT_INDEX2MSB) CASE(SDL_PIXELFORMAT_INDEX4LSB) CASE(SDL_PIXELFORMAT_INDEX4MSB) CASE(SDL_PIXELFORMAT_INDEX8) @@ -117,7 +119,10 @@ const char *SDL_GetPixelFormatName(Uint32 format) CASE(SDL_PIXELFORMAT_RGBA8888) CASE(SDL_PIXELFORMAT_ABGR8888) CASE(SDL_PIXELFORMAT_BGRA8888) + CASE(SDL_PIXELFORMAT_XRGB2101010) + CASE(SDL_PIXELFORMAT_XBGR2101010) CASE(SDL_PIXELFORMAT_ARGB2101010) + CASE(SDL_PIXELFORMAT_ABGR2101010) CASE(SDL_PIXELFORMAT_YV12) CASE(SDL_PIXELFORMAT_IYUV) CASE(SDL_PIXELFORMAT_YUY2) @@ -306,6 +311,9 @@ Uint32 SDL_GetPixelFormatEnumForMasks(int bpp, Uint32 Rmask, Uint32 Gmask, Uint3 case 1: /* SDL defaults to MSB ordering */ return SDL_PIXELFORMAT_INDEX1MSB; + case 2: + /* SDL defaults to MSB ordering */ + return SDL_PIXELFORMAT_INDEX2MSB; case 4: /* SDL defaults to MSB ordering */ return SDL_PIXELFORMAT_INDEX4MSB; @@ -440,6 +448,20 @@ Uint32 SDL_GetPixelFormatEnumForMasks(int bpp, Uint32 Rmask, Uint32 Gmask, Uint3 #endif } break; + case 30: + if (Rmask == 0x3FF00000 && + Gmask == 0x000FFC00 && + Bmask == 0x000003FF && + Amask == 0x00000000) { + return SDL_PIXELFORMAT_XRGB2101010; + } + if (Rmask == 0x000003FF && + Gmask == 0x000FFC00 && + Bmask == 0x3FF00000 && + Amask == 0x00000000) { + return SDL_PIXELFORMAT_XBGR2101010; + } + break; case 32: if (Rmask == 0) { return SDL_PIXELFORMAT_XRGB8888; @@ -492,12 +514,31 @@ Uint32 SDL_GetPixelFormatEnumForMasks(int bpp, Uint32 Rmask, Uint32 Gmask, Uint3 Amask == 0x000000FF) { return SDL_PIXELFORMAT_BGRA8888; } + if (Rmask == 0x3FF00000 && + Gmask == 0x000FFC00 && + Bmask == 0x000003FF && + Amask == 0x00000000) { + return SDL_PIXELFORMAT_XRGB2101010; + } + if (Rmask == 0x000003FF && + Gmask == 0x000FFC00 && + Bmask == 0x3FF00000 && + Amask == 0x00000000) { + return SDL_PIXELFORMAT_XBGR2101010; + } if (Rmask == 0x3FF00000 && Gmask == 0x000FFC00 && Bmask == 0x000003FF && Amask == 0xC0000000) { return SDL_PIXELFORMAT_ARGB2101010; } + if (Rmask == 0x000003FF && + Gmask == 0x000FFC00 && + Bmask == 0x3FF00000 && + Amask == 0xC0000000) { + return SDL_PIXELFORMAT_ABGR2101010; + } + break; } return SDL_PIXELFORMAT_UNKNOWN; } @@ -522,9 +563,8 @@ SDL_PixelFormat *SDL_CreatePixelFormat(Uint32 pixel_format) /* Allocate an empty pixel format structure, and initialize it */ format = SDL_malloc(sizeof(*format)); - if (format == NULL) { + if (!format) { SDL_AtomicUnlock(&formats_lock); - SDL_OutOfMemory(); return NULL; } if (SDL_InitFormat(format, pixel_format) < 0) { @@ -620,7 +660,7 @@ void SDL_DestroyPixelFormat(SDL_PixelFormat *format) { SDL_PixelFormat *prev; - if (format == NULL) { + if (!format) { return; } @@ -663,15 +703,13 @@ SDL_Palette *SDL_CreatePalette(int ncolors) } palette = (SDL_Palette *)SDL_malloc(sizeof(*palette)); - if (palette == NULL) { - SDL_OutOfMemory(); + if (!palette) { return NULL; } palette->colors = (SDL_Color *)SDL_malloc(ncolors * sizeof(*palette->colors)); if (!palette->colors) { SDL_free(palette); - SDL_OutOfMemory(); return NULL; } palette->ncolors = ncolors; @@ -685,7 +723,7 @@ SDL_Palette *SDL_CreatePalette(int ncolors) int SDL_SetPixelFormatPalette(SDL_PixelFormat *format, SDL_Palette *palette) { - if (format == NULL) { + if (!format) { return SDL_InvalidParamError("SDL_SetPixelFormatPalette(): format"); } @@ -716,7 +754,7 @@ int SDL_SetPaletteColors(SDL_Palette *palette, const SDL_Color *colors, int status = 0; /* Verify the parameters */ - if (palette == NULL) { + if (!palette) { return -1; } if (ncolors > (palette->ncolors - firstcolor)) { @@ -738,7 +776,7 @@ int SDL_SetPaletteColors(SDL_Palette *palette, const SDL_Color *colors, void SDL_DestroyPalette(SDL_Palette *palette) { - if (palette == NULL) { + if (!palette) { return; } if (--palette->refcount > 0) { @@ -859,7 +897,7 @@ Uint32 SDL_MapRGB(const SDL_PixelFormat *format, Uint8 r, Uint8 g, Uint8 b) SDL_InvalidParamError("format"); return 0; } - if (format->palette == NULL) { + if (!format->palette) { return (r >> format->Rloss) << format->Rshift | (g >> format->Gloss) << format->Gshift | (b >> format->Bloss) << format->Bshift | format->Amask; } else { return SDL_FindColor(format->palette, r, g, b, SDL_ALPHA_OPAQUE); @@ -874,7 +912,7 @@ Uint32 SDL_MapRGBA(const SDL_PixelFormat *format, Uint8 r, Uint8 g, Uint8 b, SDL_InvalidParamError("format"); return 0; } - if (format->palette == NULL) { + if (!format->palette) { return (r >> format->Rloss) << format->Rshift | (g >> format->Gloss) << format->Gshift | (b >> format->Bloss) << format->Bshift | ((Uint32)(a >> format->Aloss) << format->Ashift & format->Amask); } else { return SDL_FindColor(format->palette, r, g, b, a); @@ -884,7 +922,7 @@ Uint32 SDL_MapRGBA(const SDL_PixelFormat *format, Uint8 r, Uint8 g, Uint8 b, void SDL_GetRGB(Uint32 pixel, const SDL_PixelFormat *format, Uint8 *r, Uint8 *g, Uint8 *b) { - if (format->palette == NULL) { + if (!format->palette) { unsigned v; v = (pixel & format->Rmask) >> format->Rshift; *r = SDL_expand_byte[format->Rloss][v]; @@ -906,7 +944,7 @@ void SDL_GetRGB(Uint32 pixel, const SDL_PixelFormat *format, Uint8 *r, Uint8 *g, void SDL_GetRGBA(Uint32 pixel, const SDL_PixelFormat *format, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a) { - if (format->palette == NULL) { + if (!format->palette) { unsigned v; v = (pixel & format->Rmask) >> format->Rshift; *r = SDL_expand_byte[format->Rloss][v]; @@ -947,8 +985,7 @@ static Uint8 *Map1to1(SDL_Palette *src, SDL_Palette *dst, int *identical) *identical = 0; } map = (Uint8 *)SDL_calloc(256, sizeof(Uint8)); - if (map == NULL) { - SDL_OutOfMemory(); + if (!map) { return NULL; } for (i = 0; i < src->ncolors; ++i) { @@ -970,8 +1007,7 @@ static Uint8 *Map1toN(SDL_PixelFormat *src, Uint8 Rmod, Uint8 Gmod, Uint8 Bmod, bpp = ((dst->BytesPerPixel == 3) ? 4 : dst->BytesPerPixel); map = (Uint8 *)SDL_calloc(256, bpp); - if (map == NULL) { - SDL_OutOfMemory(); + if (!map) { return NULL; } @@ -1006,8 +1042,7 @@ SDL_BlitMap *SDL_AllocBlitMap(void) /* Allocate the empty map */ map = (SDL_BlitMap *)SDL_calloc(1, sizeof(*map)); - if (map == NULL) { - SDL_OutOfMemory(); + if (!map) { return NULL; } map->info.r = 0xFF; @@ -1035,7 +1070,7 @@ void SDL_InvalidateAllBlitMap(SDL_Surface *surface) void SDL_InvalidateMap(SDL_BlitMap *map) { - if (map == NULL) { + if (!map) { return; } if (map->dst) { @@ -1074,7 +1109,7 @@ int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst) map->info.table = Map1to1(srcfmt->palette, dstfmt->palette, &map->identity); if (!map->identity) { - if (map->info.table == NULL) { + if (!map->info.table) { return -1; } } @@ -1086,7 +1121,7 @@ int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst) map->info.table = Map1toN(srcfmt, src->map->info.r, src->map->info.g, src->map->info.b, src->map->info.a, dstfmt); - if (map->info.table == NULL) { + if (!map->info.table) { return -1; } } @@ -1095,7 +1130,7 @@ int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst) /* BitField --> Palette */ map->info.table = MapNto1(srcfmt, dstfmt, &map->identity); if (!map->identity) { - if (map->info.table == NULL) { + if (!map->info.table) { return -1; } } diff --git a/src/video/SDL_pixels_c.h b/src/video/SDL_pixels_c.h index 897f973d..e0def8aa 100644 --- a/src/video/SDL_pixels_c.h +++ b/src/video/SDL_pixels_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_rect.c b/src/video/SDL_rect.c index af3a809c..a588fa56 100644 --- a/src/video/SDL_rect.c +++ b/src/video/SDL_rect.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -37,10 +37,10 @@ SDL_bool SDL_GetSpanEnclosingRect(int width, int height, } else if (height < 1) { SDL_InvalidParamError("height"); return SDL_FALSE; - } else if (rects == NULL) { + } else if (!rects) { SDL_InvalidParamError("rects"); return SDL_FALSE; - } else if (span == NULL) { + } else if (!span) { SDL_InvalidParamError("span"); return SDL_FALSE; } else if (numrects < 1) { @@ -88,6 +88,7 @@ SDL_bool SDL_GetSpanEnclosingRect(int width, int height, #define RECTTYPE SDL_Rect #define POINTTYPE SDL_Point #define SCALARTYPE int +#define BIGSCALARTYPE Sint64 #define COMPUTEOUTCODE ComputeOutCode #define SDL_HASINTERSECTION SDL_HasRectIntersection #define SDL_INTERSECTRECT SDL_GetRectIntersection @@ -100,6 +101,7 @@ SDL_bool SDL_GetSpanEnclosingRect(int width, int height, #define RECTTYPE SDL_FRect #define POINTTYPE SDL_FPoint #define SCALARTYPE float +#define BIGSCALARTYPE double #define COMPUTEOUTCODE ComputeOutCodeFloat #define SDL_HASINTERSECTION SDL_HasRectIntersectionFloat #define SDL_INTERSECTRECT SDL_GetRectIntersectionFloat diff --git a/src/video/SDL_rect_c.h b/src/video/SDL_rect_c.h index bac380cd..e3d2a033 100644 --- a/src/video/SDL_rect_c.h +++ b/src/video/SDL_rect_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/SDL_rect_impl.h b/src/video/SDL_rect_impl.h index 4b8713c3..15492da1 100644 --- a/src/video/SDL_rect_impl.h +++ b/src/video/SDL_rect_impl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,10 +25,10 @@ SDL_bool SDL_HASINTERSECTION(const RECTTYPE *A, const RECTTYPE *B) { SCALARTYPE Amin, Amax, Bmin, Bmax; - if (A == NULL) { + if (!A) { SDL_InvalidParamError("A"); return SDL_FALSE; - } else if (B == NULL) { + } else if (!B) { SDL_InvalidParamError("B"); return SDL_FALSE; } else if (SDL_RECTEMPTY(A) || SDL_RECTEMPTY(B)) { @@ -70,13 +70,13 @@ SDL_bool SDL_INTERSECTRECT(const RECTTYPE *A, const RECTTYPE *B, RECTTYPE *resul { SCALARTYPE Amin, Amax, Bmin, Bmax; - if (A == NULL) { + if (!A) { SDL_InvalidParamError("A"); return SDL_FALSE; - } else if (B == NULL) { + } else if (!B) { SDL_InvalidParamError("B"); return SDL_FALSE; - } else if (result == NULL) { + } else if (!result) { SDL_InvalidParamError("result"); return SDL_FALSE; } else if (SDL_RECTEMPTY(A) || SDL_RECTEMPTY(B)) { /* Special cases for empty rects */ @@ -120,11 +120,11 @@ int SDL_UNIONRECT(const RECTTYPE *A, const RECTTYPE *B, RECTTYPE *result) { SCALARTYPE Amin, Amax, Bmin, Bmax; - if (A == NULL) { + if (!A) { return SDL_InvalidParamError("A"); - } else if (B == NULL) { + } else if (!B) { return SDL_InvalidParamError("B"); - } else if (result == NULL) { + } else if (!result) { return SDL_InvalidParamError("result"); } else if (SDL_RECTEMPTY(A)) { /* Special cases for empty Rects */ if (SDL_RECTEMPTY(B)) { /* A and B empty */ @@ -178,7 +178,7 @@ SDL_bool SDL_ENCLOSEPOINTS(const POINTTYPE *points, int count, const RECTTYPE *c SCALARTYPE x, y; int i; - if (points == NULL) { + if (!points) { SDL_InvalidParamError("points"); return SDL_FALSE; } else if (count < 1) { @@ -208,7 +208,7 @@ SDL_bool SDL_ENCLOSEPOINTS(const POINTTYPE *points, int count, const RECTTYPE *c } if (!added) { /* Special case: if no result was requested, we are done */ - if (result == NULL) { + if (!result) { return SDL_TRUE; } @@ -234,7 +234,7 @@ SDL_bool SDL_ENCLOSEPOINTS(const POINTTYPE *points, int count, const RECTTYPE *c } } else { /* Special case: if no result was requested, we are done */ - if (result == NULL) { + if (!result) { return SDL_TRUE; } @@ -297,19 +297,19 @@ SDL_bool SDL_INTERSECTRECTANDLINE(const RECTTYPE *rect, SCALARTYPE *X1, SCALARTY SCALARTYPE recty2; int outcode1, outcode2; - if (rect == NULL) { + if (!rect) { SDL_InvalidParamError("rect"); return SDL_FALSE; - } else if (X1 == NULL) { + } else if (!X1) { SDL_InvalidParamError("X1"); return SDL_FALSE; - } else if (Y1 == NULL) { + } else if (!Y1) { SDL_InvalidParamError("Y1"); return SDL_FALSE; - } else if (X2 == NULL) { + } else if (!X2) { SDL_InvalidParamError("X2"); return SDL_FALSE; - } else if (Y2 == NULL) { + } else if (!Y2) { SDL_InvalidParamError("Y2"); return SDL_FALSE; } else if (SDL_RECTEMPTY(rect)) { @@ -376,16 +376,16 @@ SDL_bool SDL_INTERSECTRECTANDLINE(const RECTTYPE *rect, SCALARTYPE *X1, SCALARTY if (outcode1) { if (outcode1 & CODE_TOP) { y = recty1; - x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1); + x = (SCALARTYPE) (x1 + ((BIGSCALARTYPE)(x2 - x1) * (y - y1)) / (y2 - y1)); } else if (outcode1 & CODE_BOTTOM) { y = recty2; - x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1); + x = (SCALARTYPE) (x1 + ((BIGSCALARTYPE)(x2 - x1) * (y - y1)) / (y2 - y1)); } else if (outcode1 & CODE_LEFT) { x = rectx1; - y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1); + y = (SCALARTYPE) (y1 + ((BIGSCALARTYPE)(y2 - y1) * (x - x1)) / (x2 - x1)); } else if (outcode1 & CODE_RIGHT) { x = rectx2; - y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1); + y = (SCALARTYPE) (y1 + ((BIGSCALARTYPE)(y2 - y1) * (x - x1)) / (x2 - x1)); } x1 = x; y1 = y; @@ -394,23 +394,23 @@ SDL_bool SDL_INTERSECTRECTANDLINE(const RECTTYPE *rect, SCALARTYPE *X1, SCALARTY if (outcode2 & CODE_TOP) { SDL_assert(y2 != y1); /* if equal: division by zero. */ y = recty1; - x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1); + x = (SCALARTYPE) (x1 + ((BIGSCALARTYPE)(x2 - x1) * (y - y1)) / (y2 - y1)); } else if (outcode2 & CODE_BOTTOM) { SDL_assert(y2 != y1); /* if equal: division by zero. */ y = recty2; - x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1); + x = (SCALARTYPE) (x1 + ((BIGSCALARTYPE)(x2 - x1) * (y - y1)) / (y2 - y1)); } else if (outcode2 & CODE_LEFT) { /* If this assertion ever fires, here's the static analysis that warned about it: http://buildbot.libsdl.org/sdl-static-analysis/sdl-macosx-static-analysis/sdl-macosx-static-analysis-1101/report-b0d01a.html#EndPath */ SDL_assert(x2 != x1); /* if equal: division by zero. */ x = rectx1; - y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1); + y = (SCALARTYPE) (y1 + ((BIGSCALARTYPE)(y2 - y1) * (x - x1)) / (x2 - x1)); } else if (outcode2 & CODE_RIGHT) { /* If this assertion ever fires, here's the static analysis that warned about it: http://buildbot.libsdl.org/sdl-static-analysis/sdl-macosx-static-analysis/sdl-macosx-static-analysis-1101/report-39b114.html#EndPath */ SDL_assert(x2 != x1); /* if equal: division by zero. */ x = rectx2; - y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1); + y = (SCALARTYPE) (y1 + ((BIGSCALARTYPE)(y2 - y1) * (x - x1)) / (x2 - x1)); } x2 = x; y2 = y; @@ -427,6 +427,7 @@ SDL_bool SDL_INTERSECTRECTANDLINE(const RECTTYPE *rect, SCALARTYPE *X1, SCALARTY #undef RECTTYPE #undef POINTTYPE #undef SCALARTYPE +#undef BIGSCALARTYPE #undef COMPUTEOUTCODE #undef SDL_HASINTERSECTION #undef SDL_INTERSECTRECT diff --git a/src/video/SDL_shape.c b/src/video/SDL_shape.c deleted file mode 100644 index 3082c1bd..00000000 --- a/src/video/SDL_shape.c +++ /dev/null @@ -1,304 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_sysvideo.h" -#include "SDL_shape_internals.h" - -SDL_Window *SDL_CreateShapedWindow(const char *title, int w, int h, Uint32 flags) -{ - SDL_Window *result = NULL; - result = SDL_CreateWindow(title, w, h, (flags | SDL_WINDOW_BORDERLESS | SDL_WINDOW_HIDDEN) & (~SDL_WINDOW_FULLSCREEN) & (~SDL_WINDOW_RESIZABLE)); - if (result != NULL) { - if (SDL_GetVideoDevice()->shape_driver.CreateShaper == NULL) { - SDL_DestroyWindow(result); - return NULL; - } - result->shaper = SDL_GetVideoDevice()->shape_driver.CreateShaper(result); - if (result->shaper != NULL) { - result->shaper->mode.mode = ShapeModeDefault; - result->shaper->mode.parameters.binarizationCutoff = 1; - result->shaper->hasshape = SDL_FALSE; - return result; - } else { - SDL_DestroyWindow(result); - return NULL; - } - } - return NULL; -} - -SDL_bool SDL_IsShapedWindow(const SDL_Window *window) -{ - if (window == NULL) { - return SDL_FALSE; - } - return (SDL_bool)(window->shaper != NULL); -} - -/* REQUIRES that bitmap point to a w-by-h bitmap with ppb pixels-per-byte. */ -void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shape, Uint8 *bitmap, Uint8 ppb) -{ - int x = 0; - int y = 0; - Uint8 r = 0, g = 0, b = 0, alpha = 0; - Uint8 *pixel = NULL; - Uint32 pixel_value = 0, mask_value = 0; - size_t bytes_per_scanline = (size_t)(shape->w + (ppb - 1)) / ppb; - Uint8 *bitmap_scanline; - SDL_Color key; - - if (SDL_MUSTLOCK(shape)) { - SDL_LockSurface(shape); - } - - SDL_memset(bitmap, 0, shape->h * bytes_per_scanline); - - for (y = 0; y < shape->h; y++) { - bitmap_scanline = bitmap + y * bytes_per_scanline; - for (x = 0; x < shape->w; x++) { - alpha = 0; - pixel_value = 0; - pixel = (Uint8 *)(shape->pixels) + (y * shape->pitch) + (x * shape->format->BytesPerPixel); - switch (shape->format->BytesPerPixel) { - case (1): - pixel_value = *pixel; - break; - case (2): - pixel_value = *(Uint16 *)pixel; - break; - case (3): - pixel_value = *(Uint32 *)pixel & (~shape->format->Amask); - break; - case (4): - pixel_value = *(Uint32 *)pixel; - break; - } - SDL_GetRGBA(pixel_value, shape->format, &r, &g, &b, &alpha); - switch (mode.mode) { - case (ShapeModeDefault): - mask_value = (alpha >= 1 ? 1 : 0); - break; - case (ShapeModeBinarizeAlpha): - mask_value = (alpha >= mode.parameters.binarizationCutoff ? 1 : 0); - break; - case (ShapeModeReverseBinarizeAlpha): - mask_value = (alpha <= mode.parameters.binarizationCutoff ? 1 : 0); - break; - case (ShapeModeColorKey): - key = mode.parameters.colorKey; - mask_value = ((key.r != r || key.g != g || key.b != b) ? 1 : 0); - break; - } - bitmap_scanline[x / ppb] |= mask_value << (x % ppb); - } - } - - if (SDL_MUSTLOCK(shape)) { - SDL_UnlockSurface(shape); - } -} - -static SDL_ShapeTree *RecursivelyCalculateShapeTree(SDL_WindowShapeMode mode, SDL_Surface *mask, SDL_Rect dimensions) -{ - int x = 0, y = 0; - Uint8 *pixel = NULL; - Uint32 pixel_value = 0; - Uint8 r = 0, g = 0, b = 0, a = 0; - SDL_bool pixel_opaque = SDL_FALSE; - int last_opaque = -1; - SDL_Color key; - SDL_ShapeTree *result = (SDL_ShapeTree *)SDL_malloc(sizeof(SDL_ShapeTree)); - SDL_Rect next = { 0, 0, 0, 0 }; - - if (result == NULL) { - SDL_OutOfMemory(); - return NULL; - } - - for (y = dimensions.y; y < dimensions.y + dimensions.h; y++) { - for (x = dimensions.x; x < dimensions.x + dimensions.w; x++) { - pixel_value = 0; - pixel = (Uint8 *)(mask->pixels) + (y * mask->pitch) + (x * mask->format->BytesPerPixel); - switch (mask->format->BytesPerPixel) { - case (1): - pixel_value = *pixel; - break; - case (2): - pixel_value = *(Uint16 *)pixel; - break; - case (3): - pixel_value = *(Uint32 *)pixel & (~mask->format->Amask); - break; - case (4): - pixel_value = *(Uint32 *)pixel; - break; - } - SDL_GetRGBA(pixel_value, mask->format, &r, &g, &b, &a); - switch (mode.mode) { - case (ShapeModeDefault): - pixel_opaque = (a >= 1 ? SDL_TRUE : SDL_FALSE); - break; - case (ShapeModeBinarizeAlpha): - pixel_opaque = (a >= mode.parameters.binarizationCutoff ? SDL_TRUE : SDL_FALSE); - break; - case (ShapeModeReverseBinarizeAlpha): - pixel_opaque = (a <= mode.parameters.binarizationCutoff ? SDL_TRUE : SDL_FALSE); - break; - case (ShapeModeColorKey): - key = mode.parameters.colorKey; - pixel_opaque = ((key.r != r || key.g != g || key.b != b) ? SDL_TRUE : SDL_FALSE); - break; - } - if (last_opaque == -1) { - last_opaque = pixel_opaque; - } - if (last_opaque != pixel_opaque) { - const int halfwidth = dimensions.w / 2; - const int halfheight = dimensions.h / 2; - - result->kind = QuadShape; - - next.x = dimensions.x; - next.y = dimensions.y; - next.w = halfwidth; - next.h = halfheight; - result->data.children.upleft = (struct SDL_ShapeTree *)RecursivelyCalculateShapeTree(mode, mask, next); - - next.x = dimensions.x + halfwidth; - next.w = dimensions.w - halfwidth; - result->data.children.upright = (struct SDL_ShapeTree *)RecursivelyCalculateShapeTree(mode, mask, next); - - next.x = dimensions.x; - next.w = halfwidth; - next.y = dimensions.y + halfheight; - next.h = dimensions.h - halfheight; - result->data.children.downleft = (struct SDL_ShapeTree *)RecursivelyCalculateShapeTree(mode, mask, next); - - next.x = dimensions.x + halfwidth; - next.w = dimensions.w - halfwidth; - result->data.children.downright = (struct SDL_ShapeTree *)RecursivelyCalculateShapeTree(mode, mask, next); - - return result; - } - } - } - - /* If we never recursed, all the pixels in this quadrant have the same "value". */ - result->kind = (last_opaque == SDL_TRUE ? OpaqueShape : TransparentShape); - result->data.shape = dimensions; - return result; -} - -SDL_ShapeTree *SDL_CalculateShapeTree(SDL_WindowShapeMode mode, SDL_Surface *shape) -{ - SDL_Rect dimensions; - SDL_ShapeTree *result = NULL; - - dimensions.x = 0; - dimensions.y = 0; - dimensions.w = shape->w; - dimensions.h = shape->h; - - if (SDL_MUSTLOCK(shape)) { - SDL_LockSurface(shape); - } - result = RecursivelyCalculateShapeTree(mode, shape, dimensions); - if (SDL_MUSTLOCK(shape)) { - SDL_UnlockSurface(shape); - } - return result; -} - -void SDL_TraverseShapeTree(SDL_ShapeTree *tree, SDL_TraversalFunction function, void *closure) -{ - SDL_assert(tree != NULL); - if (tree->kind == QuadShape) { - SDL_TraverseShapeTree((SDL_ShapeTree *)tree->data.children.upleft, function, closure); - SDL_TraverseShapeTree((SDL_ShapeTree *)tree->data.children.upright, function, closure); - SDL_TraverseShapeTree((SDL_ShapeTree *)tree->data.children.downleft, function, closure); - SDL_TraverseShapeTree((SDL_ShapeTree *)tree->data.children.downright, function, closure); - } else { - function(tree, closure); - } -} - -void SDL_FreeShapeTree(SDL_ShapeTree **shape_tree) -{ - if ((*shape_tree)->kind == QuadShape) { - SDL_FreeShapeTree((SDL_ShapeTree **)(char *)&(*shape_tree)->data.children.upleft); - SDL_FreeShapeTree((SDL_ShapeTree **)(char *)&(*shape_tree)->data.children.upright); - SDL_FreeShapeTree((SDL_ShapeTree **)(char *)&(*shape_tree)->data.children.downleft); - SDL_FreeShapeTree((SDL_ShapeTree **)(char *)&(*shape_tree)->data.children.downright); - } - SDL_free(*shape_tree); - *shape_tree = NULL; -} - -int SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode) -{ - SDL_VideoDevice *_this = SDL_GetVideoDevice(); - int result; - - if (window == NULL || !SDL_IsShapedWindow(window)) { - /* The window given was not a shapeable window. */ - return SDL_NONSHAPEABLE_WINDOW; - } - if (shape == NULL) { - /* Invalid shape argument. */ - return SDL_INVALID_SHAPE_ARGUMENT; - } - - if (shape_mode != NULL) { - window->shaper->mode = *shape_mode; - } - result = _this->shape_driver.SetWindowShape(window->shaper, shape, shape_mode); - if (result == 0) { - window->shaper->hasshape = SDL_TRUE; - SDL_ShowWindow(window); - } - return result; -} - -static SDL_bool SDL_WindowHasAShape(SDL_Window *window) -{ - if (window == NULL || !SDL_IsShapedWindow(window)) { - return SDL_FALSE; - } - return window->shaper->hasshape; -} - -int SDL_GetShapedWindowMode(SDL_Window *window, SDL_WindowShapeMode *shape_mode) -{ - if (window != NULL && SDL_IsShapedWindow(window)) { - if (shape_mode == NULL) { - if (SDL_WindowHasAShape(window)) { - return 0; /* The window given has a shape. */ - } else { - return SDL_WINDOW_LACKS_SHAPE; /* The window given is shapeable but lacks a shape. */ - } - } else { - *shape_mode = window->shaper->mode; - return 0; - } - } - return SDL_NONSHAPEABLE_WINDOW; /* The window given is not a valid shapeable window. */ -} diff --git a/src/video/SDL_shape_internals.h b/src/video/SDL_shape_internals.h deleted file mode 100644 index fa8e90ad..00000000 --- a/src/video/SDL_shape_internals.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_shape_internals_h_ -#define SDL_shape_internals_h_ - -struct SDL_ShapeTree; - -typedef struct -{ - struct SDL_ShapeTree *upleft, *upright, *downleft, *downright; -} SDL_QuadTreeChildren; - -typedef union -{ - SDL_QuadTreeChildren children; - SDL_Rect shape; -} SDL_ShapeUnion; - -typedef enum -{ - QuadShape, - TransparentShape, - OpaqueShape -} SDL_ShapeKind; - -typedef struct SDL_ShapeTree -{ - SDL_ShapeKind kind; - SDL_ShapeUnion data; -} SDL_ShapeTree; - -typedef void (*SDL_TraversalFunction)(SDL_ShapeTree *, void *); - -extern void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shape, Uint8 *bitmap, Uint8 ppb); -extern SDL_ShapeTree *SDL_CalculateShapeTree(SDL_WindowShapeMode mode, SDL_Surface *shape); -extern void SDL_TraverseShapeTree(SDL_ShapeTree *tree, SDL_TraversalFunction function, void *closure); -extern void SDL_FreeShapeTree(SDL_ShapeTree **shape_tree); - -#endif /* SDL_shape_internals_h_ */ diff --git a/src/video/SDL_stretch.c b/src/video/SDL_stretch.c index 3b628c26..77e415cf 100644 --- a/src/video/SDL_stretch.c +++ b/src/video/SDL_stretch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,22 +24,10 @@ static int SDL_LowerSoftStretchNearest(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect); static int SDL_LowerSoftStretchLinear(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect); -static int SDL_UpperSoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode); int SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect) -{ - return SDL_UpperSoftStretch(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST); -} - -int SDL_SoftStretchLinear(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect) -{ - return SDL_UpperSoftStretch(src, srcrect, dst, dstrect, SDL_SCALEMODE_LINEAR); -} - -static int SDL_UpperSoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode) + SDL_Surface *dst, const SDL_Rect *dstrect, + SDL_ScaleMode scaleMode) { int ret; int src_locked; @@ -47,11 +35,20 @@ static int SDL_UpperSoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Rect full_src; SDL_Rect full_dst; + if (src->format->format != dst->format->format) { return SDL_SetError("Only works with same format surfaces"); } + if (scaleMode != SDL_SCALEMODE_NEAREST && scaleMode != SDL_SCALEMODE_LINEAR && scaleMode != SDL_SCALEMODE_BEST) { + return SDL_InvalidParamError("scaleMode"); + } + if (scaleMode != SDL_SCALEMODE_NEAREST) { + scaleMode = SDL_SCALEMODE_LINEAR; + } + + if (scaleMode == SDL_SCALEMODE_LINEAR) { if (src->format->BytesPerPixel != 4 || src->format->format == SDL_PIXELFORMAT_ARGB2101010) { return SDL_SetError("Wrong format"); } diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 4a5d2985..c051d836 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -21,8 +21,10 @@ #include "SDL_internal.h" #include "SDL_sysvideo.h" +#include "SDL_video_c.h" #include "SDL_blit.h" #include "SDL_RLEaccel_c.h" +#include "SDL_surface_pixel_impl.h" #include "SDL_pixels_c.h" #include "SDL_yuv_c.h" #include "../render/SDL_sysrender.h" @@ -34,6 +36,11 @@ SDL_COMPILE_TIME_ASSERT(surface_size_assumptions, SDL_COMPILE_TIME_ASSERT(can_indicate_overflow, SDL_SIZE_MAX > SDL_MAX_SINT32); +int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a) +{ + return SDL_ReadSurfacePixel_impl(surface, x, y, r, g, b, a); +} + /* Public routines */ /* @@ -129,14 +136,12 @@ SDL_Surface *SDL_CreateSurface(int width, int height, Uint32 format) if (SDL_CalculateSize(format, width, height, &size, &pitch, SDL_FALSE /* not minimal pitch */) < 0) { /* Overflow... */ - SDL_OutOfMemory(); return NULL; } /* Allocate the surface */ surface = (SDL_Surface *)SDL_calloc(1, sizeof(*surface)); - if (surface == NULL) { - SDL_OutOfMemory(); + if (!surface) { return NULL; } @@ -153,7 +158,7 @@ SDL_Surface *SDL_CreateSurface(int width, int height, Uint32 format) if (SDL_ISPIXELFORMAT_INDEXED(surface->format->format)) { SDL_Palette *palette = SDL_CreatePalette((1 << surface->format->BitsPerPixel)); - if (palette == NULL) { + if (!palette) { SDL_DestroySurface(surface); return NULL; } @@ -175,7 +180,6 @@ SDL_Surface *SDL_CreateSurface(int width, int height, Uint32 format) surface->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), size); if (!surface->pixels) { SDL_DestroySurface(surface); - SDL_OutOfMemory(); return NULL; } surface->flags |= SDL_SIMD_ALIGNED; @@ -204,9 +208,7 @@ SDL_Surface *SDL_CreateSurface(int width, int height, Uint32 format) * Create an RGB surface from an existing memory buffer using the given given * enum SDL_PIXELFORMAT_* format */ -SDL_Surface *SDL_CreateSurfaceFrom(void *pixels, - int width, int height, int pitch, - Uint32 format) +SDL_Surface *SDL_CreateSurfaceFrom(void *pixels, int width, int height, int pitch, Uint32 format) { SDL_Surface *surface; @@ -220,14 +222,13 @@ SDL_Surface *SDL_CreateSurfaceFrom(void *pixels, return NULL; } - if (pitch == 0 && pixels == NULL) { + if (pitch == 0 && !pixels) { /* The application will fill these in later with valid values */ } else { size_t minimalPitch; if (SDL_CalculateSize(format, width, height, NULL, &minimalPitch, SDL_TRUE /* minimal pitch */) < 0) { /* Overflow... */ - SDL_OutOfMemory(); return NULL; } @@ -238,7 +239,7 @@ SDL_Surface *SDL_CreateSurfaceFrom(void *pixels, } surface = SDL_CreateSurface(0, 0, format); - if (surface != NULL) { + if (surface) { surface->flags |= SDL_PREALLOC; surface->pixels = pixels; surface->w = width; @@ -249,10 +250,36 @@ SDL_Surface *SDL_CreateSurfaceFrom(void *pixels, return surface; } +SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface) +{ + SDL_PropertiesID props; + + if (!surface) { + SDL_InvalidParamError("surface"); + return 0; + } + + if (surface->flags & SDL_SURFACE_USES_PROPERTIES) { + props = (SDL_PropertiesID)(uintptr_t)surface->reserved; + } else { + if (surface->reserved != NULL) { + SDL_SetError("Surface has userdata, incompatible with properties"); + return 0; + } + + props = SDL_CreateProperties(); + if (props) { + surface->reserved = (void *)(uintptr_t)props; + surface->flags |= SDL_SURFACE_USES_PROPERTIES; + } + } + return props; +} + int SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette) { - if (surface == NULL) { - return SDL_InvalidParamError("SDL_SetSurfacePalette(): surface"); + if (!surface) { + return SDL_InvalidParamError("surface"); } if (SDL_SetPixelFormatPalette(surface->format, palette) < 0) { return -1; @@ -266,8 +293,8 @@ int SDL_SetSurfaceRLE(SDL_Surface *surface, int flag) { int flags; - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } flags = surface->map->info.flags; @@ -284,7 +311,7 @@ int SDL_SetSurfaceRLE(SDL_Surface *surface, int flag) SDL_bool SDL_SurfaceHasRLE(SDL_Surface *surface) { - if (surface == NULL) { + if (!surface) { return SDL_FALSE; } @@ -299,7 +326,7 @@ int SDL_SetSurfaceColorKey(SDL_Surface *surface, int flag, Uint32 key) { int flags; - if (surface == NULL) { + if (!surface) { return SDL_InvalidParamError("surface"); } @@ -327,7 +354,7 @@ int SDL_SetSurfaceColorKey(SDL_Surface *surface, int flag, Uint32 key) SDL_bool SDL_SurfaceHasColorKey(SDL_Surface *surface) { - if (surface == NULL) { + if (!surface) { return SDL_FALSE; } @@ -340,7 +367,7 @@ SDL_bool SDL_SurfaceHasColorKey(SDL_Surface *surface) int SDL_GetSurfaceColorKey(SDL_Surface *surface, Uint32 *key) { - if (surface == NULL) { + if (!surface) { return SDL_InvalidParamError("surface"); } @@ -360,7 +387,7 @@ static void SDL_ConvertColorkeyToAlpha(SDL_Surface *surface, SDL_bool ignore_alp { int x, y, bpp; - if (surface == NULL) { + if (!surface) { return; } @@ -449,8 +476,8 @@ int SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b) { int flags; - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } surface->map->info.r = r; @@ -471,8 +498,8 @@ int SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b) int SDL_GetSurfaceColorMod(SDL_Surface *surface, Uint8 *r, Uint8 *g, Uint8 *b) { - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } if (r) { @@ -491,8 +518,8 @@ int SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha) { int flags; - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } surface->map->info.a = alpha; @@ -511,8 +538,8 @@ int SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha) int SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Uint8 *alpha) { - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } if (alpha) { @@ -525,8 +552,8 @@ int SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode) { int flags, status; - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } status = 0; @@ -562,11 +589,11 @@ int SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode) int SDL_GetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode *blendMode) { - if (surface == NULL) { - return -1; + if (!surface) { + return SDL_InvalidParamError("surface"); } - if (blendMode == NULL) { + if (!blendMode) { return 0; } @@ -595,7 +622,7 @@ SDL_bool SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect) SDL_Rect full_rect; /* Don't do anything if there's no surface to act on */ - if (surface == NULL) { + if (!surface) { return SDL_FALSE; } @@ -606,7 +633,7 @@ SDL_bool SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect) full_rect.h = surface->h; /* Set the clipping rectangle */ - if (rect == NULL) { + if (!rect) { surface->clip_rect = full_rect; return SDL_TRUE; } @@ -661,10 +688,11 @@ int SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) { SDL_Rect fulldst; - int srcx, srcy, w, h; + int srcx, srcy; + Sint64 w, h; /* Make sure the surfaces aren't locked */ - if (src == NULL || dst == NULL) { + if (!src || !dst) { return SDL_InvalidParamError("SDL_BlitSurface(): src/dst"); } if (src->locked || dst->locked) { @@ -672,7 +700,7 @@ int SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, } /* If the destination rectangle is NULL, use the entire dest surface */ - if (dstrect == NULL) { + if (!dstrect) { fulldst.x = fulldst.y = 0; fulldst.w = dst->w; fulldst.h = dst->h; @@ -760,13 +788,8 @@ int SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, } int SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, SDL_Rect *dstrect) -{ - return SDL_PrivateBlitSurfaceScaled(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST); -} - -int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, SDL_Rect *dstrect, SDL_ScaleMode scaleMode) + SDL_Surface *dst, SDL_Rect *dstrect, + SDL_ScaleMode scaleMode) { double src_x0, src_y0, src_x1, src_y1; double dst_x0, dst_y0, dst_x1, dst_y1; @@ -776,14 +799,14 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, int dst_w, dst_h; /* Make sure the surfaces aren't locked */ - if (src == NULL || dst == NULL) { + if (!src || !dst) { return SDL_InvalidParamError("SDL_BlitSurfaceScaled(): src/dst"); } if (src->locked || dst->locked) { return SDL_SetError("Surfaces must not be locked during blit"); } - if (srcrect == NULL) { + if (!srcrect) { src_w = src->w; src_h = src->h; } else { @@ -791,7 +814,7 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, src_h = srcrect->h; } - if (dstrect == NULL) { + if (!dstrect) { dst_w = dst->w; dst_h = dst->h; } else { @@ -807,7 +830,7 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, scaling_w = (double)dst_w / src_w; scaling_h = (double)dst_h / src_h; - if (dstrect == NULL) { + if (!dstrect) { dst_x0 = 0; dst_y0 = 0; dst_x1 = dst_w; @@ -819,7 +842,7 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, dst_y1 = dst_y0 + dst_h; } - if (srcrect == NULL) { + if (!srcrect) { src_x0 = 0; src_y0 = 0; src_x1 = src_w; @@ -920,7 +943,7 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, return 0; } - return SDL_PrivateBlitSurfaceUncheckedScaled(src, &final_src, dst, &final_dst, scaleMode); + return SDL_BlitSurfaceUncheckedScaled(src, &final_src, dst, &final_dst, scaleMode); } /** @@ -928,18 +951,21 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, * scaled blitting only. */ int SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect) -{ - return SDL_PrivateBlitSurfaceUncheckedScaled(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST); -} - -int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, - SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode) + SDL_Surface *dst, const SDL_Rect *dstrect, + SDL_ScaleMode scaleMode) { static const Uint32 complex_copy_flags = (SDL_COPY_MODULATE_COLOR | SDL_COPY_MODULATE_ALPHA | SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL | SDL_COPY_COLORKEY); + if (scaleMode != SDL_SCALEMODE_NEAREST && scaleMode != SDL_SCALEMODE_LINEAR && scaleMode != SDL_SCALEMODE_BEST) { + return SDL_InvalidParamError("scaleMode"); + } + + if (scaleMode != SDL_SCALEMODE_NEAREST) { + scaleMode = SDL_SCALEMODE_LINEAR; + } + if (srcrect->w > SDL_MAX_UINT16 || srcrect->h > SDL_MAX_UINT16 || dstrect->w > SDL_MAX_UINT16 || dstrect->h > SDL_MAX_UINT16) { return SDL_SetError("Size too large for scaling"); @@ -954,7 +980,7 @@ int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcr if (!(src->map->info.flags & complex_copy_flags) && src->format->format == dst->format->format && !SDL_ISPIXELFORMAT_INDEXED(src->format->format)) { - return SDL_SoftStretch(src, srcrect, dst, dstrect); + return SDL_SoftStretch(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST); } else { return SDL_BlitSurfaceUnchecked(src, srcrect, dst, dstrect); } @@ -965,7 +991,7 @@ int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcr src->format->BytesPerPixel == 4 && src->format->format != SDL_PIXELFORMAT_ARGB2101010) { /* fast path */ - return SDL_SoftStretchLinear(src, srcrect, dst, dstrect); + return SDL_SoftStretch(src, srcrect, dst, dstrect, SDL_SCALEMODE_LINEAR); } else { /* Use intermediate surface(s) */ SDL_Surface *tmp1 = NULL; @@ -1015,7 +1041,7 @@ int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcr if (is_complex_copy_flags || src->format->format != dst->format->format) { SDL_Rect tmprect; SDL_Surface *tmp2 = SDL_CreateSurface(dstrect->w, dstrect->h, src->format->format); - SDL_SoftStretchLinear(src, &srcrect2, tmp2, NULL); + SDL_SoftStretch(src, &srcrect2, tmp2, NULL, SDL_SCALEMODE_LINEAR); SDL_SetSurfaceColorMod(tmp2, r, g, b); SDL_SetSurfaceAlphaMod(tmp2, alpha); @@ -1028,7 +1054,7 @@ int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcr ret = SDL_BlitSurfaceUnchecked(tmp2, &tmprect, dst, dstrect); SDL_DestroySurface(tmp2); } else { - ret = SDL_SoftStretchLinear(src, &srcrect2, dst, dstrect); + ret = SDL_SoftStretch(src, &srcrect2, dst, dstrect, SDL_SCALEMODE_LINEAR); } SDL_DestroySurface(tmp1); @@ -1102,17 +1128,17 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for Uint8 *palette_saved_alpha = NULL; int palette_saved_alpha_ncolors = 0; - if (surface == NULL) { + if (!surface) { SDL_InvalidParamError("surface"); return NULL; } - if (format == NULL) { + if (!format) { SDL_InvalidParamError("format"); return NULL; } /* Check for empty destination palette! (results in empty image) */ - if (format->palette != NULL) { + if (format->palette) { int i; for (i = 0; i < format->palette->ncolors; ++i) { if ((format->palette->colors[i].r != 0xFF) || (format->palette->colors[i].g != 0xFF) || (format->palette->colors[i].b != 0xFF)) { @@ -1127,7 +1153,7 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for /* Create a new surface with the desired format */ convert = SDL_CreateSurface(surface->w, surface->h, format->format); - if (convert == NULL) { + if (!convert) { return NULL; } @@ -1196,10 +1222,12 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for if (set_opaque) { int i; palette_saved_alpha_ncolors = surface->format->palette->ncolors; - palette_saved_alpha = SDL_stack_alloc(Uint8, palette_saved_alpha_ncolors); - for (i = 0; i < palette_saved_alpha_ncolors; i++) { - palette_saved_alpha[i] = surface->format->palette->colors[i].a; - surface->format->palette->colors[i].a = SDL_ALPHA_OPAQUE; + if (palette_saved_alpha_ncolors > 0) { + palette_saved_alpha = SDL_stack_alloc(Uint8, palette_saved_alpha_ncolors); + for (i = 0; i < palette_saved_alpha_ncolors; i++) { + palette_saved_alpha[i] = surface->format->palette->colors[i].a; + surface->format->palette->colors[i].a = SDL_ALPHA_OPAQUE; + } } } } @@ -1285,7 +1313,7 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for /* Create a dummy surface to get the colorkey converted */ tmp = SDL_CreateSurface(1, 1, surface->format->format); - if (tmp == NULL) { + if (!tmp) { SDL_DestroySurface(convert); return NULL; } @@ -1301,7 +1329,7 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for /* Conversion of the colorkey */ tmp2 = SDL_ConvertSurface(tmp, format); - if (tmp2 == NULL) { + if (!tmp2) { SDL_DestroySurface(tmp); SDL_DestroySurface(convert); return NULL; @@ -1407,13 +1435,13 @@ int SDL_ConvertPixels(int width, int height, void *nonconst_src = (void *)src; int ret; - if (src == NULL) { + if (!src) { return SDL_InvalidParamError("src"); } if (!src_pitch) { return SDL_InvalidParamError("src_pitch"); } - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("dst"); } if (!dst_pitch) { @@ -1489,13 +1517,13 @@ int SDL_PremultiplyAlpha(int width, int height, Uint32 dstpixel; Uint32 dstR, dstG, dstB, dstA; - if (src == NULL) { + if (!src) { return SDL_InvalidParamError("src"); } if (!src_pitch) { return SDL_InvalidParamError("src_pitch"); } - if (dst == NULL) { + if (!dst) { return SDL_InvalidParamError("dst"); } if (!dst_pitch) { @@ -1537,19 +1565,24 @@ int SDL_PremultiplyAlpha(int width, int height, */ void SDL_DestroySurface(SDL_Surface *surface) { - if (surface == NULL) { + if (!surface) { return; } if (surface->flags & SDL_DONTFREE) { return; } - SDL_InvalidateMap(surface->map); - - SDL_InvalidateAllBlitMap(surface); - if (--surface->refcount > 0) { return; } + + if (surface->flags & SDL_SURFACE_USES_PROPERTIES) { + SDL_PropertiesID props = (SDL_PropertiesID)(uintptr_t)surface->reserved; + SDL_DestroyProperties(props); + } + + SDL_InvalidateMap(surface->map); + SDL_InvalidateAllBlitMap(surface); + while (surface->locked > 0) { SDL_UnlockSurface(surface); } diff --git a/src/video/SDL_surface_pixel_impl.h b/src/video/SDL_surface_pixel_impl.h new file mode 100644 index 00000000..67f772fe --- /dev/null +++ b/src/video/SDL_surface_pixel_impl.h @@ -0,0 +1,80 @@ +/* + Copyright 1997-2024 Sam Lantinga + Copyright 2023 Collabora Ltd. + + 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 "SDL3/SDL.h" + +/* Internal implementation of SDL_ReadSurfacePixel, shared between SDL_shape + * and SDLTest */ +static int SDL_ReadSurfacePixel_impl(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a) +{ + Uint32 pixel = 0; + size_t bytes_per_pixel; + void *p; + + if (!surface || !surface->format || !surface->pixels) { + return SDL_InvalidParamError("surface"); + } + + if (x < 0 || x >= surface->w) { + return SDL_InvalidParamError("x"); + } + + if (y < 0 || y >= surface->h) { + return SDL_InvalidParamError("y"); + } + + if (!r) { + return SDL_InvalidParamError("r"); + } + + if (!g) { + return SDL_InvalidParamError("g"); + } + + if (!b) { + return SDL_InvalidParamError("b"); + } + + if (!a) { + return SDL_InvalidParamError("a"); + } + + bytes_per_pixel = surface->format->BytesPerPixel; + + if (bytes_per_pixel > sizeof(pixel)) { + return SDL_InvalidParamError("surface->format->BytesPerPixel"); + } + + SDL_LockSurface(surface); + + p = (Uint8 *)surface->pixels + y * surface->pitch + x * bytes_per_pixel; + /* Fill the appropriate number of least-significant bytes of pixel, + * leaving the most-significant bytes set to zero */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + SDL_memcpy(((Uint8 *) &pixel) + (sizeof(pixel) - bytes_per_pixel), p, bytes_per_pixel); +#else + SDL_memcpy(&pixel, p, bytes_per_pixel); +#endif + SDL_GetRGBA(pixel, surface->format, r, g, b, a); + + SDL_UnlockSurface(surface); + return 0; +} diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 0d55708a..0273f1ad 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,8 +27,6 @@ /* The SDL video driver */ -typedef struct SDL_WindowShaper SDL_WindowShaper; -typedef struct SDL_ShapeDriver SDL_ShapeDriver; typedef struct SDL_VideoDisplay SDL_VideoDisplay; typedef struct SDL_VideoDevice SDL_VideoDevice; typedef struct SDL_VideoData SDL_VideoData; @@ -36,35 +34,6 @@ typedef struct SDL_DisplayData SDL_DisplayData; typedef struct SDL_DisplayModeData SDL_DisplayModeData; typedef struct SDL_WindowData SDL_WindowData; -/* Define the SDL window-shaper structure */ -struct SDL_WindowShaper -{ - /* The window associated with the shaper */ - SDL_Window *window; - - /* The parameters for shape calculation. */ - SDL_WindowShapeMode mode; - - /* Has this window been assigned a shape? */ - SDL_bool hasshape; - - void *driverdata; -}; - -/* Define the SDL shape driver structure */ -struct SDL_ShapeDriver -{ - SDL_WindowShaper *(*CreateShaper)(SDL_Window *window); - int (*SetWindowShape)(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode); -}; - -typedef struct SDL_WindowUserData -{ - char *name; - void *data; - struct SDL_WindowUserData *next; -} SDL_WindowUserData; - /* Define the SDL window structure, corresponding to toplevel windows */ struct SDL_Window { @@ -84,9 +53,27 @@ struct SDL_Window SDL_DisplayID last_fullscreen_exclusive_display; /* The last fullscreen_exclusive display */ SDL_DisplayID last_displayID; - /* Stored position and size for windowed mode */ + /* Stored position and size for the window in the non-fullscreen state, + * including when the window is maximized or tiled. + * + * This is the size and position to which the window should return when + * leaving the fullscreen state. + */ SDL_Rect windowed; + /* Stored position and size for the window in the base 'floating' state; + * when not fullscreen, nor in a state such as maximized or tiled. + * + * This is the size and position to which the window should return when + * it's maximized and SDL_RestoreWindow() is called. + */ + SDL_Rect floating; + + /* Toggle for drivers to indicate that the current window state is + * not floating, but may not have any fixed-size flags (e.g. tiled) + */ + SDL_bool state_not_floating; + /* Whether or not the initial position was defined */ SDL_bool undefined_x; SDL_bool undefined_y; @@ -106,12 +93,10 @@ struct SDL_Window SDL_Rect mouse_rect; - SDL_WindowShaper *shaper; - SDL_HitTest hit_test; void *hit_test_data; - SDL_WindowUserData *data; + SDL_PropertiesID props; SDL_WindowData *driverdata; @@ -128,10 +113,9 @@ struct SDL_Window (((W)->flags & SDL_WINDOW_HIDDEN) == 0) && \ (((W)->flags & SDL_WINDOW_MINIMIZED) == 0)) -#define SDL_WINDOW_IS_POPUP(W) \ - ((((W)->flags & SDL_WINDOW_TOOLTIP) != 0) || \ - (((W)->flags & SDL_WINDOW_POPUP_MENU) != 0)) \ - \ +#define SDL_WINDOW_IS_POPUP(W) \ + (((W)->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) + /* * Define the SDL display structure. * This corresponds to physical monitors attached to the system. @@ -153,19 +137,18 @@ struct SDL_VideoDisplay SDL_VideoDevice *device; + SDL_PropertiesID props; + SDL_DisplayData *driverdata; }; -/* Forward declaration */ -struct SDL_SysWMinfo; - /* Video device flags */ typedef enum { - VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED = 0x01, - VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE = 0x02, - VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT = 0x04, -} DeviceQuirkFlags; + VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED = 0x01, + VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT = 0x02, + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS = 0x04 +} DeviceCaps; struct SDL_VideoDevice { @@ -230,8 +213,7 @@ struct SDL_VideoDevice /* * Window functions */ - int (*CreateSDLWindow)(SDL_VideoDevice *_this, SDL_Window *window); - int (*CreateSDLWindowFrom)(SDL_VideoDevice *_this, SDL_Window *window, const void *data); + int (*CreateSDLWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void (*SetWindowTitle)(SDL_VideoDevice *_this, SDL_Window *window); int (*SetWindowIcon)(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); int (*SetWindowPosition)(SDL_VideoDevice *_this, SDL_Window *window); @@ -252,7 +234,7 @@ struct SDL_VideoDevice void (*SetWindowBordered)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); void (*SetWindowResizable)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); void (*SetWindowAlwaysOnTop)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top); - void (*SetWindowFullscreen)(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); + int (*SetWindowFullscreen)(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); void *(*GetWindowICCProfile)(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); SDL_DisplayID (*GetDisplayForWindow)(SDL_VideoDevice *_this, SDL_Window *window); void (*SetWindowMouseRect)(SDL_VideoDevice *_this, SDL_Window *window); @@ -265,15 +247,7 @@ struct SDL_VideoDevice void (*OnWindowEnter)(SDL_VideoDevice *_this, SDL_Window *window); int (*FlashWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); int (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); - - /* * * */ - /* - * Shaped-window functions - */ - SDL_ShapeDriver shape_driver; - - /* Get some platform dependent window information */ - int (*GetWindowWMInfo)(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); + int (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window); /* * * */ /* @@ -297,8 +271,8 @@ struct SDL_VideoDevice */ int (*Vulkan_LoadLibrary)(SDL_VideoDevice *_this, const char *path); void (*Vulkan_UnloadLibrary)(SDL_VideoDevice *_this); - SDL_bool (*Vulkan_GetInstanceExtensions)(SDL_VideoDevice *_this, unsigned *count, const char **names); - SDL_bool (*Vulkan_CreateSurface)(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, VkSurfaceKHR *surface); + char const* const* (*Vulkan_GetInstanceExtensions)(SDL_VideoDevice *_this, Uint32 *count); + SDL_bool (*Vulkan_CreateSurface)(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); /* * * */ /* @@ -371,7 +345,6 @@ struct SDL_VideoDevice SDL_Window *windows; SDL_Window *grabbed_window; Uint8 window_magic; - SDL_WindowID next_object_id; Uint32 clipboard_sequence; SDL_ClipboardDataCallback clipboard_callback; SDL_ClipboardCleanupCallback clipboard_cleanup; @@ -380,7 +353,7 @@ struct SDL_VideoDevice size_t num_clipboard_mime_types; char *primary_selection_text; SDL_bool setting_display_mode; - Uint32 quirk_flags; + Uint32 device_caps; SDL_SystemTheme system_theme; /* * * */ @@ -514,8 +487,10 @@ extern void SDL_ResetFullscreenDisplayModes(SDL_VideoDisplay *display); extern void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode); extern void SDL_SetCurrentDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode); extern void SDL_SetDisplayContentScale(SDL_VideoDisplay *display, float scale); +extern int SDL_SetDisplayModeForDisplay(SDL_VideoDisplay *display, SDL_DisplayMode *mode); extern SDL_VideoDisplay *SDL_GetVideoDisplay(SDL_DisplayID display); extern SDL_VideoDisplay *SDL_GetVideoDisplayForWindow(SDL_Window *window); +extern SDL_VideoDisplay *SDL_GetVideoDisplayForFullscreenWindow(SDL_Window *window); extern int SDL_GetDisplayIndex(SDL_DisplayID displayID); extern SDL_DisplayData *SDL_GetDisplayDriverData(SDL_DisplayID display); extern SDL_DisplayData *SDL_GetDisplayDriverDataForWindow(SDL_Window *window); @@ -528,7 +503,7 @@ extern SDL_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); -extern void SDL_OnDisplayConnected(SDL_VideoDisplay *display); +extern void SDL_OnDisplayAdded(SDL_VideoDisplay *display); extern void SDL_OnWindowShown(SDL_Window *window); extern void SDL_OnWindowHidden(SDL_Window *window); extern void SDL_OnWindowMoved(SDL_Window *window); @@ -544,6 +519,7 @@ extern void SDL_OnWindowFocusGained(SDL_Window *window); extern void SDL_OnWindowFocusLost(SDL_Window *window); extern void SDL_OnWindowDisplayChanged(SDL_Window *window); extern void SDL_UpdateWindowGrab(SDL_Window *window); +extern int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen, SDL_bool commit); extern SDL_Window *SDL_GetToplevelForKeyboardFocus(void); extern SDL_bool SDL_ShouldAllowTopmost(void); diff --git a/src/video/SDL_sysvideocapture.h b/src/video/SDL_sysvideocapture.h new file mode 100644 index 00000000..e7ba6e96 --- /dev/null +++ b/src/video/SDL_sysvideocapture.h @@ -0,0 +1,92 @@ +/* + Simple DirectMedia Layer + 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 + 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_sysvideocapture_h_ +#define SDL_sysvideocapture_h_ + +#include "../SDL_list.h" + +/* The SDL video_capture driver */ +typedef struct SDL_VideoCaptureDevice SDL_VideoCaptureDevice; + +/* Define the SDL video_capture driver structure */ +struct SDL_VideoCaptureDevice +{ + /* * * */ + /* Data common to all devices */ + + /* The device's current video_capture specification */ + SDL_VideoCaptureSpec spec; + + /* Device name */ + char *dev_name; + + /* Current state flags */ + SDL_AtomicInt shutdown; + SDL_AtomicInt enabled; + SDL_bool is_spec_set; + + /* A mutex for locking the queue buffers */ + SDL_Mutex *device_lock; + SDL_Mutex *acquiring_lock; + + /* A thread to feed the video_capture device */ + SDL_Thread *thread; + SDL_threadID threadid; + + /* Queued buffers (if app not using callback). */ + SDL_ListNode *buffer_queue; + + /* * * */ + /* Data private to this driver */ + struct SDL_PrivateVideoCaptureData *hidden; +}; + +extern int SDL_SYS_VideoCaptureInit(void); +extern int SDL_SYS_VideoCaptureQuit(void); + +extern int OpenDevice(SDL_VideoCaptureDevice *_this); +extern void CloseDevice(SDL_VideoCaptureDevice *_this); + +extern int InitDevice(SDL_VideoCaptureDevice *_this); + +extern int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec); + +extern int StartCapture(SDL_VideoCaptureDevice *_this); +extern int StopCapture(SDL_VideoCaptureDevice *_this); + +extern int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame); +extern int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame); + +extern int GetNumFormats(SDL_VideoCaptureDevice *_this); +extern int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format); + +extern int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format); +extern int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height); + +extern int GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size); +extern SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count); + +extern SDL_bool check_all_device_closed(void); +extern SDL_bool check_device_playing(void); + +#endif /* SDL_sysvideocapture_h_ */ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index fbd63a8c..1bd3055c 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,9 +30,9 @@ #include "SDL_rect_c.h" #include "SDL_video_c.h" #include "../events/SDL_events_c.h" +#include "../SDL_hints_c.h" #include "../timer/SDL_timer_c.h" - -#include +#include "SDL_video_capture_c.h" #ifdef SDL_VIDEO_OPENGL #include @@ -158,32 +158,44 @@ static VideoBootStrap *bootstrap[] = { #if defined(__MACOS__) && defined(SDL_VIDEO_DRIVER_COCOA) /* Support for macOS fullscreen spaces */ extern SDL_bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window); -extern SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool state); +extern SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool state, SDL_bool blocking); #endif static void SDL_CheckWindowDisplayChanged(SDL_Window *window); static void SDL_CheckWindowDisplayScaleChanged(SDL_Window *window); /* Convenience functions for reading driver flags */ -static SDL_bool ModeSwitchingEmulated(SDL_VideoDevice *_this) +static SDL_bool SDL_ModeSwitchingEmulated(SDL_VideoDevice *_this) { - if (_this->quirk_flags & VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED) { + if (_this->device_caps & VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED) { return SDL_TRUE; } return SDL_FALSE; } -static SDL_bool DisableUnsetFullscreenOnMinimize(SDL_VideoDevice *_this) +static SDL_bool SDL_SendsFullscreenDimensions(SDL_VideoDevice *_this) { - if (_this->quirk_flags & VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE) { - return SDL_TRUE; + return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS); +} + +/* Hint to treat all window ops as synchronous */ +static SDL_bool syncHint; + +static void SDL_SyncHintWatcher(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + syncHint = SDL_GetStringBoolean(newValue, SDL_FALSE); +} + +static void SDL_SyncIfRequired(SDL_Window *window) +{ + if (syncHint) { + SDL_SyncWindow(window); } - return SDL_FALSE; } /* Support for framebuffer emulation using an accelerated renderer */ -#define SDL_WINDOWTEXTUREDATA "_SDL_WindowTextureData" +#define SDL_PROPERTY_WINDOW_TEXTUREDATA "SDL.internal.window.texturedata" typedef struct { @@ -197,29 +209,44 @@ typedef struct static Uint32 SDL_DefaultGraphicsBackends(SDL_VideoDevice *_this) { #if (defined(SDL_VIDEO_OPENGL) && defined(__MACOS__)) || (defined(__IOS__) && !TARGET_OS_MACCATALYST) || defined(__ANDROID__) - if (_this->GL_CreateContext != NULL) { + if (_this->GL_CreateContext) { return SDL_WINDOW_OPENGL; } #endif #if defined(SDL_VIDEO_METAL) && (TARGET_OS_MACCATALYST || defined(__MACOS__) || defined(__IOS__)) - if (_this->Metal_CreateView != NULL) { + if (_this->Metal_CreateView) { return SDL_WINDOW_METAL; } #endif return 0; } +static void SDL_CleanupWindowTextureData(void *userdata, void *value) +{ + SDL_WindowTextureData *data = (SDL_WindowTextureData *)value; + + if (data->texture) { + SDL_DestroyTexture(data->texture); + } + if (data->renderer) { + SDL_DestroyRenderer(data->renderer); + } + SDL_free(data->pixels); + SDL_free(data); +} + static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch) { SDL_RendererInfo info; - SDL_WindowTextureData *data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA); - const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? SDL_TRUE : SDL_FALSE; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_WindowTextureData *data = (SDL_WindowTextureData *)SDL_GetProperty(props, SDL_PROPERTY_WINDOW_TEXTUREDATA, NULL); + const SDL_bool transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? SDL_TRUE : SDL_FALSE; int i; int w, h; SDL_GetWindowSizeInPixels(window, &w, &h); - if (data == NULL) { + if (!data) { SDL_Renderer *renderer = NULL; const char *hint = SDL_GetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION); const SDL_bool specific_accelerated_renderer = (hint && *hint != '0' && *hint != '1' && @@ -230,7 +257,7 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, U /* Check to see if there's a specific driver requested */ if (specific_accelerated_renderer) { renderer = SDL_CreateRenderer(window, hint, 0); - if (renderer == NULL || (SDL_GetRendererInfo(renderer, &info) == -1)) { + if (!renderer || (SDL_GetRendererInfo(renderer, &info) == -1)) { if (renderer) { SDL_DestroyRenderer(renderer); } @@ -252,7 +279,7 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, U } } } - if (renderer == NULL) { + if (!renderer) { return SDL_SetError("No hardware accelerated renderers available"); } } @@ -261,11 +288,11 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, U /* Create the data after we successfully create the renderer (bug #1116) */ data = (SDL_WindowTextureData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { SDL_DestroyRenderer(renderer); - return SDL_OutOfMemory(); + return -1; } - SDL_SetWindowData(window, SDL_WINDOWTEXTUREDATA, data); + SDL_SetPropertyWithCleanup(props, SDL_PROPERTY_WINDOW_TEXTUREDATA, data, SDL_CleanupWindowTextureData, NULL); data->renderer = renderer; } else { @@ -297,7 +324,7 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, U SDL_TEXTUREACCESS_STREAMING, w, h); if (!data->texture) { - /* codechecker_false_positive [Malloc] Static analyzer doesn't realize allocated `data` is saved to SDL_WINDOWTEXTUREDATA and not leaked here. */ + /* codechecker_false_positive [Malloc] Static analyzer doesn't realize allocated `data` is saved to SDL_PROPERTY_WINDOW_TEXTUREDATA and not leaked here. */ return -1; /* NOLINT(clang-analyzer-unix.Malloc) */ } @@ -310,7 +337,7 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, U const size_t allocsize = (size_t)h * data->pitch; data->pixels = SDL_malloc((allocsize > 0) ? allocsize : 1); if (!data->pixels) { - return SDL_OutOfMemory(); + return -1; } } @@ -335,8 +362,8 @@ static int SDL_UpdateWindowTexture(SDL_VideoDevice *unused, SDL_Window *window, SDL_GetWindowSizeInPixels(window, &w, &h); - data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA); - if (data == NULL || !data->texture) { + data = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_TEXTUREDATA, NULL); + if (!data || !data->texture) { return SDL_SetError("No window texture data"); } @@ -360,31 +387,18 @@ static int SDL_UpdateWindowTexture(SDL_VideoDevice *unused, SDL_Window *window, static void SDL_DestroyWindowTexture(SDL_VideoDevice *unused, SDL_Window *window) { - SDL_WindowTextureData *data; - - data = SDL_SetWindowData(window, SDL_WINDOWTEXTUREDATA, NULL); - if (data == NULL) { - return; - } - if (data->texture) { - SDL_DestroyTexture(data->texture); - } - if (data->renderer) { - SDL_DestroyRenderer(data->renderer); - } - SDL_free(data->pixels); - SDL_free(data); + SDL_ClearProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_TEXTUREDATA); } int SDL_SetWindowTextureVSync(SDL_Window *window, int vsync) { SDL_WindowTextureData *data; - data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA); - if (data == NULL) { + data = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_TEXTUREDATA, NULL); + if (!data) { return -1; } - if (data->renderer == NULL ) { + if (!data->renderer) { return -1; } return SDL_SetRenderVSync(data->renderer, vsync); @@ -443,16 +457,15 @@ int SDL_VideoInit(const char *driver_name) SDL_bool init_keyboard = SDL_FALSE; SDL_bool init_mouse = SDL_FALSE; SDL_bool init_touch = SDL_FALSE; + SDL_bool init_video_capture = SDL_FALSE; int i = 0; /* Check to make sure we don't overwrite '_this' */ - if (_this != NULL) { + if (_this) { SDL_VideoQuit(); } -#ifndef SDL_TIMERS_DISABLED SDL_InitTicks(); -#endif /* Start the event loop */ if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) { @@ -471,17 +484,21 @@ int SDL_VideoInit(const char *driver_name) goto pre_driver_error; } init_touch = SDL_TRUE; + if (SDL_VideoCaptureInit() < 0) { + goto pre_driver_error; + } + init_video_capture = SDL_TRUE; /* Select the proper video driver */ video = NULL; - if (driver_name == NULL) { + if (!driver_name) { driver_name = SDL_GetHint(SDL_HINT_VIDEO_DRIVER); } - if (driver_name != NULL && *driver_name != 0) { + if (driver_name && *driver_name != 0) { const char *driver_attempt = driver_name; - while (driver_attempt != NULL && *driver_attempt != 0 && video == NULL) { + while (driver_attempt && *driver_attempt != 0 && !video) { const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); - size_t driver_attempt_len = (driver_attempt_end != NULL) ? (driver_attempt_end - driver_attempt) + size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) : SDL_strlen(driver_attempt); for (i = 0; bootstrap[i]; ++i) { @@ -492,17 +509,17 @@ int SDL_VideoInit(const char *driver_name) } } - driver_attempt = (driver_attempt_end != NULL) ? (driver_attempt_end + 1) : NULL; + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; } } else { for (i = 0; bootstrap[i]; ++i) { video = bootstrap[i]->create(); - if (video != NULL) { + if (video) { break; } } } - if (video == NULL) { + if (!video) { if (driver_name) { SDL_SetError("%s not available", driver_name); goto pre_driver_error; @@ -515,7 +532,6 @@ int SDL_VideoInit(const char *driver_name) pre_driver_error. */ _this = video; _this->name = bootstrap[i]->name; - _this->next_object_id = 1; _this->thread = SDL_ThreadID(); /* Set some very sane GL defaults */ @@ -538,6 +554,8 @@ int SDL_VideoInit(const char *driver_name) return SDL_SetError("The video driver did not add any displays"); } + SDL_AddHintCallback(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, SDL_SyncHintWatcher, NULL); + /* Disable the screen saver by default. This is a change from <= 2.0.1, but most things using SDL are games or media players; you wouldn't want a screensaver to trigger if you're playing exclusively with a @@ -566,6 +584,9 @@ int SDL_VideoInit(const char *driver_name) pre_driver_error: SDL_assert(_this == NULL); + if (init_video_capture) { + SDL_QuitVideoCapture(); + } if (init_touch) { SDL_QuitTouch(); } @@ -583,7 +604,7 @@ pre_driver_error: const char *SDL_GetCurrentVideoDriver(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return NULL; } @@ -597,7 +618,7 @@ SDL_VideoDevice *SDL_GetVideoDevice(void) SDL_bool SDL_OnVideoThread(void) { - return (_this && SDL_ThreadID() == _this->thread) ? SDL_TRUE : SDL_FALSE; + return (_this && SDL_ThreadID() == _this->thread); } SDL_bool SDL_IsVideoContextExternal(void) @@ -649,20 +670,18 @@ SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send new_display = (SDL_VideoDisplay *)SDL_malloc(sizeof(*new_display)); if (!new_display) { - SDL_OutOfMemory(); return 0; } displays = (SDL_VideoDisplay **)SDL_realloc(_this->displays, (_this->num_displays + 1) * sizeof(*displays)); if (!displays) { - SDL_OutOfMemory(); SDL_free(new_display); return 0; } _this->displays = displays; _this->displays[_this->num_displays++] = new_display; - id = _this->next_object_id++; + id = SDL_GetNextObjectID(); SDL_memcpy(new_display, display, sizeof(*new_display)); new_display->id = id; new_display->device = _this; @@ -687,12 +706,12 @@ SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send } if (send_event) { - SDL_SendDisplayEvent(new_display, SDL_EVENT_DISPLAY_CONNECTED, 0); + SDL_SendDisplayEvent(new_display, SDL_EVENT_DISPLAY_ADDED, 0); } return id; } -void SDL_OnDisplayConnected(SDL_VideoDisplay *display) +void SDL_OnDisplayAdded(SDL_VideoDisplay *display) { SDL_Window *window; @@ -713,9 +732,10 @@ void SDL_DelVideoDisplay(SDL_DisplayID displayID, SDL_bool send_event) display = _this->displays[display_index]; if (send_event) { - SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_DISCONNECTED, 0); + SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_REMOVED, 0); } + SDL_DestroyProperties(display->props); SDL_free(display->name); SDL_ResetFullscreenDisplayModes(display); SDL_free(display->desktop_mode.driverdata); @@ -758,8 +778,6 @@ SDL_DisplayID *SDL_GetDisplays(int *count) if (count) { *count = 0; } - - SDL_OutOfMemory(); } return displays; } @@ -819,6 +837,18 @@ SDL_DisplayData *SDL_GetDisplayDriverDataForWindow(SDL_Window *window) return SDL_GetDisplayDriverData(SDL_GetDisplayForWindow(window)); } +SDL_PropertiesID SDL_GetDisplayProperties(SDL_DisplayID displayID) +{ + SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID); + + CHECK_DISPLAY_MAGIC(display, 0); + + if (display->props == 0) { + display->props = SDL_CreateProperties(); + } + return display->props; +} + const char *SDL_GetDisplayName(SDL_DisplayID displayID) { SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID); @@ -834,7 +864,7 @@ int SDL_GetDisplayBounds(SDL_DisplayID displayID, SDL_Rect *rect) CHECK_DISPLAY_MAGIC(display, -1); - if (rect == NULL) { + if (!rect) { return SDL_InvalidParamError("rect"); } @@ -869,7 +899,7 @@ int SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect) CHECK_DISPLAY_MAGIC(display, -1); - if (rect == NULL) { + if (!rect) { return SDL_InvalidParamError("rect"); } @@ -1011,7 +1041,7 @@ SDL_bool SDL_AddFullscreenDisplayMode(SDL_VideoDisplay *display, const SDL_Displ /* Go ahead and add the new mode */ if (nmodes == display->max_fullscreen_modes) { modes = (SDL_DisplayMode *)SDL_malloc((display->max_fullscreen_modes + 32) * sizeof(*modes)); - if (modes == NULL) { + if (!modes) { return SDL_FALSE; } @@ -1084,8 +1114,6 @@ const SDL_DisplayMode **SDL_GetFullscreenDisplayModes(SDL_DisplayID displayID, i if (count) { *count = 0; } - - SDL_OutOfMemory(); } return modes; } @@ -1152,6 +1180,9 @@ const SDL_DisplayMode *SDL_GetClosestFullscreenDisplayMode(SDL_DisplayID display void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode) { + if (display->desktop_mode.driverdata) { + SDL_free(display->desktop_mode.driverdata); + } SDL_memcpy(&display->desktop_mode, mode, sizeof(*mode)); display->desktop_mode.displayID = display->id; SDL_FinalizeDisplayMode(&display->desktop_mode); @@ -1185,10 +1216,10 @@ const SDL_DisplayMode *SDL_GetCurrentDisplayMode(SDL_DisplayID displayID) return display->current_mode; } -static int SDL_SetDisplayModeForDisplay(SDL_VideoDisplay *display, SDL_DisplayMode *mode) +int SDL_SetDisplayModeForDisplay(SDL_VideoDisplay *display, SDL_DisplayMode *mode) { /* Mode switching is being emulated per-window; nothing to do and cannot fail. */ - if (ModeSwitchingEmulated(_this)) { + if (SDL_ModeSwitchingEmulated(_this)) { return 0; } @@ -1288,7 +1319,7 @@ void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int if (SDL_WINDOW_IS_POPUP(window)) { /* Calculate the total offset of the popup from the parents */ - for (w = window->parent; w != NULL; w = w->parent) { + for (w = window->parent; w; w = w->parent) { rel_x += w->x; rel_y += w->y; } @@ -1308,7 +1339,7 @@ void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int if (SDL_WINDOW_IS_POPUP(window)) { /* Convert absolute window coordinates to relative for a popup */ - for (w = window->parent; w != NULL; w = w->parent) { + for (w = window->parent; w; w = w->parent) { abs_x -= w->x; abs_y -= w->y; } @@ -1359,6 +1390,34 @@ static SDL_DisplayID SDL_GetDisplayForWindowPosition(SDL_Window *window) return displayID; } +SDL_VideoDisplay *SDL_GetVideoDisplayForFullscreenWindow(SDL_Window *window) +{ + SDL_DisplayID displayID = 0; + + CHECK_WINDOW_MAGIC(window, 0); + + /* An explicit fullscreen display overrides all */ + if (window->current_fullscreen_mode.displayID) { + displayID = window->current_fullscreen_mode.displayID; + } + + /* The floating position is used here as a very common pattern is + * SDL_SetWindowPosition() followed by SDL_SetWindowFullscreen to make the + * window fullscreen desktop on a specific display. If the backend doesn't + * support changing the window position, or the compositor hasn't yet actually + * moved the window, the actual position won't be updated at the time of the + * fullscreen call. + */ + if (!displayID) { + displayID = GetDisplayForRect(window->floating.x, window->floating.y, window->w, window->h); + } + if (!displayID) { + /* Use the primary display for a window if we can't find it anywhere else */ + displayID = SDL_GetPrimaryDisplay(); + } + return SDL_GetVideoDisplay(displayID); +} + SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window) { SDL_DisplayID displayID = 0; @@ -1461,7 +1520,7 @@ static void SDL_RestoreMousePosition(SDL_Window *window) } } -static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) +int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen, SDL_bool commit) { SDL_VideoDisplay *display = NULL; SDL_DisplayMode *mode = NULL; @@ -1478,7 +1537,7 @@ static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) /* Get the correct display for this operation */ if (fullscreen) { - display = SDL_GetVideoDisplayForWindow(window); + display = SDL_GetVideoDisplayForFullscreenWindow(window); if (!display) { /* This should never happen, but it did... */ goto done; @@ -1490,15 +1549,15 @@ static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) break; } } - if (i == _this->num_displays) { + if (!display || i == _this->num_displays) { /* Already not fullscreen on any display */ - goto done; + display = NULL; } } if (fullscreen) { mode = (SDL_DisplayMode *)SDL_GetWindowFullscreenMode(window); - if (mode != NULL) { + if (mode) { window->fullscreen_exclusive = SDL_TRUE; } else { /* Make sure the current mode is zeroed for fullscreen desktop. */ @@ -1513,39 +1572,34 @@ static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) if (SDL_strcmp(_this->name, "cocoa") == 0) { /* don't do this for X11, etc */ if (window->is_destroying && !window->last_fullscreen_exclusive_display) { window->fullscreen_exclusive = SDL_FALSE; - display->fullscreen_window = NULL; - goto done; - } - - /* If we're switching between a fullscreen Space and exclusive fullscreen, we need to get back to normal first. */ - if (fullscreen && !window->last_fullscreen_exclusive_display && window->fullscreen_exclusive) { - if (!Cocoa_SetWindowFullscreenSpace(window, SDL_FALSE)) { - goto error; - } - } else if (fullscreen && window->last_fullscreen_exclusive_display && !window->fullscreen_exclusive) { - for (i = 0; i < _this->num_displays; ++i) { - SDL_VideoDisplay *last_display = _this->displays[i]; - if (last_display->fullscreen_window == window) { - SDL_SetDisplayModeForDisplay(last_display, NULL); - if (_this->SetWindowFullscreen) { - _this->SetWindowFullscreen(_this, window, last_display, SDL_FALSE); - } - last_display->fullscreen_window = NULL; - } - } - } - - if (Cocoa_SetWindowFullscreenSpace(window, fullscreen)) { - if (Cocoa_IsWindowInFullscreenSpace(window) != fullscreen) { - goto error; - } - if (fullscreen) { - display->fullscreen_window = window; - } else { + if (display) { display->fullscreen_window = NULL; } goto done; } + if (commit) { + /* If we're switching between a fullscreen Space and exclusive fullscreen, we need to get back to normal first. */ + if (fullscreen && Cocoa_IsWindowInFullscreenSpace(window) && !window->last_fullscreen_exclusive_display && window->fullscreen_exclusive) { + if (!Cocoa_SetWindowFullscreenSpace(window, SDL_FALSE, SDL_TRUE)) { + goto error; + } + } else if (fullscreen && window->last_fullscreen_exclusive_display && !window->fullscreen_exclusive) { + for (i = 0; i < _this->num_displays; ++i) { + SDL_VideoDisplay *last_display = _this->displays[i]; + if (last_display->fullscreen_window == window) { + SDL_SetDisplayModeForDisplay(last_display, NULL); + if (_this->SetWindowFullscreen) { + _this->SetWindowFullscreen(_this, window, last_display, SDL_FALSE); + } + last_display->fullscreen_window = NULL; + } + } + } + + if (Cocoa_SetWindowFullscreenSpace(window, fullscreen, syncHint)) { + goto done; + } + } } #elif defined(__WINRT__) && (NTDDI_VERSION < NTDDI_WIN10) /* HACK: WinRT 8.x apps can't choose whether or not they are fullscreen @@ -1578,15 +1632,14 @@ static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) } #endif - /* Restore the video mode on other displays if needed */ - for (i = 0; i < _this->num_displays; ++i) { - SDL_VideoDisplay *other = _this->displays[i]; - if (other != display && other->fullscreen_window == window) { - SDL_SetDisplayModeForDisplay(other, NULL); - if (_this->SetWindowFullscreen) { - _this->SetWindowFullscreen(_this, window, other, SDL_FALSE); + if (display) { + /* Restore the video mode on other displays if needed */ + for (i = 0; i < _this->num_displays; ++i) { + SDL_VideoDisplay *other = _this->displays[i]; + if (other != display && other->fullscreen_window == window) { + SDL_SetDisplayModeForDisplay(other, NULL); + other->fullscreen_window = NULL; } - other->fullscreen_window = NULL; } } @@ -1603,75 +1656,108 @@ static int SDL_UpdateFullscreenMode(SDL_Window *window, SDL_bool fullscreen) if (SDL_SetDisplayModeForDisplay(display, mode) < 0) { goto error; } - if (_this->SetWindowFullscreen) { - _this->SetWindowFullscreen(_this, window, display, SDL_TRUE); - } else { - resized = SDL_TRUE; - } - display->fullscreen_window = window; + if (commit) { + int ret = 0; + if (_this->SetWindowFullscreen) { + ret = _this->SetWindowFullscreen(_this, window, display, SDL_TRUE); + } else { + resized = SDL_TRUE; + } - if (mode) { - mode_w = mode->w; - mode_h = mode->h; - } else { - mode_w = display->desktop_mode.w; - mode_h = display->desktop_mode.h; - } -#ifdef __ANDROID__ - /* Android may not resize the window to exactly what our fullscreen mode is, - * especially on windowed Android environments like the Chromebook or Samsung DeX. - * Given this, we shouldn't use the mode size. Android's SetWindowFullscreen - * will generate the window event for us with the proper final size. - */ -#elif defined(__WIN32__) - /* This is also unnecessary on Win32 (WIN_SetWindowFullscreen calls SetWindowPos, - * WM_WINDOWPOSCHANGED will send SDL_EVENT_WINDOW_RESIZED). - */ -#else - if (window->w != mode_w || window->h != mode_h) { - resized = SDL_TRUE; - } -#endif - if (resized) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h); - } else { - SDL_OnWindowResized(window); + if (ret == 0) { + /* Window is fullscreen immediately upon return. If the driver hasn't already sent the event, do so now. */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); + } + } else if (ret < 0) { + goto error; + } } - /* Restore the cursor position */ - SDL_RestoreMousePosition(window); + if (window->flags & SDL_WINDOW_FULLSCREEN) { + display->fullscreen_window = window; + /* Android may not resize the window to exactly what our fullscreen mode is, + * especially on windowed Android environments like the Chromebook or Samsung DeX. + * Given this, we shouldn't use the mode size. Android's SetWindowFullscreen + * will generate the window event for us with the proper final size. + * + * This is also unnecessary on Cocoa, Wayland, Win32, and X11 (will send SDL_EVENT_WINDOW_RESIZED). + */ + if (!SDL_SendsFullscreenDimensions(_this)) { + if (mode) { + mode_w = mode->w; + mode_h = mode->h; + } else { + mode_w = display->desktop_mode.w; + mode_h = display->desktop_mode.h; + } + + if (window->w != mode_w || window->h != mode_h) { + resized = SDL_TRUE; + } + + if (resized) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h); + } else { + SDL_OnWindowResized(window); + } + } + + /* Restore the cursor position */ + SDL_RestoreMousePosition(window); + } } else { SDL_bool resized = SDL_FALSE; /* Restore the desktop mode */ - SDL_SetDisplayModeForDisplay(display, NULL); - if (_this->SetWindowFullscreen) { - _this->SetWindowFullscreen(_this, window, display, SDL_FALSE); - } else { - resized = SDL_TRUE; + if (display) { + SDL_SetDisplayModeForDisplay(display, NULL); } - display->fullscreen_window = NULL; + if (commit) { + int ret = 0; + if (_this->SetWindowFullscreen) { + ret = _this->SetWindowFullscreen(_this, window, display ? display : SDL_GetVideoDisplayForFullscreenWindow(window), SDL_FALSE); + } else { + resized = SDL_TRUE; + } - if (resized) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h); - } else { - SDL_OnWindowResized(window); + if (ret == 0) { + /* Window left fullscreen immediately upon return. If the driver hasn't already sent the event, do so now. */ + if (window->flags & SDL_WINDOW_FULLSCREEN) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + } + } else if (ret < 0) { + goto error; + } } - /* Restore the cursor position */ - SDL_RestoreMousePosition(window); + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (display) { + display->fullscreen_window = NULL; + } + + if (!SDL_SendsFullscreenDimensions(_this)) { + if (resized) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h); + } else { + SDL_OnWindowResized(window); + } + } + + /* Restore the cursor position */ + SDL_RestoreMousePosition(window); + } } done: - window->last_fullscreen_exclusive_display = display && window->fullscreen_exclusive ? display->id : 0; + window->last_fullscreen_exclusive_display = display && (window->flags & SDL_WINDOW_FULLSCREEN) && window->fullscreen_exclusive ? display->id : 0; return 0; error: if (fullscreen) { /* Something went wrong and the window is no longer fullscreen. */ - window->flags &= ~SDL_WINDOW_FULLSCREEN; - SDL_UpdateFullscreenMode(window, SDL_FALSE); + SDL_UpdateFullscreenMode(window, SDL_FALSE, commit); } return -1; } @@ -1687,16 +1773,18 @@ int SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode) } /* Save the mode so we can look up the closest match later */ - SDL_memcpy(&window->requested_fullscreen_mode, mode, sizeof(window->requested_fullscreen_mode)); + SDL_copyp(&window->requested_fullscreen_mode, mode); } else { SDL_zero(window->requested_fullscreen_mode); } - if (window->flags & SDL_WINDOW_FULLSCREEN) { - SDL_memcpy(&window->current_fullscreen_mode, &window->requested_fullscreen_mode, sizeof(window->current_fullscreen_mode)); - if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - SDL_UpdateFullscreenMode(window, SDL_TRUE); - } + /* Copy to the current mode now, in case an asynchronous fullscreen window request + * is in progress. It will be overwritten if a new request is made. + */ + SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode); + if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_TRUE); + SDL_SyncIfRequired(window); } return 0; @@ -1772,18 +1860,19 @@ void SDL_ToggleDragAndDropSupport(void) static void ApplyWindowFlags(SDL_Window *window, Uint32 flags) { - if (flags & SDL_WINDOW_MAXIMIZED) { - SDL_MaximizeWindow(window); - } - if (flags & SDL_WINDOW_MINIMIZED) { - SDL_MinimizeWindow(window); - } if (!(flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED))) { SDL_RestoreWindow(window); } + if (flags & SDL_WINDOW_MAXIMIZED) { + SDL_MaximizeWindow(window); + } SDL_SetWindowFullscreen(window, (flags & SDL_WINDOW_FULLSCREEN) != 0); + if (flags & SDL_WINDOW_MINIMIZED) { + SDL_MinimizeWindow(window); + } + if (flags & SDL_WINDOW_MOUSE_GRABBED) { /* We must specifically call SDL_SetWindowGrab() and not SDL_SetWindowMouseGrab() here because older applications may use @@ -1820,41 +1909,101 @@ static int SDL_DllNotSupported(const char *name) return SDL_SetError("No dynamic %s support in current SDL video driver (%s)", name, _this->name); } -static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int w, int h, SDL_Window *parent, Uint32 flags) +static struct { + const char *property_name; + Uint32 flag; + SDL_bool invert_value; +} SDL_WindowFlagProperties[] = { + { SDL_PROPERTY_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN, SDL_WINDOW_ALWAYS_ON_TOP, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN, SDL_WINDOW_BORDERLESS, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_FOCUSABLE_BOOLEAN, SDL_WINDOW_NOT_FOCUSABLE, SDL_TRUE }, + { SDL_PROPERTY_WINDOW_CREATE_FULLSCREEN_BOOLEAN, SDL_WINDOW_FULLSCREEN, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_HIDDEN_BOOLEAN, SDL_WINDOW_HIDDEN, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, SDL_WINDOW_HIGH_PIXEL_DENSITY, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_MAXIMIZED_BOOLEAN, SDL_WINDOW_MAXIMIZED, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_MENU_BOOLEAN, SDL_WINDOW_POPUP_MENU, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_METAL_BOOLEAN, SDL_WINDOW_METAL, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_MINIMIZED_BOOLEAN, SDL_WINDOW_MINIMIZED, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN, SDL_WINDOW_MOUSE_GRABBED, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_WINDOW_OPENGL, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_RESIZABLE_BOOLEAN, SDL_WINDOW_RESIZABLE, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_TRANSPARENT_BOOLEAN, SDL_WINDOW_TRANSPARENT, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_TOOLTIP_BOOLEAN, SDL_WINDOW_TOOLTIP, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_UTILITY_BOOLEAN, SDL_WINDOW_UTILITY, SDL_FALSE }, + { SDL_PROPERTY_WINDOW_CREATE_VULKAN_BOOLEAN, SDL_WINDOW_VULKAN, SDL_FALSE } +}; + +static Uint32 SDL_GetWindowFlagProperties(SDL_PropertiesID props) +{ + unsigned i; + Uint32 flags = (Uint32)SDL_GetNumberProperty(props, "flags", 0); + + for (i = 0; i < SDL_arraysize(SDL_WindowFlagProperties); ++i) { + if (SDL_WindowFlagProperties[i].invert_value) { + if (!SDL_GetBooleanProperty(props, SDL_WindowFlagProperties[i].property_name, SDL_TRUE)) { + flags |= SDL_WindowFlagProperties[i].flag; + } + } else { + if (SDL_GetBooleanProperty(props, SDL_WindowFlagProperties[i].property_name, SDL_FALSE)) { + flags |= SDL_WindowFlagProperties[i].flag; + } + } + } + return flags; +} + +SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) { SDL_Window *window; + const char *title = SDL_GetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, NULL); + int x = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED); + int y = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED); + int w = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, 0); + int h = (int)SDL_GetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, 0); + SDL_Window *parent = SDL_GetProperty(props, SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER, NULL); + Uint32 flags = SDL_GetWindowFlagProperties(props); Uint32 type_flags, graphics_flags; SDL_bool undefined_x = SDL_FALSE; SDL_bool undefined_y = SDL_FALSE; - if (_this == NULL) { + if (!_this) { /* Initialize the video system if needed */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { return NULL; } /* Make clang-tidy happy */ - if (_this == NULL) { + if (!_this) { return NULL; } } - /* Make sure the display list is up to date for window placement */ - if (_this->RefreshDisplays) { - _this->RefreshDisplays(_this); + if ((flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) { + if (!(_this->device_caps & VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT)) { + SDL_Unsupported(); + return NULL; + } + + /* Tooltip and popup menu window must specify a parent window */ + if (!parent || parent->magic != &_this->window_magic) { + SDL_SetError("Tooltip and popup menu windows must specify a parent window"); + return NULL; + } + + /* Remove invalid flags */ + flags &= ~(SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS); } - /* ensure no more than one of these flags is set */ + /* Ensure no more than one of these flags is set */ type_flags = flags & (SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU); if (type_flags & (type_flags - 1)) { SDL_SetError("Conflicting window type flags specified: 0x%.8x", (unsigned int)type_flags); return NULL; } - /* Tooltip and popup menu window must specify a parent window */ - if (!parent && ((type_flags & SDL_WINDOW_TOOLTIP) || (type_flags & SDL_WINDOW_POPUP_MENU))) { - SDL_SetError("Tooltip and popup menu windows must specify a parent window"); - return NULL; + /* Make sure the display list is up to date for window placement */ + if (_this->RefreshDisplays) { + _this->RefreshDisplays(_this); } /* Some platforms can't create zero-sized windows */ @@ -1865,14 +2014,6 @@ static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int h = 1; } - /* Some platforms blow up if the windows are too large. Raise it later? */ - if (w > 16384) { - w = 16384; - } - if (h > 16384) { - h = 16384; - } - if (SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISUNDEFINED(y) || SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISCENTERED(y)) { SDL_DisplayID displayID = 0; @@ -1943,16 +2084,15 @@ static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int } window = (SDL_Window *)SDL_calloc(1, sizeof(*window)); - if (window == NULL) { - SDL_OutOfMemory(); + if (!window) { return NULL; } window->magic = &_this->window_magic; - window->id = _this->next_object_id++; - window->windowed.x = window->x = x; - window->windowed.y = window->y = y; - window->windowed.w = window->w = w; - window->windowed.h = window->h = h; + window->id = SDL_GetNextObjectID(); + window->floating.x = window->windowed.x = window->x = x; + window->floating.y = window->windowed.y = window->y = y; + window->floating.w = window->windowed.w = window->w = w; + window->floating.h = window->windowed.h = window->h = h; window->undefined_x = undefined_x; window->undefined_y = undefined_y; @@ -1989,7 +2129,7 @@ static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int parent->first_child = window; } - if (_this->CreateSDLWindow && _this->CreateSDLWindow(_this, window) < 0) { + if (_this->CreateSDLWindow && _this->CreateSDLWindow(_this, window, props) < 0) { SDL_DestroyWindow(window); return NULL; } @@ -2020,11 +2160,6 @@ static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int } SDL_FinishWindowCreation(window, flags); - /* If the window was created fullscreen, make sure the display mode matches */ - if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - SDL_UpdateFullscreenMode(window, SDL_TRUE); - } - /* Make sure window pixel size is up to date */ SDL_CheckWindowPixelSizeChanged(window); @@ -2033,28 +2168,23 @@ static SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) { - return SDL_CreateWindowInternal(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w , h, NULL, flags); -} - -SDL_Window *SDL_CreateWindowWithPosition(const char *title, int x, int y, int w, int h, Uint32 flags) -{ - return SDL_CreateWindowInternal(title, x, y, w , h, NULL, flags); + SDL_Window *window; + SDL_PropertiesID props = SDL_CreateProperties(); + if (title && *title) { + SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, title); + } + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, "flags", flags); + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + return window; } SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, Uint32 flags) { - if (_this == NULL) { - SDL_UninitializedVideo(); - return NULL; - } - - if (!(_this->quirk_flags & VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT)) { - SDL_Unsupported(); - return NULL; - } - - /* Parent must be a valid window */ - CHECK_WINDOW_MAGIC(parent, NULL); + SDL_Window *window; + SDL_PropertiesID props = SDL_CreateProperties(); /* Popups must specify either the tooltip or popup menu window flags */ if (!(flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))) { @@ -2062,77 +2192,14 @@ SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y return NULL; } - /* Remove invalid flags */ - flags &= ~(SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS); - - return SDL_CreateWindowInternal(NULL, offset_x, offset_y, w, h, parent, flags); -} - -SDL_Window *SDL_CreateWindowFrom(const void *data) -{ - SDL_Window *window; - Uint32 flags = SDL_WINDOW_FOREIGN; - - if (_this == NULL) { - SDL_UninitializedVideo(); - return NULL; - } - if (!_this->CreateSDLWindowFrom) { - SDL_Unsupported(); - return NULL; - } - - if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL, SDL_FALSE)) { - if (!_this->GL_CreateContext) { - SDL_ContextNotSupported("OpenGL"); - return NULL; - } - if (SDL_GL_LoadLibrary(NULL) < 0) { - return NULL; - } - flags |= SDL_WINDOW_OPENGL; - } - - if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN, SDL_FALSE)) { - if (!_this->Vulkan_CreateSurface) { - SDL_ContextNotSupported("Vulkan"); - return NULL; - } - if (flags & SDL_WINDOW_OPENGL) { - SDL_SetError("Vulkan and OpenGL not supported on same window"); - return NULL; - } - if (SDL_Vulkan_LoadLibrary(NULL) < 0) { - return NULL; - } - flags |= SDL_WINDOW_VULKAN; - } - - window = (SDL_Window *)SDL_calloc(1, sizeof(*window)); - if (window == NULL) { - SDL_OutOfMemory(); - return NULL; - } - window->magic = &_this->window_magic; - window->id = _this->next_object_id++; - window->flags = flags; - window->is_destroying = SDL_FALSE; - window->display_scale = 1.0f; - window->opacity = 1.0f; - window->next = _this->windows; - if (_this->windows) { - _this->windows->prev = window; - } - _this->windows = window; - - if (_this->CreateSDLWindowFrom(_this, window, data) < 0) { - SDL_DestroyWindow(window); - return NULL; - } - - window->last_displayID = SDL_GetDisplayForWindow(window); - PrepareDragAndDropSupport(window); - + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER, parent); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_X_NUMBER, offset_x); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER, offset_y); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, "flags", flags); + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); return window; } @@ -2162,15 +2229,15 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) return SDL_ContextNotSupported("Metal"); } - if (window->flags & SDL_WINDOW_FOREIGN) { - /* Can't destroy and re-create foreign windows, hrm */ - flags |= SDL_WINDOW_FOREIGN; + if (window->flags & SDL_WINDOW_EXTERNAL) { + /* Can't destroy and re-create external windows, hrm */ + flags |= SDL_WINDOW_EXTERNAL; } else { - flags &= ~SDL_WINDOW_FOREIGN; + flags &= ~SDL_WINDOW_EXTERNAL; } /* Restore video mode, etc. */ - if (!(window->flags & SDL_WINDOW_FOREIGN)) { + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { const SDL_bool restore_on_show = window->restore_on_show; SDL_HideWindow(window); window->restore_on_show = restore_on_show; @@ -2215,7 +2282,7 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) SDL_Vulkan_UnloadLibrary(); } - if (_this->DestroyWindow && !(flags & SDL_WINDOW_FOREIGN)) { + if (_this->DestroyWindow && !(flags & SDL_WINDOW_EXTERNAL)) { _this->DestroyWindow(_this, window); } @@ -2236,8 +2303,16 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) window->flags = ((flags & CREATE_FLAGS) | SDL_WINDOW_HIDDEN); window->is_destroying = SDL_FALSE; - if (_this->CreateSDLWindow && !(flags & SDL_WINDOW_FOREIGN)) { - if (_this->CreateSDLWindow(_this, window) < 0) { + if (_this->CreateSDLWindow && !(flags & SDL_WINDOW_EXTERNAL)) { + /* Reset the window size to the original floating value, so the + * recreated window has the proper base size. + */ + window->x = window->windowed.x = window->floating.x; + window->y = window->windowed.y = window->floating.y; + window->w = window->windowed.w = window->floating.w; + window->h = window->windowed.h = window->floating.h; + + if (_this->CreateSDLWindow(_this, window, 0) < 0) { if (loaded_opengl) { SDL_GL_UnloadLibrary(); window->flags &= ~SDL_WINDOW_OPENGL; @@ -2250,8 +2325,8 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) } } - if (flags & SDL_WINDOW_FOREIGN) { - window->flags |= SDL_WINDOW_FOREIGN; + if (flags & SDL_WINDOW_EXTERNAL) { + window->flags |= SDL_WINDOW_EXTERNAL; } if (_this->SetWindowTitle && window->title) { @@ -2262,6 +2337,14 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) _this->SetWindowIcon(_this, window, window->icon); } + if (_this->SetWindowMinimumSize && (window->min_w || window->min_h)) { + _this->SetWindowMinimumSize(_this, window); + } + + if (_this->SetWindowMaximumSize && (window->max_w || window->max_h)) { + _this->SetWindowMaximumSize(_this, window); + } + if (window->hit_test) { _this->SetWindowHitTest(window, SDL_TRUE); } @@ -2273,7 +2356,7 @@ int SDL_RecreateWindow(SDL_Window *window, Uint32 flags) SDL_bool SDL_HasWindows(void) { - return _this && _this->windows != NULL; + return _this && _this->windows; } SDL_WindowID SDL_GetWindowID(SDL_Window *window) @@ -2287,7 +2370,7 @@ SDL_Window *SDL_GetWindowFromID(SDL_WindowID id) { SDL_Window *window; - if (_this == NULL) { + if (!_this) { return NULL; } for (window = _this->windows; window; window = window->next) { @@ -2305,11 +2388,21 @@ SDL_Window *SDL_GetWindowParent(SDL_Window *window) return window->parent; } +SDL_PropertiesID SDL_GetWindowProperties(SDL_Window *window) +{ + CHECK_WINDOW_MAGIC(window, 0); + + if (window->props == 0) { + window->props = SDL_CreateProperties(); + } + return window->props; +} + Uint32 SDL_GetWindowFlags(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, 0); - return window->flags; + return window->flags | window->pending_flags; } int SDL_SetWindowTitle(SDL_Window *window, const char *title) @@ -2341,7 +2434,7 @@ int SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon) { CHECK_WINDOW_MAGIC(window, -1); - if (icon == NULL) { + if (!icon) { return SDL_InvalidParamError("icon"); } @@ -2360,72 +2453,6 @@ int SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon) return _this->SetWindowIcon(_this, window, window->icon); } -void *SDL_SetWindowData(SDL_Window *window, const char *name, void *userdata) -{ - SDL_WindowUserData *prev, *data; - - CHECK_WINDOW_MAGIC(window, NULL); - - /* Input validation */ - if (name == NULL || name[0] == '\0') { - SDL_InvalidParamError("name"); - return NULL; - } - - /* See if the named data already exists */ - prev = NULL; - for (data = window->data; data; prev = data, data = data->next) { - if (data->name && SDL_strcmp(data->name, name) == 0) { - void *last_value = data->data; - - if (userdata) { - /* Set the new value */ - data->data = userdata; - } else { - /* Delete this value */ - if (prev) { - prev->next = data->next; - } else { - window->data = data->next; - } - SDL_free(data->name); - SDL_free(data); - } - return last_value; - } - } - - /* Add new data to the window */ - if (userdata) { - data = (SDL_WindowUserData *)SDL_malloc(sizeof(*data)); - data->name = SDL_strdup(name); - data->data = userdata; - data->next = window->data; - window->data = data; - } - return NULL; -} - -void *SDL_GetWindowData(SDL_Window *window, const char *name) -{ - SDL_WindowUserData *data; - - CHECK_WINDOW_MAGIC(window, NULL); - - /* Input validation */ - if (name == NULL || name[0] == '\0') { - SDL_InvalidParamError("name"); - return NULL; - } - - for (data = window->data; data; data = data->next) { - if (data->name && SDL_strcmp(data->name, name) == 0) { - return data->data; - } - } - return NULL; -} - int SDL_SetWindowPosition(SDL_Window *window, int x, int y) { SDL_DisplayID original_displayID; @@ -2465,31 +2492,19 @@ int SDL_SetWindowPosition(SDL_Window *window, int x, int y) } } - window->windowed.x = x; - window->windowed.y = y; + window->floating.x = x; + window->floating.y = y; window->undefined_x = SDL_FALSE; window->undefined_y = SDL_FALSE; - if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - if (!window->fullscreen_exclusive) { - /* See if we should move to another display */ - SDL_DisplayID displayID = GetDisplayForRect(x, y, 1, 1); - - if (displayID != original_displayID) { - /* Set the new target display and update the fullscreen mode */ - window->current_fullscreen_mode.displayID = displayID; - return SDL_UpdateFullscreenMode(window, SDL_TRUE); - } - } - } else { - window->x = x; - window->y = y; - window->last_displayID = SDL_GetDisplayForWindow(window); - - if (_this->SetWindowPosition) { - return _this->SetWindowPosition(_this, window); + if (_this->SetWindowPosition) { + const int ret = _this->SetWindowPosition(_this, window); + if (!ret) { + SDL_SyncIfRequired(window); } + return ret; } + return SDL_Unsupported(); } @@ -2539,18 +2554,18 @@ int SDL_SetWindowBordered(SDL_Window *window, SDL_bool bordered) { CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - const int want = (bordered != SDL_FALSE); /* normalize the flag. */ - const int have = !(window->flags & SDL_WINDOW_BORDERLESS); - if ((want != have) && (_this->SetWindowBordered)) { - if (want) { - window->flags &= ~SDL_WINDOW_BORDERLESS; - } else { - window->flags |= SDL_WINDOW_BORDERLESS; - } - _this->SetWindowBordered(_this, window, (SDL_bool)want); + + const SDL_bool want = (bordered != SDL_FALSE); /* normalize the flag. */ + const SDL_bool have = !(window->flags & SDL_WINDOW_BORDERLESS); + if ((want != have) && (_this->SetWindowBordered)) { + if (want) { + window->flags &= ~SDL_WINDOW_BORDERLESS; + } else { + window->flags |= SDL_WINDOW_BORDERLESS; } + _this->SetWindowBordered(_this, window, want); } + return 0; } @@ -2558,18 +2573,19 @@ int SDL_SetWindowResizable(SDL_Window *window, SDL_bool resizable) { CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - const int want = (resizable != SDL_FALSE); /* normalize the flag. */ - const int have = ((window->flags & SDL_WINDOW_RESIZABLE) != 0); - if ((want != have) && (_this->SetWindowResizable)) { - if (want) { - window->flags |= SDL_WINDOW_RESIZABLE; - } else { - window->flags &= ~SDL_WINDOW_RESIZABLE; - } - _this->SetWindowResizable(_this, window, (SDL_bool)want); + + const SDL_bool want = (resizable != SDL_FALSE); /* normalize the flag. */ + const SDL_bool have = ((window->flags & SDL_WINDOW_RESIZABLE) != 0); + if ((want != have) && (_this->SetWindowResizable)) { + if (want) { + window->flags |= SDL_WINDOW_RESIZABLE; + } else { + window->flags &= ~SDL_WINDOW_RESIZABLE; + SDL_copyp(&window->windowed, &window->floating); } + _this->SetWindowResizable(_this, window, want); } + return 0; } @@ -2577,18 +2593,18 @@ int SDL_SetWindowAlwaysOnTop(SDL_Window *window, SDL_bool on_top) { CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - const int want = (on_top != SDL_FALSE); /* normalize the flag. */ - const int have = ((window->flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0); - if ((want != have) && (_this->SetWindowAlwaysOnTop)) { - if (want) { - window->flags |= SDL_WINDOW_ALWAYS_ON_TOP; - } else { - window->flags &= ~SDL_WINDOW_ALWAYS_ON_TOP; - } - _this->SetWindowAlwaysOnTop(_this, window, (SDL_bool)want); + + const SDL_bool want = (on_top != SDL_FALSE); /* normalize the flag. */ + const SDL_bool have = ((window->flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0); + if ((want != have) && (_this->SetWindowAlwaysOnTop)) { + if (want) { + window->flags |= SDL_WINDOW_ALWAYS_ON_TOP; + } else { + window->flags &= ~SDL_WINDOW_ALWAYS_ON_TOP; } + _this->SetWindowAlwaysOnTop(_this, window, want); } + return 0; } @@ -2616,16 +2632,14 @@ int SDL_SetWindowSize(SDL_Window *window, int w, int h) h = window->max_h; } - window->windowed.w = w; - window->windowed.h = h; + window->floating.w = w; + window->floating.h = h; - if (!SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - window->w = w; - window->h = h; - - if (_this->SetWindowSize) { - _this->SetWindowSize(_this, window); - } + if (_this->SetWindowSize) { + _this->SetWindowSize(_this, window); + SDL_SyncIfRequired(window); + } else { + return SDL_Unsupported(); } return 0; } @@ -2646,16 +2660,16 @@ int SDL_GetWindowBordersSize(SDL_Window *window, int *top, int *left, int *botto { int dummy = 0; - if (top == NULL) { + if (!top) { top = &dummy; } - if (left == NULL) { + if (!left) { left = &dummy; } - if (right == NULL) { + if (!right) { right = &dummy; } - if (bottom == NULL) { + if (!bottom) { bottom = &dummy; } @@ -2677,11 +2691,11 @@ int SDL_GetWindowSizeInPixels(SDL_Window *window, int *w, int *h) CHECK_WINDOW_MAGIC(window, -1); - if (w == NULL) { + if (!w) { w = &filter; } - if (h == NULL) { + if (!h) { h = &filter; } @@ -2708,6 +2722,8 @@ int SDL_GetWindowSizeInPixels(SDL_Window *window, int *w, int *h) int SDL_SetWindowMinimumSize(SDL_Window *window, int min_w, int min_h) { + int w, h; + CHECK_WINDOW_MAGIC(window, -1); if (min_w < 0) { return SDL_InvalidParamError("min_w"); @@ -2724,19 +2740,14 @@ int SDL_SetWindowMinimumSize(SDL_Window *window, int min_w, int min_h) window->min_w = min_w; window->min_h = min_h; - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - int w, h; - - if (_this->SetWindowMinimumSize) { - _this->SetWindowMinimumSize(_this, window); - } - - /* Ensure that window is not smaller than minimal size */ - w = window->min_w ? SDL_max(window->w, window->min_w) : window->w; - h = window->min_h ? SDL_max(window->h, window->min_h) : window->h; - return SDL_SetWindowSize(window, w, h); + if (_this->SetWindowMinimumSize) { + _this->SetWindowMinimumSize(_this, window); } - return 0; + + /* Ensure that window is not smaller than minimal size */ + w = window->min_w ? SDL_max(window->floating.w, window->min_w) : window->floating.w; + h = window->min_h ? SDL_max(window->floating.h, window->min_h) : window->floating.h; + return SDL_SetWindowSize(window, w, h); } int SDL_GetWindowMinimumSize(SDL_Window *window, int *min_w, int *min_h) @@ -2753,6 +2764,8 @@ int SDL_GetWindowMinimumSize(SDL_Window *window, int *min_w, int *min_h) int SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h) { + int w, h; + CHECK_WINDOW_MAGIC(window, -1); if (max_w < 0) { return SDL_InvalidParamError("max_w"); @@ -2768,19 +2781,14 @@ int SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h) window->max_w = max_w; window->max_h = max_h; - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - int w, h; - - if (_this->SetWindowMaximumSize) { - _this->SetWindowMaximumSize(_this, window); - } - - /* Ensure that window is not larger than maximal size */ - w = window->max_w ? SDL_min(window->w, window->max_w) : window->w; - h = window->max_h ? SDL_min(window->h, window->max_h) : window->h; - return SDL_SetWindowSize(window, w, h); + if (_this->SetWindowMaximumSize) { + _this->SetWindowMaximumSize(_this, window); } - return 0; + + /* Ensure that window is not larger than maximal size */ + w = window->max_w ? SDL_min(window->floating.w, window->max_w) : window->floating.w; + h = window->max_h ? SDL_min(window->floating.h, window->max_h) : window->floating.h; + return SDL_SetWindowSize(window, w, h); } int SDL_GetWindowMaximumSize(SDL_Window *window, int *max_w, int *max_h) @@ -2818,12 +2826,8 @@ int SDL_ShowWindow(SDL_Window *window) } SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); - /* Set window state if we have pending window flags cached */ - ApplyWindowFlags(window, window->pending_flags); - window->pending_flags = 0; - /* Restore child windows */ - for (child = window->first_child; child != NULL; child = child->next_sibling) { + for (child = window->first_child; child; child = child->next_sibling) { if (!child->restore_on_show && (child->flags & SDL_WINDOW_HIDDEN)) { break; } @@ -2844,7 +2848,7 @@ int SDL_HideWindow(SDL_Window *window) } /* Hide all child windows */ - for (child = window->first_child; child != NULL; child = child->next_sibling) { + for (child = window->first_child; child; child = child->next_sibling) { if (child->flags & SDL_WINDOW_HIDDEN) { break; } @@ -2885,8 +2889,12 @@ int SDL_MaximizeWindow(SDL_Window *window) CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (window->flags & SDL_WINDOW_MAXIMIZED) { - return 0; + if (!_this->MaximizeWindow) { + return SDL_Unsupported(); + } + + if (!(window->flags & SDL_WINDOW_RESIZABLE)) { + return SDL_SetError("A window without the 'SDL_WINDOW_RESIZABLE' flag can't be maximized"); } if (window->flags & SDL_WINDOW_HIDDEN) { @@ -2894,29 +2902,18 @@ int SDL_MaximizeWindow(SDL_Window *window) return 0; } - /* !!! FIXME: should this check if the window is resizable? */ - - if (_this->MaximizeWindow) { - _this->MaximizeWindow(_this, window); - } + _this->MaximizeWindow(_this, window); + SDL_SyncIfRequired(window); return 0; } -static SDL_bool SDL_CanMinimizeWindow(SDL_Window *window) -{ - if (!_this->MinimizeWindow || SDL_WINDOW_IS_POPUP(window)) { - return SDL_FALSE; - } - return SDL_TRUE; -} - int SDL_MinimizeWindow(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (window->flags & SDL_WINDOW_MINIMIZED) { - return 0; + if (!_this->MinimizeWindow) { + return SDL_Unsupported(); } if (window->flags & SDL_WINDOW_HIDDEN) { @@ -2924,17 +2921,8 @@ int SDL_MinimizeWindow(SDL_Window *window) return 0; } - if (!SDL_CanMinimizeWindow(window)) { - return 0; - } - - if (!DisableUnsetFullscreenOnMinimize(_this)) { - SDL_UpdateFullscreenMode(window, SDL_FALSE); - } - - if (_this->MinimizeWindow) { - _this->MinimizeWindow(_this, window); - } + _this->MinimizeWindow(_this, window); + SDL_SyncIfRequired(window); return 0; } @@ -2943,8 +2931,8 @@ int SDL_RestoreWindow(SDL_Window *window) CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); - if (!(window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { - return 0; + if (!_this->RestoreWindow) { + return SDL_Unsupported(); } if (window->flags & SDL_WINDOW_HIDDEN) { @@ -2952,16 +2940,14 @@ int SDL_RestoreWindow(SDL_Window *window) return 0; } - if (_this->RestoreWindow) { - _this->RestoreWindow(_this, window); - } + _this->RestoreWindow(_this, window); + SDL_SyncIfRequired(window); return 0; } int SDL_SetWindowFullscreen(SDL_Window *window, SDL_bool fullscreen) { int ret = 0; - Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN : 0; CHECK_WINDOW_MAGIC(window, -1); CHECK_WINDOW_NOT_POPUP(window, -1); @@ -2975,28 +2961,36 @@ int SDL_SetWindowFullscreen(SDL_Window *window, SDL_bool fullscreen) return 0; } - if (flags == (window->flags & SDL_WINDOW_FULLSCREEN)) { - return 0; - } - - /* Clear the previous flags and OR in the new ones */ - window->flags = (window->flags & ~SDL_WINDOW_FULLSCREEN) | flags; - if (fullscreen) { /* Set the current fullscreen mode to the desired mode */ - SDL_memcpy(&window->current_fullscreen_mode, &window->requested_fullscreen_mode, sizeof(window->current_fullscreen_mode)); + SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode); } - ret = SDL_UpdateFullscreenMode(window, SDL_WINDOW_FULLSCREEN_VISIBLE(window)); + ret = SDL_UpdateFullscreenMode(window, fullscreen, SDL_TRUE); if (!fullscreen || ret != 0) { /* Clear the current fullscreen mode. */ SDL_zero(window->current_fullscreen_mode); } + if (ret == 0) { + SDL_SyncIfRequired(window); + } + return ret; } +int SDL_SyncWindow(SDL_Window *window) +{ + CHECK_WINDOW_MAGIC(window, -1) + + if (_this->SyncWindow) { + return _this->SyncWindow(_this, window); + } + + return 0; +} + static SDL_Surface *SDL_CreateWindowFramebuffer(SDL_Window *window) { Uint32 format = 0; @@ -3027,7 +3021,7 @@ static SDL_Surface *SDL_CreateWindowFramebuffer(SDL_Window *window) #ifdef __LINUX__ /* On WSL, direct X11 is faster than using OpenGL for window framebuffers, so try to detect WSL and avoid texture framebuffer. */ - else if ((_this->CreateWindowFramebuffer != NULL) && (SDL_strcmp(_this->name, "x11") == 0)) { + else if ((_this->CreateWindowFramebuffer) && (SDL_strcmp(_this->name, "x11") == 0)) { struct stat sb; if ((stat("/proc/sys/fs/binfmt_misc/WSLInterop", &sb) == 0) || (stat("/run/WSL", &sb) == 0)) { /* if either of these exist, we're on WSL. */ attempt_texture_framebuffer = SDL_FALSE; @@ -3035,7 +3029,7 @@ static SDL_Surface *SDL_CreateWindowFramebuffer(SDL_Window *window) } #endif #if defined(__WIN32__) || defined(__WINGDK__) /* GDI BitBlt() is way faster than Direct3D dynamic textures right now. (!!! FIXME: is this still true?) */ - else if ((_this->CreateWindowFramebuffer != NULL) && (SDL_strcmp(_this->name, "windows") == 0)) { + else if ((_this->CreateWindowFramebuffer) && (SDL_strcmp(_this->name, "windows") == 0)) { attempt_texture_framebuffer = SDL_FALSE; } #endif @@ -3209,15 +3203,15 @@ int SDL_SetWindowFocusable(SDL_Window *window, SDL_bool focusable) { CHECK_WINDOW_MAGIC(window, -1); - const int want = (focusable != SDL_FALSE); /* normalize the flag. */ - const int have = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE); + const SDL_bool want = (focusable != SDL_FALSE); /* normalize the flag. */ + const SDL_bool have = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE); if ((want != have) && (_this->SetWindowFocusable)) { if (want) { window->flags &= ~SDL_WINDOW_NOT_FOCUSABLE; } else { window->flags |= SDL_WINDOW_NOT_FOCUSABLE; } - _this->SetWindowFocusable(_this, window, (SDL_bool)want); + _this->SetWindowFocusable(_this, window, want); } return 0; @@ -3401,12 +3395,15 @@ int SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation) void SDL_OnWindowShown(SDL_Window *window) { - SDL_OnWindowRestored(window); + /* Set window state if we have pending window flags cached */ + ApplyWindowFlags(window, window->pending_flags); + window->pending_flags = 0; } void SDL_OnWindowHidden(SDL_Window *window) { - SDL_UpdateFullscreenMode(window, SDL_FALSE); + /* The window is already hidden at this point, so just change the mode back if necessary. */ + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE); } void SDL_OnWindowDisplayChanged(SDL_Window *window) @@ -3425,13 +3422,13 @@ void SDL_OnWindowDisplayChanged(SDL_Window *window) } if (new_mode) { - SDL_memcpy(&window->current_fullscreen_mode, new_mode, sizeof(window->current_fullscreen_mode)); + SDL_copyp(&window->current_fullscreen_mode, new_mode); } else { SDL_zero(window->current_fullscreen_mode); } if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - SDL_UpdateFullscreenMode(window, SDL_TRUE); + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_TRUE); } } @@ -3466,8 +3463,8 @@ void SDL_OnWindowPixelSizeChanged(SDL_Window *window) void SDL_OnWindowMinimized(SDL_Window *window) { - if (!DisableUnsetFullscreenOnMinimize(_this)) { - SDL_UpdateFullscreenMode(window, SDL_FALSE); + if (window->flags & SDL_WINDOW_FULLSCREEN) { + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE); } } @@ -3485,8 +3482,8 @@ void SDL_OnWindowRestored(SDL_Window *window) */ /*SDL_RaiseWindow(window);*/ - if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) { - SDL_UpdateFullscreenMode(window, SDL_TRUE); + if (window->flags & SDL_WINDOW_FULLSCREEN) { + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_FALSE); } } @@ -3542,8 +3539,8 @@ static SDL_bool SDL_ShouldMinimizeOnFocusLoss(SDL_Window *window) /* Real fullscreen windows should minimize on focus loss so the desktop video mode is restored */ hint = SDL_GetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS); - if (hint == NULL || !*hint || SDL_strcasecmp(hint, "auto") == 0) { - if (window->fullscreen_exclusive && !ModeSwitchingEmulated(_this)) { + if (!hint || !*hint || SDL_strcasecmp(hint, "auto") == 0) { + if (window->fullscreen_exclusive && !SDL_ModeSwitchingEmulated(_this)) { return SDL_TRUE; } else { return SDL_FALSE; @@ -3588,6 +3585,8 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0); + SDL_DestroyProperties(window->props); + /* If this is a child window, unlink it from its siblings */ if (window->parent) { if (window->next_sibling) { @@ -3601,8 +3600,8 @@ void SDL_DestroyWindow(SDL_Window *window) } /* Restore video mode, etc. */ - SDL_UpdateFullscreenMode(window, SDL_FALSE); - if (!(window->flags & SDL_WINDOW_FOREIGN)) { + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_TRUE); + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { SDL_HideWindow(window); } @@ -3622,12 +3621,18 @@ void SDL_DestroyWindow(SDL_Window *window) } } - /* make no context current if this is the current context window. */ + /* Make no context current if this is the current context window */ if (window->flags & SDL_WINDOW_OPENGL) { if (_this->current_glwin == window) { SDL_GL_MakeCurrent(window, NULL); } } + + if (_this->DestroyWindow) { + _this->DestroyWindow(_this, window); + } + + /* Unload the graphics libraries after the window is destroyed, which may clean up EGL surfaces */ if (window->flags & SDL_WINDOW_OPENGL) { SDL_GL_UnloadLibrary(); } @@ -3635,10 +3640,6 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_Vulkan_UnloadLibrary(); } - if (_this->DestroyWindow) { - _this->DestroyWindow(_this, window); - } - if (_this->grabbed_window == window) { _this->grabbed_window = NULL; /* ungrabbing input. */ } @@ -3657,13 +3658,6 @@ void SDL_DestroyWindow(SDL_Window *window) /* Free memory associated with the window */ SDL_free(window->title); SDL_DestroySurface(window->icon); - while (window->data) { - SDL_WindowUserData *data = window->data; - - window->data = data->next; - SDL_free(data->name); - SDL_free(data); - } /* Unlink the window from the list */ if (window->next) { @@ -3680,15 +3674,15 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_bool SDL_ScreenSaverEnabled(void) { - if (_this == NULL) { + if (!_this) { return SDL_TRUE; } - return _this->suspend_screensaver ? SDL_FALSE : SDL_TRUE; + return !_this->suspend_screensaver; } int SDL_EnableScreenSaver(void) { - if (_this == NULL) { + if (!_this) { return 0; } if (!_this->suspend_screensaver) { @@ -3704,7 +3698,7 @@ int SDL_EnableScreenSaver(void) int SDL_DisableScreenSaver(void) { - if (_this == NULL) { + if (!_this) { return 0; } if (_this->suspend_screensaver) { @@ -3722,7 +3716,7 @@ void SDL_VideoQuit(void) { int i; - if (_this == NULL) { + if (!_this) { return; } @@ -3730,6 +3724,7 @@ void SDL_VideoQuit(void) SDL_ClearClipboardData(); /* Halt event processing before doing anything else */ + SDL_QuitVideoCapture(); SDL_QuitTouch(); SDL_QuitMouse(); SDL_QuitKeyboard(); @@ -3747,12 +3742,11 @@ void SDL_VideoQuit(void) SDL_VideoDisplay *display = _this->displays[i]; SDL_DelVideoDisplay(display->id, SDL_FALSE); } - if (_this->displays) { - SDL_assert(_this->num_displays == 0); - SDL_free(_this->displays); - _this->displays = NULL; - _this->num_displays = 0; - } + + SDL_assert(_this->num_displays == 0); + SDL_free(_this->displays); + _this->displays = NULL; + if (_this->primary_selection_text) { SDL_free(_this->primary_selection_text); _this->primary_selection_text = NULL; @@ -3765,7 +3759,7 @@ int SDL_GL_LoadLibrary(const char *path) { int retval; - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); } if (_this->gl_config.driver_loaded) { @@ -3793,7 +3787,7 @@ SDL_FunctionPointer SDL_GL_GetProcAddress(const char *proc) { SDL_FunctionPointer func; - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return NULL; } @@ -3836,7 +3830,7 @@ SDL_FunctionPointer SDL_EGL_GetProcAddress(const char *proc) void SDL_GL_UnloadLibrary(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return; } @@ -3886,7 +3880,7 @@ SDL_bool SDL_GL_ExtensionSupported(const char *extension) /* Lookup the available extensions */ glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString"); - if (glGetStringFunc == NULL) { + if (!glGetStringFunc) { return SDL_FALSE; } @@ -3898,7 +3892,7 @@ SDL_bool SDL_GL_ExtensionSupported(const char *extension) glGetStringiFunc = (PFNGLGETSTRINGIPROC)SDL_GL_GetProcAddress("glGetStringi"); glGetIntegervFunc = (PFNGLGETINTEGERVPROC)SDL_GL_GetProcAddress("glGetIntegerv"); - if ((glGetStringiFunc == NULL) || (glGetIntegervFunc == NULL)) { + if ((!glGetStringiFunc) || (!glGetIntegervFunc)) { return SDL_FALSE; } @@ -3919,7 +3913,7 @@ SDL_bool SDL_GL_ExtensionSupported(const char *extension) /* Try the old way with glGetString(GL_EXTENSIONS) ... */ extensions = (const char *)glGetStringFunc(GL_EXTENSIONS); - if (extensions == NULL) { + if (!extensions) { return SDL_FALSE; } /* @@ -3931,7 +3925,7 @@ SDL_bool SDL_GL_ExtensionSupported(const char *extension) for (;;) { where = SDL_strstr(start, extension); - if (where == NULL) { + if (!where) { break; } @@ -3994,7 +3988,7 @@ void SDL_EGL_SetEGLAttributeCallbacks(SDL_EGLAttribArrayCallback platformAttribC void SDL_GL_ResetAttributes(void) { - if (_this == NULL) { + if (!_this) { return; } @@ -4057,7 +4051,7 @@ int SDL_GL_SetAttribute(SDL_GLattr attr, int value) #if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) int retval; - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); } retval = 0; @@ -4190,14 +4184,14 @@ int SDL_GL_GetAttribute(SDL_GLattr attr, int *value) GLenum attachmentattrib = 0; #endif - if (value == NULL) { + if (!value) { return SDL_InvalidParamError("value"); } /* Clear value in any case */ *value = 0; - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); } @@ -4368,7 +4362,7 @@ int SDL_GL_GetAttribute(SDL_GLattr attr, int *value) #ifdef SDL_VIDEO_OPENGL glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString"); - if (glGetStringFunc == NULL) { + if (!glGetStringFunc) { return -1; } @@ -4405,7 +4399,7 @@ int SDL_GL_GetAttribute(SDL_GLattr attr, int *value) } glGetErrorFunc = (PFNGLGETERRORPROC)SDL_GL_GetProcAddress("glGetError"); - if (glGetErrorFunc == NULL) { + if (!glGetErrorFunc) { return -1; } @@ -4452,7 +4446,7 @@ int SDL_GL_MakeCurrent(SDL_Window *window, SDL_GLContext context) { int retval; - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); } @@ -4486,7 +4480,7 @@ int SDL_GL_MakeCurrent(SDL_Window *window, SDL_GLContext context) SDL_Window *SDL_GL_GetCurrentWindow(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return NULL; } @@ -4495,7 +4489,7 @@ SDL_Window *SDL_GL_GetCurrentWindow(void) SDL_GLContext SDL_GL_GetCurrentContext(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return NULL; } @@ -4561,7 +4555,7 @@ SDL_EGLConfig SDL_EGL_GetWindowEGLSurface(SDL_Window *window) int SDL_GL_SetSwapInterval(int interval) { - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); } else if (SDL_GL_GetCurrentContext() == NULL) { return SDL_SetError("No OpenGL context has been made current"); @@ -4574,13 +4568,13 @@ int SDL_GL_SetSwapInterval(int interval) int SDL_GL_GetSwapInterval(int *interval) { - if (interval == NULL) { + if (!interval) { return SDL_InvalidParamError("interval"); } *interval = 0; - if (_this == NULL) { + if (!_this) { return SDL_SetError("no video driver");; } else if (SDL_GL_GetCurrentContext() == NULL) { return SDL_SetError("no current context");; @@ -4608,7 +4602,7 @@ int SDL_GL_SwapWindow(SDL_Window *window) int SDL_GL_DeleteContext(SDL_GLContext context) { - if (_this == NULL) { + if (!_this) { return SDL_UninitializedVideo(); \ } if (!context) { @@ -4719,33 +4713,6 @@ void SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask) } #endif -int SDL_GetWindowWMInfo(SDL_Window *window, struct SDL_SysWMinfo *info, Uint32 version) -{ - CHECK_WINDOW_MAGIC(window, -1); - - if (info == NULL) { - return SDL_InvalidParamError("info"); - } - - /* Set the version in the structure to the minimum of our version and the application expected version */ - version = SDL_min(version, SDL_SYSWM_CURRENT_VERSION); - - if (version == 1) { - SDL_memset(info, 0, SDL_SYSWM_INFO_SIZE_V1); - } else { - return SDL_SetError("Unknown info version"); - } - - info->subsystem = SDL_SYSWM_UNKNOWN; - info->version = version; - - if (_this->GetWindowWMInfo) { - return (_this->GetWindowWMInfo(_this, window, info)); - } else { - return 0; - } -} - void SDL_StartTextInput(void) { SDL_Window *window; @@ -4813,7 +4780,7 @@ void SDL_StopTextInput(void) int SDL_SetTextInputRect(const SDL_Rect *rect) { - if (rect == NULL) { + if (!rect) { return SDL_InvalidParamError("rect"); } @@ -4875,20 +4842,6 @@ int SDL_GetMessageBoxCount(void) #include "vita/SDL_vitamessagebox.h" #endif -#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_WINRT) || defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT) || defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_HAIKU) || defined(SDL_VIDEO_DRIVER_RISCOS) -static SDL_bool SDL_IsMessageboxValidForDriver(const SDL_MessageBoxData *messageboxdata, SDL_SYSWM_TYPE drivertype) -{ - SDL_SysWMinfo info; - SDL_Window *window = messageboxdata->window; - - if (window == NULL || SDL_GetWindowWMInfo(window, &info, SDL_SYSWM_CURRENT_VERSION) < 0) { - return SDL_TRUE; - } else { - return info.subsystem == (Uint32)drivertype; - } -} -#endif - int SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) { int dummybutton; @@ -4898,7 +4851,7 @@ int SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) SDL_Window *current_window; SDL_MessageBoxData mbdata; - if (messageboxdata == NULL) { + if (!messageboxdata) { return SDL_InvalidParamError("messageboxdata"); } else if (messageboxdata->numbuttons < 0) { return SDL_SetError("Invalid number of buttons"); @@ -4914,7 +4867,7 @@ int SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) SDL_ShowCursor(); SDL_ResetKeyboard(); - if (buttonid == NULL) { + if (!buttonid) { buttonid = &dummybutton; } @@ -4942,56 +4895,48 @@ int SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) #endif #if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__) if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_WINDOWS) && WIN_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_WINRT if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_WINRT) && WINRT_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_COCOA if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_COCOA) && Cocoa_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_UIKIT if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_UIKIT) && UIKit_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_WAYLAND) && Wayland_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_X11 if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_X11) && X11_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_HAIKU if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_HAIKU) && HAIKU_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } #endif #ifdef SDL_VIDEO_DRIVER_RISCOS if (retval == -1 && - SDL_IsMessageboxValidForDriver(messageboxdata, SDL_SYSWM_RISCOS) && RISCOS_ShowMessageBox(messageboxdata, buttonid) == 0) { retval = 0; } @@ -5008,6 +4953,8 @@ int SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) if (!*error) { SDL_SetError("No message system available"); } + } else { + SDL_ClearError(); } (void)SDL_AtomicDecRef(&SDL_messagebox_count); @@ -5032,10 +4979,10 @@ int SDL_ShowSimpleMessageBox(Uint32 flags, const char *title, const char *messag /* Web browsers don't (currently) have an API for a custom message box that can block, but for the most common case (SDL_ShowSimpleMessageBox), we can use the standard Javascript alert() function. */ - if (title == NULL) { + if (!title) { title = ""; } - if (message == NULL) { + if (!message) { message = ""; } EM_ASM({ @@ -5115,7 +5062,7 @@ void SDL_OnApplicationWillResignActive(void) { if (_this) { SDL_Window *window; - for (window = _this->windows; window != NULL; window = window->next) { + for (window = _this->windows; window; window = window->next) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); } SDL_SetKeyboardFocus(NULL); @@ -5139,7 +5086,7 @@ void SDL_OnApplicationDidBecomeActive(void) if (_this) { SDL_Window *window; - for (window = _this->windows; window != NULL; window = window->next) { + for (window = _this->windows; window; window = window->next) { SDL_SetKeyboardFocus(window); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); } @@ -5151,7 +5098,7 @@ void SDL_OnApplicationDidBecomeActive(void) int SDL_Vulkan_LoadLibrary(const char *path) { int retval; - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return -1; } @@ -5174,7 +5121,7 @@ int SDL_Vulkan_LoadLibrary(const char *path) SDL_FunctionPointer SDL_Vulkan_GetVkGetInstanceProcAddr(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return NULL; } @@ -5187,7 +5134,7 @@ SDL_FunctionPointer SDL_Vulkan_GetVkGetInstanceProcAddr(void) void SDL_Vulkan_UnloadLibrary(void) { - if (_this == NULL) { + if (!_this) { SDL_UninitializedVideo(); return; } @@ -5201,18 +5148,14 @@ void SDL_Vulkan_UnloadLibrary(void) } } -SDL_bool SDL_Vulkan_GetInstanceExtensions(unsigned *count, const char **names) +char const* const* SDL_Vulkan_GetInstanceExtensions(Uint32 *count) { - if (count == NULL) { - SDL_InvalidParamError("count"); - return SDL_FALSE; - } - - return _this->Vulkan_GetInstanceExtensions(_this, count, names); + return _this->Vulkan_GetInstanceExtensions(_this, count); } SDL_bool SDL_Vulkan_CreateSurface(SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { CHECK_WINDOW_MAGIC(window, SDL_FALSE); @@ -5227,18 +5170,23 @@ SDL_bool SDL_Vulkan_CreateSurface(SDL_Window *window, return SDL_FALSE; } - if (surface == NULL) { + if (!surface) { SDL_InvalidParamError("surface"); return SDL_FALSE; } - return _this->Vulkan_CreateSurface(_this, window, instance, surface); + return _this->Vulkan_CreateSurface(_this, window, instance, allocator, surface); } SDL_MetalView SDL_Metal_CreateView(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, NULL); + if (!_this->Metal_CreateView) { + SDL_Unsupported(); + return NULL; + } + if (!(window->flags & SDL_WINDOW_METAL)) { /* No problem, we can convert to Metal */ if (window->flags & SDL_WINDOW_OPENGL) { diff --git a/src/video/SDL_video_c.h b/src/video/SDL_video_c.h index 6282e770..ceacd4cc 100644 --- a/src/video/SDL_video_c.h +++ b/src/video/SDL_video_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,4 +57,6 @@ extern void SDL_VideoQuit(void); extern int SDL_SetWindowTextureVSync(SDL_Window *window, int vsync); +extern int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); + #endif /* SDL_video_c_h_ */ diff --git a/src/video/SDL_video_capture.c b/src/video/SDL_video_capture.c new file mode 100644 index 00000000..01fbb49d --- /dev/null +++ b/src/video/SDL_video_capture.c @@ -0,0 +1,968 @@ +/* + Simple DirectMedia Layer + 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 + 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 "SDL3/SDL.h" +#include "SDL3/SDL_video_capture.h" +#include "SDL_sysvideocapture.h" +#include "SDL_video_capture_c.h" +#include "SDL_pixels_c.h" +#include "../thread/SDL_systhread.h" + +#define DEBUG_VIDEO_CAPTURE_CAPTURE 0 + + +#ifdef SDL_VIDEO_CAPTURE +/* list node entries to share frames between SDL and user app */ +typedef struct entry_t +{ + SDL_VideoCaptureFrame frame; +} entry_t; + +static SDL_VideoCaptureDevice *open_devices[16]; + +static void +close_device(SDL_VideoCaptureDevice *device) +{ + if (!device) { + return; + } + + SDL_AtomicSet(&device->shutdown, 1); + SDL_AtomicSet(&device->enabled, 1); + + if (device->thread != NULL) { + SDL_WaitThread(device->thread, NULL); + } + if (device->device_lock != NULL) { + SDL_DestroyMutex(device->device_lock); + } + if (device->acquiring_lock != NULL) { + SDL_DestroyMutex(device->acquiring_lock); + } + + { + int i, n = SDL_arraysize(open_devices); + for (i = 0; i < n; i++) { + if (open_devices[i] == device) { + open_devices[i] = NULL; + } + } + } + + { + entry_t *entry = NULL; + while (device->buffer_queue != NULL) { + SDL_ListPop(&device->buffer_queue, (void**)&entry); + if (entry) { + SDL_VideoCaptureFrame f = entry->frame; + /* Release frames not acquired, if any */ + if (f.timestampNS) { + ReleaseFrame(device, &f); + } + SDL_free(entry); + } + } + } + + CloseDevice(device); + + SDL_free(device->dev_name); + SDL_free(device); +} + +/* Tell if all device are closed */ +SDL_bool check_all_device_closed(void) +{ + int i, n = SDL_arraysize(open_devices); + int all_closed = SDL_TRUE; + for (i = 0; i < n; i++) { + if (open_devices[i]) { + all_closed = SDL_FALSE; + break; + } + } + return all_closed; +} + +/* Tell if at least one device is in playing state */ +SDL_bool check_device_playing(void) +{ + int i, n = SDL_arraysize(open_devices); + for (i = 0; i < n; i++) { + if (open_devices[i]) { + if (SDL_GetVideoCaptureStatus(open_devices[i]) == SDL_VIDEO_CAPTURE_PLAYING) { + return SDL_TRUE; + } + } + } + return SDL_FALSE; +} + + +#endif /* SDL_VIDEO_CAPTURE */ + +void +SDL_CloseVideoCapture(SDL_VideoCaptureDevice *device) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + SDL_InvalidParamError("device"); + return; + } + close_device(device); +#endif +} + +int +SDL_StartVideoCapture(SDL_VideoCaptureDevice *device) +{ +#ifdef SDL_VIDEO_CAPTURE + SDL_VideoCaptureStatus status; + int result; + if (!device) { + return SDL_InvalidParamError("device"); + } + + if (device->is_spec_set == SDL_FALSE) { + return SDL_SetError("no spec set"); + } + + status = SDL_GetVideoCaptureStatus(device); + if (status != SDL_VIDEO_CAPTURE_INIT) { + return SDL_SetError("invalid state"); + } + + result = StartCapture(device); + if (result < 0) { + return result; + } + + SDL_AtomicSet(&device->enabled, 1); + + return 0; +#else + return SDL_Unsupported(); +#endif +} + +int +SDL_GetVideoCaptureSpec(SDL_VideoCaptureDevice *device, SDL_VideoCaptureSpec *spec) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + + if (!spec) { + return SDL_InvalidParamError("spec"); + } + + SDL_zerop(spec); + + return GetDeviceSpec(device, spec); +#else + return SDL_Unsupported(); +#endif +} + +int +SDL_StopVideoCapture(SDL_VideoCaptureDevice *device) +{ +#ifdef SDL_VIDEO_CAPTURE + SDL_VideoCaptureStatus status; + int ret; + if (!device) { + return SDL_InvalidParamError("device"); + } + + status = SDL_GetVideoCaptureStatus(device); + + if (status != SDL_VIDEO_CAPTURE_PLAYING) { + return SDL_SetError("invalid state"); + } + + SDL_AtomicSet(&device->enabled, 0); + SDL_AtomicSet(&device->shutdown, 1); + + SDL_LockMutex(device->acquiring_lock); + ret = StopCapture(device); + SDL_UnlockMutex(device->acquiring_lock); + + if (ret < 0) { + return -1; + } + + return 0; +#else + return SDL_Unsupported(); +#endif +} + +#ifdef SDL_VIDEO_CAPTURE + +/* Check spec has valid format and frame size */ +static int +prepare_video_capturespec(SDL_VideoCaptureDevice *device, const SDL_VideoCaptureSpec *desired, SDL_VideoCaptureSpec *obtained, int allowed_changes) +{ + /* Check format */ + { + int i, num = SDL_GetNumVideoCaptureFormats(device); + int is_format_valid = 0; + + for (i = 0; i < num; i++) { + Uint32 format; + if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) { + if (format == desired->format && format != SDL_PIXELFORMAT_UNKNOWN) { + is_format_valid = 1; + obtained->format = format; + break; + } + } + } + + if (!is_format_valid) { + if (allowed_changes) { + for (i = 0; i < num; i++) { + Uint32 format; + if (SDL_GetVideoCaptureFormat(device, i, &format) == 0) { + if (format != SDL_PIXELFORMAT_UNKNOWN) { + obtained->format = format; + is_format_valid = 1; + break; + } + } + } + + } else { + SDL_SetError("Not allowed to change the format"); + return -1; + } + } + + if (!is_format_valid) { + SDL_SetError("Invalid format"); + return -1; + } + } + + /* Check frame size */ + { + int i, num = SDL_GetNumVideoCaptureFrameSizes(device, obtained->format); + int is_framesize_valid = 0; + + for (i = 0; i < num; i++) { + int w, h; + if (SDL_GetVideoCaptureFrameSize(device, obtained->format, i, &w, &h) == 0) { + if (desired->width == w && desired->height == h) { + is_framesize_valid = 1; + obtained->width = w; + obtained->height = h; + break; + } + } + } + + if (!is_framesize_valid) { + if (allowed_changes) { + int w, h; + if (SDL_GetVideoCaptureFrameSize(device, obtained->format, 0, &w, &h) == 0) { + is_framesize_valid = 1; + obtained->width = w; + obtained->height = h; + } + } else { + SDL_SetError("Not allowed to change the frame size"); + return -1; + } + } + + if (!is_framesize_valid) { + SDL_SetError("Invalid frame size"); + return -1; + } + + } + + return 0; +} + +#endif /* SDL_VIDEO_CAPTURE */ + +const char * +SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id) +{ +#ifdef SDL_VIDEO_CAPTURE + static char buf[256]; + buf[0] = 0; + buf[255] = 0; + + if (instance_id == 0) { + SDL_InvalidParamError("instance_id"); + return NULL; + } + + if (GetDeviceName(instance_id, buf, sizeof (buf)) < 0) { + buf[0] = 0; + } + return buf; +#else + SDL_Unsupported(); + return NULL; +#endif +} + + +SDL_VideoCaptureDeviceID * +SDL_GetVideoCaptureDevices(int *count) +{ + + int num = 0; + SDL_VideoCaptureDeviceID *ret = NULL; +#ifdef SDL_VIDEO_CAPTURE + ret = GetVideoCaptureDevices(&num); +#endif + + if (ret) { + if (count) { + *count = num; + } + return ret; + } + + /* return list of 0 ID, null terminated */ + num = 0; + ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret)); + + if (ret == NULL) { + SDL_OutOfMemory(); + if (count) { + *count = 0; + } + return NULL; + } + + ret[num] = 0; + if (count) { + *count = num; + } + + return ret; +} + +#ifdef SDL_VIDEO_CAPTURE + +/* Video capture thread function */ +static int SDLCALL +SDL_CaptureVideoThread(void *devicep) +{ + const int delay = 20; + SDL_VideoCaptureDevice *device = (SDL_VideoCaptureDevice *) devicep; + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("Start thread 'SDL_CaptureVideo'"); +#endif + + +#ifdef SDL_VIDEO_DRIVER_ANDROID + // TODO + /* + { + // Set thread priority to THREAD_PRIORITY_VIDEO + extern void Android_JNI_VideoCaptureSetThreadPriority(int, int); + Android_JNI_VideoCaptureSetThreadPriority(device->iscapture, device); + }*/ +#else + /* The video_capture mixing is always a high priority thread */ + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); +#endif + + /* Perform any thread setup */ + device->threadid = SDL_ThreadID(); + + /* Init state */ + while (!SDL_AtomicGet(&device->enabled)) { + SDL_Delay(delay); + } + + /* Loop, filling the video_capture buffers */ + while (!SDL_AtomicGet(&device->shutdown)) { + SDL_VideoCaptureFrame f; + int ret; + entry_t *entry; + + SDL_zero(f); + + SDL_LockMutex(device->acquiring_lock); + ret = AcquireFrame(device, &f); + SDL_UnlockMutex(device->acquiring_lock); + + if (ret == 0) { + if (f.num_planes == 0) { + continue; + } + } + + if (ret < 0) { + /* Flag it as an error */ +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("dev[%p] error AcquireFrame: %d %s", (void *)device, ret, SDL_GetError()); +#endif + f.num_planes = 0; + } + + + entry = SDL_malloc(sizeof (entry_t)); + if (entry == NULL) { + goto error_mem; + } + + entry->frame = f; + + SDL_LockMutex(device->device_lock); + ret = SDL_ListAdd(&device->buffer_queue, entry); + SDL_UnlockMutex(device->device_lock); + + if (ret < 0) { + SDL_free(entry); + goto error_mem; + } + } + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("dev[%p] End thread 'SDL_CaptureVideo'", (void *)device); +#endif + return 0; + +error_mem: +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("dev[%p] End thread 'SDL_CaptureVideo' with error: %s", (void *)device, SDL_GetError()); +#endif + SDL_AtomicSet(&device->shutdown, 1); + SDL_OutOfMemory(); + return 0; +} +#endif + +SDL_VideoCaptureDevice * +SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id) +{ +#ifdef SDL_VIDEO_CAPTURE + int i, n = SDL_arraysize(open_devices); + int id = -1; + SDL_VideoCaptureDevice *device = NULL; + const char *device_name = NULL; + + if (!SDL_WasInit(SDL_INIT_VIDEO)) { + SDL_SetError("Video subsystem is not initialized"); + goto error; + } + + /* !!! FIXME: there is a race condition here if two devices open from two threads at once. */ + /* Find an available device ID... */ + for (i = 0; i < n; i++) { + if (open_devices[i] == NULL) { + id = i; + break; + } + } + + if (id == -1) { + SDL_SetError("Too many open video capture devices"); + goto error; + } + + if (instance_id != 0) { + device_name = SDL_GetVideoCaptureDeviceName(instance_id); + if (device_name == NULL) { + goto error; + } + } else { + SDL_VideoCaptureDeviceID *devices = SDL_GetVideoCaptureDevices(NULL); + if (devices && devices[0]) { + device_name = SDL_GetVideoCaptureDeviceName(devices[0]); + SDL_free(devices); + } + } + +#if 0 + // FIXME do we need this ? + /* Let the user override. */ + { + const char *dev = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME"); + if (dev && dev[0]) { + device_name = dev; + } + } +#endif + + if (device_name == NULL) { + goto error; + } + + device = (SDL_VideoCaptureDevice *) SDL_calloc(1, sizeof (SDL_VideoCaptureDevice)); + if (device == NULL) { + SDL_OutOfMemory(); + goto error; + } + device->dev_name = SDL_strdup(device_name); + + + SDL_AtomicSet(&device->shutdown, 0); + SDL_AtomicSet(&device->enabled, 0); + + device->device_lock = SDL_CreateMutex(); + if (device->device_lock == NULL) { + SDL_SetError("Couldn't create acquiring_lock"); + goto error; + } + + device->acquiring_lock = SDL_CreateMutex(); + if (device->acquiring_lock == NULL) { + SDL_SetError("Couldn't create acquiring_lock"); + goto error; + } + + if (OpenDevice(device) < 0) { + goto error; + } + + /* empty */ + device->buffer_queue = NULL; + open_devices[id] = device; /* add it to our list of open devices. */ + + + /* Start the video_capture thread */ + { + const size_t stacksize = 64 * 1024; + char threadname[64]; + + SDL_snprintf(threadname, sizeof (threadname), "SDLVideoC%d", id); + device->thread = SDL_CreateThreadInternal(SDL_CaptureVideoThread, threadname, stacksize, device); + + if (device->thread == NULL) { + SDL_SetError("Couldn't create video_capture thread"); + goto error; + } + } + + return device; + +error: + close_device(device); + return NULL; +#else + SDL_Unsupported(); + return NULL; +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_SetVideoCaptureSpec(SDL_VideoCaptureDevice *device, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes) +{ +#ifdef SDL_VIDEO_CAPTURE + SDL_VideoCaptureSpec _obtained; + SDL_VideoCaptureSpec _desired; + int result; + + if (!device) { + return SDL_InvalidParamError("device"); + } + + if (device->is_spec_set == SDL_TRUE) { + return SDL_SetError("already configured"); + } + + if (!desired) { + SDL_zero(_desired); + desired = &_desired; + allowed_changes = SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE; + } else { + /* in case desired == obtained */ + _desired = *desired; + desired = &_desired; + } + + if (!obtained) { + obtained = &_obtained; + } + + SDL_zerop(obtained); + + if (prepare_video_capturespec(device, desired, obtained, allowed_changes) < 0) { + return -1; + } + + device->spec = *obtained; + + result = InitDevice(device); + if (result < 0) { + return result; + } + + *obtained = device->spec; + + device->is_spec_set = SDL_TRUE; + + return 0; +#else + SDL_zero(*obtained); + return SDL_Unsupported(); +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_AcquireVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + + if (!frame) { + return SDL_InvalidParamError("frame"); + } + + SDL_zerop(frame); + + if (device->thread == NULL) { + int ret; + + /* Wait for a frame */ + while ((ret = AcquireFrame(device, frame)) == 0) { + if (frame->num_planes) { + return 0; + } + } + return -1; + } else { + entry_t *entry = NULL; + + SDL_LockMutex(device->device_lock); + SDL_ListPop(&device->buffer_queue, (void**)&entry); + SDL_UnlockMutex(device->device_lock); + + if (entry) { + *frame = entry->frame; + SDL_free(entry); + + /* Error from thread */ + if (frame->num_planes == 0 && frame->timestampNS == 0) { + return SDL_SetError("error from acquisition thread"); + } + + + } else { + /* Queue is empty. Not an error. */ + } + } + + return 0; +#else + return SDL_Unsupported(); +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_ReleaseVideoCaptureFrame(SDL_VideoCaptureDevice *device, SDL_VideoCaptureFrame *frame) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + + if (frame == NULL) { + return SDL_InvalidParamError("frame"); + } + + if (ReleaseFrame(device, frame) < 0) { + return -1; + } + + SDL_zerop(frame); + + return 0; +#else + return SDL_Unsupported(); +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_GetNumVideoCaptureFormats(SDL_VideoCaptureDevice *device) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + return GetNumFormats(device); +#else + return 0; +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_GetVideoCaptureFormat(SDL_VideoCaptureDevice *device, int index, Uint32 *format) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + if (!format) { + return SDL_InvalidParamError("format"); + } + *format = 0; + return GetFormat(device, index, format); +#else + return SDL_Unsupported(); +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_GetNumVideoCaptureFrameSizes(SDL_VideoCaptureDevice *device, Uint32 format) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + return GetNumFrameSizes(device, format); +#else + return 0; +#endif /* SDL_VIDEO_CAPTURE */ +} + +int +SDL_GetVideoCaptureFrameSize(SDL_VideoCaptureDevice *device, Uint32 format, int index, int *width, int *height) +{ +#ifdef SDL_VIDEO_CAPTURE + if (!device) { + return SDL_InvalidParamError("device"); + } + if (!width) { + return SDL_InvalidParamError("width"); + } + if (!height) { + return SDL_InvalidParamError("height"); + } + *width = 0; + *height = 0; + return GetFrameSize(device, format, index, width, height); +#else + return SDL_Unsupported(); +#endif +} + +SDL_VideoCaptureDevice * +SDL_OpenVideoCaptureWithSpec( + SDL_VideoCaptureDeviceID instance_id, + const SDL_VideoCaptureSpec *desired, + SDL_VideoCaptureSpec *obtained, + int allowed_changes) +{ +#ifdef SDL_VIDEO_CAPTURE + SDL_VideoCaptureDevice *device; + + if ((device = SDL_OpenVideoCapture(instance_id)) == NULL) { + return NULL; + } + + if (SDL_SetVideoCaptureSpec(device, desired, obtained, allowed_changes) < 0) { + SDL_CloseVideoCapture(device); + return NULL; + } + return device; +#else + SDL_Unsupported(); + return NULL; +#endif +} + +SDL_VideoCaptureStatus +SDL_GetVideoCaptureStatus(SDL_VideoCaptureDevice *device) +{ +#ifdef SDL_VIDEO_CAPTURE + if (device == NULL) { + return SDL_VIDEO_CAPTURE_INIT; + } + + if (device->is_spec_set == SDL_FALSE) { + return SDL_VIDEO_CAPTURE_INIT; + } + + if (SDL_AtomicGet(&device->shutdown)) { + return SDL_VIDEO_CAPTURE_STOPPED; + } + + if (SDL_AtomicGet(&device->enabled)) { + return SDL_VIDEO_CAPTURE_PLAYING; + } + return SDL_VIDEO_CAPTURE_INIT; +#else + SDL_Unsupported(); + return SDL_VIDEO_CAPTURE_FAIL; +#endif +} + +int +SDL_VideoCaptureInit(void) +{ +#ifdef SDL_VIDEO_CAPTURE + SDL_zeroa(open_devices); + + SDL_SYS_VideoCaptureInit(); + return 0; +#else + return 0; +#endif +} + +void +SDL_QuitVideoCapture(void) +{ +#ifdef SDL_VIDEO_CAPTURE + int i, n = SDL_arraysize(open_devices); + for (i = 0; i < n; i++) { + close_device(open_devices[i]); + } + + SDL_zeroa(open_devices); + + SDL_SYS_VideoCaptureQuit(); +#endif +} + +#ifdef SDL_VIDEO_CAPTURE + +#if defined(__linux__) && !defined(__ANDROID__) + +/* See SDL_video_capture_v4l2.c */ + +#elif defined(__ANDROID__) && __ANDROID_API__ >= 24 + +/* See SDL_android_video_capture.c */ + +#elif defined(__IOS__) || defined(__MACOS__) + +/* See SDL_video_capture_apple.m */ +#else + +int SDL_SYS_VideoCaptureInit(void) +{ + return 0; +} + +int SDL_SYS_VideoCaptureQuit(void) +{ + return 0; +} + +int +OpenDevice(SDL_VideoCaptureDevice *_this) +{ + return SDL_SetError("not implemented"); +} + +void +CloseDevice(SDL_VideoCaptureDevice *_this) +{ + return; +} + +int +InitDevice(SDL_VideoCaptureDevice *_this) +{ + size_t size, pitch; + SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE); + SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height); + return -1; +} + +int +GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) +{ + return SDL_Unsupported(); +} + +int +StartCapture(SDL_VideoCaptureDevice *_this) +{ + return SDL_Unsupported(); +} + +int +StopCapture(SDL_VideoCaptureDevice *_this) +{ + return -1; +} + +int +AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + return -1; +} + +int +ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + return -1; +} + +int +GetNumFormats(SDL_VideoCaptureDevice *_this) +{ + return -1; +} + +int +GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) +{ + return -1; +} + +int +GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) +{ + return -1; +} + +int +GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) +{ + return -1; +} + +int +GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) +{ + return -1; +} + +SDL_VideoCaptureDeviceID * +GetVideoCaptureDevices(int *count) +{ + return NULL; +} + +#endif + +#endif /* SDL_VIDEO_CAPTURE */ diff --git a/src/video/SDL_video_capture_apple.m b/src/video/SDL_video_capture_apple.m new file mode 100644 index 00000000..7e88ea08 --- /dev/null +++ b/src/video/SDL_video_capture_apple.m @@ -0,0 +1,659 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2021 Valve Corporation + + 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_CAPTURE + +#include "SDL3/SDL.h" +#include "SDL3/SDL_video_capture.h" +#include "SDL_sysvideocapture.h" +#include "SDL_video_capture_c.h" +#include "../thread/SDL_systhread.h" + +#if defined(HAVE_COREMEDIA) && defined(__MACOS__) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101500) +/* AVCaptureDeviceTypeBuiltInWideAngleCamera requires macOS SDK 10.15 */ +#undef HAVE_COREMEDIA +#endif + +#if TARGET_OS_TV +#undef HAVE_COREMEDIA +#endif + +#ifndef HAVE_COREMEDIA +int InitDevice(SDL_VideoCaptureDevice *_this) { + return -1; +} +int OpenDevice(SDL_VideoCaptureDevice *_this) { + return -1; +} +int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) { + return -1; +} +void CloseDevice(SDL_VideoCaptureDevice *_this) { +} +int GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) { + return -1; +} +int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) { + return -1; +} +int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) { + return -1; +} +int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) { + return -1; +} +SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) { + return NULL; +} +int GetNumFormats(SDL_VideoCaptureDevice *_this) { + return 0; +} +int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) { + return 0; +} +int ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) { + return 0; +} +int StartCapture(SDL_VideoCaptureDevice *_this) { + return 0; +} +int StopCapture(SDL_VideoCaptureDevice *_this) { + return 0; +} +int SDL_SYS_VideoCaptureInit(void) { + return 0; +} +int SDL_SYS_VideoCaptureQuit(void) { + return 0; +} + + +#else + +#import +#import + +/* + * Need to link with:: CoreMedia CoreVideo + * + * Add in pInfo.list: + * NSCameraUsageDescription Access camera + * + * + * MACOSX: + * Add to the Code Sign Entitlement file: + * com.apple.security.device.camera + * + * + * IOS: + * + * - Need to link with:: CoreMedia CoreVideo + * - Add #define SDL_VIDEO_CAPTURE 1 + * to SDL_build_config_ios.h + */ + +@class MySampleBufferDelegate; + +struct SDL_PrivateVideoCaptureData +{ + dispatch_queue_t queue; + MySampleBufferDelegate *delegate; + AVCaptureSession *session; + CMSimpleQueueRef frame_queue; +}; + +static NSString * +fourcc_to_nstring(Uint32 code) +{ + Uint8 buf[4]; + *(Uint32 *)buf = code; + return [NSString stringWithFormat:@"%c%c%c%c", buf[3], buf[2], buf[1], buf[0]]; +} + +static NSArray * +discover_devices() +{ + NSArray *deviceType = @[AVCaptureDeviceTypeBuiltInWideAngleCamera]; + + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:deviceType + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + + NSArray *devices = discoverySession.devices; + + if ([devices count] > 0) { + return devices; + } else { + AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (captureDevice == nil) { + return devices; + } else { + NSArray *default_device = @[ captureDevice ]; + return default_device; + } + } + + return devices; +} + +static AVCaptureDevice * +get_device_by_name(const char *dev_name) +{ + NSArray *devices = discover_devices(); + + for (AVCaptureDevice *device in devices) { + char buf[1024]; + NSString *cameraID = [device localizedName]; + const char *str = [cameraID UTF8String]; + SDL_snprintf(buf, sizeof (buf) - 1, "%s", str); + if (SDL_strcmp(buf, dev_name) == 0) { + return device; + } + } + return nil; +} + +static Uint32 +nsfourcc_to_sdlformat(NSString *nsfourcc) +{ + const char *str = [nsfourcc UTF8String]; + + /* FIXME + * on IOS this mode gives 2 planes, and it's NV12 + * on macos, 1 plane/ YVYU + * + */ +#ifdef __MACOS__ + if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_YVYU; +#else + if (SDL_strcmp("420v", str) == 0) return SDL_PIXELFORMAT_NV12; +#endif + if (SDL_strcmp("yuvs", str) == 0) return SDL_PIXELFORMAT_UYVY; + if (SDL_strcmp("420f", str) == 0) return SDL_PIXELFORMAT_UNKNOWN; + + SDL_Log("Unknown format '%s'", str); + + return SDL_PIXELFORMAT_UNKNOWN; +} + +static NSString * +sdlformat_to_nsfourcc(Uint32 fmt) +{ + const char *str = ""; + NSString *result; + +#ifdef __MACOS__ + if (fmt == SDL_PIXELFORMAT_YVYU) str = "420v"; +#else + if (fmt == SDL_PIXELFORMAT_NV12) str = "420v"; +#endif + if (fmt == SDL_PIXELFORMAT_UYVY) str = "yuvs"; + + result = [[NSString alloc] initWithUTF8String: str]; + + return result; +} + + +@interface MySampleBufferDelegate : NSObject + @property struct SDL_PrivateVideoCaptureData *hidden; + - (void) set: (struct SDL_PrivateVideoCaptureData *) val; +@end + +@implementation MySampleBufferDelegate + + - (void) set: (struct SDL_PrivateVideoCaptureData *) val { + _hidden = val; + } + + - (void) captureOutput:(AVCaptureOutput *)output + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *) connection { + CFRetain(sampleBuffer); + CMSimpleQueueEnqueue(_hidden->frame_queue, sampleBuffer); + } + + - (void)captureOutput:(AVCaptureOutput *)output + didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + SDL_Log("Drop frame.."); + } +@end + +int +OpenDevice(SDL_VideoCaptureDevice *_this) +{ + _this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData)); + if (_this->hidden == NULL) { + SDL_OutOfMemory(); + goto error; + } + + return 0; + +error: + return -1; +} + +void +CloseDevice(SDL_VideoCaptureDevice *_this) +{ + if (!_this) { + return; + } + + if (_this->hidden) { + AVCaptureSession *session = _this->hidden->session; + + if (session) { + AVCaptureInput *input; + AVCaptureVideoDataOutput *output; + input = [session.inputs objectAtIndex:0]; + [session removeInput:input]; + output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]; + [session removeOutput:output]; + // TODO more cleanup ? + } + + if (_this->hidden->frame_queue) { + CFRelease(_this->hidden->frame_queue); + } + + SDL_free(_this->hidden); + _this->hidden = NULL; + } +} + +int +InitDevice(SDL_VideoCaptureDevice *_this) +{ + NSString *fmt = sdlformat_to_nsfourcc(_this->spec.format); + int w = _this->spec.width; + int h = _this->spec.height; + + NSError *error = nil; + AVCaptureDevice *device = nil; + AVCaptureDeviceInput *input = nil; + AVCaptureVideoDataOutput *output = nil; + + AVCaptureDeviceFormat *spec_format = nil; + +#ifdef __MACOS__ + if (@available(macOS 10.15, *)) { + /* good. */ + } else { + return -1; + } +#endif + + device = get_device_by_name(_this->dev_name); + if (!device) { + goto error; + } + + _this->hidden->session = [[AVCaptureSession alloc] init]; + if (_this->hidden->session == nil) { + goto error; + } + + [_this->hidden->session setSessionPreset:AVCaptureSessionPresetHigh]; + + // Pick format that matches the spec + { + NSArray *formats = [device formats]; + for (AVCaptureDeviceFormat *format in formats) { + CMFormatDescriptionRef formatDescription = [format formatDescription]; + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + NSString *str = fourcc_to_nstring(mediaSubType); + if (str == fmt) { + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription); + if (dim.width == w && dim.height == h) { + spec_format = format; + break; + } + } + } + } + + if (spec_format == nil) { + SDL_SetError("format not found"); + goto error; + } + + // Set format + if ([device lockForConfiguration:NULL] == YES) { + device.activeFormat = spec_format; + [device unlockForConfiguration]; + } else { + SDL_SetError("Cannot lockForConfiguration"); + goto error; + } + + // Input + input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + if (!input) { + SDL_SetError("Cannot create AVCaptureDeviceInput"); + goto error; + } + + // Output + output = [[AVCaptureVideoDataOutput alloc] init]; + +#ifdef __MACOS__ + // FIXME this now fail on ios ... but not using anything works... + + // Specify the pixel format + output.videoSettings = + [NSDictionary dictionaryWithObject: + [NSNumber numberWithInt:kCVPixelFormatType_422YpCbCr8] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; +#endif + + _this->hidden->delegate = [[MySampleBufferDelegate alloc] init]; + [_this->hidden->delegate set:_this->hidden]; + + + CMSimpleQueueCreate(kCFAllocatorDefault, 30 /* buffers */, &_this->hidden->frame_queue); + if (_this->hidden->frame_queue == nil) { + goto error; + } + + _this->hidden->queue = dispatch_queue_create("my_queue", NULL); + [output setSampleBufferDelegate:_this->hidden->delegate queue:_this->hidden->queue]; + + + if ([_this->hidden->session canAddInput:input] ){ + [_this->hidden->session addInput:input]; + } else { + SDL_SetError("Cannot add AVCaptureDeviceInput"); + goto error; + } + + if ([_this->hidden->session canAddOutput:output] ){ + [_this->hidden->session addOutput:output]; + } else { + SDL_SetError("Cannot add AVCaptureVideoDataOutput"); + goto error; + } + + [_this->hidden->session commitConfiguration]; + + return 0; + +error: + return -1; +} + +int +GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) +{ + if (spec) { + *spec = _this->spec; + return 0; + } + return -1; +} + +int +StartCapture(SDL_VideoCaptureDevice *_this) +{ + [_this->hidden->session startRunning]; + return 0; +} + +int +StopCapture(SDL_VideoCaptureDevice *_this) +{ + [_this->hidden->session stopRunning]; + return 0; +} + +int +AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + if (CMSimpleQueueGetCount(_this->hidden->frame_queue) > 0) { + int i, numPlanes, planar; + CMSampleBufferRef sampleBuffer; + CVImageBufferRef image; + + sampleBuffer = (CMSampleBufferRef)CMSimpleQueueDequeue(_this->hidden->frame_queue); + frame->internal = (void *) sampleBuffer; + frame->timestampNS = SDL_GetTicksNS(); + + i = 0; + image = CMSampleBufferGetImageBuffer(sampleBuffer); + numPlanes = CVPixelBufferGetPlaneCount(image); + planar = CVPixelBufferIsPlanar(image); + +#if 0 + int w = CVPixelBufferGetWidth(image); + int h = CVPixelBufferGetHeight(image); + int sz = CVPixelBufferGetDataSize(image); + int pitch = CVPixelBufferGetBytesPerRow(image); + SDL_Log("buffer planar=%d count:%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch); +#endif + + CVPixelBufferLockBaseAddress(image, 0); + + if (planar == 0 && numPlanes == 0) { + frame->pitch[0] = CVPixelBufferGetBytesPerRow(image); + frame->data[0] = CVPixelBufferGetBaseAddress(image); + frame->num_planes = 1; + } else { + for (i = 0; i < numPlanes && i < 3; i++) { + int rowStride = 0; + uint8_t *data = NULL; + frame->num_planes += 1; + + rowStride = CVPixelBufferGetBytesPerRowOfPlane(image, i); + data = CVPixelBufferGetBaseAddressOfPlane(image, i); + frame->data[i] = data; + frame->pitch[i] = rowStride; + } + } + + /* Unlocked when frame is released */ + + } else { + // no frame + SDL_Delay(20); // TODO fix some delay + } + return 0; +} + +int +ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + if (frame->internal){ + CMSampleBufferRef sampleBuffer = (CMSampleBufferRef) frame->internal; + + CVImageBufferRef image = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferUnlockBaseAddress(image, 0); + + CFRelease(sampleBuffer); + } + return 0; +} + +int +GetNumFormats(SDL_VideoCaptureDevice *_this) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + // LIST FORMATS + NSMutableOrderedSet *array_formats = [NSMutableOrderedSet new]; + NSArray *formats = [device formats]; + for (AVCaptureDeviceFormat *format in formats) { + // NSLog(@"%@", formats); + CMFormatDescriptionRef formatDescription = [format formatDescription]; + //NSLog(@"%@", formatDescription); + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + NSString *str = fourcc_to_nstring(mediaSubType); + [array_formats addObject:str]; + } + return [array_formats count]; + } + return 0; +} + +int +GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + // LIST FORMATS + NSMutableOrderedSet *array_formats = [NSMutableOrderedSet new]; + NSArray *formats = [device formats]; + NSString *str; + + for (AVCaptureDeviceFormat *f in formats) { + FourCharCode mediaSubType; + CMFormatDescriptionRef formatDescription; + + formatDescription = [f formatDescription]; + mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + str = fourcc_to_nstring(mediaSubType); + [array_formats addObject:str]; + } + + str = array_formats[index]; + *format = nsfourcc_to_sdlformat(str); + + return 0; + } + return -1; +} + +int +GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + NSString *fmt = sdlformat_to_nsfourcc(format); + int count = 0; + + NSArray *formats = [device formats]; + for (AVCaptureDeviceFormat *f in formats) { + CMFormatDescriptionRef formatDescription = [f formatDescription]; + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + NSString *str = fourcc_to_nstring(mediaSubType); + + if (str == fmt) { + count += 1; + } + } + return count; + } + return 0; +} + +int +GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) +{ + AVCaptureDevice *device = get_device_by_name(_this->dev_name); + if (device) { + NSString *fmt = sdlformat_to_nsfourcc(format); + int count = 0; + + NSArray *formats = [device formats]; + for (AVCaptureDeviceFormat *f in formats) { + CMFormatDescriptionRef formatDescription = [f formatDescription]; + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + NSString *str = fourcc_to_nstring(mediaSubType); + + if (str == fmt) { + if (index == count) { + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription); + *width = dim.width; + *height = dim.height; + return 0; + } + count += 1; + } + } + } + return -1; +} + +int +GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) +{ + int index = instance_id - 1; + NSArray *devices = discover_devices(); + if (index < [devices count]) { + AVCaptureDevice *device = devices[index]; + NSString *cameraID = [device localizedName]; + const char *str = [cameraID UTF8String]; + SDL_snprintf(buf, size, "%s", str); + return 0; + } + return -1; +} + +static int +GetNumDevices(void) +{ + NSArray *devices = discover_devices(); + return [devices count]; +} + +SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) +{ + /* hard-coded list of ID */ + int i; + int num = GetNumDevices(); + SDL_VideoCaptureDeviceID *ret; + + ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret)); + + if (ret == NULL) { + SDL_OutOfMemory(); + *count = 0; + return NULL; + } + + for (i = 0; i < num; i++) { + ret[i] = i + 1; + } + ret[num] = 0; + *count = num; + return ret; +} + +int SDL_SYS_VideoCaptureInit(void) +{ + return 0; +} + +int SDL_SYS_VideoCaptureQuit(void) +{ + return 0; +} + + + + +#endif /* HAVE_COREMEDIA */ + +#endif /* SDL_VIDEO_CAPTURE */ + diff --git a/src/video/windows/SDL_windowsshape.h b/src/video/SDL_video_capture_c.h similarity index 64% rename from src/video/windows/SDL_windowsshape.h rename to src/video/SDL_video_capture_c.h index 074f7edf..d7f1aa17 100644 --- a/src/video/windows/SDL_windowsshape.h +++ b/src/video/SDL_video_capture_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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,21 +18,16 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ +#include "../SDL_internal.h" +#include "../../include/SDL3/SDL_video_capture.h" -#include "SDL_internal.h" +#ifndef SDL_video_capture_c_h_ +#define SDL_video_capture_c_h_ -#ifndef SDL_windowsshape_h_ -#define SDL_windowsshape_h_ +/* Initialize the video_capture subsystem */ +int SDL_VideoCaptureInit(void); -#include "../SDL_sysvideo.h" -#include "../SDL_shape_internals.h" +/* Shutdown the video_capture subsystem */ +void SDL_QuitVideoCapture(void); -typedef struct -{ - SDL_ShapeTree *mask_tree; -} SDL_ShapeData; - -extern SDL_WindowShaper *Win32_CreateShaper(SDL_Window *window); -extern int Win32_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode); - -#endif /* SDL_windowsshape_h_ */ +#endif /* SDL_video_capture_c_h_ */ diff --git a/src/video/SDL_video_capture_v4l2.c b/src/video/SDL_video_capture_v4l2.c new file mode 100644 index 00000000..3edc2632 --- /dev/null +++ b/src/video/SDL_video_capture_v4l2.c @@ -0,0 +1,1217 @@ +/* + Simple DirectMedia Layer + 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 + 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_CAPTURE + +#include "SDL3/SDL.h" +#include "SDL3/SDL_video_capture.h" +#include "SDL_sysvideocapture.h" +#include "SDL_video_capture_c.h" +#include "SDL_pixels_c.h" +#include "../thread/SDL_systhread.h" +#include "../../core/linux/SDL_evdev_capabilities.h" +#include "../../core/linux/SDL_udev.h" +#include /* INT_MAX */ + +#define DEBUG_VIDEO_CAPTURE_CAPTURE 0 + +#if defined(__linux__) && !defined(__ANDROID__) + + +#define MAX_CAPTURE_DEVICES 128 /* It's doubtful someone has more than that */ + +static int MaybeAddDevice(const char *path); +#ifdef SDL_USE_LIBUDEV +static int MaybeRemoveDevice(const char *path); +static void capture_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath); +#endif /* SDL_USE_LIBUDEV */ + +/* + * List of available capture devices. + */ +typedef struct SDL_capturelist_item +{ + char *fname; /* Dev path name (like /dev/video0) */ + char *bus_info; /* don't add two paths with same bus_info (eg /dev/video0 and /dev/video1 */ + SDL_VideoCaptureDeviceID instance_id; + SDL_VideoCaptureDevice *device; /* Associated device */ + struct SDL_capturelist_item *next; +} SDL_capturelist_item; + +static SDL_capturelist_item *SDL_capturelist = NULL; +static SDL_capturelist_item *SDL_capturelist_tail = NULL; +static int num_video_captures = 0; + + + +enum io_method { + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR +}; + +struct buffer { + void *start; + size_t length; + int available; /* Is available in userspace */ +}; + +struct SDL_PrivateVideoCaptureData +{ + int fd; + enum io_method io; + int nb_buffers; + struct buffer *buffers; + int first_start; + int driver_pitch; +}; + +#include +#include +#include /* low-level i/o */ +#include +#include +#include +#include + +static int +xioctl(int fh, int request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (r == -1 && errno == EINTR); + + return r; +} + +/* -1:error 1:frame 0:no frame*/ +static int +acquire_frame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + struct v4l2_buffer buf; + int i; + + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + size_t size = _this->hidden->buffers[0].length; + + switch (io) { + case IO_METHOD_READ: + if (read(fd, _this->hidden->buffers[0].start, size) == -1) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + return SDL_SetError("read"); + } + } + + frame->num_planes = 1; + frame->data[0] = _this->hidden->buffers[0].start; + frame->pitch[0] = _this->hidden->driver_pitch; + break; + + case IO_METHOD_MMAP: + SDL_zero(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + return SDL_SetError("VIDIOC_DQBUF: %d", errno); + } + } + + if ((int)buf.index < 0 || (int)buf.index >= _this->hidden->nb_buffers) { + return SDL_SetError("invalid buffer index"); + } + + frame->num_planes = 1; + frame->data[0] = _this->hidden->buffers[buf.index].start; + frame->pitch[0] = _this->hidden->driver_pitch; + _this->hidden->buffers[buf.index].available = 1; + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("debug mmap: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]); +#endif + break; + + case IO_METHOD_USERPTR: + SDL_zero(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + + if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + return SDL_SetError("VIDIOC_DQBUF"); + } + } + + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + if (buf.m.userptr == (unsigned long)_this->hidden->buffers[i].start && buf.length == size) { + break; + } + } + + if (i >= _this->hidden->nb_buffers) { + return SDL_SetError("invalid buffer index"); + } + + frame->num_planes = 1; + frame->data[0] = (void*)buf.m.userptr; + frame->pitch[0] = _this->hidden->driver_pitch; + _this->hidden->buffers[i].available = 1; +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("debug userptr: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]); +#endif + break; + } + + return 1; +} + + +int +ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + struct v4l2_buffer buf; + int i; + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + if (frame->num_planes && frame->data[0] == _this->hidden->buffers[i].start) { + break; + } + } + + if (i >= _this->hidden->nb_buffers) { + return SDL_SetError("invalid buffer index"); + } + + switch (io) { + case IO_METHOD_READ: + break; + + case IO_METHOD_MMAP: + SDL_zero(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + return SDL_SetError("VIDIOC_QBUF"); + } + _this->hidden->buffers[i].available = 0; + break; + + case IO_METHOD_USERPTR: + SDL_zero(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)frame->data[0]; + buf.length = (int) _this->hidden->buffers[i].length; + + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + return SDL_SetError("VIDIOC_QBUF"); + } + _this->hidden->buffers[i].available = 0; + break; + } + + return 0; +} + + +int +AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + fd_set fds; + struct timeval tv; + int ret; + + int fd = _this->hidden->fd; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + /* Timeout. */ + tv.tv_sec = 0; + tv.tv_usec = 300 * 1000; + + ret = select(fd + 1, &fds, NULL, NULL, &tv); + + if (ret == -1) { + if (errno == EINTR) { +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("continue .."); +#endif + return 0; + } + return SDL_SetError("select"); + } + + if (ret == 0) { + /* Timeout. Not an error */ + SDL_SetError("timeout select"); + return 0; + } + + ret = acquire_frame(_this, frame); + if (ret < 0) { + return -1; + } + + if (ret == 1){ + frame->timestampNS = SDL_GetTicksNS(); + } else if (ret == 0) { +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("No frame continue: %s", SDL_GetError()); +#endif + } + + /* EAGAIN - continue select loop. */ + return 0; +} + + +int +StopCapture(SDL_VideoCaptureDevice *_this) +{ + enum v4l2_buf_type type; + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + + switch (io) { + case IO_METHOD_READ: + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { + return SDL_SetError("VIDIOC_STREAMOFF"); + } + break; + } + + return 0; +} + +static int +enqueue_buffers(SDL_VideoCaptureDevice *_this) +{ + int i; + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + switch (io) { + case IO_METHOD_READ: + break; + + case IO_METHOD_MMAP: + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + if (_this->hidden->buffers[i].available == 0) { + struct v4l2_buffer buf; + + SDL_zero(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + return SDL_SetError("VIDIOC_QBUF"); + } + } + } + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + if (_this->hidden->buffers[i].available == 0) { + struct v4l2_buffer buf; + + SDL_zero(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)_this->hidden->buffers[i].start; + buf.length = (int) _this->hidden->buffers[i].length; + + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + return SDL_SetError("VIDIOC_QBUF"); + } + } + } + break; + } + return 0; +} + +static int +pre_enqueue_buffers(SDL_VideoCaptureDevice *_this) +{ + struct v4l2_requestbuffers req; + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + + switch (io) { + case IO_METHOD_READ: + break; + + case IO_METHOD_MMAP: + { + SDL_zero(req); + req.count = _this->hidden->nb_buffers; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + if (errno == EINVAL) { + return SDL_SetError("Does not support memory mapping"); + } else { + return SDL_SetError("VIDIOC_REQBUFS"); + } + } + + if (req.count < 2) { + return SDL_SetError("Insufficient buffer memory"); + } + + _this->hidden->nb_buffers = req.count; + } + break; + + case IO_METHOD_USERPTR: + { + SDL_zero(req); + req.count = _this->hidden->nb_buffers; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + if (errno == EINVAL) { + return SDL_SetError("Does not support user pointer i/o"); + } else { + return SDL_SetError("VIDIOC_REQBUFS"); + } + } + } + break; + } + return 0; +} + +int +StartCapture(SDL_VideoCaptureDevice *_this) +{ + enum v4l2_buf_type type; + + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + + + if (_this->hidden->first_start == 0) { + _this->hidden->first_start = 1; + } else { + int old = _this->hidden->nb_buffers; + // TODO mmap; doesn't work with stop->start +#if 1 + /* Can change nb_buffers for mmap */ + if (pre_enqueue_buffers(_this) < 0) { + return -1; + } + if (old != _this->hidden->nb_buffers) { + SDL_SetError("different nb of buffers requested"); + return -1; + } +#endif + _this->hidden->first_start = 1; + } + + if (enqueue_buffers(_this) < 0) { + return -1; + } + + switch (io) { + case IO_METHOD_READ: + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { + return SDL_SetError("VIDIOC_STREAMON"); + } + break; + } + + return 0; +} + +static int alloc_buffer_read(SDL_VideoCaptureDevice *_this, size_t buffer_size) +{ + _this->hidden->buffers[0].length = buffer_size; + _this->hidden->buffers[0].start = SDL_calloc(1, buffer_size); + + if (!_this->hidden->buffers[0].start) { + return SDL_OutOfMemory(); + } + return 0; +} + +static int +alloc_buffer_mmap(SDL_VideoCaptureDevice *_this) +{ + int fd = _this->hidden->fd; + int i; + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + struct v4l2_buffer buf; + + SDL_zero(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { + return SDL_SetError("VIDIOC_QUERYBUF"); + } + + _this->hidden->buffers[i].length = buf.length; + _this->hidden->buffers[i].start = + mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + fd, buf.m.offset); + + if (MAP_FAILED == _this->hidden->buffers[i].start) { + return SDL_SetError("mmap"); + } + } + return 0; +} + +static int +alloc_buffer_userp(SDL_VideoCaptureDevice *_this, size_t buffer_size) +{ + int i; + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + _this->hidden->buffers[i].length = buffer_size; + _this->hidden->buffers[i].start = SDL_calloc(1, buffer_size); + + if (!_this->hidden->buffers[i].start) { + return SDL_OutOfMemory(); + } + } + return 0; +} + +static Uint32 +format_v4l2_2_sdl(Uint32 fmt) +{ + switch (fmt) { +#define CASE(x, y) case x: return y + CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); + CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN); +#undef CASE + default: + SDL_Log("Unknown format V4L2_PIX_FORMAT '%d'", fmt); + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +static Uint32 +format_sdl_2_v4l2(Uint32 fmt) +{ + switch (fmt) { +#define CASE(y, x) case x: return y + CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); + CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN); +#undef CASE + default: + return 0; + } +} + +int +GetNumFormats(SDL_VideoCaptureDevice *_this) +{ + int fd = _this->hidden->fd; + int i = 0; + struct v4l2_fmtdesc fmtdesc; + + SDL_zero(fmtdesc); + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + while (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) { + fmtdesc.index++; + i++; + } + return i; +} + +int +GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) +{ + int fd = _this->hidden->fd; + struct v4l2_fmtdesc fmtdesc; + + SDL_zero(fmtdesc); + fmtdesc.index = index; + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) { + *format = format_v4l2_2_sdl(fmtdesc.pixelformat); + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + if (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) { + SDL_Log("%s format emulated", SDL_GetPixelFormatName(*format)); + } + if (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) { + SDL_Log("%s format compressed", SDL_GetPixelFormatName(*format)); + } +#endif + return 0; + } + + return -1; +} + +int +GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) +{ + int fd = _this->hidden->fd; + int i = 0; + struct v4l2_frmsizeenum frmsizeenum; + + SDL_zero(frmsizeenum); + frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + frmsizeenum.pixel_format = format_sdl_2_v4l2(format); + while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { + frmsizeenum.index++; + if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + i++; + } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + i += (1 + (frmsizeenum.stepwise.max_width - frmsizeenum.stepwise.min_width) / frmsizeenum.stepwise.step_width) + * (1 + (frmsizeenum.stepwise.max_height - frmsizeenum.stepwise.min_height) / frmsizeenum.stepwise.step_height); + } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { + SDL_SetError("V4L2_FRMSIZE_TYPE_CONTINUOUS not handled"); + } + } + return i; +} + +int +GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) +{ + int fd = _this->hidden->fd; + struct v4l2_frmsizeenum frmsizeenum; + int i = 0; + + SDL_zero(frmsizeenum); + frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + frmsizeenum.pixel_format = format_sdl_2_v4l2(format); + while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { + frmsizeenum.index++; + + if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + if (i == index) { + *width = frmsizeenum.discrete.width; + *height = frmsizeenum.discrete.height; + return 0; + } + i++; + } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + unsigned int w; + for (w = frmsizeenum.stepwise.min_width; w <= frmsizeenum.stepwise.max_width; w += frmsizeenum.stepwise.step_width) { + unsigned int h; + for (h = frmsizeenum.stepwise.min_height; h <= frmsizeenum.stepwise.max_height; h += frmsizeenum.stepwise.step_height) { + if (i == index) { + *width = h; + *height = w; + return 0; + } + i++; + } + } + } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { + } + } + + return -1; +} + + + +#if DEBUG_VIDEO_CAPTURE_CAPTURE +static void +dbg_v4l2_pixelformat(const char *str, int f) { + SDL_Log("%s V4L2_format=%d %c%c%c%c", str, f, + (f >> 0) & 0xff, + (f >> 8) & 0xff, + (f >> 16) & 0xff, + (f >> 24) & 0xff); +} +#endif + +int +GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) +{ + struct v4l2_format fmt; + int fd = _this->hidden->fd; + unsigned int min; + + SDL_zero(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Preserve original settings as set by v4l2-ctl for example */ + if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { + return SDL_SetError("Error VIDIOC_G_FMT"); + } + + /* Buggy driver paranoia. */ + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) { + fmt.fmt.pix.bytesperline = min; + } + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) { + fmt.fmt.pix.sizeimage = min; + } + + //spec->width = fmt.fmt.pix.width; + //spec->height = fmt.fmt.pix.height; + _this->hidden->driver_pitch = fmt.fmt.pix.bytesperline; + //spec->format = format_v4l2_2_sdl(fmt.fmt.pix.pixelformat); + + return 0; +} + +int +InitDevice(SDL_VideoCaptureDevice *_this) +{ + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + + int fd = _this->hidden->fd; + enum io_method io = _this->hidden->io; + int ret = -1; + + /* Select video input, video standard and tune here. */ + SDL_zero(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } + + + { + struct v4l2_format fmt; + SDL_zero(fmt); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = _this->spec.width; + fmt.fmt.pix.height = _this->spec.height; + + + fmt.fmt.pix.pixelformat = format_sdl_2_v4l2(_this->spec.format); + // fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("set SDL format %s", SDL_GetPixelFormatName(_this->spec.format)); + dbg_v4l2_pixelformat("set format", fmt.fmt.pix.pixelformat); +#endif + + if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { + return SDL_SetError("Error VIDIOC_S_FMT"); + } + } + + GetDeviceSpec(_this, &_this->spec); + + if (pre_enqueue_buffers(_this) < 0) { + return -1; + } + + { + _this->hidden->buffers = SDL_calloc(_this->hidden->nb_buffers, sizeof(*_this->hidden->buffers)); + if (!_this->hidden->buffers) { + return SDL_OutOfMemory(); + } + } + + { + size_t size, pitch; + SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE); + + switch (io) { + case IO_METHOD_READ: + ret = alloc_buffer_read(_this, size); + break; + + case IO_METHOD_MMAP: + ret = alloc_buffer_mmap(_this); + break; + + case IO_METHOD_USERPTR: + ret = alloc_buffer_userp(_this, size); + break; + } + } + + if (ret < 0) { + return -1; + } + + return 0; +} + +void +CloseDevice(SDL_VideoCaptureDevice *_this) +{ + if (!_this) { + return; + } + + if (_this->hidden) { + if (_this->hidden->buffers) { + int i; + enum io_method io = _this->hidden->io; + + switch (io) { + case IO_METHOD_READ: + SDL_free(_this->hidden->buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + if (munmap(_this->hidden->buffers[i].start, _this->hidden->buffers[i].length) == -1) { + SDL_SetError("munmap"); + } + } + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < _this->hidden->nb_buffers; ++i) { + SDL_free(_this->hidden->buffers[i].start); + } + break; + } + + SDL_free(_this->hidden->buffers); + } + + if (_this->hidden->fd != -1) { + if (close(_this->hidden->fd)) { + SDL_SetError("close video capture device"); + } + } + SDL_free(_this->hidden); + + _this->hidden = NULL; + } +} + + +int +OpenDevice(SDL_VideoCaptureDevice *_this) +{ + struct stat st; + struct v4l2_capability cap; + int fd; + enum io_method io; + + _this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData)); + if (_this->hidden == NULL) { + SDL_OutOfMemory(); + return -1; + } + + _this->hidden->fd = -1; + + if (stat(_this->dev_name, &st) == -1) { + SDL_SetError("Cannot identify '%s': %d, %s", _this->dev_name, errno, strerror(errno)); + return -1; + } + + if (!S_ISCHR(st.st_mode)) { + SDL_SetError("%s is no device", _this->dev_name); + return -1; + } + + fd = open(_this->dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); + if (fd == -1) { + SDL_SetError("Cannot open '%s': %d, %s", _this->dev_name, errno, strerror(errno)); + return -1; + } + + _this->hidden->fd = fd; + _this->hidden->io = IO_METHOD_MMAP; +// _this->hidden->io = IO_METHOD_USERPTR; +// _this->hidden->io = IO_METHOD_READ; +// + if (_this->hidden->io == IO_METHOD_READ) { + _this->hidden->nb_buffers = 1; + } else { + _this->hidden->nb_buffers = 8; /* Number of image as internal buffer, */ + } + io = _this->hidden->io; + + if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { + if (errno == EINVAL) { + return SDL_SetError("%s is no V4L2 device", _this->dev_name); + } else { + return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", errno, _this->dev_name); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + return SDL_SetError("%s is no video capture device", _this->dev_name); + } + +#if 0 + if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + SDL_Log("%s is video capture device - single plane", _this->dev_name); + } + if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) { + SDL_Log("%s is video capture device - multiple planes", _this->dev_name); + } +#endif + + switch (io) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + return SDL_SetError("%s does not support read i/o", _this->dev_name); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + return SDL_SetError("%s does not support streaming i/o", _this->dev_name); + } + break; + } + + + + + return 0; +} + +int +GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) +{ + SDL_capturelist_item *item; + for (item = SDL_capturelist; item; item = item->next) { + if (item->instance_id == instance_id) { + SDL_snprintf(buf, size, "%s", item->fname); + return 0; + } + } + + /* unknown instance_id */ + return -1; +} + + +SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) +{ + /* real list of ID */ + int i = 0; + int num = num_video_captures; + SDL_VideoCaptureDeviceID *ret; + SDL_capturelist_item *item; + + ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret)); + + if (ret == NULL) { + SDL_OutOfMemory(); + *count = 0; + return NULL; + } + + for (item = SDL_capturelist; item; item = item->next) { + ret[i] = item->instance_id; + i++; + } + + ret[num] = 0; + *count = num; + return ret; +} + + +/* + * Initializes the subsystem by finding available devices. + */ +int SDL_SYS_VideoCaptureInit(void) +{ + const char pattern[] = "/dev/video%d"; + char path[PATH_MAX]; + int i, j; + + /* + * Limit amount of checks to MAX_CAPTURE_DEVICES since we may or may not have + * permission to some or all devices. + */ + i = 0; + for (j = 0; j < MAX_CAPTURE_DEVICES; ++j) { + (void)SDL_snprintf(path, PATH_MAX, pattern, i++); + if (MaybeAddDevice(path) == -2) { + break; + } + } + +#ifdef SDL_USE_LIBUDEV + if (SDL_UDEV_Init() < 0) { + return SDL_SetError("Could not initialize UDEV"); + } + + if (SDL_UDEV_AddCallback(capture_udev_callback) < 0) { + SDL_UDEV_Quit(); + return SDL_SetError("Could not setup Video Capture <-> udev callback"); + } + + /* Force a scan to build the initial device list */ + SDL_UDEV_Scan(); +#endif /* SDL_USE_LIBUDEV */ + + return num_video_captures; +} + + +int SDL_SYS_VideoCaptureQuit(void) +{ + SDL_capturelist_item *item; + for (item = SDL_capturelist; item; ) { + SDL_capturelist_item *tmp = item->next; + + SDL_free(item->fname); + SDL_free(item->bus_info); + SDL_free(item); + item = tmp; + } + + num_video_captures = 0; + SDL_capturelist = NULL; + SDL_capturelist_tail = NULL; + + return SDL_FALSE; +} + +#ifdef SDL_USE_LIBUDEV +static void capture_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +{ + if (!devpath || !(udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) { + return; + } + + switch (udev_type) { + case SDL_UDEV_DEVICEADDED: + MaybeAddDevice(devpath); + break; + + case SDL_UDEV_DEVICEREMOVED: + MaybeRemoveDevice(devpath); + break; + + default: + break; + } +} +#endif /* SDL_USE_LIBUDEV */ + +static SDL_bool DeviceExists(const char *path, const char *bus_info) { + SDL_capturelist_item *item; + + for (item = SDL_capturelist; item; item = item->next) { + /* found same dev name */ + if (SDL_strcmp(path, item->fname) == 0) { + return SDL_TRUE; + } + /* found same bus_info */ + if (SDL_strcmp(bus_info, item->bus_info) == 0) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static int MaybeAddDevice(const char *path) +{ + char *bus_info = NULL; + struct v4l2_capability vcap; + int err; + int fd; + SDL_capturelist_item *item; + + if (!path) { + return -1; + } + + fd = open(path, O_RDWR); + if (fd < 0) { + return -2; /* stop iterating /dev/video%d */ + } + err = ioctl(fd, VIDIOC_QUERYCAP, &vcap); + close(fd); + if (err) { + return -1; + } + + bus_info = SDL_strdup((char *)vcap.bus_info); + + if (DeviceExists(path, bus_info)) { + SDL_free(bus_info); + return 0; + } + + + /* Add new item */ + item = (SDL_capturelist_item *)SDL_calloc(1, sizeof(SDL_capturelist_item)); + if (!item) { + SDL_free(bus_info); + return -1; + } + + item->fname = SDL_strdup(path); + if (!item->fname) { + SDL_free(item); + SDL_free(bus_info); + return -1; + } + + item->fname = SDL_strdup(path); + item->bus_info = bus_info; + item->instance_id = SDL_GetNextObjectID(); + + + if (!SDL_capturelist_tail) { + SDL_capturelist = SDL_capturelist_tail = item; + } else { + SDL_capturelist_tail->next = item; + SDL_capturelist_tail = item; + } + + ++num_video_captures; + + /* !!! TODO: Send a add event? */ +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("Added video capture ID: %d %s (%s) (total: %d)", item->instance_id, path, bus_info, num_video_captures); +#endif + return 0; +} + +#ifdef SDL_USE_LIBUDEV +static int MaybeRemoveDevice(const char *path) +{ + + SDL_capturelist_item *item; + SDL_capturelist_item *prev = NULL; +#if DEBUG_VIDEO_CAPTURE_CAPTURE + SDL_Log("Remove video capture %s", path); +#endif + if (!path) { + return -1; + } + + for (item = SDL_capturelist; item; item = item->next) { + /* found it, remove it. */ + if (SDL_strcmp(path, item->fname) == 0) { + if (prev) { + prev->next = item->next; + } else { + SDL_assert(SDL_capturelist == item); + SDL_capturelist = item->next; + } + if (item == SDL_capturelist_tail) { + SDL_capturelist_tail = prev; + } + + /* Need to decrement the count */ + --num_video_captures; + /* !!! TODO: Send a remove event? */ + + SDL_free(item->fname); + SDL_free(item->bus_info); + SDL_free(item); + return 0; + } + prev = item; + } + return 0; +} +#endif /* SDL_USE_LIBUDEV */ + + + +#endif + +#endif /* SDL_VIDEO_CAPTURE */ diff --git a/src/video/SDL_video_unsupported.c b/src/video/SDL_video_unsupported.c new file mode 100644 index 00000000..0731f038 --- /dev/null +++ b/src/video/SDL_video_unsupported.c @@ -0,0 +1,91 @@ +/* + Simple DirectMedia Layer + 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 + 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_VIDEO_DRIVER_WINDOWS + +DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *outputIndex); +SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *outputIndex) +{ + (void)displayID; + (void)adapterIndex; + (void)outputIndex; + SDL_Unsupported(); + return SDL_FALSE; +} + +DECLSPEC int SDLCALL SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID); +int SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID) +{ + (void)displayID; + return SDL_Unsupported(); +} + +#endif + +#ifndef __GDK__ + +DECLSPEC int SDLCALL SDL_GDKGetTaskQueue(void *outTaskQueue); +int SDL_GDKGetTaskQueue(void *outTaskQueue) +{ + (void)outTaskQueue; + return SDL_Unsupported(); +} + +#endif + +#ifndef SDL_VIDEO_DRIVER_UIKIT + +DECLSPEC void SDLCALL SDL_OnApplicationDidChangeStatusBarOrientation(void); +void SDL_OnApplicationDidChangeStatusBarOrientation(void) +{ + SDL_Unsupported(); +} + +#endif + +#ifndef SDL_VIDEO_DRIVER_UIKIT + +DECLSPEC int SDLCALL SDL_iPhoneSetAnimationCallback(SDL_Window *window, int interval, void (*callback)(void *), void *callbackParam); +int SDL_iPhoneSetAnimationCallback(SDL_Window *window, int interval, void (*callback)(void *), void *callbackParam) +{ + (void)window; + (void)interval; + (void)callback; + (void)callbackParam; + return SDL_Unsupported(); +} + +DECLSPEC void SDLCALL SDL_iPhoneSetEventPump(SDL_bool enabled); +void SDL_iPhoneSetEventPump(SDL_bool enabled) +{ + (void)enabled; + SDL_Unsupported(); +} +#endif + +#if defined(__XBOXONE__) || defined(__XBOXSERIES__) +int SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID) +{ + (void)displayID; + return SDL_Unsupported(); +} +#endif diff --git a/src/video/SDL_vulkan_internal.h b/src/video/SDL_vulkan_internal.h index 5242aece..5edf7c71 100644 --- a/src/video/SDL_vulkan_internal.h +++ b/src/video/SDL_vulkan_internal.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,19 +63,13 @@ extern VkExtensionProperties *SDL_Vulkan_CreateInstanceExtensionsList( PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties, Uint32 *extensionCount); /* free returned list with SDL_free */ -/* Implements functionality of SDL_Vulkan_GetInstanceExtensions for a list of - * names passed in nameCount and names. */ -extern SDL_bool SDL_Vulkan_GetInstanceExtensions_Helper(unsigned *userCount, - const char **userNames, - unsigned nameCount, - const char *const *names); - /* Create a surface directly from a display connected to a physical device * using the DisplayKHR extension. * This needs to be passed an instance that was created with the VK_KHR_DISPLAY_EXTENSION_NAME * extension. */ extern SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #else diff --git a/src/video/SDL_vulkan_utils.c b/src/video/SDL_vulkan_utils.c index 2993fc79..c8452aa6 100644 --- a/src/video/SDL_vulkan_utils.c +++ b/src/video/SDL_vulkan_utils.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -148,8 +148,7 @@ VkExtensionProperties *SDL_Vulkan_CreateInstanceExtensionsList( retval = SDL_calloc(count, sizeof(VkExtensionProperties)); } - if (retval == NULL) { - SDL_OutOfMemory(); + if (!retval) { return NULL; } @@ -167,27 +166,6 @@ VkExtensionProperties *SDL_Vulkan_CreateInstanceExtensionsList( return retval; } -SDL_bool SDL_Vulkan_GetInstanceExtensions_Helper(unsigned *userCount, - const char **userNames, - unsigned nameCount, - const char *const *names) -{ - if (userNames) { - unsigned i; - - if (*userCount < nameCount) { - SDL_SetError("Output array for SDL_Vulkan_GetInstanceExtensions needs to be at least %d big", nameCount); - return SDL_FALSE; - } - - for (i = 0; i < nameCount; i++) { - userNames[i] = names[i]; - } - } - *userCount = nameCount; - return SDL_TRUE; -} - /* Alpha modes, in order of preference */ static const VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = { VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR, @@ -198,6 +176,7 @@ static const VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = { SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = @@ -231,7 +210,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, goto error; } chosenDisplayId = SDL_getenv("SDL_VULKAN_DISPLAY"); - if (chosenDisplayId != NULL) { + if (chosenDisplayId) { displayId = SDL_atoi(chosenDisplayId); } @@ -248,8 +227,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, } physicalDevices = SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount); - if (physicalDevices == NULL) { - SDL_OutOfMemory(); + if (!physicalDevices) { goto error; } @@ -291,8 +269,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, } displayProperties = SDL_malloc(sizeof(VkDisplayPropertiesKHR) * displayPropertiesCount); - if (displayProperties == NULL) { - SDL_OutOfMemory(); + if (!displayProperties) { goto error; } @@ -320,8 +297,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display modes: %u", displayModePropertiesCount); displayModeProperties = SDL_malloc(sizeof(VkDisplayModePropertiesKHR) * displayModePropertiesCount); - if (displayModeProperties == NULL) { - SDL_OutOfMemory(); + if (!displayModeProperties) { goto error; } @@ -367,8 +343,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display planes: %u", displayPlanePropertiesCount); displayPlaneProperties = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * displayPlanePropertiesCount); - if (displayPlaneProperties == NULL) { - SDL_OutOfMemory(); + if (!displayPlaneProperties) { goto error; } @@ -398,9 +373,8 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of supported displays for plane %u: %u", i, planeSupportedDisplaysCount); planeSupportedDisplays = SDL_malloc(sizeof(VkDisplayKHR) * planeSupportedDisplaysCount); - if (planeSupportedDisplays == NULL) { + if (!planeSupportedDisplays) { SDL_free(displayPlaneProperties); - SDL_OutOfMemory(); goto error; } @@ -434,7 +408,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, if (extent.width >= planeCaps.minDstExtent.width && extent.height >= planeCaps.minDstExtent.height && extent.width <= planeCaps.maxDstExtent.width && extent.height <= planeCaps.maxDstExtent.height) { /* If it does, choose this plane. */ - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Choosing plane %d, minimum extent %dx%d maximum extent %dx%d", i, + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Choosing plane %u, minimum extent %ux%u maximum extent %ux%u", i, planeCaps.minDstExtent.width, planeCaps.minDstExtent.height, planeCaps.maxDstExtent.width, planeCaps.maxDstExtent.height); planeIndex = i; @@ -480,7 +454,7 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_, createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; createInfo.globalAlpha = 1.0f; - result = vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, NULL, surface); + result = vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); return SDL_FALSE; diff --git a/src/video/SDL_yuv.c b/src/video/SDL_yuv.c index 151ef95e..63e02368 100644 --- a/src/video/SDL_yuv.c +++ b/src/video/SDL_yuv.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -615,8 +615,8 @@ int SDL_ConvertPixels_YUV_to_RGB(int width, int height, int tmp_pitch = (width * sizeof(Uint32)); tmp = SDL_malloc((size_t)tmp_pitch * height); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } /* convert src/src_format to tmp/ARGB8888 */ @@ -996,8 +996,8 @@ int SDL_ConvertPixels_RGB_to_YUV(int width, int height, int tmp_pitch = (width * sizeof(Uint32)); tmp = SDL_malloc((size_t)tmp_pitch * height); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } /* convert src/src_format to tmp/ARGB8888 */ @@ -1085,8 +1085,8 @@ static int SDL_ConvertPixels_SwapUVPlanes(int width, int height, const void *src /* Allocate a temporary row for the swap */ tmp = (Uint8 *)SDL_malloc(UVwidth); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } for (y = 0; y < UVheight; ++y) { SDL_memcpy(tmp, row1, UVwidth); @@ -1144,7 +1144,7 @@ static int SDL_TARGETING("sse2") SDL_ConvertPixels_PackUVPlanes_to_NV_SSE2(int w /* Need to make a copy of the buffer so we don't clobber it while converting */ tmp = (Uint8 *)SDL_malloc((size_t)2 * UVheight * srcUVPitch); if (tmp == NULL) { - return SDL_OutOfMemory(); + return -1; } SDL_memcpy(tmp, src, (size_t)2 * UVheight * srcUVPitch); src = tmp; @@ -1210,7 +1210,7 @@ static int SDL_TARGETING("sse2") SDL_ConvertPixels_SplitNV_to_UVPlanes_SSE2(int /* Need to make a copy of the buffer so we don't clobber it while converting */ tmp = (Uint8 *)SDL_malloc((size_t)UVheight * srcUVPitch); if (tmp == NULL) { - return SDL_OutOfMemory(); + return -1; } SDL_memcpy(tmp, src, (size_t)UVheight * srcUVPitch); src = tmp; @@ -1321,8 +1321,8 @@ static int SDL_ConvertPixels_PackUVPlanes_to_NV_std(int width, int height, const if (src == dst) { /* Need to make a copy of the buffer so we don't clobber it while converting */ tmp = (Uint8 *)SDL_malloc((size_t)2 * UVheight * srcUVPitch); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } SDL_memcpy(tmp, src, (size_t)2 * UVheight * srcUVPitch); src = tmp; @@ -1375,8 +1375,8 @@ static int SDL_ConvertPixels_SplitNV_to_UVPlanes_std(int width, int height, cons if (src == dst) { /* Need to make a copy of the buffer so we don't clobber it while converting */ tmp = (Uint8 *)SDL_malloc((size_t)UVheight * srcUVPitch); - if (tmp == NULL) { - return SDL_OutOfMemory(); + if (!tmp) { + return -1; } SDL_memcpy(tmp, src, (size_t)UVheight * srcUVPitch); src = tmp; diff --git a/src/video/SDL_yuv_c.h b/src/video/SDL_yuv_c.h index d0ff4463..033a013b 100644 --- a/src/video/SDL_yuv_c.h +++ b/src/video/SDL_yuv_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_android_video_capture.c b/src/video/android/SDL_android_video_capture.c new file mode 100644 index 00000000..55393b08 --- /dev/null +++ b/src/video/android/SDL_android_video_capture.c @@ -0,0 +1,713 @@ +/* + Simple DirectMedia Layer + 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 + 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 "SDL3/SDL.h" +#include "SDL3/SDL_video_capture.h" +#include "../SDL_sysvideocapture.h" +#include "../SDL_video_capture_c.h" +#include "../SDL_pixels_c.h" +#include "../../thread/SDL_systhread.h" + +#define DEBUG_VIDEO_CAPTURE_CAPTURE 0 + +#if defined(__ANDROID__) && __ANDROID_API__ >= 24 + +/* + * APP_PLATFORM=android-24 + * minSdkVersion=24 + * + * link with: -lcamera2ndk -lmediandk + * + * AndroidManifest.xml: + * + * + * + * + * Add: #define SDL_VIDEO_CAPTURE 1 + * in: include/build_config/SDL_build_config_android.h + * + * + * Very likely SDL must be build with YUV support (done by default) + * + * https://developer.android.com/reference/android/hardware/camera2/CameraManager + * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler), + * before configuring sessions on any of the camera devices. * " + */ + +#include +#include +#include +#include + +#include "../../core/android/SDL_android.h" + + +static ACameraManager *cameraMgr = NULL; +static ACameraIdList *cameraIdList = NULL; + +static void +create_cameraMgr(void) +{ + if (cameraMgr == NULL) { + + if (!Android_JNI_RequestPermission("android.permission.CAMERA")) { + SDL_SetError("This app doesn't have CAMERA permission"); + return; + } + + cameraMgr = ACameraManager_create(); + if (cameraMgr == NULL) { + SDL_Log("Error creating ACameraManager"); + } else { + SDL_Log("Create ACameraManager"); + } + } +} + +static void +delete_cameraMgr(void) +{ + if (cameraIdList) { + ACameraManager_deleteCameraIdList(cameraIdList); + cameraIdList = NULL; + } + + if (cameraMgr) { + ACameraManager_delete(cameraMgr); + cameraMgr = NULL; + } +} + +struct SDL_PrivateVideoCaptureData +{ + ACameraDevice *device; + ACameraCaptureSession *session; + ACameraDevice_StateCallbacks dev_callbacks; + ACameraCaptureSession_stateCallbacks capture_callbacks; + ACaptureSessionOutputContainer *sessionOutputContainer; + AImageReader *reader; + int num_formats; + int count_formats[6]; // see format_2_id +}; + + +/**/ +#define FORMAT_SDL SDL_PIXELFORMAT_NV12 + +static int +format_2_id(int fmt) { + switch (fmt) { +#define CASE(x, y) case x: return y + CASE(FORMAT_SDL, 0); + CASE(SDL_PIXELFORMAT_RGB565, 1); + CASE(SDL_PIXELFORMAT_XRGB8888, 2); + CASE(SDL_PIXELFORMAT_RGBA8888, 3); + CASE(SDL_PIXELFORMAT_RGBX8888, 4); + CASE(SDL_PIXELFORMAT_UNKNOWN, 5); +#undef CASE + default: + return 5; + } +} + +static int +id_2_format(int fmt) { + switch (fmt) { +#define CASE(x, y) case y: return x + CASE(FORMAT_SDL, 0); + CASE(SDL_PIXELFORMAT_RGB565, 1); + CASE(SDL_PIXELFORMAT_XRGB8888, 2); + CASE(SDL_PIXELFORMAT_RGBA8888, 3); + CASE(SDL_PIXELFORMAT_RGBX8888, 4); + CASE(SDL_PIXELFORMAT_UNKNOWN, 5); +#undef CASE + default: + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +static Uint32 +format_android_2_sdl(Uint32 fmt) +{ + switch (fmt) { +#define CASE(x, y) case x: return y + CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL); + CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565); + CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888); + CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888); + CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888); + + CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_UNKNOWN); // 64bits + CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN); + CASE(AIMAGE_FORMAT_JPEG, SDL_PIXELFORMAT_UNKNOWN); +#undef CASE + default: + SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt); + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +static Uint32 +format_sdl_2_android(Uint32 fmt) +{ + switch (fmt) { +#define CASE(x, y) case y: return x + CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL); + CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565); + CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888); + CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888); + CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888); +#undef CASE + default: + return 0; + } +} + + +static void +onDisconnected(void *context, ACameraDevice *device) +{ + // SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context; + SDL_Log("CB onDisconnected"); +} + +static void +onError(void *context, ACameraDevice *device, int error) +{ + // SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context; + SDL_Log("CB onError"); +} + + +static void +onClosed(void* context, ACameraCaptureSession *session) +{ + // SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context; + SDL_Log("CB onClosed"); +} + +static void +onReady(void* context, ACameraCaptureSession *session) +{ + // SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context; + SDL_Log("CB onReady"); +} + +static void +onActive(void* context, ACameraCaptureSession *session) +{ + // SDL_VideoCaptureDevice *_this = (SDL_VideoCaptureDevice *) context; + SDL_Log("CB onActive"); +} + +int +OpenDevice(SDL_VideoCaptureDevice *_this) +{ + camera_status_t res; + + /* Cannot open a second camera, while the first one is opened. + * If you want to play several camera, they must all be opened first, then played. + * + * https://developer.android.com/reference/android/hardware/camera2/CameraManager + * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler), + * before configuring sessions on any of the camera devices. * " + * + */ + if (check_device_playing()) { + return SDL_SetError("A camera is already playing"); + } + + _this->hidden = (struct SDL_PrivateVideoCaptureData *) SDL_calloc(1, sizeof (struct SDL_PrivateVideoCaptureData)); + if (_this->hidden == NULL) { + return SDL_OutOfMemory(); + } + + create_cameraMgr(); + + _this->hidden->dev_callbacks.context = (void *) _this; + _this->hidden->dev_callbacks.onDisconnected = onDisconnected; + _this->hidden->dev_callbacks.onError = onError; + + res = ACameraManager_openCamera(cameraMgr, _this->dev_name, &_this->hidden->dev_callbacks, &_this->hidden->device); + if (res != ACAMERA_OK) { + return SDL_SetError("Failed to open camera"); + } + + return 0; +} + +void +CloseDevice(SDL_VideoCaptureDevice *_this) +{ + if (_this && _this->hidden) { + if (_this->hidden->session) { + ACameraCaptureSession_close(_this->hidden->session); + } + + if (_this->hidden->sessionOutputContainer) { + ACaptureSessionOutputContainer_free(_this->hidden->sessionOutputContainer); + } + + if (_this->hidden->reader) { + AImageReader_delete(_this->hidden->reader); + } + + if (_this->hidden->device) { + ACameraDevice_close(_this->hidden->device); + } + + SDL_free(_this->hidden); + + _this->hidden = NULL; + } + + if (check_all_device_closed()) { + delete_cameraMgr(); + } +} + +int +InitDevice(SDL_VideoCaptureDevice *_this) +{ + size_t size, pitch; + SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE); + SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height); + return 0; +} + +int +GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) +{ + if (spec) { + *spec = _this->spec; + return 0; + } + return -1; +} + +int +StartCapture(SDL_VideoCaptureDevice *_this) +{ + camera_status_t res; + media_status_t res2; + ANativeWindow *window = NULL; + ACaptureSessionOutput *sessionOutput; + ACameraOutputTarget *outputTarget; + ACaptureRequest *request; + + res2 = AImageReader_new(_this->spec.width, _this->spec.height, format_sdl_2_android(_this->spec.format), 10 /* nb buffers */, &_this->hidden->reader); + if (res2 != AMEDIA_OK) { + SDL_SetError("Error AImageReader_new"); + goto error; + } + res2 = AImageReader_getWindow(_this->hidden->reader, &window); + if (res2 != AMEDIA_OK) { + SDL_SetError("Error AImageReader_new"); + goto error; + + } + + + res = ACaptureSessionOutput_create(window, &sessionOutput); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACaptureSessionOutput_create"); + goto error; + } + res = ACaptureSessionOutputContainer_create(&_this->hidden->sessionOutputContainer); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACaptureSessionOutputContainer_create"); + goto error; + } + res = ACaptureSessionOutputContainer_add(_this->hidden->sessionOutputContainer, sessionOutput); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACaptureSessionOutputContainer_add"); + goto error; + } + + + res = ACameraOutputTarget_create(window, &outputTarget); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACameraOutputTarget_create"); + goto error; + } + + + res = ACameraDevice_createCaptureRequest(_this->hidden->device, TEMPLATE_RECORD, &request); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACameraDevice_createCaptureRequest"); + goto error; + } + + res = ACaptureRequest_addTarget(request, outputTarget); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACaptureRequest_addTarget"); + goto error; + } + + + _this->hidden->capture_callbacks.context = (void *) _this; + _this->hidden->capture_callbacks.onClosed = onClosed; + _this->hidden->capture_callbacks.onReady = onReady; + _this->hidden->capture_callbacks.onActive = onActive; + + res = ACameraDevice_createCaptureSession(_this->hidden->device, + _this->hidden->sessionOutputContainer, + &_this->hidden->capture_callbacks, + &_this->hidden->session); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACameraDevice_createCaptureSession"); + goto error; + } + + res = ACameraCaptureSession_setRepeatingRequest(_this->hidden->session, NULL, 1, &request, NULL); + if (res != ACAMERA_OK) { + SDL_SetError("Error ACameraDevice_createCaptureSession"); + goto error; + } + + return 0; + +error: + return -1; +} + +int +StopCapture(SDL_VideoCaptureDevice *_this) +{ + ACameraCaptureSession_close(_this->hidden->session); + _this->hidden->session = NULL; + return 0; +} + +int +AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + media_status_t res; + AImage *image; + res = AImageReader_acquireNextImage(_this->hidden->reader, &image); + /* We could also use this one: + res = AImageReader_acquireLatestImage(_this->hidden->reader, &image); + */ + if (res == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE ) { + + SDL_Delay(20); // TODO fix some delay +#if DEBUG_VIDEO_CAPTURE_CAPTURE +// SDL_Log("AImageReader_acquireNextImage: AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE"); +#endif + return 0; + } else if (res == AMEDIA_OK ) { + int i = 0; + int32_t numPlanes = 0; + AImage_getNumberOfPlanes(image, &numPlanes); + + frame->timestampNS = SDL_GetTicksNS(); + + for (i = 0; i < numPlanes && i < 3; i++) { + int dataLength = 0; + int rowStride = 0; + uint8_t *data = NULL; + frame->num_planes += 1; + AImage_getPlaneRowStride(image, i, &rowStride); + res = AImage_getPlaneData(image, i, &data, &dataLength); + if (res == AMEDIA_OK) { + frame->data[i] = data; + frame->pitch[i] = rowStride; + } + } + + if (frame->num_planes == 3) { + /* plane 2 and 3 are interleaved NV12. SDL only takes two planes for this format */ + int pixelStride = 0; + AImage_getPlanePixelStride(image, 1, &pixelStride); + if (pixelStride == 2) { + frame->num_planes -= 1; + } + } + + frame->internal = (void*)image; + return 0; + } else if (res == AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED) { + SDL_SetError("AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED"); + } else { + SDL_SetError("AImageReader_acquireNextImage: %d", res); + } + + return -1; +} + +int +ReleaseFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) +{ + if (frame->internal){ + AImage_delete((AImage *)frame->internal); + } + return 0; +} + +int +GetNumFormats(SDL_VideoCaptureDevice *_this) +{ + camera_status_t res; + int i; + int unknown = 0; + ACameraMetadata *metadata; + ACameraMetadata_const_entry entry; + + if (_this->hidden->num_formats != 0) { + return _this->hidden->num_formats; + } + + res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata); + if (res != ACAMERA_OK) { + return -1; + } + + res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); + if (res != ACAMERA_OK) { + return -1; + } + + SDL_Log("got entry ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS"); + + for (i = 0; i < entry.count; i += 4) { + int32_t format = entry.data.i32[i + 0]; + int32_t type = entry.data.i32[i + 3]; + Uint32 fmt; + + if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) { + continue; + } + + fmt = format_android_2_sdl(format); + _this->hidden->count_formats[format_2_id(fmt)] += 1; + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + if (fmt != SDL_PIXELFORMAT_UNKNOWN) { + int w = entry.data.i32[i + 1]; + int h = entry.data.i32[i + 2]; + SDL_Log("Got format android 0x%08x -> %s %d x %d", format, SDL_GetPixelFormatName(fmt), w, h); + } else { + unknown += 1; + } +#endif + } + +#if DEBUG_VIDEO_CAPTURE_CAPTURE + if (unknown) { + SDL_Log("Got unknown android"); + } +#endif + + + if ( _this->hidden->count_formats[0]) _this->hidden->num_formats += 1; + if ( _this->hidden->count_formats[1]) _this->hidden->num_formats += 1; + if ( _this->hidden->count_formats[2]) _this->hidden->num_formats += 1; + if ( _this->hidden->count_formats[3]) _this->hidden->num_formats += 1; + if ( _this->hidden->count_formats[4]) _this->hidden->num_formats += 1; + if ( _this->hidden->count_formats[5]) _this->hidden->num_formats += 1; + + return _this->hidden->num_formats; +} + +int +GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) +{ + int i; + int i2 = 0; + + if (_this->hidden->num_formats == 0) { + GetNumFormats(_this); + } + + if (index < 0 || index >= _this->hidden->num_formats) { + return -1; + } + + for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) { + if (_this->hidden->count_formats[i] == 0) { + continue; + } + + if (i2 == index) { + *format = id_2_format(i); + } + + i2++; + + } + return 0; +} + +int +GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format) +{ + int i, i2 = 0, index; + if (_this->hidden->num_formats == 0) { + GetNumFormats(_this); + } + + index = format_2_id(format); + + for (i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) { + if (_this->hidden->count_formats[i] == 0) { + continue; + } + + if (i2 == index) { + /* number of resolution for this format */ + return _this->hidden->count_formats[i]; + } + + i2++; + } + + return -1; +} + +int +GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) +{ + camera_status_t res; + int i, i2 = 0; + ACameraMetadata *metadata; + ACameraMetadata_const_entry entry; + + if (_this->hidden->num_formats == 0) { + GetNumFormats(_this); + } + + res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata); + if (res != ACAMERA_OK) { + return -1; + } + + res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); + if (res != ACAMERA_OK) { + return -1; + } + + for (i = 0; i < entry.count; i += 4) { + int32_t f = entry.data.i32[i + 0]; + int w = entry.data.i32[i + 1]; + int h = entry.data.i32[i + 2]; + int32_t type = entry.data.i32[i + 3]; + Uint32 fmt; + + if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) { + continue; + } + + + fmt = format_android_2_sdl(f); + if (fmt != format) { + continue; + } + + if (i2 == index) { + *width = w; + *height = h; + return 0; + } + + i2++; + } + return -1; +} + +static int GetNumDevices(void); + +int +GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) +{ + int index = instance_id - 1; + create_cameraMgr(); + + if (cameraIdList == NULL) { + GetNumDevices(); + } + + if (cameraIdList) { + if (index >= 0 && index < cameraIdList->numCameras) { + SDL_snprintf(buf, size, "%s", cameraIdList->cameraIds[index]); + return 0; + } + } + + return -1; +} + +static int +GetNumDevices(void) +{ + camera_status_t res; + create_cameraMgr(); + + if (cameraIdList) { + ACameraManager_deleteCameraIdList(cameraIdList); + cameraIdList = NULL; + } + + res = ACameraManager_getCameraIdList(cameraMgr, &cameraIdList); + + if (res == ACAMERA_OK) { + if (cameraIdList) { + return cameraIdList->numCameras; + } + } + return -1; +} + +SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) +{ + /* hard-coded list of ID */ + int i; + int num = GetNumDevices(); + SDL_VideoCaptureDeviceID *ret; + + ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret)); + + if (ret == NULL) { + SDL_OutOfMemory(); + *count = 0; + return NULL; + } + + for (i = 0; i < num; i++) { + ret[i] = i + 1; + } + ret[num] = 0; + *count = num; + return ret; +} + +int SDL_SYS_VideoCaptureInit(void) { + return 0; +} + +int SDL_SYS_VideoCaptureQuit(void) { + return 0; +} + + +#endif + + diff --git a/src/video/android/SDL_androidclipboard.c b/src/video/android/SDL_androidclipboard.c index 4dffa436..70c4fc15 100644 --- a/src/video/android/SDL_androidclipboard.c +++ b/src/video/android/SDL_androidclipboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidclipboard.h b/src/video/android/SDL_androidclipboard.h index d161ef01..e6b22b2a 100644 --- a/src/video/android/SDL_androidclipboard.h +++ b/src/video/android/SDL_androidclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c index 8dbe13dd..6f92e347 100644 --- a/src/video/android/SDL_androidevents.c +++ b/src/video/android/SDL_androidevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -45,6 +45,7 @@ static void android_egl_context_restore(SDL_Window *window) if (window) { SDL_Event event; SDL_WindowData *data = window->driverdata; + SDL_GL_MakeCurrent(window, NULL); if (SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context) < 0) { /* The context is no longer valid, create a new one */ data->egl_context = (EGLContext)SDL_GL_CreateContext(window); @@ -107,7 +108,7 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) #endif ANDROIDAUDIO_PauseDevices(); - openslES_PauseDevices(); + OPENSLES_PauseDevices(); AAUDIO_PauseDevices(); if (SDL_WaitSemaphore(Android_ResumeSem) == 0) { @@ -118,7 +119,7 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) SDL_SendAppEvent(SDL_EVENT_WILL_ENTER_FOREGROUND); ANDROIDAUDIO_ResumeDevices(); - openslES_ResumeDevices(); + OPENSLES_ResumeDevices(); AAUDIO_ResumeDevices(); /* Restore the GL Context from here, as this operation is thread dependent */ @@ -131,8 +132,9 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) #endif /* Make sure SW Keyboard is restored when an app becomes foreground */ - if (SDL_TextInputActive()) { - Android_StartTextInput(_this); /* Only showTextInput */ + if (SDL_TextInputActive() && + SDL_GetHintBoolean(SDL_HINT_ENABLE_SCREEN_KEYBOARD, SDL_TRUE)) { + Android_ShowScreenKeyboard(_this, Android_Window); /* Only showTextInput */ } SDL_SendAppEvent(SDL_EVENT_DID_ENTER_FOREGROUND); @@ -159,11 +161,6 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) } } } - - if (AAUDIO_DetectBrokenPlayState()) { - AAUDIO_PauseDevices(); - AAUDIO_ResumeDevices(); - } } void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) @@ -186,7 +183,7 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) if (videodata->pauseAudio) { ANDROIDAUDIO_PauseDevices(); - openslES_PauseDevices(); + OPENSLES_PauseDevices(); AAUDIO_PauseDevices(); } @@ -202,7 +199,7 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) if (videodata->pauseAudio) { ANDROIDAUDIO_ResumeDevices(); - openslES_ResumeDevices(); + OPENSLES_ResumeDevices(); AAUDIO_ResumeDevices(); } @@ -216,8 +213,9 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) #endif /* Make sure SW Keyboard is restored when an app becomes foreground */ - if (SDL_TextInputActive()) { - Android_StartTextInput(_this); /* Only showTextInput */ + if (SDL_TextInputActive() && + SDL_GetHintBoolean(SDL_HINT_ENABLE_SCREEN_KEYBOARD, SDL_TRUE)) { + Android_ShowScreenKeyboard(_this, Android_Window); /* Only showTextInput */ } SDL_SendAppEvent(SDL_EVENT_DID_ENTER_FOREGROUND); @@ -245,11 +243,6 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) } } } - - if (AAUDIO_DetectBrokenPlayState()) { - AAUDIO_PauseDevices(); - AAUDIO_ResumeDevices(); - } } #endif /* SDL_VIDEO_DRIVER_ANDROID */ diff --git a/src/video/android/SDL_androidevents.h b/src/video/android/SDL_androidevents.h index fc6753a1..35bd5b71 100644 --- a/src/video/android/SDL_androidevents.h +++ b/src/video/android/SDL_androidevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidgl.c b/src/video/android/SDL_androidgl.c index 2b02f8b6..f3b40c78 100644 --- a/src/video/android/SDL_androidgl.c +++ b/src/video/android/SDL_androidgl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidgl.h b/src/video/android/SDL_androidgl.h index 059d3dcf..ab22f83d 100644 --- a/src/video/android/SDL_androidgl.h +++ b/src/video/android/SDL_androidgl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidkeyboard.c b/src/video/android/SDL_androidkeyboard.c index 0d602345..4d80397f 100644 --- a/src/video/android/SDL_androidkeyboard.c +++ b/src/video/android/SDL_androidkeyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -341,22 +341,22 @@ SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this) return SDL_TRUE; } +void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_VideoData *videodata = _this->driverdata; + Android_JNI_ShowScreenKeyboard(&videodata->textRect); +} + +void Android_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) +{ + Android_JNI_HideScreenKeyboard(); +} + SDL_bool Android_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window) { return Android_JNI_IsScreenKeyboardShown(); } -void Android_StartTextInput(SDL_VideoDevice *_this) -{ - SDL_VideoData *videodata = _this->driverdata; - Android_JNI_ShowTextInput(&videodata->textRect); -} - -void Android_StopTextInput(SDL_VideoDevice *_this) -{ - Android_JNI_HideTextInput(); -} - int Android_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect) { SDL_VideoData *videodata = _this->driverdata; diff --git a/src/video/android/SDL_androidkeyboard.h b/src/video/android/SDL_androidkeyboard.h index ed3c6fe3..2693fa0e 100644 --- a/src/video/android/SDL_androidkeyboard.h +++ b/src/video/android/SDL_androidkeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,8 +26,7 @@ extern int Android_OnKeyDown(int keycode); extern int Android_OnKeyUp(int keycode); extern SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this); +extern void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window); +extern void Android_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window); extern SDL_bool Android_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window); - -extern void Android_StartTextInput(SDL_VideoDevice *_this); -extern void Android_StopTextInput(SDL_VideoDevice *_this); extern int Android_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect); diff --git a/src/video/android/SDL_androidmessagebox.c b/src/video/android/SDL_androidmessagebox.c index 8dc1c9e8..a582be57 100644 --- a/src/video/android/SDL_androidmessagebox.c +++ b/src/video/android/SDL_androidmessagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidmessagebox.h b/src/video/android/SDL_androidmessagebox.h index c111d00d..8b2e3883 100644 --- a/src/video/android/SDL_androidmessagebox.h +++ b/src/video/android/SDL_androidmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidmouse.c b/src/video/android/SDL_androidmouse.c index 3e38d85b..39dee434 100644 --- a/src/video/android/SDL_androidmouse.c +++ b/src/video/android/SDL_androidmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -68,10 +68,7 @@ static SDL_Cursor *Android_WrapCursor(int custom_cursor, int system_cursor) } else { SDL_free(cursor); cursor = NULL; - SDL_OutOfMemory(); } - } else { - SDL_OutOfMemory(); } return cursor; @@ -88,7 +85,7 @@ static SDL_Cursor *Android_CreateCursor(SDL_Surface *surface, int hot_x, int hot SDL_Surface *converted; converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888); - if (converted == NULL) { + if (!converted) { return NULL; } custom_cursor = Android_JNI_CreateCustomCursor(converted, hot_x, hot_y); @@ -117,7 +114,7 @@ static void Android_FreeCursor(SDL_Cursor *cursor) static SDL_Cursor *Android_CreateEmptyCursor() { - if (empty_cursor == NULL) { + if (!empty_cursor) { SDL_Surface *empty_surface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_ARGB8888); if (empty_surface) { SDL_memset(empty_surface->pixels, 0, (size_t)empty_surface->h * empty_surface->pitch); @@ -138,7 +135,7 @@ static void Android_DestroyEmptyCursor() static int Android_ShowCursor(SDL_Cursor *cursor) { - if (cursor == NULL) { + if (!cursor) { cursor = Android_CreateEmptyCursor(); } if (cursor) { @@ -215,7 +212,7 @@ void Android_OnMouse(SDL_Window *window, int state, int action, float x, float y int changes; Uint8 button; - if (window == NULL) { + if (!window) { return; } diff --git a/src/video/android/SDL_androidmouse.h b/src/video/android/SDL_androidmouse.h index c5e88c8f..d6f6f797 100644 --- a/src/video/android/SDL_androidmouse.h +++ b/src/video/android/SDL_androidmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidtouch.c b/src/video/android/SDL_androidtouch.c index 18d9cd02..6f56f6cb 100644 --- a/src/video/android/SDL_androidtouch.c +++ b/src/video/android/SDL_androidtouch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -52,7 +52,7 @@ void Android_OnTouch(SDL_Window *window, int touch_device_id_in, int pointer_fin SDL_TouchID touchDeviceId = 0; SDL_FingerID fingerId = 0; - if (window == NULL) { + if (!window) { return; } diff --git a/src/video/android/SDL_androidtouch.h b/src/video/android/SDL_androidtouch.h index 7ee5aa98..1637b3f5 100644 --- a/src/video/android/SDL_androidtouch.h +++ b/src/video/android/SDL_androidtouch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c index ede5b1c9..e000ec69 100644 --- a/src/video/android/SDL_androidvideo.c +++ b/src/video/android/SDL_androidvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -86,14 +86,12 @@ static SDL_VideoDevice *Android_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } data = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (data == NULL) { - SDL_OutOfMemory(); + if (!data) { SDL_free(device); return NULL; } @@ -117,7 +115,6 @@ static SDL_VideoDevice *Android_CreateDevice(void) device->MinimizeWindow = Android_MinimizeWindow; device->SetWindowResizable = Android_SetWindowResizable; device->DestroyWindow = Android_DestroyWindow; - device->GetWindowWMInfo = Android_GetWindowWMInfo; device->free = Android_DeleteDevice; @@ -145,12 +142,12 @@ static SDL_VideoDevice *Android_CreateDevice(void) device->SuspendScreenSaver = Android_SuspendScreenSaver; /* Text input */ - device->StartTextInput = Android_StartTextInput; - device->StopTextInput = Android_StopTextInput; device->SetTextInputRect = Android_SetTextInputRect; /* Screen keyboard */ device->HasScreenKeyboardSupport = Android_HasScreenKeyboardSupport; + device->ShowScreenKeyboard = Android_ShowScreenKeyboard; + device->HideScreenKeyboard = Android_HideScreenKeyboard; device->IsScreenKeyboardShown = Android_IsScreenKeyboardShown; /* Clipboard */ @@ -158,6 +155,8 @@ static SDL_VideoDevice *Android_CreateDevice(void) device->GetClipboardText = Android_GetClipboardText; device->HasClipboardText = Android_HasClipboardText; + device->device_caps = VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; + return device; } diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h index e8b9a09b..f6dd54eb 100644 --- a/src/video/android/SDL_androidvideo.h +++ b/src/video/android/SDL_androidvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/android/SDL_androidvulkan.c b/src/video/android/SDL_androidvulkan.c index 8a0af196..14c391a6 100644 --- a/src/video/android/SDL_androidvulkan.c +++ b/src/video/android/SDL_androidvulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,7 +35,6 @@ #include "SDL_androidvulkan.h" -#include int Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) { @@ -49,10 +48,10 @@ int Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan loader library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { path = "libvulkan.so"; } _this->vulkan_config.loader_handle = SDL_LoadObject(path); @@ -77,7 +76,7 @@ int Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) (PFN_vkEnumerateInstanceExtensionProperties) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } for (i = 0; i < extensionCount; i++) { @@ -111,25 +110,22 @@ void Android_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForAndroid[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; + if(count) { + *count = SDL_arraysize(extensionsForAndroid); } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForAndroid), - extensionsForAndroid); + return extensionsForAndroid; } SDL_bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { SDL_WindowData *windowData = window->driverdata; @@ -157,8 +153,7 @@ SDL_bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.pNext = NULL; createInfo.flags = 0; createInfo.window = windowData->native_window; - result = vkCreateAndroidSurfaceKHR(instance, &createInfo, - NULL, surface); + result = vkCreateAndroidSurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateAndroidSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); diff --git a/src/video/android/SDL_androidvulkan.h b/src/video/android/SDL_androidvulkan.h index 1b059c43..314c5d87 100644 --- a/src/video/android/SDL_androidvulkan.h +++ b/src/video/android/SDL_androidvulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void Android_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c index 49176552..3ef30228 100644 --- a/src/video/android/SDL_androidwindow.c +++ b/src/video/android/SDL_androidwindow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,12 +31,11 @@ #include "SDL_androidvideo.h" #include "SDL_androidwindow.h" -#include /* Currently only one window */ SDL_Window *Android_Window = NULL; -int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *data; int retval = 0; @@ -62,18 +61,18 @@ int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_SetKeyboardFocus(window); data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { - retval = SDL_OutOfMemory(); + if (!data) { + retval = -1; goto endfunction; } data->native_window = Android_JNI_GetNativeWindow(); - if (!data->native_window) { SDL_free(data); retval = SDL_SetError("Could not fetch native window"); goto endfunction; } + SDL_SetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_ANDROID_WINDOW_POINTER, data->native_window); /* Do not create EGLSurface for Vulkan window since it will then make the window incompatible with vkCreateAndroidSurfaceKHR */ @@ -88,6 +87,7 @@ int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) goto endfunction; } } + SDL_SetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface); #endif window->driverdata = data; @@ -105,7 +105,7 @@ void Android_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) Android_JNI_SetActivityTitle(window->title); } -void Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +int Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { SDL_LockMutex(Android_ActivityMutex); @@ -129,7 +129,7 @@ void Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL } data = window->driverdata; - if (data == NULL || !data->native_window) { + if (!data || !data->native_window) { if (data && !data->native_window) { SDL_SetError("Missing native window"); } @@ -154,6 +154,7 @@ void Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL endfunction: SDL_UnlockMutex(Android_ActivityMutex); + return 0; } void Android_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -194,18 +195,4 @@ void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_UnlockMutex(Android_ActivityMutex); } -int Android_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - SDL_WindowData *data = window->driverdata; - - info->subsystem = SDL_SYSWM_ANDROID; - info->info.android.window = data->native_window; - -#ifdef SDL_VIDEO_OPENGL_EGL - info->info.android.surface = data->egl_surface; -#endif - - return 0; -} - #endif /* SDL_VIDEO_DRIVER_ANDROID */ diff --git a/src/video/android/SDL_androidwindow.h b/src/video/android/SDL_androidwindow.h index 43915611..dff81c7b 100644 --- a/src/video/android/SDL_androidwindow.h +++ b/src/video/android/SDL_androidwindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,14 +26,13 @@ #include "../../core/android/SDL_android.h" #include "../SDL_egl_c.h" -extern int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void Android_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); -extern void Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void Android_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); extern void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int Android_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); extern SDL_Window *Android_Window; struct SDL_WindowData diff --git a/src/video/cocoa/SDL_cocoaclipboard.h b/src/video/cocoa/SDL_cocoaclipboard.h index 8e4b2acd..4fdfb408 100644 --- a/src/video/cocoa/SDL_cocoaclipboard.h +++ b/src/video/cocoa/SDL_cocoaclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m index a6f85a13..b51f8307 100644 --- a/src/video/cocoa/SDL_cocoaclipboard.m +++ b/src/video/cocoa/SDL_cocoaclipboard.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -148,8 +148,6 @@ void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size if (data) { [itemData getBytes: data length: length]; SDL_memset((Uint8 *)data + length, 0, sizeof(Uint32)); - } else { - SDL_OutOfMemory(); } break; } diff --git a/src/video/cocoa/SDL_cocoaevents.h b/src/video/cocoa/SDL_cocoaevents.h index c5846d70..096db4dc 100644 --- a/src/video/cocoa/SDL_cocoaevents.h +++ b/src/video/cocoa/SDL_cocoaevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m index 221a61d9..a3c31991 100644 --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -133,6 +133,7 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) ofObject:(id)object change:(NSDictionary *)change context:(void *)context; +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; @end @implementation SDLAppDelegate : NSObject @@ -141,18 +142,21 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) self = [super init]; if (self) { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + SDL_bool registerActivationHandlers = SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", SDL_TRUE); seenFirstActivate = NO; - [center addObserver:self - selector:@selector(windowWillClose:) - name:NSWindowWillCloseNotification - object:nil]; + if (registerActivationHandlers) { + [center addObserver:self + selector:@selector(windowWillClose:) + name:NSWindowWillCloseNotification + object:nil]; - [center addObserver:self - selector:@selector(focusSomeWindow:) - name:NSApplicationDidBecomeActiveNotification - object:nil]; + [center addObserver:self + selector:@selector(focusSomeWindow:) + name:NSApplicationDidBecomeActiveNotification + object:nil]; + } [center addObserver:self selector:@selector(localeDidChange:) @@ -287,11 +291,14 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL); + return (BOOL)SDL_SendDropFile(NULL, NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL); } - (void)applicationDidFinishLaunching:(NSNotification *)notification { + if (!SDL_GetHintBoolean("SDL_MAC_REGISTER_ACTIVATION_HANDLERS", SDL_TRUE)) + return; + /* The menu bar of SDL apps which don't have the typical .app bundle * structure fails to work the first time a window is created (until it's * de-focused and re-focused), if this call is in Cocoa_RegisterApp instead @@ -315,10 +322,26 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) - (void)handleURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { NSString *path = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - SDL_SendDropFile(NULL, [path UTF8String]); + SDL_SendDropFile(NULL, NULL, [path UTF8String]); SDL_SendDropComplete(NULL); } +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + // This just tells Cocoa that we didn't do any custom save state magic for the app, + // so the system is safe to use NSSecureCoding internally, instead of using unencrypted + // save states for backwards compatibility. If we don't return YES here, we'll get a + // warning on the console at startup: + // + // ``` + // WARNING: Secure coding is not enabled for restorable state! Enable secure coding by implementing NSApplicationDelegate.applicationSupportsSecureRestorableState: and returning YES. + // ``` + // + // More-detailed explanation: + // https://stackoverflow.com/questions/77283578/sonoma-and-nsapplicationdelegate-applicationsupportssecurerestorablestate/77320845#77320845 + return YES; +} + @end static SDLAppDelegate *appDelegate = nil; diff --git a/src/video/cocoa/SDL_cocoakeyboard.h b/src/video/cocoa/SDL_cocoakeyboard.h index d522a9c6..b1be77dc 100644 --- a/src/video/cocoa/SDL_cocoakeyboard.h +++ b/src/video/cocoa/SDL_cocoakeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoakeyboard.m b/src/video/cocoa/SDL_cocoakeyboard.m index 9334c7ab..04b06bf6 100644 --- a/src/video/cocoa/SDL_cocoakeyboard.m +++ b/src/video/cocoa/SDL_cocoakeyboard.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -435,9 +435,19 @@ void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event) case NSEventTypeKeyUp: SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_RELEASED, code); break; - case NSEventTypeFlagsChanged: - HandleModifiers(_this, code, (unsigned int)[event modifierFlags]); + case NSEventTypeFlagsChanged: { + // see if the new modifierFlags mean any existing keys should be pressed/released... + const unsigned int modflags = (unsigned int)[event modifierFlags]; + HandleModifiers(_this, SDL_SCANCODE_LSHIFT, modflags); + HandleModifiers(_this, SDL_SCANCODE_LCTRL, modflags); + HandleModifiers(_this, SDL_SCANCODE_LALT, modflags); + HandleModifiers(_this, SDL_SCANCODE_LGUI, modflags); + HandleModifiers(_this, SDL_SCANCODE_RSHIFT, modflags); + HandleModifiers(_this, SDL_SCANCODE_RCTRL, modflags); + HandleModifiers(_this, SDL_SCANCODE_RALT, modflags); + HandleModifiers(_this, SDL_SCANCODE_RGUI, modflags); break; + } default: /* just to avoid compiler warnings */ break; } diff --git a/src/video/cocoa/SDL_cocoamessagebox.h b/src/video/cocoa/SDL_cocoamessagebox.h index 7dbd81a5..f5673453 100644 --- a/src/video/cocoa/SDL_cocoamessagebox.h +++ b/src/video/cocoa/SDL_cocoamessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoamessagebox.m b/src/video/cocoa/SDL_cocoamessagebox.m index 39ec8772..9ed5ea50 100644 --- a/src/video/cocoa/SDL_cocoamessagebox.m +++ b/src/video/cocoa/SDL_cocoamessagebox.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoametalview.h b/src/video/cocoa/SDL_cocoametalview.h index aff42f84..3e9799ed 100644 --- a/src/video/cocoa/SDL_cocoametalview.h +++ b/src/video/cocoa/SDL_cocoametalview.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoametalview.m b/src/video/cocoa/SDL_cocoametalview.m index 46d11251..71ede955 100644 --- a/src/video/cocoa/SDL_cocoametalview.m +++ b/src/video/cocoa/SDL_cocoametalview.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,8 +30,6 @@ #if defined(SDL_VIDEO_DRIVER_COCOA) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL)) -#include - static int SDLCALL SDL_MetalViewEventWatch(void *userdata, SDL_Event *event) { /* Update the drawable size when SDL receives a size changed event for diff --git a/src/video/cocoa/SDL_cocoamodes.h b/src/video/cocoa/SDL_cocoamodes.h index b3f44c0c..475d3ab2 100644 --- a/src/video/cocoa/SDL_cocoamodes.h +++ b/src/video/cocoa/SDL_cocoamodes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m index 34e9c2d5..072e49a6 100644 --- a/src/video/cocoa/SDL_cocoamodes.m +++ b/src/video/cocoa/SDL_cocoamodes.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -489,6 +489,8 @@ int Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_ CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken; CGError result = kCGErrorSuccess; + b_inModeTransition = SDL_TRUE; + /* Fade to black to hide resolution-switching flicker */ if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) { CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); @@ -508,6 +510,8 @@ int Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_ CGReleaseDisplayFadeReservation(fade_token); } + b_inModeTransition = SDL_FALSE; + if (result != kCGErrorSuccess) { CG_SetError("CGDisplaySwitchToMode()", result); return -1; diff --git a/src/video/cocoa/SDL_cocoamouse.h b/src/video/cocoa/SDL_cocoamouse.h index 4e89b752..b6b3e749 100644 --- a/src/video/cocoa/SDL_cocoamouse.h +++ b/src/video/cocoa/SDL_cocoamouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index fc29c5a5..3be095b3 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -188,6 +188,30 @@ static SDL_Cursor *Cocoa_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_HAND: nscursor = [NSCursor pointingHandCursor]; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); + break; default: SDL_assert(!"Unknown system cursor"); return NULL; @@ -219,15 +243,11 @@ static int Cocoa_ShowCursor(SDL_Cursor *cursor) SDL_VideoDevice *device = SDL_GetVideoDevice(); SDL_Window *window = (device ? device->windows : NULL); for (; window != NULL; window = window->next) { - SDL_Mouse *mouse = SDL_GetMouse(); - if(mouse->focus) { - if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) { - [(__bridge NSCursor*)mouse->cur_cursor->driverdata set]; - } else { - [[NSCursor invisibleCursor] set]; - } - } else { - [[NSCursor arrowCursor] set]; + SDL_CocoaWindowData *driverdata = (__bridge SDL_CocoaWindowData *)window->driverdata; + if (driverdata) { + [driverdata.nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:) + withObject:[driverdata.nswindow contentView] + waitUntilDone:NO]; } } return 0; @@ -373,7 +393,7 @@ int Cocoa_InitMouse(SDL_VideoDevice *_this) SDL_Mouse *mouse = SDL_GetMouse(); SDL_MouseData *driverdata = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); if (driverdata == NULL) { - return SDL_OutOfMemory(); + return -1; } mouse->driverdata = driverdata; diff --git a/src/video/cocoa/SDL_cocoaopengl.h b/src/video/cocoa/SDL_cocoaopengl.h index 1ec05d8f..9839c488 100644 --- a/src/video/cocoa/SDL_cocoaopengl.h +++ b/src/video/cocoa/SDL_cocoaopengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoaopengl.m b/src/video/cocoa/SDL_cocoaopengl.m index ec0250c3..022d0786 100644 --- a/src/video/cocoa/SDL_cocoaopengl.m +++ b/src/video/cocoa/SDL_cocoaopengl.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoaopengles.h b/src/video/cocoa/SDL_cocoaopengles.h index cbdaadd6..57da08aa 100644 --- a/src/video/cocoa/SDL_cocoaopengles.h +++ b/src/video/cocoa/SDL_cocoaopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoaopengles.m b/src/video/cocoa/SDL_cocoaopengles.m index bf28563e..0cac7dfb 100644 --- a/src/video/cocoa/SDL_cocoaopengles.m +++ b/src/video/cocoa/SDL_cocoaopengles.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoashape.m b/src/video/cocoa/SDL_cocoashape.m deleted file mode 100644 index 2e16741b..00000000 --- a/src/video/cocoa/SDL_cocoashape.m +++ /dev/null @@ -1,115 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_COCOA - -#include "SDL_cocoavideo.h" -#include "SDL_cocoashape.h" -#include "../SDL_sysvideo.h" - -@implementation SDL_ShapeData -@end - -@interface SDL_CocoaClosure : NSObject -@property(nonatomic) NSView *view; -@property(nonatomic) NSBezierPath *path; -@property(nonatomic) SDL_Window *window; -@end - -@implementation SDL_CocoaClosure -@end - -SDL_WindowShaper *Cocoa_CreateShaper(SDL_Window *window) -{ - @autoreleasepool { - SDL_WindowShaper *result; - SDL_ShapeData *data; - SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata; - - result = (SDL_WindowShaper *)SDL_malloc(sizeof(SDL_WindowShaper)); - if (!result) { - SDL_OutOfMemory(); - return NULL; - } - - [windata.nswindow setOpaque:NO]; - - [windata.nswindow setStyleMask:NSWindowStyleMaskBorderless]; - - result->window = window; - result->mode.mode = ShapeModeDefault; - result->mode.parameters.binarizationCutoff = 1; - window->shaper = result; - - data = [[SDL_ShapeData alloc] init]; - data.context = [windata.nswindow graphicsContext]; - data.saved = SDL_FALSE; - data.shape = NULL; - - /* TODO: There's no place to release this... */ - result->driverdata = (void *)CFBridgingRetain(data); - - return result; - } -} - -void ConvertRects(SDL_ShapeTree *tree, void *closure) -{ - SDL_CocoaClosure *data = (__bridge SDL_CocoaClosure *)closure; - if (tree->kind == OpaqueShape) { - NSRect rect = NSMakeRect(tree->data.shape.x, data.window->h - tree->data.shape.y, tree->data.shape.w, tree->data.shape.h); - [data.path appendBezierPathWithRect:[data.view convertRect:rect toView:nil]]; - } -} - -int Cocoa_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode) -{ - @autoreleasepool { - SDL_ShapeData *data = (__bridge SDL_ShapeData *)shaper->driverdata; - SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)shaper->window->driverdata; - SDL_CocoaClosure *closure; - if (data.saved == SDL_TRUE) { - [data.context restoreGraphicsState]; - data.saved = SDL_FALSE; - } - - /*[data.context saveGraphicsState];*/ - /*data.saved = SDL_TRUE;*/ - [NSGraphicsContext setCurrentContext:data.context]; - - [[NSColor clearColor] set]; - NSRectFill([windata.sdlContentView frame]); - data.shape = SDL_CalculateShapeTree(*shape_mode, shape); - - closure = [[SDL_CocoaClosure alloc] init]; - - closure.view = windata.sdlContentView; - closure.path = [NSBezierPath bezierPath]; - closure.window = shaper->window; - SDL_TraverseShapeTree(data.shape, &ConvertRects, (__bridge void *)closure); - [closure.path addClip]; - - return 0; - } -} - -#endif /* SDL_VIDEO_DRIVER_COCOA */ diff --git a/src/video/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h index 775035fd..2929fe0a 100644 --- a/src/video/cocoa/SDL_cocoavideo.h +++ b/src/video/cocoa/SDL_cocoavideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 3b6092b0..1af270e3 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,7 +27,6 @@ #endif #include "SDL_cocoavideo.h" -#include "SDL_cocoashape.h" #include "SDL_cocoavulkan.h" #include "SDL_cocoametalview.h" #include "SDL_cocoaopengles.h" @@ -69,7 +68,6 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) data = nil; } if (!data) { - SDL_OutOfMemory(); SDL_free(device); return NULL; } @@ -90,7 +88,6 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) device->SuspendScreenSaver = Cocoa_SuspendScreenSaver; device->CreateSDLWindow = Cocoa_CreateWindow; - device->CreateSDLWindowFrom = Cocoa_CreateWindowFrom; device->SetWindowTitle = Cocoa_SetWindowTitle; device->SetWindowIcon = Cocoa_SetWindowIcon; device->SetWindowPosition = Cocoa_SetWindowPosition; @@ -115,14 +112,11 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) device->SetWindowMouseGrab = Cocoa_SetWindowMouseGrab; device->SetWindowKeyboardGrab = Cocoa_SetWindowKeyboardGrab; device->DestroyWindow = Cocoa_DestroyWindow; - device->GetWindowWMInfo = Cocoa_GetWindowWMInfo; device->SetWindowHitTest = Cocoa_SetWindowHitTest; device->AcceptDragAndDrop = Cocoa_AcceptDragAndDrop; device->FlashWindow = Cocoa_FlashWindow; device->SetWindowFocusable = Cocoa_SetWindowFocusable; - - device->shape_driver.CreateShaper = Cocoa_CreateShaper; - device->shape_driver.SetWindowShape = Cocoa_SetWindowShape; + device->SyncWindow = Cocoa_SyncWindow; #ifdef SDL_VIDEO_OPENGL_CGL device->GL_LoadLibrary = Cocoa_GL_LoadLibrary; @@ -178,8 +172,8 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) device->free = Cocoa_DeleteDevice; - device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; - + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; return device; } } diff --git a/src/video/cocoa/SDL_cocoavulkan.h b/src/video/cocoa/SDL_cocoavulkan.h index 3a49b2ad..2d94c179 100644 --- a/src/video/cocoa/SDL_cocoavulkan.h +++ b/src/video/cocoa/SDL_cocoavulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int Cocoa_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void Cocoa_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/cocoa/SDL_cocoavulkan.m b/src/video/cocoa/SDL_cocoavulkan.m index 027ba2ac..edcfa441 100644 --- a/src/video/cocoa/SDL_cocoavulkan.m +++ b/src/video/cocoa/SDL_cocoavulkan.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,8 +33,6 @@ #include "SDL_cocoametalview.h" #include "SDL_cocoavulkan.h" -#include - #include const char *defaultPaths[] = { @@ -163,25 +161,22 @@ void Cocoa_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* Cocoa_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForCocoa[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; + if(count) { + *count = SDL_arraysize(extensionsForCocoa); } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForCocoa), - extensionsForCocoa); + return extensionsForCocoa; } static SDL_bool Cocoa_Vulkan_CreateSurfaceViaMetalView(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface, PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT, PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK) @@ -199,7 +194,7 @@ static SDL_bool Cocoa_Vulkan_CreateSurfaceViaMetalView(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.pLayer = (__bridge const CAMetalLayer *) Cocoa_Metal_GetLayer(_this, metalview); - result = vkCreateMetalSurfaceEXT(instance, &createInfo, NULL, surface); + result = vkCreateMetalSurfaceEXT(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { Cocoa_Metal_DestroyView(_this, metalview); SDL_SetError("vkCreateMetalSurfaceEXT failed: %s", @@ -235,6 +230,7 @@ static SDL_bool Cocoa_Vulkan_CreateSurfaceViaMetalView(SDL_VideoDevice *_this, SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = @@ -260,7 +256,7 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, return SDL_FALSE; } - if (window->flags & SDL_WINDOW_FOREIGN) { + if (window->flags & SDL_WINDOW_EXTERNAL) { @autoreleasepool { SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; if (![data.sdlContentView.layer isKindOfClass:[CAMetalLayer class]]) { @@ -273,7 +269,7 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.pNext = NULL; createInfo.flags = 0; createInfo.pLayer = (CAMetalLayer *)data.sdlContentView.layer; - result = vkCreateMetalSurfaceEXT(instance, &createInfo, NULL, surface); + result = vkCreateMetalSurfaceEXT(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateMetalSurfaceEXT failed: %s", SDL_Vulkan_GetResultString(result)); @@ -286,7 +282,7 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.pView = (__bridge const void *)data.sdlContentView; result = vkCreateMacOSSurfaceMVK(instance, &createInfo, - NULL, surface); + allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateMacOSSurfaceMVK failed: %s", SDL_Vulkan_GetResultString(result)); @@ -295,7 +291,7 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this, } } } else { - return Cocoa_Vulkan_CreateSurfaceViaMetalView(_this, window, instance, surface, vkCreateMetalSurfaceEXT, vkCreateMacOSSurfaceMVK); + return Cocoa_Vulkan_CreateSurfaceViaMetalView(_this, window, instance, allocator, surface, vkCreateMetalSurfaceEXT, vkCreateMacOSSurfaceMVK); } return SDL_TRUE; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 1408784b..96fec957 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,14 +29,16 @@ #include "../SDL_egl_c.h" #endif +#define SDL_METALVIEW_TAG 255 + @class SDL_CocoaWindowData; typedef enum { - PENDING_OPERATION_NONE, - PENDING_OPERATION_ENTER_FULLSCREEN, - PENDING_OPERATION_LEAVE_FULLSCREEN, - PENDING_OPERATION_MINIMIZE + PENDING_OPERATION_NONE = 0x00, + PENDING_OPERATION_ENTER_FULLSCREEN = 0x01, + PENDING_OPERATION_LEAVE_FULLSCREEN = 0x02, + PENDING_OPERATION_MINIMIZE = 0x04 } PendingWindowOperation; @interface Cocoa_WindowListener : NSResponder @@ -52,6 +54,7 @@ typedef enum BOOL inFullscreenTransition; PendingWindowOperation pendingWindowOperation; BOOL isMoving; + BOOL isMiniaturizing; NSInteger focusClickPending; float pendingWindowWarpX, pendingWindowWarpY; BOOL isDragAreaRunning; @@ -128,20 +131,25 @@ typedef enum @property(nonatomic) NSView *sdlContentView; @property(nonatomic) NSMutableArray *nscontexts; @property(nonatomic) SDL_bool created; -@property(nonatomic) SDL_bool inWindowFullscreenTransition; +@property(nonatomic) BOOL in_blocking_transition; +@property(nonatomic) BOOL was_zoomed; @property(nonatomic) NSInteger window_number; @property(nonatomic) NSInteger flash_request; @property(nonatomic) SDL_Window *keyboard_focus; @property(nonatomic) Cocoa_WindowListener *listener; @property(nonatomic) SDL_CocoaVideoData *videodata; +@property(nonatomic) SDL_bool send_floating_size; +@property(nonatomic) SDL_bool send_floating_position; +@property(nonatomic) SDL_bool border_toggled; +@property(nonatomic) BOOL checking_zoom; #ifdef SDL_VIDEO_OPENGL_EGL @property(nonatomic) EGLSurface egl_surface; #endif @end -extern int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int Cocoa_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, - const void *data); +extern SDL_bool b_inModeTransition; + +extern int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void Cocoa_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern int Cocoa_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); extern int Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); @@ -159,16 +167,16 @@ extern void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); extern void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); extern void Cocoa_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top); -extern void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void *Cocoa_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); extern SDL_DisplayID Cocoa_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int Cocoa_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern void Cocoa_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern int Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); +extern int Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); #endif /* SDL_cocoawindow_h_ */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index ce110528..85b18662 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,22 +26,19 @@ #error SDL for macOS must be built with a 10.9 SDK or above. #endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1090 */ -#include /* For FLT_MAX */ +#include /* For FLT_MAX */ -#include "../SDL_sysvideo.h" +#include "../../events/SDL_dropevents_c.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_touch_c.h" #include "../../events/SDL_windowevents_c.h" -#include "../../events/SDL_dropevents_c.h" +#include "../SDL_sysvideo.h" -#include "SDL_cocoavideo.h" -#include "SDL_cocoashape.h" #include "SDL_cocoamouse.h" #include "SDL_cocoaopengl.h" #include "SDL_cocoaopengles.h" - -#include +#include "SDL_cocoavideo.h" /* #define DEBUG_COCOAWINDOW */ @@ -177,7 +174,7 @@ float x, y; x = point.x; y = (sdlwindow->h - point.y); - SDL_SendDropPosition(sdlwindow, NULL, x, y); /* FIXME, should we get the filename */ + SDL_SendDropPosition(sdlwindow, x, y); return NSDragOperationGeneric; } @@ -242,7 +239,7 @@ } } - if (!SDL_SendDropFile(sdlwindow, [[fileURL path] UTF8String])) { + if (!SDL_SendDropFile(sdlwindow, NULL, [[fileURL path] UTF8String])) { return NO; } } @@ -277,7 +274,7 @@ @end -static Uint64 s_moveHack; +SDL_bool b_inModeTransition; static CGFloat SqDistanceToRect(const NSPoint *point, const NSRect *rect) { @@ -302,7 +299,8 @@ static CGFloat SqDistanceToRect(const NSPoint *point, const NSRect *rect) return delta.x * delta.x + delta.y * delta.y; } -static NSScreen *ScreenForPoint(const NSPoint *point) { +static NSScreen *ScreenForPoint(const NSPoint *point) +{ NSScreen *screen; /* Do a quick check first to see if the point lies on a specific screen*/ @@ -330,7 +328,8 @@ static NSScreen *ScreenForPoint(const NSPoint *point) { return screen; } -static NSScreen *ScreenForRect(const NSRect *rect) { +static NSScreen *ScreenForRect(const NSRect *rect) +{ NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect)); return ScreenForPoint(¢er); } @@ -550,7 +549,7 @@ static void Cocoa_UpdateClipCursor(SDL_Window *window) static void Cocoa_SetKeyboardFocus(SDL_Window *window) { SDL_Window *topmost = window; - SDL_CocoaWindowData* topmost_data; + SDL_CocoaWindowData *topmost_data; /* Find the topmost parent */ while (topmost->parent != NULL) { @@ -564,12 +563,37 @@ static void Cocoa_SetKeyboardFocus(SDL_Window *window) static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) { - NSWindow *nswindow = ((__bridge SDL_CocoaWindowData*)window->driverdata).nswindow; + NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow; if ([nswindow occlusionState] & NSWindowOcclusionStateVisible) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); } } +static void Cocoa_WaitForMiniaturizable(SDL_Window *window) +{ + NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow; + NSButton *button = [nswindow standardWindowButton:NSWindowMiniaturizeButton]; + if (button) { + int iterations = 0; + while (![button isEnabled] && (iterations < 100)) { + SDL_Delay(10); + SDL_PumpEvents(); + iterations++; + } + } +} + +static SDL_bool Cocoa_IsZoomed(SDL_Window *window) +{ + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; + + data.checking_zoom = YES; + const SDL_bool ret = [data.nswindow isZoomed]; + data.checking_zoom = NO; + + return ret; +} + @implementation Cocoa_WindowListener - (void)listen:(SDL_CocoaWindowData *)data @@ -586,6 +610,7 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) inFullscreenTransition = NO; pendingWindowOperation = PENDING_OPERATION_NONE; isMoving = NO; + isMiniaturizing = NO; isDragAreaRunning = NO; pendingWindowWarpX = pendingWindowWarpY = FLT_MAX; @@ -593,8 +618,10 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) if ([window delegate] != nil) { [center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window]; + [center addObserver:self selector:@selector(windowWillMove:) name:NSWindowWillMoveNotification object:window]; [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window]; [center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window]; + [center addObserver:self selector:@selector(windowWillMiniaturize:) name:NSWindowWillMiniaturizeNotification object:window]; [center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window]; [center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window]; [center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window]; @@ -712,9 +739,25 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) return inFullscreenTransition; } +- (void)clearPendingWindowOperation:(PendingWindowOperation)operation +{ + pendingWindowOperation &= ~operation; +} + - (void)addPendingWindowOperation:(PendingWindowOperation)operation { - pendingWindowOperation = operation; + pendingWindowOperation |= operation; +} + +- (BOOL)windowOperationIsPending:(PendingWindowOperation)operation +{ + return !!(pendingWindowOperation & operation); +} + +- (BOOL)hasPendingWindowOperation +{ + return pendingWindowOperation != PENDING_OPERATION_NONE || + isMiniaturizing || inFullscreenTransition; } - (void)close @@ -727,8 +770,10 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) if ([window delegate] != self) { [center removeObserver:self name:NSWindowDidExposeNotification object:window]; + [center removeObserver:self name:NSWindowWillMoveNotification object:window]; [center removeObserver:self name:NSWindowDidMoveNotification object:window]; [center removeObserver:self name:NSWindowDidResizeNotification object:window]; + [center removeObserver:self name:NSWindowWillMiniaturizeNotification object:window]; [center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window]; [center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window]; [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window]; @@ -864,27 +909,11 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; ConvertNSRect(fullscreen, &rect); - if (inFullscreenTransition) { + if (inFullscreenTransition || b_inModeTransition) { /* We'll take care of this at the end of the transition */ return; } - if (s_moveHack) { - SDL_bool blockMove = ((SDL_GetTicks() - s_moveHack) < 500); - - s_moveHack = 0; - - if (blockMove) { - /* Cocoa is adjusting the window in response to a mode change */ - SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y ); - rect.origin.x = x; - rect.origin.y = y; - ConvertNSRect(fullscreen, &rect); - [nswindow setFrameOrigin:rect.origin]; - return; - } - } - x = (int)rect.origin.x; y = (int)rect.origin.y; @@ -895,6 +924,34 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); } +- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize +{ + SDL_Window *window = _data.window; + + /* XXX: Calling [isZoomed] calls this function, and calling [isZoomed] + * from within this function will recurse until the stack overflows, + * so a recursion guard is required. + */ + if (!_data.checking_zoom) { + _data.checking_zoom = YES; + if ([_data.nswindow isZoomed] && !_data.was_zoomed && _data.send_floating_size) { + NSRect rect; + + _data.send_floating_size = NO; + rect.origin.x = window->floating.x; + rect.origin.y = window->floating.y; + rect.size.width = window->floating.w; + rect.size.height = window->floating.h; + ConvertNSRect(SDL_FALSE, &rect); + + frameSize = rect.size; + } + _data.checking_zoom = NO; + } + + return frameSize; +} + - (void)windowDidResize:(NSNotification *)aNotification { SDL_Window *window; @@ -903,7 +960,8 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) int x, y, w, h; BOOL zoomed; BOOL fullscreen; - if (inFullscreenTransition) { + + if (inFullscreenTransition || b_inModeTransition) { /* We'll take care of this at the end of the transition */ return; } @@ -912,7 +970,6 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) focusClickPending = 0; [self onMovingOrFocusClickPendingStateCleared]; } - window = _data.window; nswindow = _data.nswindow; rect = [nswindow contentRectForFrameRect:[nswindow frame]]; @@ -925,14 +982,11 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) ScheduleContextUpdates(_data); - /* The window can move during a resize event, such as when maximizing - or resizing from a corner */ - SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h); - - /* isZoomed always returns true if the window is not resizable */ - if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) { + /* isZoomed always returns true if the window is not resizable + * and fullscreen windows are considered zoomed. + */ + if ((window->flags & SDL_WINDOW_RESIZABLE) && Cocoa_IsZoomed(window) && + !(window->flags & SDL_WINDOW_FULLSCREEN) && ![self isInFullscreenSpace]) { zoomed = YES; } else { zoomed = NO; @@ -941,7 +995,22 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); } else if (zoomed) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { + [nswindow miniaturize:nil]; + } } + + /* The window can move during a resize event, such as when maximizing + or resizing from a corner */ + SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h); +} + +- (void)windowWillMiniaturize:(NSNotification *)aNotification +{ + isMiniaturizing = YES; + Cocoa_WaitForMiniaturizable(_data.window); } - (void)windowDidMiniaturize:(NSNotification *)aNotification @@ -950,16 +1019,23 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) focusClickPending = 0; [self onMovingOrFocusClickPendingStateCleared]; } + isMiniaturizing = NO; + [self clearPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); } - (void)windowDidDeminiaturize:(NSNotification *)aNotification { - /* isZoomed always returns true if the window is not resizable */ - if ((_data.window->flags & SDL_WINDOW_RESIZABLE) && [_data.nswindow isZoomed]) { + /* Always send restored before maximized. */ + SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + + /* isZoomed always returns true if the window is not resizable. */ + if ((_data.window->flags & SDL_WINDOW_RESIZABLE) && Cocoa_IsZoomed(_data.window)) { SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); - } else { - SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + + if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) { + SDL_UpdateFullscreenMode(_data.window, SDL_TRUE, SDL_TRUE); } } @@ -1064,8 +1140,15 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) - (void)windowWillEnterFullScreen:(NSNotification *)aNotification { SDL_Window *window = _data.window; + const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled; - SetWindowStyle(window, (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable)); + /* For some reason, the fullscreen window won't get any mouse button events + * without the NSWindowStyleMaskTitled flag being set when entering fullscreen, + * so it's needed even if the window is borderless. + */ + SetWindowStyle(window, flags); + + _data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED); isFullscreenSpace = YES; inFullscreenTransition = YES; @@ -1081,6 +1164,7 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) SetWindowStyle(window, GetWindowStyle(window)); + [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; isFullscreenSpace = NO; inFullscreenTransition = NO; @@ -1092,16 +1176,24 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) SDL_Window *window = _data.window; inFullscreenTransition = NO; + [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; - if (pendingWindowOperation == PENDING_OPERATION_LEAVE_FULLSCREEN) { - pendingWindowOperation = PENDING_OPERATION_NONE; + if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) { [self setFullscreenSpace:NO]; } else { if (window->fullscreen_exclusive) { [NSMenu setMenuBarVisible:NO]; } - pendingWindowOperation = PENDING_OPERATION_NONE; + /* Don't recurse back into UpdateFullscreenMode() if this was hit in + * a blocking transition, as the caller is already waiting in + * UpdateFullscreenMode(). + */ + if (!_data.in_blocking_transition) { + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_FALSE); + } + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); + /* Force the size change event in case it was delivered earlier while the window was still animating into place. */ @@ -1116,29 +1208,31 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) { SDL_Window *window = _data.window; + /* If the windowed mode borders were toggled on while in a fullscreen space, + * NSWindowStyleMaskTitled has to be cleared here, or the window can end up + * in a weird, semi-decorated state upon returning to windowed mode. + */ + if (_data.border_toggled && !(window->flags & SDL_WINDOW_BORDERLESS)) { + const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + + SetWindowStyle(window, flags); + _data.border_toggled = SDL_FALSE; + } + isFullscreenSpace = NO; inFullscreenTransition = YES; - - /* As of macOS 10.11, the window seems to need to be resizable when exiting - a Space, in order for it to resize back to its windowed-mode size. - As of macOS 10.15, the window decorations can go missing sometimes after - certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows - sometimes. Making sure the style mask always uses the windowed mode style - when returning to windowed mode from a space (instead of using a pending - fullscreen mode style mask) seems to work around that issue. - */ - SetWindowStyle(window, GetWindowWindowedStyle(window) | NSWindowStyleMaskResizable); } - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification { SDL_Window *window = _data.window; + const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (window->is_destroying) { return; } - - SetWindowStyle(window, (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable)); + + SetWindowStyle(window, flags); isFullscreenSpace = YES; inFullscreenTransition = NO; @@ -1150,7 +1244,6 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) { SDL_Window *window = _data.window; NSWindow *nswindow = _data.nswindow; - NSButton *button = nil; inFullscreenTransition = NO; @@ -1162,17 +1255,35 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) */ SetWindowStyle(window, GetWindowWindowedStyle(window)); + /* Don't recurse back into UpdateFullscreenMode() if this was hit in + * a blocking transition, as the caller is already waiting in + * UpdateFullscreenMode(). + */ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + if (!_data.in_blocking_transition) { + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE); + } + if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { [nswindow setLevel:NSFloatingWindowLevel]; } else { [nswindow setLevel:kCGNormalWindowLevel]; } - if (pendingWindowOperation == PENDING_OPERATION_ENTER_FULLSCREEN) { - pendingWindowOperation = PENDING_OPERATION_NONE; + [self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; + + if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) { [self setFullscreenSpace:YES]; - } else if (pendingWindowOperation == PENDING_OPERATION_MINIMIZE) { - pendingWindowOperation = PENDING_OPERATION_NONE; + } else if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { + /* There's some state that isn't quite back to normal when + * windowDidExitFullScreen triggers. For example, the minimize button on + * the title bar doesn't actually enable for another 200 milliseconds or + * so on this MacBook. Camp here and wait for that to happen before + * going on, in case we're exiting fullscreen to minimize, which need + * that window state to be normal before it will work. + */ + Cocoa_WaitForMiniaturizable(_data.window); + [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; [nswindow miniaturize:nil]; } else { /* Adjust the fullscreen toggle button and readd menu now that we're here. */ @@ -1184,62 +1295,34 @@ static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) } [NSMenu setMenuBarVisible:YES]; - pendingWindowOperation = PENDING_OPERATION_NONE; - -#if 0 -/* This fixed bug 3719, which is that changing window size while fullscreen - doesn't take effect when leaving fullscreen, but introduces bug 3809, - which is that a maximized window doesn't go back to normal size when - restored, so this code is disabled until we can properly handle the - beginning and end of maximize and restore. - */ /* Restore windowed size and position in case it changed while fullscreen */ - { - int x, y; - NSRect rect; - SDL_RelativeToGlobalForWindow(window, window->windowed.x, window->windowed.y, x, y); - rect.origin.x = x; - rect.origin.y = y; - rect.size.width = window->windowed.w; - rect.size.height = window->windowed.h; - ConvertNSRect(NO, &rect); + NSRect rect; + rect.origin.x = _data.was_zoomed ? window->windowed.x : window->floating.x; + rect.origin.y = _data.was_zoomed ? window->windowed.y : window->floating.y; + rect.size.width = _data.was_zoomed ? window->windowed.w : window->floating.w; + rect.size.height = _data.was_zoomed ? window->windowed.h : window->floating.h; + ConvertNSRect(NO, &rect); - s_moveHack = 0; - [nswindow setContentSize:rect.size]; - [nswindow setFrameOrigin:rect.origin]; - s_moveHack = SDL_GetTicks(); - } -#endif /* 0 */ + _data.send_floating_position = NO; + _data.send_floating_size = NO; + [nswindow setContentSize:rect.size]; + [nswindow setFrameOrigin:rect.origin]; /* Force the size change event in case it was delivered earlier - while the window was still animating into place. + * while the window was still animating into place. */ window->w = 0; window->h = 0; [self windowDidMove:aNotification]; [self windowDidResize:aNotification]; + _data.was_zoomed = SDL_FALSE; + /* FIXME: Why does the window get hidden? */ if (!(window->flags & SDL_WINDOW_HIDDEN)) { Cocoa_ShowWindow(SDL_GetVideoDevice(), window); } } - - /* There's some state that isn't quite back to normal when - windowDidExitFullScreen triggers. For example, the minimize button on - the titlebar doesn't actually enable for another 200 milliseconds or - so on this MacBook. Camp here and wait for that to happen before - going on, in case we're exiting fullscreen to minimize, which need - that window state to be normal before it will work. */ - button = [nswindow standardWindowButton:NSWindowMiniaturizeButton]; - if (button) { - int iterations = 0; - while (![button isEnabled] && (iterations < 100)) { - SDL_Delay(10); - SDL_PumpEvents(); - iterations++; - } - } } - (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions @@ -1727,6 +1810,21 @@ static int Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_ return YES; } +- (void)resetCursorRects +{ + SDL_Mouse *mouse; + [super resetCursorRects]; + mouse = SDL_GetMouse(); + + if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) { + [self addCursorRect:[self bounds] + cursor:(__bridge NSCursor *)mouse->cur_cursor->driverdata]; + } else { + [self addCursorRect:[self bounds] + cursor:[NSCursor invisibleCursor]]; + } +} + - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { @@ -1738,7 +1836,7 @@ static int Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_ @end -static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview, SDL_bool created) +static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview) { @autoreleasepool { SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->driverdata; @@ -1751,7 +1849,6 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow } data.window = window; data.nswindow = nswindow; - data.created = created; data.videodata = videodata; data.window_number = nswindow.windowNumber; data.nscontexts = [[NSMutableArray alloc] init]; @@ -1801,7 +1898,7 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow } /* isZoomed always returns true if the window is not resizable */ - if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) { + if ((window->flags & SDL_WINDOW_RESIZABLE) && Cocoa_IsZoomed(window)) { window->flags |= SDL_WINDOW_MAXIMIZED; } else { window->flags &= ~SDL_WINDOW_MAXIMIZED; @@ -1829,6 +1926,16 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow Cocoa_SetKeyboardFocus(window); } } + + /* FIXME: Should not need to call addChildWindow then orderOut. + Attaching a hidden child window to a hidden parent window will cause the child window + to show when the parent does. We therefore shouldn't attach the child window here as we're + going to do so when the child window is explicitly shown later but skipping the addChildWindow + entirely causes the child window to not get key focus correctly the first time it's shown. Adding + then immediately ordering out (removing) the window does work. */ + if (window->flags & SDL_WINDOW_HIDDEN) { + [nswindow orderOut:nil]; + } } if (nswindow.isOpaque) { @@ -1848,75 +1955,117 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow */ [nswindow setOneShot:NO]; + if (window->flags & SDL_WINDOW_EXTERNAL) { + /* Query the title from the existing window */ + NSString *title = [nswindow title]; + if (title) { + window->title = SDL_strdup([title UTF8String]); + } + } + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_COCOA_WINDOW_POINTER, (__bridge void *)data.nswindow); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG); + /* All done! */ window->driverdata = (SDL_WindowData *)CFBridgingRetain(data); return 0; } } -int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { @autoreleasepool { SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->driverdata; - NSWindow *nswindow; - int x, y; - NSScreen *screen; - NSRect rect, screenRect; - BOOL fullscreen; - NSUInteger style; - SDLView *contentView; - BOOL highdpi; + const void *data = SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL); + NSWindow *nswindow = nil; + NSView *nsview = nil; - SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y); - rect.origin.x = x; - rect.origin.y = y; - rect.size.width = window->w; - rect.size.height = window->h; - fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; - ConvertNSRect(fullscreen, &rect); - - style = GetWindowStyle(window); - - /* Figure out which screen to place this window */ - screen = ScreenForRect(&rect); - screenRect = [screen frame]; - rect.origin.x -= screenRect.origin.x; - rect.origin.y -= screenRect.origin.y; - - /* Constrain the popup */ - if (SDL_WINDOW_IS_POPUP(window)) { - if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { - rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); + if (data) { + if ([(__bridge id)data isKindOfClass:[NSWindow class]]) { + nswindow = (__bridge NSWindow *)data; + } else if ([(__bridge id)data isKindOfClass:[NSView class]]) { + nsview = (__bridge NSView *)data; + } else { + SDL_assert(false); } - if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { - rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); + } else { + nswindow = (__bridge NSWindow *)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_COCOA_WINDOW_POINTER, NULL); + nsview = (__bridge NSView *)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_COCOA_VIEW_POINTER, NULL); + } + if (nswindow && !nsview) { + nsview = [nswindow contentView]; + } + if (nsview && !nswindow) { + nswindow = [nsview window]; + } + if (nswindow) { + window->flags |= SDL_WINDOW_EXTERNAL; + } else { + int x, y; + NSScreen *screen; + NSRect rect, screenRect; + BOOL fullscreen; + NSUInteger style; + SDLView *contentView; + + SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y); + rect.origin.x = x; + rect.origin.y = y; + rect.size.width = window->w; + rect.size.height = window->h; + fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; + ConvertNSRect(fullscreen, &rect); + + style = GetWindowStyle(window); + + /* Figure out which screen to place this window */ + screen = ScreenForRect(&rect); + screenRect = [screen frame]; + rect.origin.x -= screenRect.origin.x; + rect.origin.y -= screenRect.origin.y; + + /* Constrain the popup */ + if (SDL_WINDOW_IS_POPUP(window)) { + if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { + rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); + } + if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { + rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); + } + rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); + rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); } - rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); - rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); - } - @try { - nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen]; - } - @catch (NSException *e) { - return SDL_SetError("%s", [[e reason] UTF8String]); - } + @try { + nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen]; + } + @catch (NSException *e) { + return SDL_SetError("%s", [[e reason] UTF8String]); + } - [nswindow setColorSpace:[NSColorSpace sRGBColorSpace]]; + [nswindow setColorSpace:[NSColorSpace sRGBColorSpace]]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 /* Added in the 10.12.0 SDK. */ - /* By default, don't allow users to make our window tabbed in 10.12 or later */ - if ([nswindow respondsToSelector:@selector(setTabbingMode:)]) { - [nswindow setTabbingMode:NSWindowTabbingModeDisallowed]; - } + /* By default, don't allow users to make our window tabbed in 10.12 or later */ + if ([nswindow respondsToSelector:@selector(setTabbingMode:)]) { + [nswindow setTabbingMode:NSWindowTabbingModeDisallowed]; + } #endif - if (videodata.allow_spaces) { - /* we put fullscreen desktop windows in their own Space, without a toggle button or menubar, later */ - if (window->flags & SDL_WINDOW_RESIZABLE) { - /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */ - [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + if (videodata.allow_spaces) { + /* we put fullscreen desktop windows in their own Space, without a toggle button or menubar, later */ + if (window->flags & SDL_WINDOW_RESIZABLE) { + /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */ + [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + } } + + /* Create a default view for this window */ + rect = [nswindow contentRectForFrameRect:[nswindow frame]]; + contentView = [[SDLView alloc] initWithFrame:rect]; + [contentView setSDLWindow:window]; + nsview = contentView; } if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { @@ -1929,11 +2078,6 @@ int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) nswindow.backgroundColor = [NSColor clearColor]; } - /* Create a default view for this window */ - rect = [nswindow contentRectForFrameRect:[nswindow frame]]; - contentView = [[SDLView alloc] initWithFrame:rect]; - [contentView setSDLWindow:window]; - /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */ #ifdef __clang__ #pragma clang diagnostic push @@ -1941,8 +2085,8 @@ int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) #endif /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when * the NSHighResolutionCapable boolean is set in Info.plist. */ - highdpi = (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) ? YES : NO; - [contentView setWantsBestResolutionOpenGLSurface:highdpi]; + BOOL highdpi = (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) ? YES : NO; + [nsview setWantsBestResolutionOpenGLSurface:highdpi]; #ifdef __clang__ #pragma clang diagnostic pop #endif @@ -1951,19 +2095,19 @@ int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) #ifdef SDL_VIDEO_OPENGL_EGL if ((window->flags & SDL_WINDOW_OPENGL) && _this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) { - [contentView setWantsLayer:TRUE]; + [nsview setWantsLayer:TRUE]; if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) && [nswindow.screen respondsToSelector:@selector(backingScaleFactor)]) { - contentView.layer.contentsScale = nswindow.screen.backingScaleFactor; + nsview.layer.contentsScale = nswindow.screen.backingScaleFactor; } else { - contentView.layer.contentsScale = 1; + nsview.layer.contentsScale = 1; } } #endif /* SDL_VIDEO_OPENGL_EGL */ #endif /* SDL_VIDEO_OPENGL_ES2 */ - [nswindow setContentView:contentView]; + [nswindow setContentView:nsview]; - if (SetupWindowData(_this, window, nswindow, contentView, SDL_TRUE) < 0) { + if (SetupWindowData(_this, window, nswindow, nsview) < 0) { return -1; } @@ -1989,47 +2133,6 @@ int Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } } -int Cocoa_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - @autoreleasepool { - NSView *nsview = nil; - NSWindow *nswindow = nil; - NSString *title; - BOOL highdpi; - - if ([(__bridge id)data isKindOfClass:[NSWindow class]]) { - nswindow = (__bridge NSWindow *)data; - nsview = [nswindow contentView]; - } else if ([(__bridge id)data isKindOfClass:[NSView class]]) { - nsview = (__bridge NSView *)data; - nswindow = [nsview window]; - } else { - SDL_assert(false); - } - - /* Query the title from the existing window */ - title = [nswindow title]; - if (title) { - window->title = SDL_strdup([title UTF8String]); - } - -/* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */ -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when - * the NSHighResolutionCapable boolean is set in Info.plist. */ - highdpi = (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) ? YES : NO; - [nsview setWantsBestResolutionOpenGLSurface:highdpi]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - - return SetupWindowData(_this, window, nswindow, nsview, SDL_FALSE); - } -} - void Cocoa_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { @autoreleasepool { @@ -2061,36 +2164,49 @@ int Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata; NSWindow *nswindow = windata.nswindow; NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; - BOOL fullscreen; - Uint64 moveHack; + BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; int x, y; - SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y); - rect.origin.x = x; - rect.origin.y = y; - fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; - ConvertNSRect(fullscreen, &rect); - - /* Position and constrain the popup */ - if (SDL_WINDOW_IS_POPUP(window)) { - NSRect screenRect = [ScreenForRect(&rect) frame]; - - if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { - rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); - } - if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { - rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); - } - rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); - rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); + if ([windata.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] || + [windata.listener isInFullscreenSpaceTransition]) { + Cocoa_SyncWindow(_this, window); } - moveHack = s_moveHack; - s_moveHack = 0; - [nswindow setFrameOrigin:rect.origin]; - s_moveHack = moveHack; + if (!(window->flags & SDL_WINDOW_MAXIMIZED)) { + if (fullscreen) { + SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window); + SDL_Rect r; + SDL_GetDisplayBounds(display->id, &r); - ScheduleContextUpdates(windata); + rect.origin.x = r.x; + rect.origin.y = r.y; + } else { + SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, &x, &y); + rect.origin.x = x; + rect.origin.y = y; + } + ConvertNSRect(fullscreen, &rect); + + /* Position and constrain the popup */ + if (SDL_WINDOW_IS_POPUP(window)) { + NSRect screenRect = [ScreenForRect(&rect) frame]; + + if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { + rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); + } + if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { + rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); + } + rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); + rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); + } + + [nswindow setFrameOrigin:rect.origin]; + + ScheduleContextUpdates(windata); + } else { + windata.send_floating_position = SDL_TRUE; + } } return 0; } @@ -2101,17 +2217,26 @@ void Cocoa_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata; NSWindow *nswindow = windata.nswindow; NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; - Uint64 moveHack; - rect.size.width = window->w; - rect.size.height = window->h; + rect.size.width = window->floating.w; + rect.size.height = window->floating.h; - moveHack = s_moveHack; - s_moveHack = 0; - [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES]; - s_moveHack = moveHack; + if ([windata.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] || + [windata.listener isInFullscreenSpaceTransition]) { + Cocoa_SyncWindow(_this, window); + } - ScheduleContextUpdates(windata); + /* isZoomed always returns true if the window is not resizable */ + if (!(window->flags & SDL_WINDOW_RESIZABLE) || !Cocoa_IsZoomed(window)) { + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES]; + ScheduleContextUpdates(windata); + } else if (windata.was_zoomed) { + windata.send_floating_size = SDL_TRUE; + } + } else { + windata.send_floating_size = SDL_TRUE; + } } } @@ -2254,9 +2379,17 @@ void Cocoa_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata; NSWindow *nswindow = windata.nswindow; - [nswindow zoom:nil]; + if ([windata.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] || + [windata.listener isInFullscreenSpaceTransition]) { + Cocoa_SyncWindow(_this, window); + } - ScheduleContextUpdates(windata); + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && + ![windata.listener isInFullscreenSpaceTransition] && + ![windata.listener isInFullscreenSpace]) { + [nswindow zoom:nil]; + ScheduleContextUpdates(windata); + } } } @@ -2265,8 +2398,13 @@ void Cocoa_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) @autoreleasepool { SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; NSWindow *nswindow = data.nswindow; - if ([data.listener isInFullscreenSpaceTransition]) { - [data.listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; + + [data.listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; + if ([data.listener isInFullscreenSpace] || (window->flags & SDL_WINDOW_FULLSCREEN)) { + [data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_TRUE); + } else if ([data.listener isInFullscreenSpaceTransition]) { + [data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; } else { [nswindow miniaturize:nil]; } @@ -2276,12 +2414,42 @@ void Cocoa_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { @autoreleasepool { - NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow; + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; + NSWindow *nswindow = data.nswindow; - if ([nswindow isMiniaturized]) { - [nswindow deminiaturize:nil]; - } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) { - [nswindow zoom:nil]; + if ([data.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] || + [data.listener isInFullscreenSpaceTransition]) { + Cocoa_SyncWindow(_this, window); + } + + [data.listener clearPendingWindowOperation:(PENDING_OPERATION_MINIMIZE)]; + + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && + ![data.listener isInFullscreenSpaceTransition] && + ![data.listener isInFullscreenSpace]) { + if ([nswindow isMiniaturized]) { + [nswindow deminiaturize:nil]; + } else if ((window->flags & SDL_WINDOW_RESIZABLE) && Cocoa_IsZoomed(window)) { + NSRect rect; + + /* Update the floating coordinates */ + rect.origin.x = window->floating.x; + rect.origin.y = window->floating.y; + + /* The floating size will be set in windowWillResize */ + [nswindow zoom:nil]; + + rect.size.width = window->floating.w; + rect.size.height = window->floating.h; + + ConvertNSRect(SDL_FALSE, &rect); + + if (data.send_floating_position) { + data.send_floating_position = SDL_FALSE; + [nswindow setFrameOrigin:rect.origin]; + ScheduleContextUpdates(data); + } + } } } } @@ -2289,10 +2457,17 @@ void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered) { @autoreleasepool { - if (SetWindowStyle(window, GetWindowStyle(window))) { - if (bordered) { - Cocoa_SetWindowTitle(_this, window); /* this got blanked out. */ + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; + + /* If the window is in or transitioning to/from fullscreen, this will be set on leave. */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) { + if (SetWindowStyle(window, GetWindowStyle(window))) { + if (bordered) { + Cocoa_SetWindowTitle(_this, window); /* this got blanked out. */ + } } + } else { + data.border_toggled = SDL_TRUE; } } } @@ -2300,7 +2475,7 @@ void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_boo void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable) { @autoreleasepool { - /* Don't set this if we're in a space! + /* Don't set this if we're in or transitioning to/from a space! * The window will get permanently stuck if resizable is false. * -flibit */ @@ -2308,7 +2483,7 @@ void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bo Cocoa_WindowListener *listener = data.listener; NSWindow *nswindow = data.nswindow; SDL_CocoaVideoData *videodata = data.videodata; - if (![listener isInFullscreenSpace]) { + if (![listener isInFullscreenSpace] && ![listener isInFullscreenSpaceTransition]) { SetWindowStyle(window, GetWindowStyle(window)); } if (videodata.allow_spaces) { @@ -2325,16 +2500,21 @@ void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bo void Cocoa_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top) { @autoreleasepool { - NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow; - if (on_top) { - [nswindow setLevel:NSFloatingWindowLevel]; - } else { - [nswindow setLevel:kCGNormalWindowLevel]; + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; + NSWindow *nswindow = data.nswindow; + + /* If the window is in or transitioning to/from fullscreen, this will be set on leave. */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) { + if (on_top) { + [nswindow setLevel:NSFloatingWindowLevel]; + } else { + [nswindow setLevel:kCGNormalWindowLevel]; + } } } } -void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +int Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { @autoreleasepool { SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; @@ -2349,6 +2529,11 @@ void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V if (fullscreen) { SDL_Rect bounds; + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED); + } + + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); Cocoa_GetDisplayBounds(_this, display, &bounds); rect.origin.x = bounds.x; rect.origin.y = bounds.y; @@ -2369,10 +2554,14 @@ void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V [nswindow setStyleMask:NSWindowStyleMaskBorderless]; } else { NSRect frameRect; - rect.origin.x = window->windowed.x; - rect.origin.y = window->windowed.y; - rect.size.width = window->windowed.w; - rect.size.height = window->windowed.h; + + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + + rect.origin.x = data.was_zoomed ? window->windowed.x : window->floating.x; + rect.origin.y = data.was_zoomed ? window->windowed.y : window->floating.y; + rect.size.width = data.was_zoomed ? window->windowed.w : window->floating.w; + rect.size.height = data.was_zoomed ? window->windowed.h : window->floating.h; + ConvertNSRect(fullscreen, &rect); /* The window is not meant to be fullscreen, but its flags might have a @@ -2394,14 +2583,20 @@ void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V [data.sdlContentView setNextResponder:data.listener]; } - s_moveHack = 0; [nswindow setContentSize:rect.size]; [nswindow setFrameOrigin:rect.origin]; - s_moveHack = SDL_GetTicks(); /* When the window style changes the title is cleared */ if (!fullscreen) { Cocoa_SetWindowTitle(_this, window); + + data.was_zoomed = NO; + + if ([data.listener windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { + Cocoa_WaitForMiniaturizable(window); + [data.listener addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; + [nswindow miniaturize:nil]; + } } if (SDL_ShouldAllowTopmost() && fullscreen) { @@ -2421,6 +2616,8 @@ void Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V ScheduleContextUpdates(data); } + + return 0; } void *Cocoa_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) @@ -2450,7 +2647,6 @@ void *Cocoa_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size retIccProfileData = SDL_malloc([iccProfileData length]); if (!retIccProfileData) { - SDL_OutOfMemory(); return NULL; } @@ -2554,28 +2750,11 @@ void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } #endif /* SDL_VIDEO_OPENGL */ - - if (window->shaper) { - CFBridgingRelease(window->shaper->driverdata); - SDL_free(window->shaper); - window->shaper = NULL; - } } window->driverdata = NULL; } } -int Cocoa_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - @autoreleasepool { - NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow; - - info->subsystem = SDL_SYSWM_COCOA; - info->info.cocoa.window = nswindow; - return 0; - } -} - SDL_bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) { @autoreleasepool { @@ -2589,47 +2768,46 @@ SDL_bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) } } -SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool state) +SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool state, SDL_bool blocking) { @autoreleasepool { SDL_bool succeeded = SDL_FALSE; SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; - if (data.inWindowFullscreenTransition) { - return SDL_FALSE; - } - - data.inWindowFullscreenTransition = SDL_TRUE; + data.in_blocking_transition = blocking; if ([data.listener setFullscreenSpace:(state ? YES : NO)]) { - const int maxattempts = 3; - int attempt = 0; - while (++attempt <= maxattempts) { - /* Wait for the transition to complete, so application changes - take effect properly (e.g. setting the window size, etc.) - */ - const int limit = 10000; - int count = 0; - while ([data.listener isInFullscreenSpaceTransition]) { - if (++count == limit) { - /* Uh oh, transition isn't completing. Should we assert? */ + if (blocking) { + const int maxattempts = 3; + int attempt = 0; + while (++attempt <= maxattempts) { + /* Wait for the transition to complete, so application changes + take effect properly (e.g. setting the window size, etc.) + */ + const int limit = 10000; + int count = 0; + while ([data.listener isInFullscreenSpaceTransition]) { + if (++count == limit) { + /* Uh oh, transition isn't completing. Should we assert? */ + break; + } + SDL_Delay(1); + SDL_PumpEvents(); + } + if ([data.listener isInFullscreenSpace] == (state ? YES : NO)) { break; } - SDL_Delay(1); - SDL_PumpEvents(); - } - if ([data.listener isInFullscreenSpace] == (state ? YES : NO)) { - break; - } - /* Try again, the last attempt was interrupted by user gestures */ - if (![data.listener setFullscreenSpace:(state ? YES : NO)]) { - break; /* ??? */ + /* Try again, the last attempt was interrupted by user gestures */ + if (![data.listener setFullscreenSpace:(state ? YES : NO)]) { + break; /* ??? */ + } } } + /* Return TRUE to prevent non-space fullscreen logic from running */ succeeded = SDL_TRUE; } - data.inWindowFullscreenTransition = SDL_FALSE; + data.in_blocking_transition = NO; return succeeded; } } @@ -2693,4 +2871,32 @@ int Cocoa_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opa } } +int Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + int ret = 0; + + @autoreleasepool { + /* The timeout needs to be high enough that animated fullscreen + * spaces transitions won't cause it to time out. + */ + Uint64 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(2000); + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->driverdata; + while (SDL_TRUE) { + SDL_PumpEvents(); + + if (SDL_GetTicksNS() >= timeout) { + ret = 1; + break; + } + if (![data.listener hasPendingWindowOperation]) { + break; + } + + SDL_Delay(10); + } + } + + return ret; +} + #endif /* SDL_VIDEO_DRIVER_COCOA */ diff --git a/src/video/dummy/SDL_nullevents.c b/src/video/dummy/SDL_nullevents.c index 51e2a8f1..6849efbb 100644 --- a/src/video/dummy/SDL_nullevents.c +++ b/src/video/dummy/SDL_nullevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/dummy/SDL_nullevents_c.h b/src/video/dummy/SDL_nullevents_c.h index 5cae376b..5b1e15fe 100644 --- a/src/video/dummy/SDL_nullevents_c.h +++ b/src/video/dummy/SDL_nullevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/dummy/SDL_nullframebuffer.c b/src/video/dummy/SDL_nullframebuffer.c index ba7f43fc..87d1b1be 100644 --- a/src/video/dummy/SDL_nullframebuffer.c +++ b/src/video/dummy/SDL_nullframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,14 @@ #include "../SDL_sysvideo.h" #include "SDL_nullframebuffer_c.h" -#define DUMMY_SURFACE "_SDL_DummySurface" +#define DUMMY_SURFACE "SDL.internal.window.surface" + +static void CleanupSurface(void *userdata, void *value) +{ + SDL_Surface *surface = (SDL_Surface *)value; + + SDL_DestroySurface(surface); +} int SDL_DUMMY_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch) { @@ -33,18 +40,15 @@ int SDL_DUMMY_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window const Uint32 surface_format = SDL_PIXELFORMAT_XRGB8888; int w, h; - /* Free the old framebuffer surface */ - SDL_DUMMY_DestroyWindowFramebuffer(_this, window); - - /* Create a new one */ + /* Create a new framebuffer */ SDL_GetWindowSizeInPixels(window, &w, &h); surface = SDL_CreateSurface(w, h, surface_format); - if (surface == NULL) { + if (!surface) { return -1; } /* Save the info and return! */ - SDL_SetWindowData(window, DUMMY_SURFACE, surface); + SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), DUMMY_SURFACE, surface, CleanupSurface, NULL); *format = surface_format; *pixels = surface->pixels; *pitch = surface->pitch; @@ -56,8 +60,8 @@ int SDL_DUMMY_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window static int frame_number; SDL_Surface *surface; - surface = (SDL_Surface *)SDL_GetWindowData(window, DUMMY_SURFACE); - if (surface == NULL) { + surface = (SDL_Surface *)SDL_GetProperty(SDL_GetWindowProperties(window), DUMMY_SURFACE, NULL); + if (!surface) { return SDL_SetError("Couldn't find dummy surface for window"); } @@ -73,10 +77,7 @@ int SDL_DUMMY_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window void SDL_DUMMY_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_Surface *surface; - - surface = (SDL_Surface *)SDL_SetWindowData(window, DUMMY_SURFACE, NULL); - SDL_DestroySurface(surface); + SDL_ClearProperty(SDL_GetWindowProperties(window), DUMMY_SURFACE); } #endif /* SDL_VIDEO_DRIVER_DUMMY */ diff --git a/src/video/dummy/SDL_nullframebuffer_c.h b/src/video/dummy/SDL_nullframebuffer_c.h index 41ecfaaf..d1e51e91 100644 --- a/src/video/dummy/SDL_nullframebuffer_c.h +++ b/src/video/dummy/SDL_nullframebuffer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/dummy/SDL_nullvideo.c b/src/video/dummy/SDL_nullvideo.c index d2bd677b..76336d6f 100644 --- a/src/video/dummy/SDL_nullvideo.c +++ b/src/video/dummy/SDL_nullvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -55,6 +55,17 @@ static int DUMMY_VideoInit(SDL_VideoDevice *_this); static void DUMMY_VideoQuit(SDL_VideoDevice *_this); +static int DUMMY_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, window->floating.x, window->floating.y); + return 0; +} + +static void DUMMY_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->floating.w, window->floating.h); +} + /* DUMMY driver bootstrap functions */ static int DUMMY_Available(const char *enable_hint) @@ -83,8 +94,7 @@ static SDL_VideoDevice *DUMMY_InternalCreateDevice(const char *enable_hint) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } device->is_dummy = SDL_TRUE; @@ -93,6 +103,8 @@ static SDL_VideoDevice *DUMMY_InternalCreateDevice(const char *enable_hint) device->VideoInit = DUMMY_VideoInit; device->VideoQuit = DUMMY_VideoQuit; device->PumpEvents = DUMMY_PumpEvents; + device->SetWindowSize = DUMMY_SetWindowSize; + device->SetWindowPosition = DUMMY_SetWindowPosition; device->CreateWindowFramebuffer = SDL_DUMMY_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = SDL_DUMMY_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = SDL_DUMMY_DestroyWindowFramebuffer; diff --git a/src/video/dummy/SDL_nullvideo.h b/src/video/dummy/SDL_nullvideo.h index d7ee2fad..97534133 100644 --- a/src/video/dummy/SDL_nullvideo.h +++ b/src/video/dummy/SDL_nullvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c index e862bbc8..271e870a 100644 --- a/src/video/emscripten/SDL_emscriptenevents.c +++ b/src/video/emscripten/SDL_emscriptenevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -759,8 +759,16 @@ static EM_BOOL Emscripten_HandleTouch(int eventType, const EmscriptenTouchEvent } id = touchEvent->touches[i].identifier; - x = touchEvent->touches[i].targetX / client_w; - y = touchEvent->touches[i].targetY / client_h; + if (client_w <= 1) { + x = 0.5f; + } else { + x = touchEvent->touches[i].targetX / (client_w - 1); + } + if (client_h <= 1) { + y = 0.5f; + } else { + y = touchEvent->touches[i].targetY / (client_h - 1); + } if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) { SDL_SendTouch(0, deviceId, id, window_data->window, SDL_TRUE, x, y, 1.0f); @@ -835,23 +843,16 @@ static EM_BOOL Emscripten_HandleKeyPress(int eventType, const EmscriptenKeyboard static EM_BOOL Emscripten_HandleFullscreenChange(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData) { SDL_WindowData *window_data = userData; - SDL_VideoDisplay *display; if (fullscreenChangeEvent->isFullscreen) { - window_data->window->flags |= window_data->fullscreen_mode_flags; - + SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); window_data->fullscreen_mode_flags = 0; } else { - window_data->window->flags &= ~SDL_WINDOW_FULLSCREEN; - - /* reset fullscreen window if the browser left fullscreen */ - display = SDL_GetVideoDisplayForWindow(window_data->window); - - if (display->fullscreen_window == window_data->window) { - display->fullscreen_window = NULL; - } + SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); } + SDL_UpdateFullscreenMode(window_data->window, fullscreenChangeEvent->isFullscreen, SDL_FALSE); + return 0; } @@ -956,7 +957,7 @@ void Emscripten_RegisterEventHandlers(SDL_WindowData *data) /* Keyboard events are awkward */ keyElement = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT); - if (keyElement == NULL) { + if (!keyElement) { keyElement = EMSCRIPTEN_EVENT_TARGET_WINDOW; } @@ -999,7 +1000,7 @@ void Emscripten_UnregisterEventHandlers(SDL_WindowData *data) emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL); target = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT); - if (target == NULL) { + if (!target) { target = EMSCRIPTEN_EVENT_TARGET_WINDOW; } diff --git a/src/video/emscripten/SDL_emscriptenevents.h b/src/video/emscripten/SDL_emscriptenevents.h index 230b1722..41859805 100644 --- a/src/video/emscripten/SDL_emscriptenevents.h +++ b/src/video/emscripten/SDL_emscriptenevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.c b/src/video/emscripten/SDL_emscriptenframebuffer.c index b620d554..7d812203 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,7 +42,7 @@ int Emscripten_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *windo SDL_GetWindowSizeInPixels(window, &w, &h); surface = SDL_CreateSurface(w, h, surface_format); - if (surface == NULL) { + if (!surface) { return -1; } @@ -60,7 +60,7 @@ int Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *windo SDL_WindowData *data = window->driverdata; surface = data->surface; - if (surface == NULL) { + if (!surface) { return SDL_SetError("Couldn't find framebuffer surface for window"); } diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.h b/src/video/emscripten/SDL_emscriptenframebuffer.h index 0192e350..2384ef58 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.h +++ b/src/video/emscripten/SDL_emscriptenframebuffer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/emscripten/SDL_emscriptenmouse.c b/src/video/emscripten/SDL_emscriptenmouse.c index 9aa79467..e6beab7e 100644 --- a/src/video/emscripten/SDL_emscriptenmouse.c +++ b/src/video/emscripten/SDL_emscriptenmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,14 +42,11 @@ static SDL_Cursor *Emscripten_CreateCursorFromString(const char *cursor_str, SDL_bool is_custom) { - SDL_Cursor *cursor; Emscripten_CursorData *curdata; - - cursor = SDL_calloc(1, sizeof(SDL_Cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(SDL_Cursor)); if (cursor) { curdata = (Emscripten_CursorData *)SDL_calloc(1, sizeof(*curdata)); - if (curdata == NULL) { - SDL_OutOfMemory(); + if (!curdata) { SDL_free(cursor); return NULL; } @@ -57,8 +54,6 @@ static SDL_Cursor *Emscripten_CreateCursorFromString(const char *cursor_str, SDL curdata->system_cursor = cursor_str; curdata->is_custom = is_custom; cursor->driverdata = curdata; - } else { - SDL_OutOfMemory(); } return cursor; @@ -78,7 +73,7 @@ static SDL_Cursor *Emscripten_CreateCursor(SDL_Surface *surface, int hot_x, int conv_surf = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ABGR8888); - if (conv_surf == NULL) { + if (!conv_surf) { return NULL; } @@ -161,6 +156,30 @@ static SDL_Cursor *Emscripten_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_HAND: cursor_name = "pointer"; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + cursor_name = "nwse-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + cursor_name = "ns-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + cursor_name = "nesw-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + cursor_name = "ew-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + cursor_name = "nwse-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + cursor_name = "ns-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + cursor_name = "nesw-resize"; + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + cursor_name = "ew-resize"; + break; default: SDL_assert(0); return NULL; @@ -175,7 +194,7 @@ static void Emscripten_FreeCursor(SDL_Cursor *cursor) if (cursor) { curdata = (Emscripten_CursorData *)cursor->driverdata; - if (curdata != NULL) { + if (curdata) { if (curdata->is_custom) { SDL_free((char *)curdata->system_cursor); } @@ -223,7 +242,7 @@ static int Emscripten_SetRelativeMouseMode(SDL_bool enabled) /* TODO: pointer lock isn't actually enabled yet */ if (enabled) { window = SDL_GetMouseFocus(); - if (window == NULL) { + if (!window) { return -1; } diff --git a/src/video/emscripten/SDL_emscriptenmouse.h b/src/video/emscripten/SDL_emscriptenmouse.h index 94fd44df..f78b2c20 100644 --- a/src/video/emscripten/SDL_emscriptenmouse.h +++ b/src/video/emscripten/SDL_emscriptenmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c index 6a40c24c..c195e026 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.c +++ b/src/video/emscripten/SDL_emscriptenopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -117,7 +117,7 @@ int Emscripten_GLES_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context) SDL_Window *window; /* remove the context from its window */ - for (window = _this->windows; window != NULL; window = window->next) { + for (window = _this->windows; window; window = window->next) { SDL_WindowData *window_data = window->driverdata; if (window_data->gl_context == context) { diff --git a/src/video/emscripten/SDL_emscriptenopengles.h b/src/video/emscripten/SDL_emscriptenopengles.h index ecd87929..4a751623 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.h +++ b/src/video/emscripten/SDL_emscriptenopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 54411843..1e6b0839 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,11 +40,11 @@ static int Emscripten_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *d static void Emscripten_VideoQuit(SDL_VideoDevice *_this); static int Emscripten_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); -static int Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +static int Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -static void Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +static int Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); static void Emscripten_PumpEvents(SDL_VideoDevice *_this); static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); @@ -61,8 +61,7 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } @@ -171,7 +170,7 @@ static void Emscripten_PumpEvents(SDL_VideoDevice *_this) /* do nothing. */ } -static int Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +static int Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) { SDL_WindowData *wdata; double scaled_w, scaled_h; @@ -180,8 +179,8 @@ static int Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Allocate window internal data */ wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (wdata == NULL) { - return SDL_OutOfMemory(); + if (!wdata) { + return -1; } selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR); @@ -248,12 +247,14 @@ static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { data->pixel_ratio = emscripten_get_device_pixel_ratio(); } - emscripten_set_canvas_element_size(data->canvas_id, window->w * data->pixel_ratio, window->h * data->pixel_ratio); + emscripten_set_canvas_element_size(data->canvas_id, window->floating.w * data->pixel_ratio, window->floating.h * data->pixel_ratio); /*scale canvas down*/ if (!data->external_size && data->pixel_ratio != 1.0f) { - emscripten_set_element_css_size(data->canvas_id, window->w, window->h); + emscripten_set_element_css_size(data->canvas_id, window->floating.w, window->floating.h); } + + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->floating.w, window->floating.h); } } @@ -285,16 +286,17 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } } -static void Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +static int Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { SDL_WindowData *data; + int res = -1; + if (window->driverdata) { data = window->driverdata; if (fullscreen) { EmscriptenFullscreenStrategy strategy; - SDL_bool is_fullscreen_desktop = window->fullscreen_exclusive ? SDL_FALSE : SDL_TRUE; - int res; + SDL_bool is_fullscreen_desktop = !window->fullscreen_exclusive; strategy.scaleMode = is_fullscreen_desktop ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT; @@ -315,12 +317,17 @@ static void Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *w data->fullscreen_resize = is_fullscreen_desktop; res = emscripten_request_fullscreen_strategy(data->canvas_id, 1, &strategy); - if (res != EMSCRIPTEN_RESULT_SUCCESS && res != EMSCRIPTEN_RESULT_DEFERRED) { - /* unset flags, fullscreen failed */ - window->flags &= ~SDL_WINDOW_FULLSCREEN; - } - } else - emscripten_exit_fullscreen(); + } else { + res = emscripten_exit_fullscreen(); + } + } + + if (res == EMSCRIPTEN_RESULT_SUCCESS) { + return 0; + } else if (res == EMSCRIPTEN_RESULT_DEFERRED) { + return 1; + } else { + return -1; } } diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h index e5e08e14..44172058 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.h +++ b/src/video/emscripten/SDL_emscriptenvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/gdk/SDL_gdktextinput.cpp b/src/video/gdk/SDL_gdktextinput.cpp index 934a029e..29ffcc0d 100644 --- a/src/video/gdk/SDL_gdktextinput.cpp +++ b/src/video/gdk/SDL_gdktextinput.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,7 +63,7 @@ static void SDLCALL GDK_InternalHintCallback( const char *oldValue, const char *newValue) { - if (userdata == NULL) { + if (!userdata) { return; } @@ -72,7 +72,7 @@ static void SDLCALL GDK_InternalHintCallback( if (userdata == &g_TextInputScope || userdata == &g_MaxTextLength) { /* int32 hint */ - Sint32 intValue = (newValue == NULL || newValue[0] == '\0') ? 0 : SDL_atoi(newValue); + Sint32 intValue = (!newValue || newValue[0] == '\0') ? 0 : SDL_atoi(newValue); if (userdata == &g_MaxTextLength && intValue <= 0) { intValue = g_DefaultMaxTextLength; } else if (userdata == &g_TextInputScope && intValue < 0) { @@ -82,16 +82,13 @@ static void SDLCALL GDK_InternalHintCallback( *(Sint32 *)userdata = intValue; } else { /* string hint */ - if (newValue == NULL || newValue[0] == '\0') { + if (!newValue || newValue[0] == '\0') { /* treat empty or NULL strings as just NULL for this impl */ SDL_free(*(char **)userdata); *(char **)userdata = NULL; } else { char *newString = SDL_strdup(newValue); - if (newString == NULL) { - /* couldn't strdup, oh well */ - SDL_OutOfMemory(); - } else { + if (newString) { /* free previous value and write the new one */ SDL_free(*(char **)userdata); *(char **)userdata = newString; @@ -102,7 +99,7 @@ static void SDLCALL GDK_InternalHintCallback( static int GDK_InternalEnsureTaskQueue(void) { - if (g_TextTaskQueue == NULL) { + if (!g_TextTaskQueue) { if (SDL_GDKGetTaskQueue(&g_TextTaskQueue) < 0) { /* SetError will be done for us. */ return -1; @@ -128,9 +125,7 @@ static void CALLBACK GDK_InternalTextEntryCallback(XAsyncBlock *asyncBlock) } else if (resultSize > 0) { /* +1 to be super sure that the buffer will be null terminated */ resultBuffer = (char *)SDL_calloc(sizeof(*resultBuffer), 1 + (size_t)resultSize); - if (resultBuffer == NULL) { - SDL_OutOfMemory(); - } else { + if (resultBuffer) { /* still pass the original size that we got from ResultSize */ if (FAILED(hR = XGameUiShowTextEntryResult( asyncBlock, @@ -230,7 +225,7 @@ SDL_bool GDK_IsTextInputShown(SDL_VideoDevice *_this) * just below the text box, so technically * this is true whenever the window is shown. */ - return (g_TextBlock != NULL) ? SDL_TRUE : SDL_FALSE; + return (g_TextBlock != NULL); } SDL_bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this) @@ -251,7 +246,7 @@ void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) HRESULT hR = S_OK; - if (g_TextBlock != NULL) { + if (g_TextBlock) { /* already showing the keyboard */ return; } @@ -262,8 +257,7 @@ void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) } g_TextBlock = (XAsyncBlock *)SDL_calloc(1, sizeof(*g_TextBlock)); - if (g_TextBlock == NULL) { - SDL_OutOfMemory(); + if (!g_TextBlock) { return; } @@ -285,7 +279,7 @@ void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) void GDK_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) { - if (g_TextBlock != NULL) { + if (g_TextBlock) { XAsyncCancel(g_TextBlock); /* the completion callback will free the block */ } diff --git a/src/video/gdk/SDL_gdktextinput.h b/src/video/gdk/SDL_gdktextinput.h index 41f77178..7bce9c38 100644 --- a/src/video/gdk/SDL_gdktextinput.h +++ b/src/video/gdk/SDL_gdktextinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_BApp.h b/src/video/haiku/SDL_BApp.h index 6deaff2e..39fec778 100644 --- a/src/video/haiku/SDL_BApp.h +++ b/src/video/haiku/SDL_BApp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_BWin.h b/src/video/haiku/SDL_BWin.h index e053f483..1a9a786c 100644 --- a/src/video/haiku/SDL_BWin.h +++ b/src/video/haiku/SDL_BWin.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,8 +29,6 @@ extern "C" { #include "SDL_internal.h" #include "SDL_bframebuffer.h" -#include - #ifdef __cplusplus } #endif diff --git a/src/video/haiku/SDL_bclipboard.cc b/src/video/haiku/SDL_bclipboard.cc index 04625ad8..f33072f5 100644 --- a/src/video/haiku/SDL_bclipboard.cc +++ b/src/video/haiku/SDL_bclipboard.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -64,7 +64,7 @@ char *HAIKU_GetClipboardText(SDL_VideoDevice *_this) { be_clipboard->Unlock(); } - if (text == NULL) { + if (!text) { result = SDL_strdup(""); } else { /* Copy the data and pass on to SDL */ @@ -79,7 +79,7 @@ SDL_bool HAIKU_HasClipboardText(SDL_VideoDevice *_this) { SDL_bool result = SDL_FALSE; char *text = HAIKU_GetClipboardText(_this); if (text) { - result = text[0] != '\0' ? SDL_TRUE : SDL_FALSE; + result = (text[0] != '\0'); SDL_free(text); } return result; diff --git a/src/video/haiku/SDL_bclipboard.h b/src/video/haiku/SDL_bclipboard.h index 62a6791b..ce36bf3c 100644 --- a/src/video/haiku/SDL_bclipboard.h +++ b/src/video/haiku/SDL_bclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bevents.cc b/src/video/haiku/SDL_bevents.cc index 6075eb85..c3af6c23 100644 --- a/src/video/haiku/SDL_bevents.cc +++ b/src/video/haiku/SDL_bevents.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bevents.h b/src/video/haiku/SDL_bevents.h index d8db1b54..1edc7229 100644 --- a/src/video/haiku/SDL_bevents.h +++ b/src/video/haiku/SDL_bevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bframebuffer.cc b/src/video/haiku/SDL_bframebuffer.cc index 0151c72c..5beb4567 100644 --- a/src/video/haiku/SDL_bframebuffer.cc +++ b/src/video/haiku/SDL_bframebuffer.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -94,7 +94,7 @@ int HAIKU_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window * window, int HAIKU_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window * window, const SDL_Rect * rects, int numrects) { - if (window == NULL) { + if (!window) { return 0; } diff --git a/src/video/haiku/SDL_bframebuffer.h b/src/video/haiku/SDL_bframebuffer.h index a1ab7ee1..769a5384 100644 --- a/src/video/haiku/SDL_bframebuffer.h +++ b/src/video/haiku/SDL_bframebuffer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bkeyboard.cc b/src/video/haiku/SDL_bkeyboard.cc index 3f521c95..db64fd05 100644 --- a/src/video/haiku/SDL_bkeyboard.cc +++ b/src/video/haiku/SDL_bkeyboard.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bkeyboard.h b/src/video/haiku/SDL_bkeyboard.h index c16e9f91..d6bf2612 100644 --- a/src/video/haiku/SDL_bkeyboard.h +++ b/src/video/haiku/SDL_bkeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bmessagebox.cc b/src/video/haiku/SDL_bmessagebox.cc index 3709f2c0..fe8e963c 100644 --- a/src/video/haiku/SDL_bmessagebox.cc +++ b/src/video/haiku/SDL_bmessagebox.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2018-2019 EXL This software is provided 'as-is', without any express or implied @@ -356,15 +356,15 @@ int HAIKU_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid // "You need a valid BApplication object before interacting with the app_server." // "2 BApplication objects were created. Only one is allowed." std::unique_ptr application; - if (be_app == NULL) { + if (!be_app) { application = std::unique_ptr(new(std::nothrow) BApplication(SDL_signature)); - if (application == NULL) { + if (!application) { return SDL_SetError("Cannot create the BApplication object. Lack of memory?"); } } HAIKU_SDL_MessageBox *SDL_MessageBox = new(std::nothrow) HAIKU_SDL_MessageBox(messageboxdata); - if (SDL_MessageBox == NULL) { + if (!SDL_MessageBox) { return SDL_SetError("Cannot create the HAIKU_SDL_MessageBox (BAlert inheritor) object. Lack of memory?"); } const int closeButton = SDL_MessageBox->GetCloseButtonId(); diff --git a/src/video/haiku/SDL_bmessagebox.h b/src/video/haiku/SDL_bmessagebox.h index 9c705e8d..828e2e96 100644 --- a/src/video/haiku/SDL_bmessagebox.h +++ b/src/video/haiku/SDL_bmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2018-2019 EXL This software is provided 'as-is', without any express or implied diff --git a/src/video/haiku/SDL_bmodes.cc b/src/video/haiku/SDL_bmodes.cc index 4c1c7072..e7c83a87 100644 --- a/src/video/haiku/SDL_bmodes.cc +++ b/src/video/haiku/SDL_bmodes.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bmodes.h b/src/video/haiku/SDL_bmodes.h index 0a9ca796..661b7def 100644 --- a/src/video/haiku/SDL_bmodes.h +++ b/src/video/haiku/SDL_bmodes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bopengl.cc b/src/video/haiku/SDL_bopengl.cc index 52f2ed9c..ad47f41a 100644 --- a/src/video/haiku/SDL_bopengl.cc +++ b/src/video/haiku/SDL_bopengl.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,7 +65,7 @@ int HAIKU_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) SDL_FunctionPointer HAIKU_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) { - if (_this->gl_config.dll_handle != NULL) { + if (_this->gl_config.dll_handle) { void *location = NULL; status_t err; if ((err = @@ -92,8 +92,8 @@ int HAIKU_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window * window) { int HAIKU_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window * window, SDL_GLContext context) { BGLView* glView = (BGLView*)context; // printf("HAIKU_GL_MakeCurrent(%llx), win = %llx, thread = %d\n", (uint64)context, (uint64)window, find_thread(NULL)); - if (glView != NULL) { - if ((glView->Window() == NULL) || (window == NULL) || (_ToBeWin(window)->GetGLView() != glView)) { + if (glView) { + if ((glView->Window() == NULL) || (!window) || (_ToBeWin(window)->GetGLView() != glView)) { return SDL_SetError("MakeCurrent failed"); } } @@ -146,7 +146,7 @@ int HAIKU_GL_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context) { // printf("HAIKU_GL_DeleteContext(%llx), thread = %d\n", (uint64)context, find_thread(NULL)); BGLView* glView = (BGLView*)context; SDL_BWin *bwin = (SDL_BWin*)glView->Window(); - if (bwin == NULL) { + if (!bwin) { delete glView; } else { bwin->RemoveGLView(); diff --git a/src/video/haiku/SDL_bopengl.h b/src/video/haiku/SDL_bopengl.h index aea625cc..c17cb513 100644 --- a/src/video/haiku/SDL_bopengl.h +++ b/src/video/haiku/SDL_bopengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bvideo.cc b/src/video/haiku/SDL_bvideo.cc index 14e3ef2f..a6ee9a39 100644 --- a/src/video/haiku/SDL_bvideo.cc +++ b/src/video/haiku/SDL_bvideo.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -75,7 +75,6 @@ static SDL_VideoDevice * HAIKU_CreateDevice(void) device->PumpEvents = HAIKU_PumpEvents; device->CreateSDLWindow = HAIKU_CreateWindow; - device->CreateSDLWindowFrom = HAIKU_CreateWindowFrom; device->SetWindowTitle = HAIKU_SetWindowTitle; device->SetWindowPosition = HAIKU_SetWindowPosition; device->SetWindowSize = HAIKU_SetWindowSize; @@ -91,7 +90,6 @@ static SDL_VideoDevice * HAIKU_CreateDevice(void) device->SetWindowMouseGrab = HAIKU_SetWindowMouseGrab; device->SetWindowMinimumSize = HAIKU_SetWindowMinimumSize; device->DestroyWindow = HAIKU_DestroyWindow; - device->GetWindowWMInfo = HAIKU_GetWindowWMInfo; device->CreateWindowFramebuffer = HAIKU_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = HAIKU_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = HAIKU_DestroyWindowFramebuffer; @@ -154,13 +152,19 @@ static SDL_Cursor * HAIKU_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_SIZEALL: cursorId = B_CURSOR_ID_MOVE; break; case SDL_SYSTEM_CURSOR_NO: cursorId = B_CURSOR_ID_NOT_ALLOWED; break; case SDL_SYSTEM_CURSOR_HAND: cursorId = B_CURSOR_ID_FOLLOW_LINK; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: cursorId = B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: cursorId = B_CURSOR_ID_RESIZE_NORTH_SOUTH; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: cursorId = B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST; break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: cursorId = B_CURSOR_ID_RESIZE_EAST_WEST; break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: cursorId = B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST; break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: cursorId = B_CURSOR_ID_RESIZE_NORTH_SOUTH; break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: cursorId = B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST; break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: cursorId = B_CURSOR_ID_RESIZE_EAST_WEST; break; } cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor)); if (cursor) { cursor->driverdata = (void *)new BCursor(cursorId); - } else { - SDL_OutOfMemory(); } return cursor; @@ -185,7 +189,7 @@ static SDL_Cursor * HAIKU_CreateCursor(SDL_Surface * surface, int hot_x, int hot SDL_Surface *converted; converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888); - if (converted == NULL) { + if (!converted) { return NULL; } @@ -207,7 +211,7 @@ static int HAIKU_ShowCursor(SDL_Cursor *cursor) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return 0; } @@ -226,7 +230,7 @@ static int HAIKU_ShowCursor(SDL_Cursor *cursor) static int HAIKU_SetRelativeMouseMode(SDL_bool enabled) { SDL_Window *window = SDL_GetMouseFocus(); - if (window == NULL) { + if (!window) { return 0; } @@ -246,7 +250,7 @@ static int HAIKU_SetRelativeMouseMode(SDL_bool enabled) static void HAIKU_MouseInit(SDL_VideoDevice *_this) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return; } mouse->CreateCursor = HAIKU_CreateCursor; diff --git a/src/video/haiku/SDL_bvideo.h b/src/video/haiku/SDL_bvideo.h index 480aa2d2..3c225c30 100644 --- a/src/video/haiku/SDL_bvideo.h +++ b/src/video/haiku/SDL_bvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/haiku/SDL_bwindow.cc b/src/video/haiku/SDL_bwindow.cc index 2d309e6a..e3ca49fc 100644 --- a/src/video/haiku/SDL_bwindow.cc +++ b/src/video/haiku/SDL_bwindow.cc @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,8 +26,6 @@ #include "SDL_BWin.h" #include -#include - /* Define a path to window's BWIN data */ #ifdef __cplusplus extern "C" { @@ -67,7 +65,7 @@ static int _InitWindow(SDL_VideoDevice *_this, SDL_Window *window) { } SDL_BWin *bwin = new(std::nothrow) SDL_BWin(bounds, look, flags); - if (bwin == NULL) { + if (!bwin) { return -1; } @@ -78,7 +76,7 @@ static int _InitWindow(SDL_VideoDevice *_this, SDL_Window *window) { return 0; } -int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) { +int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { if (_InitWindow(_this, window) < 0) { return -1; } @@ -88,56 +86,24 @@ int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) { return 0; } -int HAIKU_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window * window, const void *data) { - - SDL_BWin *otherBWin = (SDL_BWin*)data; - if (!otherBWin->LockLooper()) { - return -1; - } - - /* Create the new window and initialize its members */ - window->x = (int)otherBWin->Frame().left; - window->y = (int)otherBWin->Frame().top; - window->w = (int)otherBWin->Frame().Width(); - window->h = (int)otherBWin->Frame().Height(); - - /* Set SDL flags */ - if (!(otherBWin->Flags() & B_NOT_RESIZABLE)) { - window->flags |= SDL_WINDOW_RESIZABLE; - } - - /* If we are out of memory, return the error code */ - if (_InitWindow(_this, window) < 0) { - return -1; - } - - /* TODO: Add any other SDL-supported window attributes here */ - _ToBeWin(window)->SetTitle(otherBWin->Title()); - - /* Start window loop and unlock the other window */ - _ToBeWin(window)->Show(); - - otherBWin->UnlockLooper(); - return 0; -} - void HAIKU_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window * window) { BMessage msg(BWIN_SET_TITLE); msg.AddString("window-title", window->title); _ToBeWin(window)->PostMessage(&msg); } -void HAIKU_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window * window) { +int HAIKU_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window * window) { BMessage msg(BWIN_MOVE_WINDOW); - msg.AddInt32("window-x", window->x); - msg.AddInt32("window-y", window->y); + msg.AddInt32("window-x", window->floating.x); + msg.AddInt32("window-y", window->floating.y); _ToBeWin(window)->PostMessage(&msg); + return 0; } void HAIKU_SetWindowSize(SDL_VideoDevice *_this, SDL_Window * window) { BMessage msg(BWIN_RESIZE_WINDOW); - msg.AddInt32("window-w", window->w - 1); - msg.AddInt32("window-h", window->h - 1); + msg.AddInt32("window-w", window->floating.w - 1); + msg.AddInt32("window-h", window->floating.h - 1); _ToBeWin(window)->PostMessage(&msg); } @@ -183,13 +149,13 @@ void HAIKU_RestoreWindow(SDL_VideoDevice *_this, SDL_Window * window) { _ToBeWin(window)->PostMessage(&msg); } -void HAIKU_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window * window, +int HAIKU_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen) { /* Haiku tracks all video display information */ BMessage msg(BWIN_FULLSCREEN); msg.AddBool("fullscreen", fullscreen); _ToBeWin(window)->PostMessage(&msg); - + return 0; } @@ -211,13 +177,6 @@ void HAIKU_DestroyWindow(SDL_VideoDevice *_this, SDL_Window * window) { window->driverdata = NULL; } -int HAIKU_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info) -{ - info->subsystem = SDL_SYSWM_HAIKU; - return 0; -} - - #ifdef __cplusplus } #endif diff --git a/src/video/haiku/SDL_bwindow.h b/src/video/haiku/SDL_bwindow.h index 0597acbc..0e0325ac 100644 --- a/src/video/haiku/SDL_bwindow.h +++ b/src/video/haiku/SDL_bwindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,8 +24,7 @@ #include "../SDL_sysvideo.h" -extern int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int HAIKU_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +extern int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void HAIKU_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern int HAIKU_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); extern void HAIKU_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); @@ -38,9 +37,8 @@ extern void HAIKU_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void HAIKU_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void HAIKU_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); extern void HAIKU_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); -extern void HAIKU_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int HAIKU_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void HAIKU_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void HAIKU_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int HAIKU_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); #endif diff --git a/src/video/kmsdrm/SDL_kmsdrmdyn.c b/src/video/kmsdrm/SDL_kmsdrmdyn.c index a5416c48..b03ede6d 100644 --- a/src/video/kmsdrm/SDL_kmsdrmdyn.c +++ b/src/video/kmsdrm/SDL_kmsdrmdyn.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -49,22 +49,22 @@ static void *KMSDRM_GetSym(const char *fnname, int *pHasModule) int i; void *fn = NULL; for (i = 0; i < SDL_TABLESIZE(kmsdrmlibs); i++) { - if (kmsdrmlibs[i].lib != NULL) { + if (kmsdrmlibs[i].lib) { fn = SDL_LoadFunction(kmsdrmlibs[i].lib, fnname); - if (fn != NULL) { + if (fn) { break; } } } #if DEBUG_DYNAMIC_KMSDRM - if (fn != NULL) + if (fn) SDL_Log("KMSDRM: Found '%s' in %s (%p)\n", fnname, kmsdrmlibs[i].libname, fn); else SDL_Log("KMSDRM: Symbol '%s' NOT FOUND!\n", fnname); #endif - if (fn == NULL) { + if (!fn) { *pHasModule = 0; /* kill this module. */ } @@ -98,7 +98,7 @@ void SDL_KMSDRM_UnloadSymbols(void) #ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC for (i = 0; i < SDL_TABLESIZE(kmsdrmlibs); i++) { - if (kmsdrmlibs[i].lib != NULL) { + if (kmsdrmlibs[i].lib) { SDL_UnloadObject(kmsdrmlibs[i].lib); kmsdrmlibs[i].lib = NULL; } @@ -119,7 +119,7 @@ int SDL_KMSDRM_LoadSymbols(void) int i; int *thismod = NULL; for (i = 0; i < SDL_TABLESIZE(kmsdrmlibs); i++) { - if (kmsdrmlibs[i].libname != NULL) { + if (kmsdrmlibs[i].libname) { kmsdrmlibs[i].lib = SDL_LoadObject(kmsdrmlibs[i].libname); } } diff --git a/src/video/kmsdrm/SDL_kmsdrmdyn.h b/src/video/kmsdrm/SDL_kmsdrmdyn.h index 23048d27..f9a292ec 100644 --- a/src/video/kmsdrm/SDL_kmsdrmdyn.h +++ b/src/video/kmsdrm/SDL_kmsdrmdyn.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/kmsdrm/SDL_kmsdrmevents.c b/src/video/kmsdrm/SDL_kmsdrmevents.c index 7479e074..15235204 100644 --- a/src/video/kmsdrm/SDL_kmsdrmevents.c +++ b/src/video/kmsdrm/SDL_kmsdrmevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/kmsdrm/SDL_kmsdrmevents.h b/src/video/kmsdrm/SDL_kmsdrmevents.h index e6313671..a67c5b3d 100644 --- a/src/video/kmsdrm/SDL_kmsdrmevents.h +++ b/src/video/kmsdrm/SDL_kmsdrmevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,5 @@ #define SDL_kmsdrmevents_h_ extern void KMSDRM_PumpEvents(SDL_VideoDevice *_this); -extern void KMSDRM_EventInit(SDL_VideoDevice *_this); -extern void KMSDRM_EventQuit(SDL_VideoDevice *_this); #endif /* SDL_kmsdrmevents_h_ */ diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.c b/src/video/kmsdrm/SDL_kmsdrmmouse.c index 962802ce..1b3524b9 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.c +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -147,7 +147,7 @@ static int KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor) int i; int ret; - if (curdata == NULL || !dispdata->cursor_bo) { + if (!curdata || !dispdata->cursor_bo) { return SDL_SetError("Cursor or display not initialized properly."); } @@ -158,8 +158,8 @@ static int KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor) ready_buffer = (uint8_t *)SDL_calloc(1, bufsize); - if (ready_buffer == NULL) { - ret = SDL_OutOfMemory(); + if (!ready_buffer) { + ret = -1; goto cleanup; } @@ -236,13 +236,11 @@ static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_ ret = NULL; cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); - if (cursor == NULL) { - SDL_OutOfMemory(); + if (!cursor) { goto cleanup; } curdata = (KMSDRM_CursorData *)SDL_calloc(1, sizeof(*curdata)); - if (curdata == NULL) { - SDL_OutOfMemory(); + if (!curdata) { goto cleanup; } @@ -260,7 +258,6 @@ static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_ curdata->buffer = (uint32_t *)SDL_malloc(curdata->buffer_size); if (!curdata->buffer) { - SDL_OutOfMemory(); goto cleanup; } @@ -277,7 +274,7 @@ static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_ ret = cursor; cleanup: - if (ret == NULL) { + if (!ret) { if (curdata) { if (curdata->buffer) { SDL_free(curdata->buffer); @@ -305,13 +302,13 @@ static int KMSDRM_ShowCursor(SDL_Cursor *cursor) /* Get the mouse focused window, if any. */ mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return SDL_SetError("No mouse."); } window = mouse->focus; - if (window == NULL || cursor == NULL) { + if (!window || !cursor) { /* If no window is focused by mouse or cursor is NULL, since we have no window (no mouse->focus) and hence we have no display, we simply hide mouse on all displays. diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.h b/src/video/kmsdrm/SDL_kmsdrmmouse.h index 754dc19e..69b8b4c2 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.h +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c index 2de35e0e..7c5841ce 100644 --- a/src/video/kmsdrm/SDL_kmsdrmopengles.c +++ b/src/video/kmsdrm/SDL_kmsdrmopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -96,6 +96,13 @@ int KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) even if you do async flips. */ uint32_t flip_flags = DRM_MODE_PAGE_FLIP_EVENT; + /* Skip the swap if we've switched away to another VT */ + if (windata->egl_surface == EGL_NO_SURFACE) { + /* Wait a bit, throttling to ~100 FPS */ + SDL_Delay(10); + return 0; + } + /* Recreate the GBM / EGL surfaces if the display mode has changed */ if (windata->egl_surface_dirty) { KMSDRM_CreateSurfaces(_this, window); @@ -116,7 +123,7 @@ int KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) windata->bo = windata->next_bo; - /* Mark a buffer to becume the next front buffer. + /* Mark a buffer to become the next front buffer. This won't happen until pagelip completes. */ if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) { @@ -135,7 +142,7 @@ int KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Get an actual usable fb for the next front buffer. */ fb_info = KMSDRM_FBFromBO(_this, windata->next_bo); - if (fb_info == NULL) { + if (!fb_info) { SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not get a framebuffer"); return 0; } diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.h b/src/video/kmsdrm/SDL_kmsdrmopengles.h index 08f3561f..ff1989e9 100644 --- a/src/video/kmsdrm/SDL_kmsdrmopengles.h +++ b/src/video/kmsdrm/SDL_kmsdrmopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/kmsdrm/SDL_kmsdrmsym.h b/src/video/kmsdrm/SDL_kmsdrmsym.h index e378fdfb..ad91d069 100644 --- a/src/video/kmsdrm/SDL_kmsdrmsym.h +++ b/src/video/kmsdrm/SDL_kmsdrmsym.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,6 +42,7 @@ SDL_KMSDRM_SYM(void,drmModeFreeConnector,(drmModeConnectorPtr ptr)) SDL_KMSDRM_SYM(void,drmModeFreeEncoder,(drmModeEncoderPtr ptr)) SDL_KMSDRM_SYM(int,drmGetCap,(int fd, uint64_t capability, uint64_t *value)) SDL_KMSDRM_SYM(int,drmSetMaster,(int fd)) +SDL_KMSDRM_SYM(int,drmDropMaster,(int fd)) SDL_KMSDRM_SYM(int,drmAuthMagic,(int fd, drm_magic_t magic)) SDL_KMSDRM_SYM(drmModeResPtr,drmModeGetResources,(int fd)) SDL_KMSDRM_SYM(int,drmModeAddFB,(int fd, uint32_t width, uint32_t height, uint8_t depth, diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c index 5312a584..d55d38cf 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,8 +35,6 @@ #include "../../core/openbsd/SDL_wscons.h" #endif -#include - /* KMS/DRM declarations */ #include "SDL_kmsdrmdyn.h" #include "SDL_kmsdrmevents.h" @@ -88,7 +86,7 @@ static int get_driindex(void) SDL_strlcpy(device, kmsdrm_dri_path, sizeof(device)); folder = opendir(device); - if (folder == NULL) { + if (!folder) { SDL_SetError("Failed to open directory '%s'", device); return -ENOENT; } @@ -126,7 +124,7 @@ static int get_driindex(void) KMSDRM_drmModeGetConnector( drm_fd, resources->connectors[i]); - if (conn == NULL) { + if (!conn) { continue; } @@ -262,14 +260,12 @@ static SDL_VideoDevice *KMSDRM_CreateDevice(void) } device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } viddata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (viddata == NULL) { - SDL_OutOfMemory(); + if (!viddata) { goto cleanup; } viddata->devindex = devindex; @@ -283,7 +279,6 @@ static SDL_VideoDevice *KMSDRM_CreateDevice(void) device->GetDisplayModes = KMSDRM_GetDisplayModes; device->SetDisplayMode = KMSDRM_SetDisplayMode; device->CreateSDLWindow = KMSDRM_CreateWindow; - device->CreateSDLWindowFrom = KMSDRM_CreateWindowFrom; device->SetWindowTitle = KMSDRM_SetWindowTitle; device->SetWindowPosition = KMSDRM_SetWindowPosition; device->SetWindowSize = KMSDRM_SetWindowSize; @@ -295,7 +290,6 @@ static SDL_VideoDevice *KMSDRM_CreateDevice(void) device->MinimizeWindow = KMSDRM_MinimizeWindow; device->RestoreWindow = KMSDRM_RestoreWindow; device->DestroyWindow = KMSDRM_DestroyWindow; - device->GetWindowWMInfo = KMSDRM_GetWindowWMInfo; device->GL_LoadLibrary = KMSDRM_GLES_LoadLibrary; device->GL_GetProcAddress = KMSDRM_GLES_GetProcAddress; @@ -367,8 +361,7 @@ KMSDRM_FBInfo *KMSDRM_FBFromBO(SDL_VideoDevice *_this, struct gbm_bo *bo) when the backing buffer is destroyed */ fb_info = (KMSDRM_FBInfo *)SDL_calloc(1, sizeof(KMSDRM_FBInfo)); - if (fb_info == NULL) { - SDL_OutOfMemory(); + if (!fb_info) { return NULL; } @@ -692,8 +685,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto /* Reserve memory for the new display's driverdata. */ dispdata = (SDL_DisplayData *)SDL_calloc(1, sizeof(SDL_DisplayData)); - if (dispdata == NULL) { - ret = SDL_OutOfMemory(); + if (!dispdata) { + ret = -1; goto cleanup; } @@ -713,7 +706,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto for (i = 0; i < resources->count_encoders; i++) { encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); - if (encoder == NULL) { + if (!encoder) { continue; } @@ -725,13 +718,13 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto encoder = NULL; } - if (encoder == NULL) { + if (!encoder) { /* No encoder was connected, find the first supported one */ for (i = 0; i < resources->count_encoders; i++) { encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); - if (encoder == NULL) { + if (!encoder) { continue; } @@ -750,7 +743,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto } } - if (encoder == NULL) { + if (!encoder) { ret = SDL_SetError("No connected encoder found for connector."); goto cleanup; } @@ -760,7 +753,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto /* If no CRTC was connected to the encoder, find the first CRTC that is supported by the encoder, and use that. */ - if (crtc == NULL) { + if (!crtc) { for (i = 0; i < resources->count_crtcs; i++) { if (encoder->possible_crtcs & (1 << i)) { encoder->crtc_id = resources->crtcs[i]; @@ -770,7 +763,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto } } - if (crtc == NULL) { + if (!crtc) { ret = SDL_SetError("No CRTC found for connector."); goto cleanup; } @@ -855,8 +848,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto There's no problem with it being still incomplete. */ modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData)); - if (modedata == NULL) { - ret = SDL_OutOfMemory(); + if (!modedata) { + ret = -1; goto cleanup; } @@ -927,7 +920,7 @@ static int KMSDRM_InitDisplays(SDL_VideoDevice *_this) /* Get all of the available connectors / devices / crtcs */ resources = KMSDRM_drmModeGetResources(viddata->drm_fd); - if (resources == NULL) { + if (!resources) { ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd); goto cleanup; } @@ -938,7 +931,7 @@ static int KMSDRM_InitDisplays(SDL_VideoDevice *_this) drmModeConnector *connector = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]); - if (connector == NULL) { + if (!connector) { continue; } @@ -1226,6 +1219,38 @@ cleanup: return ret; } +#ifdef SDL_INPUT_LINUXEV +static void KMSDRM_ReleaseVT(void *userdata) +{ + SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata; + SDL_VideoData *viddata = _this->driverdata; + int i; + + for (i = 0; i < viddata->num_windows; i++) { + SDL_Window *window = viddata->windows[i]; + if (!(window->flags & SDL_WINDOW_VULKAN)) { + KMSDRM_DestroySurfaces(_this, window); + } + } + KMSDRM_drmDropMaster(viddata->drm_fd); +} + +static void KMSDRM_AcquireVT(void *userdata) +{ + SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata; + SDL_VideoData *viddata = _this->driverdata; + int i; + + KMSDRM_drmSetMaster(viddata->drm_fd); + for (i = 0; i < viddata->num_windows; i++) { + SDL_Window *window = viddata->windows[i]; + if (!(window->flags & SDL_WINDOW_VULKAN)) { + KMSDRM_CreateSurfaces(_this, window); + } + } +} +#endif /* defined SDL_INPUT_LINUXEV */ + int KMSDRM_VideoInit(SDL_VideoDevice *_this) { int ret = 0; @@ -1246,6 +1271,7 @@ int KMSDRM_VideoInit(SDL_VideoDevice *_this) #ifdef SDL_INPUT_LINUXEV SDL_EVDEV_Init(); + SDL_EVDEV_SetVTSwitchCallbacks(KMSDRM_ReleaseVT, _this, KMSDRM_AcquireVT, _this); #elif defined(SDL_INPUT_WSCONS) SDL_WSCONS_Init(); #endif @@ -1264,6 +1290,7 @@ void KMSDRM_VideoQuit(SDL_VideoDevice *_this) KMSDRM_DeinitDisplays(_this); #ifdef SDL_INPUT_LINUXEV + SDL_EVDEV_SetVTSwitchCallbacks(NULL, NULL, NULL, NULL); SDL_EVDEV_Quit(); #elif defined(SDL_INPUT_WSCONS) SDL_WSCONS_Quit(); @@ -1322,7 +1349,7 @@ int KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL return 0; } - if (modedata == NULL) { + if (!modedata) { return SDL_SetError("Mode doesn't have an associated index"); } @@ -1345,7 +1372,7 @@ void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; /* Is this a VK window? */ unsigned int i, j; - if (windata == NULL) { + if (!windata) { return; } @@ -1415,7 +1442,7 @@ void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) /* reflect it: if it's fullscreen, KMSDRM_SetWindwoFullscreen() will */ /* be called by SDL later, and we can manage it there. */ /**********************************************************************/ -int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *windata = NULL; SDL_VideoData *viddata = _this->driverdata; @@ -1429,14 +1456,19 @@ int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Allocate window internal data */ windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (windata == NULL) { - return SDL_OutOfMemory(); + if (!windata) { + return -1; } /* Setup driver data for this window */ windata->viddata = viddata; window->driverdata = windata; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER, viddata->devindex); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_KMSDRM_DRM_FD_NUMBER, viddata->drm_fd); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_KMSDRM_GBM_DEVICE_POINTER, viddata->gbm_dev); + if (!is_vulkan && !vulkan_mode) { /* NON-Vulkan block. */ /* Maybe you didn't ask for an OPENGL window, but that's what you will get. @@ -1521,13 +1553,14 @@ int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) extra window as a dummy surface when working with multiple contexts */ if (viddata->num_windows >= viddata->max_windows) { unsigned int new_max_windows = viddata->max_windows + 1; - viddata->windows = (SDL_Window **)SDL_realloc(viddata->windows, - new_max_windows * sizeof(SDL_Window *)); + SDL_Window **new_windows = (SDL_Window **)SDL_realloc(viddata->windows, + new_max_windows * sizeof(SDL_Window *)); + if (!new_windows) { + return -1; + } + viddata->windows = new_windows; viddata->max_windows = new_max_windows; - if (!viddata->windows) { - return SDL_OutOfMemory(); - } } viddata->windows[viddata->num_windows++] = window; @@ -1549,11 +1582,6 @@ int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) return ret; } -int KMSDRM_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - return -1; -} - void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { } @@ -1568,13 +1596,13 @@ void KMSDRM_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) KMSDRM_DirtySurfaces(window); } } -void KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) - +int KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { SDL_VideoData *viddata = _this->driverdata; if (!viddata->vulkan_mode) { KMSDRM_DirtySurfaces(window); } + return 0; } void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) { @@ -1595,19 +1623,4 @@ void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { } -/*****************************************************************************/ -/* SDL Window Manager function */ -/*****************************************************************************/ -int KMSDRM_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info) -{ - SDL_VideoData *viddata = _this->driverdata; - - info->subsystem = SDL_SYSWM_KMSDRM; - info->info.kmsdrm.dev_index = viddata->devindex; - info->info.kmsdrm.drm_fd = viddata->drm_fd; - info->info.kmsdrm.gbm_dev = viddata->gbm_dev; - - return 0; -} - #endif /* SDL_VIDEO_DRIVER_KMSDRM */ diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.h b/src/video/kmsdrm/SDL_kmsdrmvideo.h index 24e03fb9..0304f811 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.h +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -121,12 +121,11 @@ int KMSDRM_VideoInit(SDL_VideoDevice *_this); void KMSDRM_VideoQuit(SDL_VideoDevice *_this); int KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); int KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); -int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -int KMSDRM_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +int KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); int KMSDRM_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); void KMSDRM_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); -void KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen); +int KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen); void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); void KMSDRM_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); void KMSDRM_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); @@ -135,9 +134,6 @@ void KMSDRM_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -/* Window manager function */ -int KMSDRM_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); - /* OpenGL/OpenGL ES functions */ int KMSDRM_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path); SDL_FunctionPointer KMSDRM_GLES_GetProcAddress(SDL_VideoDevice *_this, const char *proc); diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/src/video/kmsdrm/SDL_kmsdrmvulkan.c index 619053c9..a3f463ea 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvulkan.c +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,8 +34,6 @@ #include "SDL_kmsdrmdyn.h" #include "SDL_kmsdrmvulkan.h" -#include - #include #ifdef __OpenBSD__ @@ -57,10 +55,10 @@ int KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { path = DEFAULT_VULKAN; } @@ -94,7 +92,7 @@ int KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } @@ -142,20 +140,14 @@ void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) /* members of the VkInstanceCreateInfo struct passed to */ /* vkCreateInstance(). */ /*********************************************************************/ -SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForKMSDRM[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; - } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForKMSDRM), - extensionsForKMSDRM); + if(count) { *count = SDL_arraysize(extensionsForKMSDRM); } + return extensionsForKMSDRM; } /***********************************************************************/ @@ -169,6 +161,7 @@ SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, SDL_bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { VkPhysicalDevice gpu = NULL; @@ -482,7 +475,7 @@ SDL_bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this, display_plane_surface_create_info.alphaMode = alpha_mode; result = vkCreateDisplayPlaneSurfaceKHR(instance, &display_plane_surface_create_info, - NULL, + allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s", diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.h b/src/video/kmsdrm/SDL_kmsdrmvulkan.h index 68efc02d..b29cbe83 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvulkan.h +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/n3ds/SDL_n3dsevents.c b/src/video/n3ds/SDL_n3dsevents.c index 64cbe603..8c963d97 100644 --- a/src/video/n3ds/SDL_n3dsevents.c +++ b/src/video/n3ds/SDL_n3dsevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dsevents_c.h b/src/video/n3ds/SDL_n3dsevents_c.h index 25052cb0..5a7b70dc 100644 --- a/src/video/n3ds/SDL_n3dsevents_c.h +++ b/src/video/n3ds/SDL_n3dsevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dsframebuffer.c b/src/video/n3ds/SDL_n3dsframebuffer.c index 2e93c528..f368506f 100644 --- a/src/video/n3ds/SDL_n3dsframebuffer.c +++ b/src/video/n3ds/SDL_n3dsframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,51 +26,44 @@ #include "SDL_n3dsframebuffer_c.h" #include "SDL_n3dsvideo.h" -#define N3DS_SURFACE "_SDL_N3DSSurface" +#define N3DS_SURFACE "SDL.internal.window.surface" typedef struct { int width, height; } Dimensions; -static void FreePreviousWindowFramebuffer(SDL_Window *window); -static SDL_Surface *CreateNewWindowFramebuffer(SDL_Window *window); static void CopyFramebuffertoN3DS(u32 *dest, const Dimensions dest_dim, const u32 *source, const Dimensions source_dim); static int GetDestOffset(int x, int y, int dest_width); static int GetSourceOffset(int x, int y, int source_width); static void FlushN3DSBuffer(const void *buffer, u32 bufsize, gfxScreen_t screen); +static void CleanupSurface(void *userdata, void *value) +{ + SDL_Surface *surface = (SDL_Surface *)value; + + SDL_DestroySurface(surface); +} + int SDL_N3DS_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch) { SDL_Surface *framebuffer; + int w, h; - FreePreviousWindowFramebuffer(window); - framebuffer = CreateNewWindowFramebuffer(window); + SDL_GetWindowSizeInPixels(window, &w, &h); + framebuffer = SDL_CreateSurface(w, h, FRAMEBUFFER_FORMAT); - if (framebuffer == NULL) { - return SDL_OutOfMemory(); + if (!framebuffer) { + return -1; } - SDL_SetWindowData(window, N3DS_SURFACE, framebuffer); + SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), N3DS_SURFACE, framebuffer, CleanupSurface, NULL); *format = FRAMEBUFFER_FORMAT; *pixels = framebuffer->pixels; *pitch = framebuffer->pitch; return 0; } -static void FreePreviousWindowFramebuffer(SDL_Window *window) -{ - SDL_Surface *surface = (SDL_Surface *)SDL_GetWindowData(window, N3DS_SURFACE); - SDL_DestroySurface(surface); -} - -static SDL_Surface *CreateNewWindowFramebuffer(SDL_Window *window) -{ - int w, h; - SDL_GetWindowSizeInPixels(window, &w, &h); - return SDL_CreateSurface(w, h, FRAMEBUFFER_FORMAT); -} - int SDL_N3DS_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects) { SDL_WindowData *drv_data = window->driverdata; @@ -79,8 +72,8 @@ int SDL_N3DS_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, u32 *framebuffer; u32 bufsize; - surface = (SDL_Surface *)SDL_GetWindowData(window, N3DS_SURFACE); - if (surface == NULL) { + surface = (SDL_Surface *)SDL_GetProperty(SDL_GetWindowProperties(window), N3DS_SURFACE, NULL); + if (!surface) { return SDL_SetError("%s: Unable to get the window surface.", __func__); } @@ -127,9 +120,7 @@ static void FlushN3DSBuffer(const void *buffer, u32 bufsize, gfxScreen_t screen) void SDL_N3DS_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_Surface *surface; - surface = (SDL_Surface *)SDL_SetWindowData(window, N3DS_SURFACE, NULL); - SDL_DestroySurface(surface); + SDL_ClearProperty(SDL_GetWindowProperties(window), N3DS_SURFACE); } #endif /* SDL_VIDEO_DRIVER_N3DS */ diff --git a/src/video/n3ds/SDL_n3dsframebuffer_c.h b/src/video/n3ds/SDL_n3dsframebuffer_c.h index 09caedae..6a3eea0e 100644 --- a/src/video/n3ds/SDL_n3dsframebuffer_c.h +++ b/src/video/n3ds/SDL_n3dsframebuffer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dsswkb.c b/src/video/n3ds/SDL_n3dsswkb.c index 033fa5b5..0948fff0 100644 --- a/src/video/n3ds/SDL_n3dsswkb.c +++ b/src/video/n3ds/SDL_n3dsswkb.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dsswkb.h b/src/video/n3ds/SDL_n3dsswkb.h index fa942ef9..da86f0b5 100644 --- a/src/video/n3ds/SDL_n3dsswkb.h +++ b/src/video/n3ds/SDL_n3dsswkb.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dstouch.c b/src/video/n3ds/SDL_n3dstouch.c index 1bc42578..96ef93bb 100644 --- a/src/video/n3ds/SDL_n3dstouch.c +++ b/src/video/n3ds/SDL_n3dstouch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,8 +36,8 @@ internally in a portrait disposition so the GSP_SCREEN constants are flipped. */ -#define TOUCHSCREEN_SCALE_X 1.0f / GSP_SCREEN_HEIGHT_BOTTOM -#define TOUCHSCREEN_SCALE_Y 1.0f / GSP_SCREEN_WIDTH +#define TOUCHSCREEN_SCALE_X 1.0f / (GSP_SCREEN_HEIGHT_BOTTOM - 1) +#define TOUCHSCREEN_SCALE_Y 1.0f / (GSP_SCREEN_WIDTH - 1) void N3DS_InitTouch(void) { diff --git a/src/video/n3ds/SDL_n3dstouch.h b/src/video/n3ds/SDL_n3dstouch.h index d1e28e47..495688cb 100644 --- a/src/video/n3ds/SDL_n3dstouch.h +++ b/src/video/n3ds/SDL_n3dstouch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/n3ds/SDL_n3dsvideo.c b/src/video/n3ds/SDL_n3dsvideo.c index 74b52071..0c168cb7 100644 --- a/src/video/n3ds/SDL_n3dsvideo.c +++ b/src/video/n3ds/SDL_n3dsvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,7 +36,7 @@ static int AddN3DSDisplay(gfxScreen_t screen); static int N3DS_VideoInit(SDL_VideoDevice *_this); static void N3DS_VideoQuit(SDL_VideoDevice *_this); static int N3DS_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); -static int N3DS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +static int N3DS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); static void N3DS_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); struct SDL_DisplayData @@ -55,8 +55,7 @@ static void N3DS_DeleteDevice(SDL_VideoDevice *device) static SDL_VideoDevice *N3DS_CreateDevice(void) { SDL_VideoDevice *device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } @@ -104,8 +103,8 @@ static int AddN3DSDisplay(gfxScreen_t screen) SDL_DisplayMode mode; SDL_VideoDisplay display; SDL_DisplayData *display_driver_data = SDL_calloc(1, sizeof(SDL_DisplayData)); - if (display_driver_data == NULL) { - return SDL_OutOfMemory(); + if (!display_driver_data) { + return -1; } SDL_zero(mode); @@ -139,7 +138,7 @@ static int N3DS_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *displ { SDL_DisplayData *driver_data = display->driverdata; - if (driver_data == NULL) { + if (!driver_data) { return -1; } @@ -150,12 +149,12 @@ static int N3DS_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *displ return 0; } -static int N3DS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +static int N3DS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_DisplayData *display_data; SDL_WindowData *window_data = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (window_data == NULL) { - return SDL_OutOfMemory(); + if (!window_data) { + return -1; } display_data = SDL_GetDisplayDriverDataForWindow(window); window_data->screen = display_data->screen; @@ -166,7 +165,7 @@ static int N3DS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) static void N3DS_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { - if (window == NULL) { + if (!window) { return; } SDL_free(window->driverdata); diff --git a/src/video/n3ds/SDL_n3dsvideo.h b/src/video/n3ds/SDL_n3dsvideo.h index 0c0af19c..b5c50156 100644 --- a/src/video/n3ds/SDL_n3dsvideo.h +++ b/src/video/n3ds/SDL_n3dsvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/ngage/SDL_ngageevents.cpp b/src/video/ngage/SDL_ngageevents.cpp index 836fa74b..fecfaff9 100644 --- a/src/video/ngage/SDL_ngageevents.cpp +++ b/src/video/ngage/SDL_ngageevents.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/ngage/SDL_ngageevents_c.h b/src/video/ngage/SDL_ngageevents_c.h index b67c93cb..46fa9088 100644 --- a/src/video/ngage/SDL_ngageevents_c.h +++ b/src/video/ngage/SDL_ngageevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/ngage/SDL_ngageframebuffer.cpp b/src/video/ngage/SDL_ngageframebuffer.cpp index a4072f7f..1140eb51 100644 --- a/src/video/ngage/SDL_ngageframebuffer.cpp +++ b/src/video/ngage/SDL_ngageframebuffer.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,7 +57,7 @@ int SDL_NGAGE_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window /* Create a new one */ SDL_GetWindowSizeInPixels(window, &w, &h); surface = SDL_CreateSurface(w, h, surface_format); - if (surface == NULL) { + if (!surface) { return -1; } @@ -149,7 +149,7 @@ int SDL_NGAGE_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window SDL_Surface *surface; surface = (SDL_Surface *)SDL_GetWindowData(window, NGAGE_SURFACE); - if (surface == NULL) { + if (!surface) { return SDL_SetError("Couldn't find ngage surface for window"); } diff --git a/src/video/ngage/SDL_ngageframebuffer_c.h b/src/video/ngage/SDL_ngageframebuffer_c.h index 861c0140..e465eed2 100644 --- a/src/video/ngage/SDL_ngageframebuffer_c.h +++ b/src/video/ngage/SDL_ngageframebuffer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/ngage/SDL_ngagevideo.cpp b/src/video/ngage/SDL_ngagevideo.cpp index 739b2010..1eff11e5 100644 --- a/src/video/ngage/SDL_ngagevideo.cpp +++ b/src/video/ngage/SDL_ngagevideo.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -104,15 +104,13 @@ static SDL_VideoDevice *NGAGE_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } /* Initialize internal N-Gage specific data */ phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (phdata == NULL) { - SDL_OutOfMemory(); + if (!phdata) { SDL_free(device); return 0; } diff --git a/src/video/ngage/SDL_ngagevideo.h b/src/video/ngage/SDL_ngagevideo.h index 0d51b24a..77252e14 100644 --- a/src/video/ngage/SDL_ngagevideo.h +++ b/src/video/ngage/SDL_ngagevideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/ngage/SDL_ngagewindow.cpp b/src/video/ngage/SDL_ngagewindow.cpp index 5772e3e2..57df58c0 100644 --- a/src/video/ngage/SDL_ngagewindow.cpp +++ b/src/video/ngage/SDL_ngagewindow.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,12 +32,12 @@ const TUint32 WindowClientHandle = 9210; void DisableKeyBlocking(SDL_VideoDevice *_this); void ConstructWindowL(SDL_VideoDevice *_this); -int NGAGE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int NGAGE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { NGAGE_Window *ngage_window = (NGAGE_Window *)SDL_calloc(1, sizeof(NGAGE_Window)); - if (ngage_window == NULL) { - return SDL_OutOfMemory(); + if (!ngage_window) { + return -1; } window->driverdata = ngage_window; diff --git a/src/video/ngage/SDL_ngagewindow.h b/src/video/ngage/SDL_ngagewindow.h index 1418ab7c..39b6c5b5 100644 --- a/src/video/ngage/SDL_ngagewindow.h +++ b/src/video/ngage/SDL_ngagewindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,8 +24,6 @@ #include "../SDL_sysvideo.h" -#include - #include "SDL_ngagevideo.h" typedef struct @@ -34,10 +32,7 @@ typedef struct } NGAGE_Window; -extern int -NGAGE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); - -extern void -NGAGE_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int NGAGE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); +extern void NGAGE_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); #endif /* SDL_ngagewindow */ diff --git a/src/video/offscreen/SDL_offscreenevents.c b/src/video/offscreen/SDL_offscreenevents.c index 28b4453d..ce8b50c4 100644 --- a/src/video/offscreen/SDL_offscreenevents.c +++ b/src/video/offscreen/SDL_offscreenevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenevents_c.h b/src/video/offscreen/SDL_offscreenevents_c.h index 80c8011a..f1d59359 100644 --- a/src/video/offscreen/SDL_offscreenevents_c.h +++ b/src/video/offscreen/SDL_offscreenevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenframebuffer.c b/src/video/offscreen/SDL_offscreenframebuffer.c index 2ab1b988..76fa8b72 100644 --- a/src/video/offscreen/SDL_offscreenframebuffer.c +++ b/src/video/offscreen/SDL_offscreenframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,14 @@ #include "../SDL_sysvideo.h" #include "SDL_offscreenframebuffer_c.h" -#define OFFSCREEN_SURFACE "_SDL_DummySurface" +#define OFFSCREEN_SURFACE "SDL.internal.window.surface" + +static void CleanupSurface(void *userdata, void *value) +{ + SDL_Surface *surface = (SDL_Surface *)value; + + SDL_DestroySurface(surface); +} int SDL_OFFSCREEN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch) { @@ -33,18 +40,15 @@ int SDL_OFFSCREEN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wi const Uint32 surface_format = SDL_PIXELFORMAT_XRGB8888; int w, h; - /* Free the old framebuffer surface */ - SDL_OFFSCREEN_DestroyWindowFramebuffer(_this, window); - - /* Create a new one */ + /* Create a new framebuffer */ SDL_GetWindowSizeInPixels(window, &w, &h); surface = SDL_CreateSurface(w, h, surface_format); - if (surface == NULL) { + if (!surface) { return -1; } /* Save the info and return! */ - SDL_SetWindowData(window, OFFSCREEN_SURFACE, surface); + SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), OFFSCREEN_SURFACE, surface, CleanupSurface, NULL); *format = surface_format; *pixels = surface->pixels; *pitch = surface->pitch; @@ -57,8 +61,8 @@ int SDL_OFFSCREEN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wi static int frame_number; SDL_Surface *surface; - surface = (SDL_Surface *)SDL_GetWindowData(window, OFFSCREEN_SURFACE); - if (surface == NULL) { + surface = (SDL_Surface *)SDL_GetProperty(SDL_GetWindowProperties(window), OFFSCREEN_SURFACE, NULL); + if (!surface) { return SDL_SetError("Couldn't find offscreen surface for window"); } @@ -74,10 +78,7 @@ int SDL_OFFSCREEN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wi void SDL_OFFSCREEN_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_Surface *surface; - - surface = (SDL_Surface *)SDL_SetWindowData(window, OFFSCREEN_SURFACE, NULL); - SDL_DestroySurface(surface); + SDL_ClearProperty(SDL_GetWindowProperties(window), OFFSCREEN_SURFACE); } #endif /* SDL_VIDEO_DRIVER_OFFSCREEN */ diff --git a/src/video/offscreen/SDL_offscreenframebuffer_c.h b/src/video/offscreen/SDL_offscreenframebuffer_c.h index 7d50989a..5b446189 100644 --- a/src/video/offscreen/SDL_offscreenframebuffer_c.h +++ b/src/video/offscreen/SDL_offscreenframebuffer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenopengles.c b/src/video/offscreen/SDL_offscreenopengles.c index 3786d540..98601f1f 100644 --- a/src/video/offscreen/SDL_offscreenopengles.c +++ b/src/video/offscreen/SDL_offscreenopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenopengles.h b/src/video/offscreen/SDL_offscreenopengles.h index f3bd5c3c..280880f2 100644 --- a/src/video/offscreen/SDL_offscreenopengles.h +++ b/src/video/offscreen/SDL_offscreenopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenvideo.c b/src/video/offscreen/SDL_offscreenvideo.c index 61334084..e4d3148a 100644 --- a/src/video/offscreen/SDL_offscreenvideo.c +++ b/src/video/offscreen/SDL_offscreenvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -56,8 +56,7 @@ static SDL_VideoDevice *OFFSCREEN_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } @@ -87,6 +86,7 @@ static SDL_VideoDevice *OFFSCREEN_CreateDevice(void) /* "Window" */ device->CreateSDLWindow = OFFSCREEN_CreateWindow; device->DestroyWindow = OFFSCREEN_DestroyWindow; + device->SetWindowSize = OFFSCREEN_SetWindowSize; return device; } diff --git a/src/video/offscreen/SDL_offscreenvideo.h b/src/video/offscreen/SDL_offscreenvideo.h index 8c9c369b..2a998a0d 100644 --- a/src/video/offscreen/SDL_offscreenvideo.h +++ b/src/video/offscreen/SDL_offscreenvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/offscreen/SDL_offscreenwindow.c b/src/video/offscreen/SDL_offscreenwindow.c index d105e834..5950d0c3 100644 --- a/src/video/offscreen/SDL_offscreenwindow.c +++ b/src/video/offscreen/SDL_offscreenwindow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,16 +23,17 @@ #ifdef SDL_VIDEO_DRIVER_OFFSCREEN #include "../SDL_sysvideo.h" +#include "../../events/SDL_windowevents_c.h" #include "../SDL_egl_c.h" #include "SDL_offscreenwindow.h" -int OFFSCREEN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int OFFSCREEN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *offscreen_window = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (offscreen_window == NULL) { - return SDL_OutOfMemory(); + if (!offscreen_window) { + return -1; } window->driverdata = offscreen_window; @@ -82,4 +83,8 @@ void OFFSCREEN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) window->driverdata = NULL; } +void OFFSCREEN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->floating.w, window->floating.h); +} #endif /* SDL_VIDEO_DRIVER_OFFSCREEN */ diff --git a/src/video/offscreen/SDL_offscreenwindow.h b/src/video/offscreen/SDL_offscreenwindow.h index 4f502560..a0188039 100644 --- a/src/video/offscreen/SDL_offscreenwindow.h +++ b/src/video/offscreen/SDL_offscreenwindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,7 +33,8 @@ struct SDL_WindowData #endif }; -extern int OFFSCREEN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int OFFSCREEN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void OFFSCREEN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void OFFSCREEN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); #endif /* SDL_offscreenwindow_h */ diff --git a/src/video/ps2/SDL_ps2video.c b/src/video/ps2/SDL_ps2video.c index caaab5ef..ce1ea0ca 100644 --- a/src/video/ps2/SDL_ps2video.c +++ b/src/video/ps2/SDL_ps2video.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -55,7 +55,7 @@ static void PS2_DeleteDevice(SDL_VideoDevice *device) SDL_free(device); } -static int PS2_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +static int PS2_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_SetKeyboardFocus(window); @@ -95,8 +95,7 @@ static SDL_VideoDevice *PS2_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } diff --git a/src/video/ps2/SDL_ps2video.h b/src/video/ps2/SDL_ps2video.h index 33563ea8..91d8a804 100644 --- a/src/video/ps2/SDL_ps2video.h +++ b/src/video/ps2/SDL_ps2video.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,9 +32,15 @@ #include +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#endif + #include + +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA #pragma GCC diagnostic pop +#endif #endif /* SDL_ps2video_h_ */ diff --git a/src/video/psp/SDL_pspevents.c b/src/video/psp/SDL_pspevents.c index 451d621d..8cec093f 100644 --- a/src/video/psp/SDL_pspevents.c +++ b/src/video/psp/SDL_pspevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/psp/SDL_pspevents_c.h b/src/video/psp/SDL_pspevents_c.h index dd299a6f..8d41581b 100644 --- a/src/video/psp/SDL_pspevents_c.h +++ b/src/video/psp/SDL_pspevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,5 +23,7 @@ extern void PSP_InitOSKeymap(SDL_VideoDevice *_this); extern void PSP_PumpEvents(SDL_VideoDevice *_this); +extern int PSP_EventInit(SDL_VideoDevice *_this); +extern void PSP_EventQuit(SDL_VideoDevice *_this); /* end of SDL_pspevents_c.h ... */ diff --git a/src/video/psp/SDL_pspgl.c b/src/video/psp/SDL_pspgl.c index 58006480..8d64c430 100644 --- a/src/video/psp/SDL_pspgl.c +++ b/src/video/psp/SDL_pspgl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/psp/SDL_pspgl_c.h b/src/video/psp/SDL_pspgl_c.h index eea46f25..72fb6279 100644 --- a/src/video/psp/SDL_pspgl_c.h +++ b/src/video/psp/SDL_pspgl_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/psp/SDL_pspmouse.c b/src/video/psp/SDL_pspmouse.c index a59091d3..42ec898e 100644 --- a/src/video/psp/SDL_pspmouse.c +++ b/src/video/psp/SDL_pspmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/psp/SDL_pspmouse_c.h b/src/video/psp/SDL_pspmouse_c.h index 3742738a..a961232b 100644 --- a/src/video/psp/SDL_pspmouse_c.h +++ b/src/video/psp/SDL_pspmouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/psp/SDL_pspvideo.c b/src/video/psp/SDL_pspvideo.c index 7b47fa38..71542256 100644 --- a/src/video/psp/SDL_pspvideo.c +++ b/src/video/psp/SDL_pspvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,8 +28,6 @@ #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" -#include - /* PSP declarations */ #include "SDL_pspvideo.h" #include "SDL_pspevents_c.h" @@ -53,22 +51,19 @@ static SDL_VideoDevice *PSP_Create() /* Initialize SDL_VideoDevice structure */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } /* Initialize internal PSP specific data */ phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (phdata == NULL) { - SDL_OutOfMemory(); + if (!phdata) { SDL_free(device); return NULL; } gldata = (SDL_GLDriverData *)SDL_calloc(1, sizeof(SDL_GLDriverData)); - if (gldata == NULL) { - SDL_OutOfMemory(); + if (!gldata) { SDL_free(device); SDL_free(phdata); return NULL; @@ -91,7 +86,6 @@ static SDL_VideoDevice *PSP_Create() device->GetDisplayModes = PSP_GetDisplayModes; device->SetDisplayMode = PSP_SetDisplayMode; device->CreateSDLWindow = PSP_CreateWindow; - device->CreateSDLWindowFrom = PSP_CreateWindowFrom; device->SetWindowTitle = PSP_SetWindowTitle; device->SetWindowPosition = PSP_SetWindowPosition; device->SetWindowSize = PSP_SetWindowSize; @@ -134,6 +128,10 @@ int PSP_VideoInit(SDL_VideoDevice *_this) { SDL_DisplayMode mode; + if (PSP_EventInit(_this) == -1) { + return -1; /* error string would already be set */ + } + SDL_zero(mode); mode.w = 480; mode.h = 272; @@ -150,6 +148,7 @@ int PSP_VideoInit(SDL_VideoDevice *_this) void PSP_VideoQuit(SDL_VideoDevice *_this) { + PSP_EventQuit(_this); } int PSP_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) @@ -188,14 +187,14 @@ int PSP_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Di } \ } while (0) -int PSP_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int PSP_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *wdata; /* Allocate window internal data */ wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (wdata == NULL) { - return SDL_OutOfMemory(); + if (!wdata) { + return -1; } /* Setup driver data for this window */ @@ -207,11 +206,6 @@ int PSP_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) return 0; } -int PSP_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - return SDL_Unsupported(); -} - void PSP_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { } diff --git a/src/video/psp/SDL_pspvideo.h b/src/video/psp/SDL_pspvideo.h index 429ad98d..50de4bca 100644 --- a/src/video/psp/SDL_pspvideo.h +++ b/src/video/psp/SDL_pspvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -49,8 +49,7 @@ int PSP_VideoInit(SDL_VideoDevice *_this); void PSP_VideoQuit(SDL_VideoDevice *_this); int PSP_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); int PSP_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); -int PSP_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -int PSP_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +int PSP_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void PSP_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); int PSP_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); void PSP_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/qnx/SDL_qnxgl.c b/src/video/qnx/SDL_qnxgl.c index 91df47df..35a4d68e 100644 --- a/src/video/qnx/SDL_qnxgl.c +++ b/src/video/qnx/SDL_qnxgl.c @@ -84,7 +84,7 @@ int glGetConfig(EGLConfig *pconf, int *pformat) // Allocate enough memory for all configurations. egl_configs = SDL_malloc(egl_num_configs * sizeof(*egl_configs)); - if (egl_configs == NULL) { + if (!egl_configs) { return -1; } diff --git a/src/video/qnx/SDL_qnxvideo.c b/src/video/qnx/SDL_qnxvideo.c index 429f053b..cf8c5b91 100644 --- a/src/video/qnx/SDL_qnxvideo.c +++ b/src/video/qnx/SDL_qnxvideo.c @@ -74,7 +74,7 @@ static int createWindow(SDL_VideoDevice *_this, SDL_Window *window) int usage; impl = SDL_calloc(1, sizeof(*impl)); - if (impl == NULL) { + if (!impl) { return -1; } @@ -241,12 +241,11 @@ static void setWindowSize(SDL_VideoDevice *_this, SDL_Window *window) window_impl_t *impl = (window_impl_t *)window->driverdata; int size[2]; - size[0] = window->w; - size[1] = window->h; + size[0] = window->floating.w; + size[1] = window->floating.h; screen_set_window_property_iv(impl->window, SCREEN_PROPERTY_SIZE, size); - screen_set_window_property_iv(impl->window, SCREEN_PROPERTY_SOURCE_SIZE, - size); + screen_set_window_property_iv(impl->window, SCREEN_PROPERTY_SOURCE_SIZE, size); } /** @@ -310,7 +309,7 @@ static SDL_VideoDevice *createDevice() SDL_VideoDevice *device; device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { + if (!device) { return NULL; } diff --git a/src/video/raspberry/SDL_rpievents.c b/src/video/raspberry/SDL_rpievents.c index a6b9b2df..e592808e 100644 --- a/src/video/raspberry/SDL_rpievents.c +++ b/src/video/raspberry/SDL_rpievents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/raspberry/SDL_rpievents_c.h b/src/video/raspberry/SDL_rpievents_c.h index f56bee88..737e4c9e 100644 --- a/src/video/raspberry/SDL_rpievents_c.h +++ b/src/video/raspberry/SDL_rpievents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,7 +25,5 @@ #include "SDL_rpivideo.h" void RPI_PumpEvents(SDL_VideoDevice *_this); -void RPI_EventInit(SDL_VideoDevice *_this); -void RPI_EventQuit(SDL_VideoDevice *_this); #endif /* SDL_rpievents_c_h_ */ diff --git a/src/video/raspberry/SDL_rpimouse.c b/src/video/raspberry/SDL_rpimouse.c index e188b37c..6999cdb9 100644 --- a/src/video/raspberry/SDL_rpimouse.c +++ b/src/video/raspberry/SDL_rpimouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -65,13 +65,11 @@ static SDL_Cursor *RPI_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) SDL_assert(surface->pitch == surface->w * 4); cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); - if (cursor == NULL) { - SDL_OutOfMemory(); + if (!cursor) { return NULL; } curdata = (RPI_CursorData *)SDL_calloc(1, sizeof(*curdata)); - if (curdata == NULL) { - SDL_OutOfMemory(); + if (!curdata) { SDL_free(cursor); return NULL; } @@ -113,12 +111,12 @@ static int RPI_ShowCursor(SDL_Cursor *cursor) const char *env; mouse = SDL_GetMouse(); - if (mouse == NULL) { + if (!mouse) { return -1; } if (cursor != global_cursor) { - if (global_cursor != NULL) { + if (global_cursor) { curdata = (RPI_CursorData *)global_cursor->driverdata; if (curdata && curdata->element > DISPMANX_NO_HANDLE) { update = vc_dispmanx_update_start(0); @@ -133,21 +131,21 @@ static int RPI_ShowCursor(SDL_Cursor *cursor) global_cursor = cursor; } - if (cursor == NULL) { + if (!cursor) { return 0; } curdata = (RPI_CursorData *)cursor->driverdata; - if (curdata == NULL) { + if (!curdata) { return -1; } - if (mouse->focus == NULL) { + if (!mouse->focus) { return -1; } data = SDL_GetDisplayDriverDataForWindow(mouse->focus); - if (data == NULL) { + if (!data) { return -1; } @@ -188,10 +186,10 @@ static void RPI_FreeCursor(SDL_Cursor *cursor) DISPMANX_UPDATE_HANDLE_T update; RPI_CursorData *curdata; - if (cursor != NULL) { + if (cursor) { curdata = (RPI_CursorData *)cursor->driverdata; - if (curdata != NULL) { + if (curdata) { if (curdata->element != DISPMANX_NO_HANDLE) { update = vc_dispmanx_update_start(0); SDL_assert(update); @@ -224,7 +222,7 @@ static int RPI_WarpMouseGlobalGraphically(float x, float y) VC_RECT_T src_rect; SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) { + if (!mouse || !mouse->cur_cursor || !mouse->cur_cursor->driverdata) { return 0; } @@ -273,7 +271,7 @@ static int RPI_WarpMouseGlobal(float x, float y) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) { + if (!mouse || !mouse->cur_cursor || !mouse->cur_cursor->driverdata) { return 0; } diff --git a/src/video/raspberry/SDL_rpimouse.h b/src/video/raspberry/SDL_rpimouse.h index dc31b170..074b6e18 100644 --- a/src/video/raspberry/SDL_rpimouse.h +++ b/src/video/raspberry/SDL_rpimouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/raspberry/SDL_rpiopengles.c b/src/video/raspberry/SDL_rpiopengles.c index 0bb530e6..6d5718eb 100644 --- a/src/video/raspberry/SDL_rpiopengles.c +++ b/src/video/raspberry/SDL_rpiopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/raspberry/SDL_rpiopengles.h b/src/video/raspberry/SDL_rpiopengles.h index 209d80e1..93fcc011 100644 --- a/src/video/raspberry/SDL_rpiopengles.h +++ b/src/video/raspberry/SDL_rpiopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/raspberry/SDL_rpivideo.c b/src/video/raspberry/SDL_rpivideo.c index 40519a92..d3b42285 100644 --- a/src/video/raspberry/SDL_rpivideo.c +++ b/src/video/raspberry/SDL_rpivideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,8 +35,6 @@ #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" -#include - #ifdef SDL_INPUT_LINUXEV #include "../../core/linux/SDL_evdev.h" #endif @@ -78,15 +76,13 @@ static SDL_VideoDevice *RPI_Create() /* Initialize SDL_VideoDevice structure */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } /* Initialize internal data */ phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (phdata == NULL) { - SDL_OutOfMemory(); + if (!phdata) { SDL_free(device); return NULL; } @@ -103,7 +99,6 @@ static SDL_VideoDevice *RPI_Create() device->VideoInit = RPI_VideoInit; device->VideoQuit = RPI_VideoQuit; device->CreateSDLWindow = RPI_CreateWindow; - device->CreateSDLWindowFrom = RPI_CreateWindowFrom; device->SetWindowTitle = RPI_SetWindowTitle; device->SetWindowPosition = RPI_SetWindowPosition; device->SetWindowSize = RPI_SetWindowSize; @@ -172,7 +167,7 @@ static void AddDispManXDisplay(const int display_id) /* Allocate display internal data */ data = (SDL_DisplayData *)SDL_calloc(1, sizeof(SDL_DisplayData)); - if (data == NULL) { + if (!data) { vc_dispmanx_display_close(handle); return; /* oh well */ } @@ -219,7 +214,7 @@ static void RPI_vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *data) SDL_UnlockMutex(wdata->vsync_cond_mutex); } -int RPI_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int RPI_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *wdata; SDL_VideoDisplay *display; @@ -238,8 +233,8 @@ int RPI_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Allocate window internal data */ wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (wdata == NULL) { - return SDL_OutOfMemory(); + if (!wdata) { + return -1; } display = SDL_GetVideoDisplayForWindow(window); displaydata = display->driverdata; @@ -341,11 +336,6 @@ void RPI_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } } -int RPI_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - return -1; -} - void RPI_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { } diff --git a/src/video/raspberry/SDL_rpivideo.h b/src/video/raspberry/SDL_rpivideo.h index b620d5d1..ee0b612b 100644 --- a/src/video/raspberry/SDL_rpivideo.h +++ b/src/video/raspberry/SDL_rpivideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,8 +63,7 @@ int RPI_VideoInit(SDL_VideoDevice *_this); void RPI_VideoQuit(SDL_VideoDevice *_this); int RPI_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); int RPI_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); -int RPI_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -int RPI_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +int RPI_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void RPI_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); int RPI_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); void RPI_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/riscos/SDL_riscosdefs.h b/src/video/riscos/SDL_riscosdefs.h index 9817d16c..890d61be 100644 --- a/src/video/riscos/SDL_riscosdefs.h +++ b/src/video/riscos/SDL_riscosdefs.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosevents.c b/src/video/riscos/SDL_riscosevents.c index 18d8fde1..ab530115 100644 --- a/src/video/riscos/SDL_riscosevents.c +++ b/src/video/riscos/SDL_riscosevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosevents_c.h b/src/video/riscos/SDL_riscosevents_c.h index 2bc272ba..2a62ccfc 100644 --- a/src/video/riscos/SDL_riscosevents_c.h +++ b/src/video/riscos/SDL_riscosevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosframebuffer.c b/src/video/riscos/SDL_riscosframebuffer.c index 6fe8ec1a..bf03bc11 100644 --- a/src/video/riscos/SDL_riscosframebuffer.c +++ b/src/video/riscos/SDL_riscosframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,7 +63,7 @@ int RISCOS_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, U size = sizeof(sprite_area) + sizeof(sprite_header) + ((*pitch) * h); driverdata->fb_area = SDL_malloc(size); if (!driverdata->fb_area) { - return SDL_OutOfMemory(); + return -1; } driverdata->fb_area->size = size; @@ -80,7 +80,7 @@ int RISCOS_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, U regs.r[5] = h; regs.r[6] = sprite_mode; error = _kernel_swi(OS_SpriteOp, ®s, ®s); - if (error != NULL) { + if (error) { SDL_free(driverdata->fb_area); return SDL_SetError("Unable to create sprite: %s (%i)", error->errmess, error->errnum); } @@ -106,7 +106,7 @@ int RISCOS_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, c regs.r[6] = 0; regs.r[7] = 0; error = _kernel_swi(OS_SpriteOp, ®s, ®s); - if (error != NULL) { + if (error) { return SDL_SetError("OS_SpriteOp 52 failed: %s (%i)", error->errmess, error->errnum); } diff --git a/src/video/riscos/SDL_riscosframebuffer_c.h b/src/video/riscos/SDL_riscosframebuffer_c.h index 926b76a6..daa51328 100644 --- a/src/video/riscos/SDL_riscosframebuffer_c.h +++ b/src/video/riscos/SDL_riscosframebuffer_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosmessagebox.c b/src/video/riscos/SDL_riscosmessagebox.c index 223ef8d7..054199d7 100644 --- a/src/video/riscos/SDL_riscosmessagebox.c +++ b/src/video/riscos/SDL_riscosmessagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosmessagebox.h b/src/video/riscos/SDL_riscosmessagebox.h index ef16f392..dcdf36e6 100644 --- a/src/video/riscos/SDL_riscosmessagebox.h +++ b/src/video/riscos/SDL_riscosmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosmodes.c b/src/video/riscos/SDL_riscosmodes.c index f9d906bb..8640163f 100644 --- a/src/video/riscos/SDL_riscosmodes.c +++ b/src/video/riscos/SDL_riscosmodes.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -167,7 +167,7 @@ static void *convert_mode_block(const int *block) } dst = SDL_malloc(40); - if (dst == NULL) { + if (!dst) { return NULL; } @@ -208,7 +208,7 @@ int RISCOS_InitModes(SDL_VideoDevice *_this) regs.r[0] = 1; error = _kernel_swi(OS_ScreenMode, ®s, ®s); - if (error != NULL) { + if (error) { return SDL_SetError("Unable to retrieve the current screen mode: %s (%i)", error->errmess, error->errnum); } @@ -220,7 +220,7 @@ int RISCOS_InitModes(SDL_VideoDevice *_this) size = measure_mode_block(current_mode); mode.driverdata = copy_memory(current_mode, size, size); if (!mode.driverdata) { - return SDL_OutOfMemory(); + return -1; } if (SDL_AddBasicVideoDisplay(&mode) == 0) { @@ -241,19 +241,19 @@ int RISCOS_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) regs.r[6] = 0; regs.r[7] = 0; error = _kernel_swi(OS_ScreenMode, ®s, ®s); - if (error != NULL) { + if (error) { return SDL_SetError("Unable to enumerate screen modes: %s (%i)", error->errmess, error->errnum); } block = SDL_malloc(-regs.r[7]); - if (block == NULL) { - return SDL_OutOfMemory(); + if (!block) { + return -1; } regs.r[6] = (int)block; regs.r[7] = -regs.r[7]; error = _kernel_swi(OS_ScreenMode, ®s, ®s); - if (error != NULL) { + if (error) { SDL_free(block); return SDL_SetError("Unable to enumerate screen modes: %s (%i)", error->errmess, error->errnum); } @@ -270,7 +270,7 @@ int RISCOS_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) mode.driverdata = convert_mode_block(pos + 4); if (!mode.driverdata) { SDL_free(block); - return SDL_OutOfMemory(); + return -1; } if (!SDL_AddFullscreenDisplayMode(display, &mode)) { @@ -292,7 +292,7 @@ int RISCOS_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL regs.r[0] = 0; regs.r[1] = (int)mode->driverdata; error = _kernel_swi(OS_ScreenMode, ®s, ®s); - if (error != NULL) { + if (error) { return SDL_SetError("Unable to set the current screen mode: %s (%i)", error->errmess, error->errnum); } diff --git a/src/video/riscos/SDL_riscosmodes.h b/src/video/riscos/SDL_riscosmodes.h index e3600974..1307e602 100644 --- a/src/video/riscos/SDL_riscosmodes.h +++ b/src/video/riscos/SDL_riscosmodes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosmouse.c b/src/video/riscos/SDL_riscosmouse.c index 40b7cf2b..32f5d251 100644 --- a/src/video/riscos/SDL_riscosmouse.c +++ b/src/video/riscos/SDL_riscosmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,14 +30,10 @@ static SDL_Cursor *RISCOS_CreateDefaultCursor() { - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { /* NULL is used to indicate the default cursor */ cursor->driverdata = NULL; - } else { - SDL_OutOfMemory(); } return cursor; diff --git a/src/video/riscos/SDL_riscosmouse.h b/src/video/riscos/SDL_riscosmouse.h index 02c1e3ed..a71e6558 100644 --- a/src/video/riscos/SDL_riscosmouse.h +++ b/src/video/riscos/SDL_riscosmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscosvideo.c b/src/video/riscos/SDL_riscosvideo.c index dd57ce83..398628a2 100644 --- a/src/video/riscos/SDL_riscosvideo.c +++ b/src/video/riscos/SDL_riscosvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -54,15 +54,13 @@ static SDL_VideoDevice *RISCOS_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } /* Initialize internal data */ phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (phdata == NULL) { - SDL_OutOfMemory(); + if (!phdata) { SDL_free(device); return NULL; } @@ -79,7 +77,6 @@ static SDL_VideoDevice *RISCOS_CreateDevice(void) device->CreateSDLWindow = RISCOS_CreateWindow; device->DestroyWindow = RISCOS_DestroyWindow; - device->GetWindowWMInfo = RISCOS_GetWindowWMInfo; device->CreateWindowFramebuffer = RISCOS_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = RISCOS_UpdateWindowFramebuffer; diff --git a/src/video/riscos/SDL_riscosvideo.h b/src/video/riscos/SDL_riscosvideo.h index 190afa97..986bcc2f 100644 --- a/src/video/riscos/SDL_riscosvideo.h +++ b/src/video/riscos/SDL_riscosvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/riscos/SDL_riscoswindow.c b/src/video/riscos/SDL_riscoswindow.c index 80a0e94f..eaacffe3 100644 --- a/src/video/riscos/SDL_riscoswindow.c +++ b/src/video/riscos/SDL_riscoswindow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,18 +25,16 @@ #include "../SDL_sysvideo.h" #include "../../events/SDL_mouse_c.h" -#include - #include "SDL_riscosvideo.h" #include "SDL_riscoswindow.h" -int RISCOS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int RISCOS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *driverdata; driverdata = (SDL_WindowData *)SDL_calloc(1, sizeof(*driverdata)); - if (driverdata == NULL) { - return SDL_OutOfMemory(); + if (!driverdata) { + return -1; } driverdata->window = window; @@ -53,7 +51,7 @@ void RISCOS_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *driverdata = window->driverdata; - if (driverdata == NULL) { + if (!driverdata) { return; } @@ -61,10 +59,4 @@ void RISCOS_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) window->driverdata = NULL; } -int RISCOS_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info) -{ - info->subsystem = SDL_SYSWM_RISCOS; - return 0; -} - #endif /* SDL_VIDEO_DRIVER_RISCOS */ diff --git a/src/video/riscos/SDL_riscoswindow.h b/src/video/riscos/SDL_riscoswindow.h index e0546648..a692f747 100644 --- a/src/video/riscos/SDL_riscoswindow.h +++ b/src/video/riscos/SDL_riscoswindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,8 +32,7 @@ struct SDL_WindowData sprite_header *fb_sprite; }; -extern int RISCOS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int RISCOS_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void RISCOS_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int RISCOS_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); #endif /* SDL_riscoswindow_h_ */ diff --git a/src/video/riscos/scancodes_riscos.h b/src/video/riscos/scancodes_riscos.h index 3895c99e..8dfe1436 100644 --- a/src/video/riscos/scancodes_riscos.h +++ b/src/video/riscos/scancodes_riscos.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/sdlgenblit.pl b/src/video/sdlgenblit.pl index 1db1ae51..ad260d29 100755 --- a/src/video/sdlgenblit.pl +++ b/src/video/sdlgenblit.pl @@ -92,7 +92,7 @@ sub open_file { /* DO NOT EDIT! This file is generated by sdlgenblit.pl */ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitappdelegate.h b/src/video/uikit/SDL_uikitappdelegate.h index e13e6c09..c52594dc 100644 --- a/src/video/uikit/SDL_uikitappdelegate.h +++ b/src/video/uikit/SDL_uikitappdelegate.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index e9d6ca33..d8c3a9e0 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -212,7 +212,7 @@ static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) NSArray *launchimages = [bundle objectForInfoDictionaryKey:@"UILaunchImages"]; NSString *imagename = nil; UIImage *image = nil; - + #if TARGET_OS_XR int screenw = SDL_XR_SCREENWIDTH; int screenh = SDL_XR_SCREENHEIGHT; @@ -221,7 +221,7 @@ static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) int screenh = (int)([UIScreen mainScreen].bounds.size.height + 0.5); #endif - + #if !TARGET_OS_TV && !TARGET_OS_XR UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; @@ -505,13 +505,14 @@ static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) /* Do nothing. */ } -- (void)sendDropFileForURL:(NSURL *)url +- (void)sendDropFileForURL:(NSURL *)url fromSourceApplication:(NSString *)sourceApplication { NSURL *fileURL = url.filePathURL; + char *sourceApplicationCString = sourceApplication ? [sourceApplication UTF8String] : NULL; if (fileURL != nil) { - SDL_SendDropFile(NULL, fileURL.path.UTF8String); + SDL_SendDropFile(NULL, sourceApplicationCString, fileURL.path.UTF8String); } else { - SDL_SendDropFile(NULL, url.absoluteString.UTF8String); + SDL_SendDropFile(NULL, sourceApplicationCString, url.absoluteString.UTF8String); } SDL_SendDropComplete(NULL); } @@ -521,7 +522,7 @@ static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { /* TODO: Handle options */ - [self sendDropFileForURL:url]; + [self sendDropFileForURL:url fromSourceApplication:NULL]; return YES; } @@ -529,7 +530,7 @@ static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - [self sendDropFileForURL:url]; + [self sendDropFileForURL:url fromSourceApplication:sourceApplication]; return YES; } diff --git a/src/video/uikit/SDL_uikitclipboard.h b/src/video/uikit/SDL_uikitclipboard.h index cb86efdd..584f3523 100644 --- a/src/video/uikit/SDL_uikitclipboard.h +++ b/src/video/uikit/SDL_uikitclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitclipboard.m b/src/video/uikit/SDL_uikitclipboard.m index 589c55ed..cc5a757c 100644 --- a/src/video/uikit/SDL_uikitclipboard.m +++ b/src/video/uikit/SDL_uikitclipboard.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitevents.h b/src/video/uikit/SDL_uikitevents.h index 82bccad6..08c6cbcc 100644 --- a/src/video/uikit/SDL_uikitevents.h +++ b/src/video/uikit/SDL_uikitevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitevents.m b/src/video/uikit/SDL_uikitevents.m index 8913c3da..00af2cc4 100644 --- a/src/video/uikit/SDL_uikitevents.m +++ b/src/video/uikit/SDL_uikitevents.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -273,6 +273,31 @@ static int mice_connected = 0; static id mouse_connect_observer = nil; static id mouse_disconnect_observer = nil; static bool mouse_relative_mode = SDL_FALSE; +static SDL_MouseWheelDirection mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; + +static void UpdateScrollDirection(void) +{ +#if 0 /* This code doesn't work for some reason */ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + if ([userDefaults boolForKey:@"com.apple.swipescrolldirection"]) { + mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED; + } else { + mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; + } +#else + Boolean keyExistsAndHasValidFormat = NO; + Boolean naturalScrollDirection = CFPreferencesGetAppBooleanValue(CFSTR("com.apple.swipescrolldirection"), kCFPreferencesAnyApplication, &keyExistsAndHasValidFormat); + if (!keyExistsAndHasValidFormat) { + /* Couldn't read the preference, assume natural scrolling direction */ + naturalScrollDirection = YES; + } + if (naturalScrollDirection) { + mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED; + } else { + mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; + } +#endif +} static void UpdatePointerLock(void) { @@ -325,8 +350,21 @@ static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14 }; mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - SDL_SendMouseWheel(0, SDL_GetMouseFocus(), 0, xValue, yValue, SDL_MOUSEWHEEL_NORMAL); + /* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis. + * The vertical values are negative moving the mouse wheel up and positive moving it down. + * The horizontal values are negative moving the mouse wheel left and positive moving it right. + * The vertical values are inverted compared to SDL, and the horizontal values are as expected. + */ + float vertical = -xValue; + float horizontal = yValue; + if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) { + /* Since these are raw values, we need to flip them ourselves */ + vertical = -vertical; + horizontal = -horizontal; + } + SDL_SendMouseWheel(0, SDL_GetMouseFocus(), mouseID, horizontal, vertical, mouse_scroll_direction); }; + UpdateScrollDirection(); dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.mouse", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); diff --git a/src/video/uikit/SDL_uikitmessagebox.h b/src/video/uikit/SDL_uikitmessagebox.h index 38c90b5a..7930ab26 100644 --- a/src/video/uikit/SDL_uikitmessagebox.h +++ b/src/video/uikit/SDL_uikitmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitmessagebox.m b/src/video/uikit/SDL_uikitmessagebox.m index e7d80ef7..55b69b31 100644 --- a/src/video/uikit/SDL_uikitmessagebox.m +++ b/src/video/uikit/SDL_uikitmessagebox.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitmetalview.h b/src/video/uikit/SDL_uikitmetalview.h index b6c7a4ce..1bfde536 100644 --- a/src/video/uikit/SDL_uikitmetalview.h +++ b/src/video/uikit/SDL_uikitmetalview.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitmetalview.m b/src/video/uikit/SDL_uikitmetalview.m index c5317ebd..a8d34d49 100644 --- a/src/video/uikit/SDL_uikitmetalview.m +++ b/src/video/uikit/SDL_uikitmetalview.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,8 +35,6 @@ #import "SDL_uikitwindow.h" #import "SDL_uikitmetalview.h" -#include - @implementation SDL_uikitmetalview /* Returns a Metal-compatible layer. */ diff --git a/src/video/uikit/SDL_uikitmodes.h b/src/video/uikit/SDL_uikitmodes.h index b92f1901..a7171397 100644 --- a/src/video/uikit/SDL_uikitmodes.h +++ b/src/video/uikit/SDL_uikitmodes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitmodes.m b/src/video/uikit/SDL_uikitmodes.m index e397e97a..9d0f479c 100644 --- a/src/video/uikit/SDL_uikitmodes.m +++ b/src/video/uikit/SDL_uikitmodes.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitopengles.h b/src/video/uikit/SDL_uikitopengles.h index 3e280df7..27f9f431 100644 --- a/src/video/uikit/SDL_uikitopengles.h +++ b/src/video/uikit/SDL_uikitopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitopengles.m b/src/video/uikit/SDL_uikitopengles.m index 15163f99..e1e82e03 100644 --- a/src/video/uikit/SDL_uikitopengles.m +++ b/src/video/uikit/SDL_uikitopengles.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitopenglview.h b/src/video/uikit/SDL_uikitopenglview.h index bad1b2ee..48a51cd9 100644 --- a/src/video/uikit/SDL_uikitopenglview.h +++ b/src/video/uikit/SDL_uikitopenglview.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitopenglview.m b/src/video/uikit/SDL_uikitopenglview.m index 345c933c..965a86d8 100644 --- a/src/video/uikit/SDL_uikitopenglview.m +++ b/src/video/uikit/SDL_uikitopenglview.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitvideo.h b/src/video/uikit/SDL_uikitvideo.h index f1382229..4022cfbb 100644 --- a/src/video/uikit/SDL_uikitvideo.h +++ b/src/video/uikit/SDL_uikitvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m index 0e1919bd..99cffbac 100644 --- a/src/video/uikit/SDL_uikitvideo.m +++ b/src/video/uikit/SDL_uikitvideo.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -67,14 +67,12 @@ static SDL_VideoDevice *UIKit_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device) { - data = [SDL_UIKitVideoData new]; - } else { - SDL_free(device); - SDL_OutOfMemory(); - return (0); + if (!device) { + return NULL; } + data = [SDL_UIKitVideoData new]; + device->driverdata = (SDL_VideoData *)CFBridgingRetain(data); device->system_theme = UIKit_GetSystemTheme(); @@ -94,7 +92,6 @@ static SDL_VideoDevice *UIKit_CreateDevice(void) device->SetWindowFullscreen = UIKit_SetWindowFullscreen; device->SetWindowMouseGrab = UIKit_SetWindowMouseGrab; device->DestroyWindow = UIKit_DestroyWindow; - device->GetWindowWMInfo = UIKit_GetWindowWMInfo; device->GetDisplayUsableBounds = UIKit_GetDisplayUsableBounds; device->GetWindowSizeInPixels = UIKit_GetWindowSizeInPixels; @@ -134,6 +131,8 @@ static SDL_VideoDevice *UIKit_CreateDevice(void) device->Metal_GetLayer = UIKit_Metal_GetLayer; #endif + device->device_caps = VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; + device->gl_config.accelerated = 1; return device; diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h index 9fe16864..2e239ae7 100644 --- a/src/video/uikit/SDL_uikitview.h +++ b/src/video/uikit/SDL_uikitview.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index e3c92156..b159cca0 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitviewcontroller.h b/src/video/uikit/SDL_uikitviewcontroller.h index ddb634e1..5c188769 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.h +++ b/src/video/uikit/SDL_uikitviewcontroller.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m index 7dc1abee..9874b93c 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.m +++ b/src/video/uikit/SDL_uikitviewcontroller.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -511,12 +511,14 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char - (void)updateKeyboard { + SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *) window->driverdata; + CGAffineTransform t = self.view.transform; CGPoint offset = CGPointMake(0.0, 0.0); #if TARGET_OS_XR CGRect frame = UIKit_ComputeViewFrame(window); #else - CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen); + CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); #endif if (self.keyboardHeight) { diff --git a/src/video/uikit/SDL_uikitvulkan.h b/src/video/uikit/SDL_uikitvulkan.h index 0e136756..385fdfc0 100644 --- a/src/video/uikit/SDL_uikitvulkan.h +++ b/src/video/uikit/SDL_uikitvulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/uikit/SDL_uikitvulkan.m b/src/video/uikit/SDL_uikitvulkan.m index 5bdc7438..f2d00220 100644 --- a/src/video/uikit/SDL_uikitvulkan.m +++ b/src/video/uikit/SDL_uikitvulkan.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,8 +34,6 @@ #include "SDL_uikitvulkan.h" #include "SDL_uikitmetalview.h" -#include - #include const char *defaultPaths[] = { @@ -169,26 +167,22 @@ void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForUIKit[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; + if(count) { + *count = SDL_arraysize(extensionsForUIKit); } - - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForUIKit), - extensionsForUIKit); + return extensionsForUIKit; } SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = @@ -227,7 +221,7 @@ SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.pLayer = (__bridge const CAMetalLayer *) UIKit_Metal_GetLayer(_this, metalview); - result = vkCreateMetalSurfaceEXT(instance, &createInfo, NULL, surface); + result = vkCreateMetalSurfaceEXT(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { UIKit_Metal_DestroyView(_this, metalview); SDL_SetError("vkCreateMetalSurfaceEXT failed: %s", @@ -241,7 +235,7 @@ SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.pView = (const void *)metalview; result = vkCreateIOSSurfaceMVK(instance, &createInfo, - NULL, surface); + allocator, surface); if (result != VK_SUCCESS) { UIKit_Metal_DestroyView(_this, metalview); SDL_SetError("vkCreateIOSSurfaceMVK failed: %s", diff --git a/src/video/uikit/SDL_uikitwindow.h b/src/video/uikit/SDL_uikitwindow.h index ba895662..4daad157 100644 --- a/src/video/uikit/SDL_uikitwindow.h +++ b/src/video/uikit/SDL_uikitwindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,21 +26,22 @@ #import "SDL_uikitview.h" #import "SDL_uikitviewcontroller.h" -extern int UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); -extern void UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void UIKit_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); -extern int UIKit_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window); +#define SDL_METALVIEW_TAG 255 + @class UIWindow; @interface SDL_UIKitWindowData : NSObject diff --git a/src/video/uikit/SDL_uikitwindow.m b/src/video/uikit/SDL_uikitwindow.m index ed8c6dcd..f16ef5b0 100644 --- a/src/video/uikit/SDL_uikitwindow.m +++ b/src/video/uikit/SDL_uikitwindow.m @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,8 +34,6 @@ #include "SDL_uikitview.h" #include "SDL_uikitopenglview.h" -#include - #include @implementation SDL_UIKitWindowData @@ -86,13 +84,13 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->driverdata; SDL_uikitview *view; - + #if TARGET_OS_XR CGRect frame = UIKit_ComputeViewFrame(window); #else CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen); #endif - + int width = (int)frame.size.width; int height = (int)frame.size.height; @@ -115,14 +113,6 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow #if !TARGET_OS_TV && !TARGET_OS_XR if (displaydata.uiscreen == [UIScreen mainScreen]) { - /* SDL_CreateWindow sets the window w&h to the display's bounds if the - * fullscreen flag is set. But the display bounds orientation might not - * match what we want, and GetSupportedOrientations call below uses the - * window w&h. They're overridden below anyway, so we'll just set them - * to the requested size for the purposes of determining orientation. */ - window->w = window->windowed.w; - window->h = window->windowed.h; - NSUInteger orients = UIKit_GetSupportedOrientations(window); BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0; BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown)) != 0; @@ -155,10 +145,14 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow * hierarchy. */ [view setSDLWindow:window]; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG); + return 0; } -int UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { @autoreleasepool { SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); @@ -303,15 +297,22 @@ static void UIKit_UpdateWindowBorder(SDL_VideoDevice *_this, SDL_Window *window) void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered) { @autoreleasepool { + if (bordered) { + window->flags &= ~SDL_WINDOW_BORDERLESS; + } else { + window->flags |= SDL_WINDOW_BORDERLESS; + } UIKit_UpdateWindowBorder(_this, window); } } -void UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +int UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { @autoreleasepool { + SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); UIKit_UpdateWindowBorder(_this, window); } + return 0; } void UIKit_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed) @@ -385,26 +386,6 @@ void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int } } -int UIKit_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - @autoreleasepool { - SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->driverdata; - - info->subsystem = SDL_SYSWM_UIKIT; - info->info.uikit.window = data.uiwindow; - -#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) - if ([data.viewcontroller.view isKindOfClass:[SDL_uikitopenglview class]]) { - SDL_uikitopenglview *glview = (SDL_uikitopenglview *)data.viewcontroller.view; - info->info.uikit.framebuffer = glview.drawableFramebuffer; - info->info.uikit.colorbuffer = glview.drawableRenderbuffer; - info->info.uikit.resolveFramebuffer = glview.msaaResolveFramebuffer; - } -#endif - return 0; - } -} - #if !TARGET_OS_TV NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window) @@ -448,10 +429,10 @@ UIKit_GetSupportedOrientations(SDL_Window *window) } if (orientationMask == 0) { - if (window->w >= window->h) { + if (window->floating.w >= window->floating.h) { orientationMask |= UIInterfaceOrientationMaskLandscape; } - if (window->h >= window->w) { + if (window->floating.h >= window->floating.w) { orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown); } } diff --git a/src/video/vita/SDL_vitaframebuffer.c b/src/video/vita/SDL_vitaframebuffer.c index ef505d75..07d06e9d 100644 --- a/src/video/vita/SDL_vitaframebuffer.c +++ b/src/video/vita/SDL_vitaframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -103,7 +103,7 @@ void VITA_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; - if (data == NULL) { + if (!data) { /* The window wasn't fully initialized */ return; } diff --git a/src/video/vita/SDL_vitaframebuffer.h b/src/video/vita/SDL_vitaframebuffer.h index eda9b599..0502dbf2 100644 --- a/src/video/vita/SDL_vitaframebuffer.h +++ b/src/video/vita/SDL_vitaframebuffer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitagl_pvr.c b/src/video/vita/SDL_vitagl_pvr.c index af9897e5..a35a94c6 100644 --- a/src/video/vita/SDL_vitagl_pvr.c +++ b/src/video/vita/SDL_vitagl_pvr.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -51,8 +51,8 @@ int VITA_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) char *default_path = "app0:module"; char target_path[MAX_PATH]; - if (skip_init == NULL) { // we don't care about actual value - if (override != NULL) { + if (!skip_init) { // we don't care about actual value + if (override) { default_path = override; } diff --git a/src/video/vita/SDL_vitagl_pvr_c.h b/src/video/vita/SDL_vitagl_pvr_c.h index 99aceada..2b9444c6 100644 --- a/src/video/vita/SDL_vitagl_pvr_c.h +++ b/src/video/vita/SDL_vitagl_pvr_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitagles.c b/src/video/vita/SDL_vitagles.c index f058c374..da3b6fea 100644 --- a/src/video/vita/SDL_vitagles.c +++ b/src/video/vita/SDL_vitagles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitagles_c.h b/src/video/vita/SDL_vitagles_c.h index 36885982..d2e89a2b 100644 --- a/src/video/vita/SDL_vitagles_c.h +++ b/src/video/vita/SDL_vitagles_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitagles_pvr.c b/src/video/vita/SDL_vitagles_pvr.c index c862d6ca..c79c29cc 100644 --- a/src/video/vita/SDL_vitagles_pvr.c +++ b/src/video/vita/SDL_vitagles_pvr.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -40,9 +40,9 @@ int VITA_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path) char *default_path = "app0:module"; char target_path[MAX_PATH]; - if (skip_init == NULL) { // we don't care about actual value + if (!skip_init) { // we don't care about actual value - if (override != NULL) { + if (override) { default_path = override; } diff --git a/src/video/vita/SDL_vitagles_pvr_c.h b/src/video/vita/SDL_vitagles_pvr_c.h index afe60f3b..d738e1da 100644 --- a/src/video/vita/SDL_vitagles_pvr_c.h +++ b/src/video/vita/SDL_vitagles_pvr_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitakeyboard.c b/src/video/vita/SDL_vitakeyboard.c index f9276242..255c916c 100644 --- a/src/video/vita/SDL_vitakeyboard.c +++ b/src/video/vita/SDL_vitakeyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -48,7 +48,7 @@ void VITA_InitKeyboard(void) void VITA_PollKeyboard(void) { // We skip polling keyboard if no window is created - if (Vita_Window == NULL) { + if (!Vita_Window) { return; } diff --git a/src/video/vita/SDL_vitakeyboard.h b/src/video/vita/SDL_vitakeyboard.h index 2ef71266..d5fa224f 100644 --- a/src/video/vita/SDL_vitakeyboard.h +++ b/src/video/vita/SDL_vitakeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitamessagebox.c b/src/video/vita/SDL_vitamessagebox.c index 63bf89e3..0e8580f8 100644 --- a/src/video/vita/SDL_vitamessagebox.c +++ b/src/video/vita/SDL_vitamessagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitamessagebox.h b/src/video/vita/SDL_vitamessagebox.h index 87c4bda9..d8f5aadb 100644 --- a/src/video/vita/SDL_vitamessagebox.h +++ b/src/video/vita/SDL_vitamessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitamouse.c b/src/video/vita/SDL_vitamouse.c index 88dd0187..836d33ee 100644 --- a/src/video/vita/SDL_vitamouse.c +++ b/src/video/vita/SDL_vitamouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -42,7 +42,7 @@ void VITA_InitMouse(void) void VITA_PollMouse(void) { // We skip polling mouse if no window is created - if (Vita_Window == NULL) { + if (!Vita_Window) { return; } diff --git a/src/video/vita/SDL_vitamouse_c.h b/src/video/vita/SDL_vitamouse_c.h index 096fc053..8efb8c0f 100644 --- a/src/video/vita/SDL_vitamouse_c.h +++ b/src/video/vita/SDL_vitamouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitatouch.c b/src/video/vita/SDL_vitatouch.c index bdc041a3..ae13c6d4 100644 --- a/src/video/vita/SDL_vitatouch.c +++ b/src/video/vita/SDL_vitatouch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -84,7 +84,7 @@ void VITA_PollTouch(void) int port; // We skip polling touch if no window is created - if (Vita_Window == NULL) { + if (!Vita_Window) { return; } @@ -173,8 +173,18 @@ void VITA_PollTouch(void) void VITA_ConvertTouchXYToSDLXY(float *sdl_x, float *sdl_y, int vita_x, int vita_y, int port) { - float x = (vita_x - area_info[port].x) / area_info[port].w; - float y = (vita_y - area_info[port].y) / area_info[port].h; + float x, y; + + if (area_info[port].w <= 1) { + x = 0.5f; + } else { + x = (vita_x - area_info[port].x) / (area_info[port].w - 1); + } + if (area_info[port].h <= 1) { + y = 0.5f; + } else { + y = (vita_y - area_info[port].y) / (area_info[port].h - 1); + } x = SDL_max(x, 0.0); x = SDL_min(x, 1.0); diff --git a/src/video/vita/SDL_vitatouch.h b/src/video/vita/SDL_vitatouch.h index 2fc1fb06..5986f8dc 100644 --- a/src/video/vita/SDL_vitatouch.h +++ b/src/video/vita/SDL_vitatouch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vita/SDL_vitavideo.c b/src/video/vita/SDL_vitavideo.c index 97a0a254..bdb06e99 100644 --- a/src/video/vita/SDL_vitavideo.c +++ b/src/video/vita/SDL_vitavideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,8 +28,6 @@ #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" -#include - /* VITA declarations */ #include #include "SDL_vitavideo.h" @@ -69,23 +67,20 @@ static SDL_VideoDevice *VITA_Create() #endif /* Initialize SDL_VideoDevice structure */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } /* Initialize internal VITA specific data */ phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (phdata == NULL) { - SDL_OutOfMemory(); + if (!phdata) { SDL_free(device); return NULL; } #ifdef SDL_VIDEO_VITA_PIB gldata = (SDL_GLDriverData *)SDL_calloc(1, sizeof(SDL_GLDriverData)); - if (gldata == NULL) { - SDL_OutOfMemory(); + if (!gldata) { SDL_free(device); SDL_free(phdata); return NULL; @@ -107,7 +102,6 @@ static SDL_VideoDevice *VITA_Create() device->VideoInit = VITA_VideoInit; device->VideoQuit = VITA_VideoQuit; device->CreateSDLWindow = VITA_CreateWindow; - device->CreateSDLWindowFrom = VITA_CreateWindowFrom; device->SetWindowTitle = VITA_SetWindowTitle; device->SetWindowPosition = VITA_SetWindowPosition; device->SetWindowSize = VITA_SetWindowSize; @@ -221,7 +215,7 @@ void VITA_VideoQuit(SDL_VideoDevice *_this) VITA_QuitTouch(); } -int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *wdata; #ifdef SDL_VIDEO_VITA_PVR @@ -233,15 +227,15 @@ int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Allocate window internal data */ wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (wdata == NULL) { - return SDL_OutOfMemory(); + if (!wdata) { + return -1; } /* Setup driver data for this window */ window->driverdata = wdata; // Vita can only have one window - if (Vita_Window != NULL) { + if (Vita_Window) { return SDL_SetError("Only one window supported"); } @@ -295,11 +289,6 @@ int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) return 0; } -int VITA_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - return -1; -} - void VITA_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { } diff --git a/src/video/vita/SDL_vitavideo.h b/src/video/vita/SDL_vitavideo.h index aa4be1ba..8f609425 100644 --- a/src/video/vita/SDL_vitavideo.h +++ b/src/video/vita/SDL_vitavideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -62,8 +62,7 @@ int VITA_VideoInit(SDL_VideoDevice *_this); void VITA_VideoQuit(SDL_VideoDevice *_this); int VITA_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); int VITA_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); -int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -int VITA_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +int VITA_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void VITA_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); int VITA_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); void VITA_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/vivante/SDL_vivanteopengles.c b/src/video/vivante/SDL_vivanteopengles.c index fcca0cd8..67c78e3e 100644 --- a/src/video/vivante/SDL_vivanteopengles.c +++ b/src/video/vivante/SDL_vivanteopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vivante/SDL_vivanteopengles.h b/src/video/vivante/SDL_vivanteopengles.h index ab75465a..b73e3c4c 100644 --- a/src/video/vivante/SDL_vivanteopengles.h +++ b/src/video/vivante/SDL_vivanteopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vivante/SDL_vivanteplatform.c b/src/video/vivante/SDL_vivanteplatform.c index 7d966724..3ba8c7dd 100644 --- a/src/video/vivante/SDL_vivanteplatform.c +++ b/src/video/vivante/SDL_vivanteplatform.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vivante/SDL_vivanteplatform.h b/src/video/vivante/SDL_vivanteplatform.h index a3cf24aa..8831e175 100644 --- a/src/video/vivante/SDL_vivanteplatform.h +++ b/src/video/vivante/SDL_vivanteplatform.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/vivante/SDL_vivantevideo.c b/src/video/vivante/SDL_vivantevideo.c index 06c69dd3..49f15250 100644 --- a/src/video/vivante/SDL_vivantevideo.c +++ b/src/video/vivante/SDL_vivantevideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,8 +26,6 @@ #include "../SDL_sysvideo.h" #include "../../events/SDL_events_c.h" -#include - #ifdef SDL_INPUT_LINUXEV #include "../../core/linux/SDL_evdev.h" #endif @@ -50,15 +48,13 @@ static SDL_VideoDevice *VIVANTE_Create() /* Initialize SDL_VideoDevice structure */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } /* Initialize internal data */ data = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (data == NULL) { - SDL_OutOfMemory(); + if (!data) { SDL_free(device); return NULL; } @@ -81,7 +77,6 @@ static SDL_VideoDevice *VIVANTE_Create() device->ShowWindow = VIVANTE_ShowWindow; device->HideWindow = VIVANTE_HideWindow; device->DestroyWindow = VIVANTE_DestroyWindow; - device->GetWindowWMInfo = VIVANTE_GetWindowWMInfo; #ifdef SDL_VIDEO_OPENGL_EGL device->GL_LoadLibrary = VIVANTE_GLES_LoadLibrary; @@ -127,8 +122,8 @@ static int VIVANTE_AddVideoDisplays(SDL_VideoDevice *_this) unsigned long pixels = 0; data = (SDL_DisplayData *)SDL_calloc(1, sizeof(SDL_DisplayData)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } SDL_zero(mode); @@ -241,7 +236,7 @@ void VIVANTE_VideoQuit(SDL_VideoDevice *_this) #endif } -int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_VideoData *videodata = _this->driverdata; SDL_DisplayData *displaydata; @@ -251,13 +246,16 @@ int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Allocate window internal data */ data = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } /* Setup driver data for this window */ window->driverdata = data; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_VIVANTE_DISPLAY_POINTER, displaydata->native_display); + #ifdef SDL_VIDEO_DRIVER_VIVANTE_VDK data->native_window = vdkCreateWindow(displaydata->native_display, window->x, window->y, window->w, window->h); #else @@ -266,6 +264,7 @@ int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) if (!data->native_window) { return SDL_SetError("VIVANTE: Can't create native window"); } + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_VIVANTE_WINDOW_POINTER, data->native_window); #ifdef SDL_VIDEO_OPENGL_EGL if (window->flags & SDL_WINDOW_OPENGL) { @@ -276,6 +275,7 @@ int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } else { data->egl_surface = EGL_NO_SURFACE; } + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_VIVANTE_SURFACE_POINTER, data->egl_surface); #endif /* Window has been successfully created */ @@ -347,20 +347,6 @@ void VIVANTE_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_SetKeyboardFocus(NULL); } -/*****************************************************************************/ -/* SDL Window Manager function */ -/*****************************************************************************/ -int VIVANTE_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info) -{ - SDL_WindowData *data = window->driverdata; - SDL_DisplayData *displaydata = SDL_GetDisplayDriverData(SDL_GetPrimaryDisplay()); - - info->subsystem = SDL_SYSWM_VIVANTE; - info->info.vivante.display = displaydata->native_display; - info->info.vivante.window = data->native_window; - return 0; -} - /*****************************************************************************/ /* SDL event functions */ /*****************************************************************************/ diff --git a/src/video/vivante/SDL_vivantevideo.h b/src/video/vivante/SDL_vivantevideo.h index 290851fc..bf8d694f 100644 --- a/src/video/vivante/SDL_vivantevideo.h +++ b/src/video/vivante/SDL_vivantevideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -72,7 +72,7 @@ int VIVANTE_VideoInit(SDL_VideoDevice *_this); void VIVANTE_VideoQuit(SDL_VideoDevice *_this); int VIVANTE_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); int VIVANTE_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); -int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +int VIVANTE_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); void VIVANTE_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); int VIVANTE_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); void VIVANTE_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); @@ -80,9 +80,6 @@ void VIVANTE_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); void VIVANTE_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); void VIVANTE_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -/* Window manager function */ -int VIVANTE_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); - /* Event functions */ void VIVANTE_PumpEvents(SDL_VideoDevice *_this); diff --git a/src/video/vivante/SDL_vivantevulkan.c b/src/video/vivante/SDL_vivantevulkan.c index 24d91070..cc99edcf 100644 --- a/src/video/vivante/SDL_vivantevulkan.c +++ b/src/video/vivante/SDL_vivantevulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,7 +33,6 @@ #include "SDL_vivantevideo.h" #include "SDL_vivantevulkan.h" -#include int VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) { @@ -47,10 +46,10 @@ int VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan loader library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { /* If no path set, try Vivante fb vulkan driver explicitly */ path = "libvulkan-fb.so"; _this->vulkan_config.loader_handle = SDL_LoadObject(path); @@ -84,7 +83,7 @@ int VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) (PFN_vkEnumerateInstanceExtensionProperties) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } for (i = 0; i < extensionCount; i++) { @@ -118,32 +117,29 @@ void VIVANTE_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForVivante[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; + if(count) { + *count = SDL_arraysize(extensionsForVivante); } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForVivante), - extensionsForVivante); + return extensionsForVivante; } SDL_bool VIVANTE_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { if (!_this->vulkan_config.loader_handle) { SDL_SetError("Vulkan is not loaded"); return SDL_FALSE; } - return SDL_Vulkan_Display_CreateSurface(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface); + return SDL_Vulkan_Display_CreateSurface(_this->vulkan_config.vkGetInstanceProcAddr, instance, allocator, surface); } #endif diff --git a/src/video/vivante/SDL_vivantevulkan.h b/src/video/vivante/SDL_vivantevulkan.h index dc77bbf3..9aa566b2 100644 --- a/src/video/vivante/SDL_vivantevulkan.h +++ b/src/video/vivante/SDL_vivantevulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,12 +35,12 @@ int VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void VIVANTE_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool VIVANTE_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/wayland/SDL_waylandclipboard.c b/src/video/wayland/SDL_waylandclipboard.c index 0346b2cf..c3e23b78 100644 --- a/src/video/wayland/SDL_waylandclipboard.c +++ b/src/video/wayland/SDL_waylandclipboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,7 +35,7 @@ int Wayland_SetClipboardData(SDL_VideoDevice *_this) SDL_WaylandDataDevice *data_device = NULL; int status = 0; - if (video_data->input != NULL && video_data->input->data_device != NULL) { + if (video_data->input && video_data->input->data_device) { data_device = video_data->input->data_device; if (_this->clipboard_callback && _this->clipboard_mime_types) { @@ -60,7 +60,7 @@ void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, si SDL_WaylandDataDevice *data_device = NULL; void *buffer = NULL; - if (video_data->input != NULL && video_data->input->data_device != NULL) { + if (video_data->input && video_data->input->data_device) { data_device = video_data->input->data_device; if (data_device->selection_source) { buffer = SDL_GetInternalClipboardData(_this, mime_type, length); @@ -78,9 +78,9 @@ SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) SDL_WaylandDataDevice *data_device = NULL; SDL_bool result = SDL_FALSE; - if (video_data->input != NULL && video_data->input->data_device != NULL) { + if (video_data->input && video_data->input->data_device) { data_device = video_data->input->data_device; - if (data_device->selection_source != NULL) { + if (data_device->selection_source) { result = SDL_HasInternalClipboardData(_this, mime_type); } else { result = Wayland_data_offer_has_mime(data_device->selection_offer, mime_type); @@ -107,9 +107,9 @@ int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) { SDL_VideoData *video_data = _this->driverdata; SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL; - int status = 0; + int status = -1; - if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) { + if (video_data->input && video_data->input->primary_selection_device) { primary_selection_device = video_data->input->primary_selection_device; if (text[0] != '\0') { SDL_WaylandPrimarySelectionSource *source = Wayland_primary_selection_source_create(_this); @@ -137,16 +137,16 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this) char *text = NULL; size_t length = 0; - if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) { + if (video_data->input && video_data->input->primary_selection_device) { primary_selection_device = video_data->input->primary_selection_device; - if (primary_selection_device->selection_source != NULL) { + if (primary_selection_device->selection_source) { text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, TEXT_MIME, &length); } else if (Wayland_primary_selection_offer_has_mime(primary_selection_device->selection_offer, TEXT_MIME)) { text = Wayland_primary_selection_offer_receive(primary_selection_device->selection_offer, TEXT_MIME, &length); } } - if (text == NULL) { + if (!text) { text = SDL_strdup(""); } @@ -159,9 +159,9 @@ SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this) SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL; SDL_bool result = SDL_FALSE; - if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) { + if (video_data->input && video_data->input->primary_selection_device) { primary_selection_device = video_data->input->primary_selection_device; - if (primary_selection_device->selection_source != NULL) { + if (primary_selection_device->selection_source) { result = SDL_TRUE; } else { result = Wayland_primary_selection_offer_has_mime(primary_selection_device->selection_offer, TEXT_MIME); diff --git a/src/video/wayland/SDL_waylandclipboard.h b/src/video/wayland/SDL_waylandclipboard.h index 007fb791..0209a9bd 100644 --- a/src/video/wayland/SDL_waylandclipboard.h +++ b/src/video/wayland/SDL_waylandclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index ed24ddea..eea8c320 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -112,14 +112,14 @@ static ssize_t read_pipe(int fd, void **buffer, size_t *total_length) new_buffer_length = *total_length + sizeof(Uint32); - if (*buffer == NULL) { + if (!*buffer) { output_buffer = SDL_malloc(new_buffer_length); } else { output_buffer = SDL_realloc(*buffer, new_buffer_length); } - if (output_buffer == NULL) { - bytes_read = SDL_OutOfMemory(); + if (!output_buffer) { + bytes_read = -1; } else { SDL_memcpy((Uint8 *)output_buffer + pos, temp, bytes_read); SDL_memset((Uint8 *)output_buffer + (new_buffer_length - sizeof(Uint32)), 0, sizeof(Uint32)); @@ -155,35 +155,35 @@ static int mime_data_list_add(struct wl_list *list, SDL_MimeDataList *mime_data = NULL; void *internal_buffer = NULL; - if (buffer != NULL) { + if (buffer) { internal_buffer = SDL_malloc(length); - if (internal_buffer == NULL) { - return SDL_OutOfMemory(); + if (!internal_buffer) { + return -1; } SDL_memcpy(internal_buffer, buffer, length); } mime_data = mime_data_list_find(list, mime_type); - if (mime_data == NULL) { + if (!mime_data) { mime_data = SDL_calloc(1, sizeof(*mime_data)); - if (mime_data == NULL) { - status = SDL_OutOfMemory(); + if (!mime_data) { + status = -1; } else { WAYLAND_wl_list_insert(list, &(mime_data->link)); mime_type_length = SDL_strlen(mime_type) + 1; mime_data->mime_type = SDL_malloc(mime_type_length); - if (mime_data->mime_type == NULL) { - status = SDL_OutOfMemory(); + if (!mime_data->mime_type) { + status = -1; } else { SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length); } } } - if (mime_data != NULL && buffer != NULL && length > 0) { - if (mime_data->data != NULL) { + if (mime_data && buffer && length > 0) { + if (mime_data->data) { SDL_free(mime_data->data); } mime_data->data = internal_buffer; @@ -202,10 +202,10 @@ static void mime_data_list_free(struct wl_list *list) wl_list_for_each_safe(mime_data, next, list, link) { - if (mime_data->data != NULL) { + if (mime_data->data) { SDL_free(mime_data->data); } - if (mime_data->mime_type != NULL) { + if (mime_data->mime_type) { SDL_free(mime_data->mime_type); } SDL_free(mime_data); @@ -216,7 +216,7 @@ static size_t Wayland_send_data(const void *data, size_t length, int fd) { size_t result = 0; - if (length > 0 && data != NULL) { + if (length > 0 && data) { while (write_pipe(fd, data, length, &result) > 0) { /* Just keep spinning */ } @@ -276,11 +276,9 @@ void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSo static void *Wayland_clone_data_buffer(const void *buffer, size_t *len) { void *clone = NULL; - if (*len > 0 && buffer != NULL) { + if (*len > 0 && buffer) { clone = SDL_malloc((*len)+sizeof(Uint32)); - if (clone == NULL) { - SDL_OutOfMemory(); - } else { + if (clone) { SDL_memcpy(clone, buffer, *len); SDL_memset((Uint8 *)clone + *len, 0, sizeof(Uint32)); } @@ -295,9 +293,9 @@ void *Wayland_data_source_get_data(SDL_WaylandDataSource *source, const void *internal_buffer; *length = 0; - if (source == NULL) { + if (!source) { SDL_SetError("Invalid data source"); - } else if (source->callback != NULL) { + } else if (source->callback) { internal_buffer = source->callback(source->userdata.data, mime_type, length); buffer = Wayland_clone_data_buffer(internal_buffer, length); } @@ -312,7 +310,7 @@ void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSourc const void *internal_buffer; *length = 0; - if (source == NULL) { + if (!source) { SDL_SetError("Invalid primary selection source"); } else if (source->callback) { internal_buffer = source->callback(source->userdata.data, mime_type, length); @@ -324,7 +322,7 @@ void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSourc void Wayland_data_source_destroy(SDL_WaylandDataSource *source) { - if (source != NULL) { + if (source) { SDL_WaylandDataDevice *data_device = (SDL_WaylandDataDevice *)source->data_device; if (data_device && (data_device->selection_source == source)) { data_device->selection_source = NULL; @@ -341,7 +339,7 @@ void Wayland_data_source_destroy(SDL_WaylandDataSource *source) void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source) { - if (source != NULL) { + if (source) { SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *)source->primary_selection_device; if (primary_selection_device && (primary_selection_device->selection_source == source)) { primary_selection_device->selection_source = NULL; @@ -363,12 +361,12 @@ void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer, void *buffer = NULL; *length = 0; - if (offer == NULL) { + if (!offer) { SDL_SetError("Invalid data offer"); return NULL; } data_device = offer->data_device; - if (data_device == NULL) { + if (!data_device) { SDL_SetError("Data device not initialized"); } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) { SDL_SetError("Could not read pipe"); @@ -396,12 +394,12 @@ void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer * void *buffer = NULL; *length = 0; - if (offer == NULL) { + if (!offer) { SDL_SetError("Invalid data offer"); return NULL; } primary_selection_device = offer->primary_selection_device; - if (primary_selection_device == NULL) { + if (!primary_selection_device) { SDL_SetError("Primary selection device not initialized"); } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) { SDL_SetError("Could not read pipe"); @@ -437,7 +435,7 @@ SDL_bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, { SDL_bool found = SDL_FALSE; - if (offer != NULL) { + if (offer) { found = mime_data_list_find(&offer->mimes, mime_type) != NULL; } return found; @@ -448,7 +446,7 @@ SDL_bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOff { SDL_bool found = SDL_FALSE; - if (offer != NULL) { + if (offer) { found = mime_data_list_find(&offer->mimes, mime_type) != NULL; } return found; @@ -456,7 +454,7 @@ SDL_bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOff void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) { - if (offer != NULL) { + if (offer) { wl_data_offer_destroy(offer->offer); mime_data_list_free(&offer->mimes); SDL_free(offer); @@ -465,7 +463,7 @@ void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer) { - if (offer != NULL) { + if (offer) { zwp_primary_selection_offer_v1_destroy(offer->offer); mime_data_list_free(&offer->mimes); SDL_free(offer); @@ -476,9 +474,9 @@ int Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device) { int status = 0; - if (data_device == NULL || data_device->data_device == NULL) { + if (!data_device || !data_device->data_device) { status = SDL_SetError("Invalid Data Device"); - } else if (data_device->selection_source != NULL) { + } else if (data_device->selection_source) { wl_data_device_set_selection(data_device->data_device, NULL, 0); Wayland_data_source_destroy(data_device->selection_source); data_device->selection_source = NULL; @@ -490,9 +488,9 @@ int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelection { int status = 0; - if (primary_selection_device == NULL || primary_selection_device->primary_selection_device == NULL) { + if (!primary_selection_device || !primary_selection_device->primary_selection_device) { status = SDL_SetError("Invalid Primary Selection Device"); - } else if (primary_selection_device->selection_source != NULL) { + } else if (primary_selection_device->selection_source) { zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device, NULL, 0); Wayland_primary_selection_source_destroy(primary_selection_device->selection_source); @@ -508,9 +506,9 @@ int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, { int status = 0; - if (data_device == NULL) { + if (!data_device) { status = SDL_SetError("Invalid Data Device"); - } else if (source == NULL) { + } else if (!source) { status = SDL_SetError("Invalid source"); } else { size_t index = 0; @@ -532,7 +530,7 @@ int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, source->source, data_device->selection_serial); } - if (data_device->selection_source != NULL) { + if (data_device->selection_source) { Wayland_data_source_destroy(data_device->selection_source); } data_device->selection_source = source; @@ -550,9 +548,9 @@ int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDe { int status = 0; - if (primary_selection_device == NULL) { + if (!primary_selection_device) { status = SDL_SetError("Invalid Primary Selection Device"); - } else if (source == NULL) { + } else if (!source) { status = SDL_SetError("Invalid source"); } else { size_t index = 0; @@ -573,7 +571,7 @@ int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDe source->source, primary_selection_device->selection_serial); } - if (primary_selection_device->selection_source != NULL) { + if (primary_selection_device->selection_source) { Wayland_primary_selection_source_destroy(primary_selection_device->selection_source); } primary_selection_device->selection_source = source; @@ -588,11 +586,11 @@ int Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device, uint32_t serial) { int status = -1; - if (data_device != NULL) { + if (data_device) { status = 0; /* If there was no serial and there is a pending selection set it now. */ - if (data_device->selection_serial == 0 && data_device->selection_source != NULL) { + if (data_device->selection_serial == 0 && data_device->selection_source) { wl_data_device_set_selection(data_device->data_device, data_device->selection_source->source, data_device->selection_serial); @@ -608,11 +606,11 @@ int Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevic uint32_t serial) { int status = -1; - if (primary_selection_device != NULL) { + if (primary_selection_device) { status = 0; /* If there was no serial and there is a pending selection set it now. */ - if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source != NULL) { + if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) { zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device, primary_selection_device->selection_source->source, primary_selection_device->selection_serial); diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h index 1ffaf730..19fbc004 100644 --- a/src/video/wayland/SDL_waylanddatamanager.h +++ b/src/video/wayland/SDL_waylanddatamanager.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylanddyn.c b/src/video/wayland/SDL_waylanddyn.c index dde99bf4..e6021f08 100644 --- a/src/video/wayland/SDL_waylanddyn.c +++ b/src/video/wayland/SDL_waylanddyn.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -56,23 +56,23 @@ static void *WAYLAND_GetSym(const char *fnname, int *pHasModule, SDL_bool requir void *fn = NULL; waylanddynlib *dynlib; for (dynlib = waylandlibs; dynlib->libname; dynlib++) { - if (dynlib->lib != NULL) { + if (dynlib->lib) { fn = SDL_LoadFunction(dynlib->lib, fnname); - if (fn != NULL) { + if (fn) { break; } } } #if DEBUG_DYNAMIC_WAYLAND - if (fn != NULL) { + if (fn) { SDL_Log("WAYLAND: Found '%s' in %s (%p)\n", fnname, dynlib->libname, fn); } else { SDL_Log("WAYLAND: Symbol '%s' NOT FOUND!\n", fnname); } #endif - if (fn == NULL && required) { + if (!fn && required) { *pHasModule = 0; /* kill this module. */ } @@ -112,7 +112,7 @@ void SDL_WAYLAND_UnloadSymbols(void) #ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC for (i = 0; i < SDL_TABLESIZE(waylandlibs); i++) { - if (waylandlibs[i].lib != NULL) { + if (waylandlibs[i].lib) { SDL_UnloadObject(waylandlibs[i].lib); waylandlibs[i].lib = NULL; } @@ -133,7 +133,7 @@ int SDL_WAYLAND_LoadSymbols(void) int i; int *thismod = NULL; for (i = 0; i < SDL_TABLESIZE(waylandlibs); i++) { - if (waylandlibs[i].libname != NULL) { + if (waylandlibs[i].libname) { waylandlibs[i].lib = SDL_LoadObject(waylandlibs[i].libname); } } diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h index 76cfb295..e674ab8a 100644 --- a/src/video/wayland/SDL_waylanddyn.h +++ b/src/video/wayland/SDL_waylanddyn.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 498f69c5..42297290 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,7 @@ #include "SDL_waylandvideo.h" #include "SDL_waylandevents_c.h" #include "SDL_waylandwindow.h" +#include "SDL_waylandmouse.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" @@ -147,6 +148,19 @@ static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_ } } +static SDL_bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface) +{ + struct SDL_WaylandTouchPoint *tp; + + wl_list_for_each (tp, &touch_points, link) { + if (tp->surface == surface) { + return SDL_TRUE; + } + } + + return SDL_FALSE; +} + static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp) { static Uint64 last; @@ -279,7 +293,7 @@ static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms); repeat_info->scancode = scancode; if (has_text) { - SDL_memcpy(repeat_info->text, text, 8); + SDL_copyp(repeat_info->text, text); } else { repeat_info->text[0] = '\0'; } @@ -297,7 +311,7 @@ static uint32_t keyboard_repeat_get_key(SDL_WaylandKeyboardRepeat *repeat_info) static void keyboard_repeat_set_text(SDL_WaylandKeyboardRepeat *repeat_info, const char text[8]) { if (repeat_info->is_initialized) { - SDL_memcpy(repeat_info->text, text, 8); + SDL_copyp(repeat_info->text, text); } } @@ -360,7 +374,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) WAYLAND_wl_display_flush(d->display); #ifdef SDL_USE_IME - if (d->text_input_manager == NULL && SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { + if (!d->text_input_manager && SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { SDL_IME_PumpEvents(); } #endif @@ -433,7 +447,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) int err; #ifdef SDL_USE_IME - if (d->text_input_manager == NULL && SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { + if (!d->text_input_manager && SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { SDL_IME_PumpEvents(); } #endif @@ -490,13 +504,26 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) { struct SDL_WaylandInput *input = data; - SDL_WindowData *window = input->pointer_focus; + SDL_WindowData *window_data = input->pointer_focus; + SDL_Window *window = window_data ? window_data->sdlwindow : NULL; + input->sx_w = sx_w; input->sy_w = sy_w; if (input->pointer_focus) { - float sx = (float)(wl_fixed_to_double(sx_w) * window->pointer_scale_x); - float sy = (float)(wl_fixed_to_double(sy_w) * window->pointer_scale_y); - SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window->sdlwindow, 0, 0, sx, sy); + float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale_x); + float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale_y); + SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window_data->sdlwindow, 0, 0, sx, sy); + } + + if (window && window->hit_test) { + const SDL_Point point = { wl_fixed_to_int(sx_w), wl_fixed_to_int(sy_w) }; + SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + if (rc == window_data->hit_test_result) { + return; + } + + Wayland_SetHitTestCursor(rc); + window_data->hit_test_result = rc; } } @@ -507,23 +534,17 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, struct SDL_WaylandInput *input = data; SDL_WindowData *window; - if (surface == NULL) { + if (!surface) { /* enter event for a window we've just destroyed */ return; } - /* check that this surface belongs to one of the SDL windows */ - if (!SDL_WAYLAND_own_surface(surface)) { - return; - } - /* This handler will be called twice in Wayland 1.4 * Once for the window surface which has valid user data * and again for the mouse cursor surface which does not have valid user data * We ignore the later */ - - window = (SDL_WindowData *)wl_surface_get_user_data(surface); + window = Wayland_GetWindowDataForOwnedSurface(surface); if (window) { input->pointer_focus = window; @@ -540,7 +561,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, /* If the cursor was changed while our window didn't have pointer * focus, we might need to trigger another call to * wl_pointer_set_cursor() for the new cursor to be displayed. */ - SDL_SetCursor(NULL); + Wayland_SetHitTestCursor(window->hit_test_result); } } @@ -549,12 +570,12 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, { struct SDL_WaylandInput *input = data; - if (surface == NULL || !SDL_WAYLAND_own_surface(surface)) { + if (!surface) { return; } if (input->pointer_focus) { - SDL_WindowData *wind = (SDL_WindowData *)wl_surface_get_user_data(surface); + SDL_WindowData *wind = Wayland_GetWindowDataForOwnedSurface(surface); if (wind) { /* Clear the capture flag and raise all buttons */ @@ -567,20 +588,25 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_X2); } - SDL_SetMouseFocus(NULL); + + /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event. + * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode. + */ + if (!Wayland_SurfaceHasActiveTouches(surface)) { + SDL_SetMouseFocus(NULL); + } input->pointer_focus = NULL; } } -static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial) +static SDL_bool ProcessHitTest(SDL_WindowData *window_data, + struct wl_seat *seat, + wl_fixed_t sx_w, wl_fixed_t sy_w, + uint32_t serial) { - SDL_WindowData *window_data = input->pointer_focus; SDL_Window *window = window_data->sdlwindow; if (window->hit_test) { - const SDL_Point point = { wl_fixed_to_int(input->sx_w), wl_fixed_to_int(input->sy_w) }; - const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); - static const uint32_t directions[] = { XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_TOP, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT, @@ -597,19 +623,21 @@ static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial) }; #endif - switch (rc) { + switch (window_data->hit_test_result) { case SDL_HITTEST_DRAGGABLE: #ifdef HAVE_LIBDECOR_H if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { if (window_data->shell_surface.libdecor.frame) { - libdecor_frame_move(window_data->shell_surface.libdecor.frame, input->seat, serial); + libdecor_frame_move(window_data->shell_surface.libdecor.frame, + seat, + serial); } } else #endif if (window_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (window_data->shell_surface.xdg.roleobj.toplevel) { xdg_toplevel_move(window_data->shell_surface.xdg.roleobj.toplevel, - input->seat, + seat, serial); } } @@ -626,16 +654,19 @@ static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial) #ifdef HAVE_LIBDECOR_H if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { if (window_data->shell_surface.libdecor.frame) { - libdecor_frame_resize(window_data->shell_surface.libdecor.frame, input->seat, serial, directions_libdecor[rc - SDL_HITTEST_RESIZE_TOPLEFT]); + libdecor_frame_resize(window_data->shell_surface.libdecor.frame, + seat, + serial, + directions_libdecor[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]); } } else #endif if (window_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (window_data->shell_surface.xdg.roleobj.toplevel) { xdg_toplevel_resize(window_data->shell_surface.xdg.roleobj.toplevel, - input->seat, + seat, serial, - directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]); + directions[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]); } } return SDL_TRUE; @@ -660,7 +691,7 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_ switch (button) { case BTN_LEFT: sdl_button = SDL_BUTTON_LEFT; - if (ProcessHitTest(input, serial)) { + if (ProcessHitTest(input->pointer_focus, input->seat, input->sx_w, input->sy_w, serial)) { return; /* don't pass this event on to app. */ } break; @@ -832,19 +863,19 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer, } static void pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer, - uint32_t axis, uint32_t axis_relative_direction) + uint32_t axis, uint32_t axis_relative_direction) { struct SDL_WaylandInput *input = data; if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { return; } switch (axis_relative_direction) { - case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL: - input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL; - break; - case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED: - input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED; - break; + case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL: + input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL; + break; + case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED: + input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED; + break; } } @@ -928,11 +959,11 @@ static const struct wl_pointer_listener pointer_listener = { pointer_handle_motion, pointer_handle_button, pointer_handle_axis, - pointer_handle_frame, /* Version 5 */ - pointer_handle_axis_source, /* Version 5 */ - pointer_handle_axis_stop, /* Version 5 */ - pointer_handle_axis_discrete, /* Version 5 */ - pointer_handle_axis_value120, /* Version 8 */ + pointer_handle_frame, /* Version 5 */ + pointer_handle_axis_source, /* Version 5 */ + pointer_handle_axis_stop, /* Version 5 */ + pointer_handle_axis_discrete, /* Version 5 */ + pointer_handle_axis_value120, /* Version 8 */ pointer_handle_axis_relative_direction /* Version 9 */ }; @@ -943,18 +974,30 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data; SDL_WindowData *window_data; - /* Check that this surface belongs to one of the SDL windows */ - if (!SDL_WAYLAND_own_surface(surface)) { + /* Check that this surface is valid. */ + if (!surface) { return; } touch_add(id, fx, fy, surface); Wayland_UpdateImplicitGrabSerial(input, serial); - window_data = (SDL_WindowData *)wl_surface_get_user_data(surface); + window_data = Wayland_GetWindowDataForOwnedSurface(surface); if (window_data) { - const float x = wl_fixed_to_double(fx) / window_data->wl_window_width; - const float y = wl_fixed_to_double(fy) / window_data->wl_window_height; + float x, y; + + if (window_data->wl_window_width <= 1) { + x = 0.5f; + } else { + x = wl_fixed_to_double(fx) / (window_data->wl_window_width - 1); + } + if (window_data->wl_window_height <= 1) { + y = 0.5f; + } else { + y = wl_fixed_to_double(fy) / (window_data->wl_window_height - 1); + } + + SDL_SetMouseFocus(window_data->sdlwindow); SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f); @@ -979,6 +1022,14 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id, window_data->sdlwindow, SDL_FALSE, x, y, 0.0f); + + /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window curently + * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost. + */ + if (!input->pointer_focus && input->keyboard_focus != window_data && + SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(surface)) { + SDL_SetMouseFocus(NULL); + } } } } @@ -1087,7 +1138,7 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, char *map_str; const char *locale; - if (data == NULL) { + if (!data) { close(fd); return; } @@ -1160,11 +1211,11 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, /* Look up the preferred locale, falling back to "C" as default */ locale = SDL_getenv("LC_ALL"); - if (locale == NULL) { + if (!locale) { locale = SDL_getenv("LC_CTYPE"); - if (locale == NULL) { + if (!locale) { locale = SDL_getenv("LANG"); - if (locale == NULL) { + if (!locale) { locale = "C"; } } @@ -1368,24 +1419,23 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, SDL_WindowData *window; uint32_t *key; - if (surface == NULL) { + if (!surface) { /* enter event for a window we've just destroyed */ return; } - if (!SDL_WAYLAND_own_surface(surface)) { + window = Wayland_GetWindowDataForOwnedSurface(surface); + + if (!window) { return; } - window = wl_surface_get_user_data(surface); + input->keyboard_focus = window; + window->keyboard_device = input; - if (window) { - input->keyboard_focus = window; - window->keyboard_device = input; + /* Restore the keyboard focus to the child popup that was holding it */ + SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow); - /* Restore the keyboard focus to the child popup that was holding it */ - SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow); - } #ifdef SDL_USE_IME if (!input->text_input) { SDL_IME_SetFocus(SDL_TRUE); @@ -1419,22 +1469,28 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { struct SDL_WaylandInput *input = data; - SDL_WindowData *window; + SDL_WindowData *wind; + SDL_Window *window = NULL; - if (surface == NULL || !SDL_WAYLAND_own_surface(surface)) { + if (!surface) { return; } - window = wl_surface_get_user_data(surface); - if (window) { - window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; + wind = Wayland_GetWindowDataForOwnedSurface(surface); + if (!wind) { + return; } + wind->keyboard_device = NULL; + window = wind->sdlwindow; + window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; + /* Stop key repeat before clearing keyboard focus */ keyboard_repeat_clear(&input->keyboard_repeat); /* This will release any keys still pressed */ SDL_SetKeyboardFocus(NULL); + input->keyboard_focus = NULL; /* Clear the pressed modifiers. */ input->pressed_modifiers = SDL_KMOD_NONE; @@ -1444,6 +1500,13 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, SDL_IME_SetFocus(SDL_FALSE); } #endif + + /* If the surface had a pointer leave event while still having active touch events, it retained mouse focus. + * Clear it now if all touch events are raised. + */ + if (!input->pointer_focus && SDL_GetMouseFocus() == window && !Wayland_SurfaceHasActiveTouches(surface)) { + SDL_SetMouseFocus(NULL); + } } static SDL_bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, Uint8 state, SDL_bool *handled_by_ime) @@ -1452,7 +1515,7 @@ static SDL_bool keyboard_input_get_text(char text[8], const struct SDL_WaylandIn const xkb_keysym_t *syms; xkb_keysym_t sym; - if (window == NULL || window->keyboard_device != input || !input->xkb.state) { + if (!window || window->keyboard_device != input || !input->xkb.state) { return SDL_FALSE; } @@ -1623,7 +1686,6 @@ static void seat_handle_capabilities(void *data, struct wl_seat *seat, } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { - WAYLAND_wl_list_init(&touch_points); input->touch = wl_seat_get_touch(seat); SDL_AddTouch((SDL_TouchID)(intptr_t)input->touch, SDL_TOUCH_DEVICE_DIRECT, "wayland_touch"); wl_touch_set_user_data(input->touch, input); @@ -1672,7 +1734,7 @@ static void data_source_handle_send(void *data, struct wl_data_source *wl_data_s static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source) { SDL_WaylandDataSource *source = data; - if (source != NULL) { + if (source) { Wayland_data_source_destroy(source); } } @@ -1722,22 +1784,21 @@ SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this) SDL_VideoData *driver_data = NULL; struct wl_data_source *id = NULL; - if (_this == NULL || _this->driverdata == NULL) { + if (!_this || !_this->driverdata) { SDL_SetError("Video driver uninitialized"); } else { driver_data = _this->driverdata; - if (driver_data->data_device_manager != NULL) { + if (driver_data->data_device_manager) { id = wl_data_device_manager_create_data_source( driver_data->data_device_manager); } - if (id == NULL) { + if (!id) { SDL_SetError("Wayland unable to create data source"); } else { data_source = SDL_calloc(1, sizeof(*data_source)); - if (data_source == NULL) { - SDL_OutOfMemory(); + if (!data_source) { wl_data_source_destroy(id); } else { data_source->source = id; @@ -1756,22 +1817,21 @@ SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_V SDL_VideoData *driver_data = NULL; struct zwp_primary_selection_source_v1 *id = NULL; - if (_this == NULL || _this->driverdata == NULL) { + if (!_this || !_this->driverdata) { SDL_SetError("Video driver uninitialized"); } else { driver_data = _this->driverdata; - if (driver_data->primary_selection_device_manager != NULL) { + if (driver_data->primary_selection_device_manager) { id = zwp_primary_selection_device_manager_v1_create_source( driver_data->primary_selection_device_manager); } - if (id == NULL) { + if (!id) { SDL_SetError("Wayland unable to create primary selection source"); } else { primary_selection_source = SDL_calloc(1, sizeof(*primary_selection_source)); - if (primary_selection_source == NULL) { - SDL_OutOfMemory(); + if (!primary_selection_source) { zwp_primary_selection_source_v1_destroy(id); } else { primary_selection_source->source = id; @@ -1820,12 +1880,8 @@ static const struct zwp_primary_selection_offer_v1_listener primary_selection_of static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { - SDL_WaylandDataOffer *data_offer = NULL; - - data_offer = SDL_calloc(1, sizeof(*data_offer)); - if (data_offer == NULL) { - SDL_OutOfMemory(); - } else { + SDL_WaylandDataOffer *data_offer = SDL_calloc(1, sizeof(*data_offer)); + if (data_offer) { data_offer->offer = id; data_offer->data_device = data; WAYLAND_wl_list_init(&(data_offer->mimes)); @@ -1844,7 +1900,7 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_ data_device->drag_serial = serial; - if (id != NULL) { + if (id) { data_device->drag_offer = wl_data_offer_get_user_data(id); /* TODO: SDL Support more mime types */ @@ -1874,11 +1930,9 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_ /* find the current window */ if (surface) { - if (SDL_WAYLAND_own_surface(surface)) { - SDL_WindowData *window = (SDL_WindowData *)wl_surface_get_user_data(surface); - if (window) { - data_device->dnd_window = window->sdlwindow; - } + SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface); + if (window) { + data_device->dnd_window = window->sdlwindow; } else { data_device->dnd_window = NULL; } @@ -1890,7 +1944,7 @@ static void data_device_handle_leave(void *data, struct wl_data_device *wl_data_ { SDL_WaylandDataDevice *data_device = data; - if (data_device->drag_offer != NULL) { + if (data_device->drag_offer) { Wayland_data_offer_destroy(data_device->drag_offer); data_device->drag_offer = NULL; } @@ -1901,7 +1955,7 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_data { SDL_WaylandDataDevice *data_device = data; - if (data_device->drag_offer != NULL && data_device->dnd_window) { + if (data_device->drag_offer && data_device->dnd_window) { const float dx = (float)wl_fixed_to_double(x); const float dy = (float)wl_fixed_to_double(y); @@ -1909,7 +1963,7 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_data * Any future implementation should cache the filenames, as otherwise this could * hammer the DBus interface hundreds or even thousands of times per second. */ - SDL_SendDropPosition(data_device->dnd_window, NULL, dx, dy); + SDL_SendDropPosition(data_device->dnd_window, dx, dy); } } @@ -1932,7 +1986,7 @@ static int Wayland_URIDecode(char *buf, int len) { int ri, wi, di; char decode = '\0'; - if (buf == NULL || len < 0) { + if (!buf || len < 0) { errno = EINVAL; return -1; } @@ -2010,7 +2064,7 @@ static char *Wayland_URIToLocal(char *uri) /* got a hostname? */ if (!local && uri[0] == '/' && uri[2] != '/') { char *hostname_end = SDL_strchr(uri + 1, '/'); - if (hostname_end != NULL) { + if (hostname_end) { char hostname[257]; if (gethostname(hostname, 255) == 0) { hostname[256] = '\0'; @@ -2038,7 +2092,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d { SDL_WaylandDataDevice *data_device = data; - if (data_device->drag_offer != NULL && data_device->dnd_window) { + if (data_device->drag_offer && data_device->dnd_window) { /* TODO: SDL Support more mime types */ size_t length; SDL_bool drop_handled = SDL_FALSE; @@ -2055,7 +2109,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d /* If dropped files contain a directory the list is empty */ if (paths && path_count > 0) { for (int i = 0; i < path_count; i++) { - SDL_SendDropFile(data_device->dnd_window, paths[i]); + SDL_SendDropFile(data_device->dnd_window, NULL, paths[i]); } dbus->free_string_array(paths); SDL_SendDropComplete(data_device->dnd_window); @@ -2071,16 +2125,16 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d * non paths that are not visible to the application */ if (!drop_handled && Wayland_data_offer_has_mime( - data_device->drag_offer, FILE_MIME)) { + data_device->drag_offer, FILE_MIME)) { void *buffer = Wayland_data_offer_receive(data_device->drag_offer, FILE_MIME, &length); if (buffer) { char *saveptr = NULL; char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr); - while (token != NULL) { + while (token) { char *fn = Wayland_URIToLocal(token); if (fn) { - SDL_SendDropFile(data_device->dnd_window, fn); + SDL_SendDropFile(data_device->dnd_window, NULL, fn); } token = SDL_strtok_r(NULL, "\r\n", &saveptr); } @@ -2106,7 +2160,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d SDL_WaylandDataDevice *data_device = data; SDL_WaylandDataOffer *offer = NULL; - if (id != NULL) { + if (id) { offer = wl_data_offer_get_user_data(id); } @@ -2130,12 +2184,8 @@ static const struct wl_data_device_listener data_device_listener = { static void primary_selection_device_handle_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) { - SDL_WaylandPrimarySelectionOffer *primary_selection_offer = NULL; - - primary_selection_offer = SDL_calloc(1, sizeof(*primary_selection_offer)); - if (primary_selection_offer == NULL) { - SDL_OutOfMemory(); - } else { + SDL_WaylandPrimarySelectionOffer *primary_selection_offer = SDL_calloc(1, sizeof(*primary_selection_offer)); + if (primary_selection_offer) { primary_selection_offer->offer = id; primary_selection_offer->primary_selection_device = data; WAYLAND_wl_list_init(&(primary_selection_offer->mimes)); @@ -2150,7 +2200,7 @@ static void primary_selection_device_handle_selection(void *data, struct zwp_pri SDL_WaylandPrimarySelectionDevice *primary_selection_device = data; SDL_WaylandPrimarySelectionOffer *offer = NULL; - if (id != NULL) { + if (id) { offer = zwp_primary_selection_offer_v1_get_user_data(id); } @@ -2191,33 +2241,19 @@ static void text_input_preedit_string(void *data, char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; text_input->has_preedit = SDL_TRUE; if (text) { - if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE)) { - int cursor_begin_utf8 = cursor_begin >= 0 ? (int)SDL_utf8strnlen(text, cursor_begin) : -1; - int cursor_end_utf8 = cursor_end >= 0 ? (int)SDL_utf8strnlen(text, cursor_end) : -1; - int cursor_size_utf8; - if (cursor_end_utf8 >= 0) { - if (cursor_begin_utf8 >= 0) { - cursor_size_utf8 = cursor_end_utf8 - cursor_begin_utf8; - } else { - cursor_size_utf8 = cursor_end_utf8; - } + int cursor_begin_utf8 = cursor_begin >= 0 ? (int)SDL_utf8strnlen(text, cursor_begin) : -1; + int cursor_end_utf8 = cursor_end >= 0 ? (int)SDL_utf8strnlen(text, cursor_end) : -1; + int cursor_size_utf8; + if (cursor_end_utf8 >= 0) { + if (cursor_begin_utf8 >= 0) { + cursor_size_utf8 = cursor_end_utf8 - cursor_begin_utf8; } else { - cursor_size_utf8 = -1; + cursor_size_utf8 = cursor_end_utf8; } - SDL_SendEditingText(text, cursor_begin_utf8, cursor_size_utf8); } else { - int text_bytes = (int)SDL_strlen(text), i = 0; - int cursor = 0; - do { - const int sz = (int)SDL_utf8strlcpy(buf, text + i, sizeof(buf)); - const int chars = (int)SDL_utf8strlen(buf); - - SDL_SendEditingText(buf, cursor, chars); - - i += sz; - cursor += chars; - } while (i < text_bytes); + cursor_size_utf8 = -1; } + SDL_SendEditingText(text, cursor_begin_utf8, cursor_size_utf8); } else { buf[0] = '\0'; SDL_SendEditingText(buf, 0, 0); @@ -2274,7 +2310,7 @@ static void Wayland_create_data_device(SDL_VideoData *d) SDL_WaylandDataDevice *data_device = NULL; data_device = SDL_calloc(1, sizeof(*data_device)); - if (data_device == NULL) { + if (!data_device) { return; } @@ -2282,7 +2318,7 @@ static void Wayland_create_data_device(SDL_VideoData *d) d->data_device_manager, d->input->seat); data_device->video_data = d; - if (data_device->data_device == NULL) { + if (!data_device->data_device) { SDL_free(data_device); } else { wl_data_device_set_user_data(data_device->data_device, data_device); @@ -2297,7 +2333,7 @@ static void Wayland_create_primary_selection_device(SDL_VideoData *d) SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL; primary_selection_device = SDL_calloc(1, sizeof(*primary_selection_device)); - if (primary_selection_device == NULL) { + if (!primary_selection_device) { return; } @@ -2305,7 +2341,7 @@ static void Wayland_create_primary_selection_device(SDL_VideoData *d) d->primary_selection_device_manager, d->input->seat); primary_selection_device->video_data = d; - if (primary_selection_device->primary_selection_device == NULL) { + if (!primary_selection_device->primary_selection_device) { SDL_free(primary_selection_device); } else { zwp_primary_selection_device_v1_set_user_data(primary_selection_device->primary_selection_device, @@ -2321,14 +2357,14 @@ static void Wayland_create_text_input(SDL_VideoData *d) SDL_WaylandTextInput *text_input = NULL; text_input = SDL_calloc(1, sizeof(*text_input)); - if (text_input == NULL) { + if (!text_input) { return; } text_input->text_input = zwp_text_input_manager_v3_get_text_input( d->text_input_manager, d->input->seat); - if (text_input->text_input == NULL) { + if (!text_input->text_input) { SDL_free(text_input); } else { zwp_text_input_v3_set_user_data(text_input->text_input, text_input); @@ -2342,7 +2378,7 @@ void Wayland_add_data_device_manager(SDL_VideoData *d, uint32_t id, uint32_t ver { d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, SDL_min(3, version)); - if (d->input != NULL) { + if (d->input) { Wayland_create_data_device(d); } } @@ -2351,7 +2387,7 @@ void Wayland_add_primary_selection_device_manager(SDL_VideoData *d, uint32_t id, { d->primary_selection_device_manager = wl_registry_bind(d->registry, id, &zwp_primary_selection_device_manager_v1_interface, 1); - if (d->input != NULL) { + if (d->input) { Wayland_create_primary_selection_device(d); } } @@ -2360,209 +2396,426 @@ void Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32_t vers { d->text_input_manager = wl_registry_bind(d->registry, id, &zwp_text_input_manager_v3_interface, 1); - if (d->input != NULL) { + if (d->input) { Wayland_create_text_input(d); } } +static SDL_PenID Wayland_get_penid(void *data, struct zwp_tablet_tool_v2 *tool) +{ + struct SDL_WaylandTool *sdltool = data; + return sdltool->penid; +} + +/* For registering pens */ +static SDL_Pen *Wayland_get_current_pen(void *data, struct zwp_tablet_tool_v2 *tool) +{ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + + if (!input->current_pen.builder) { + /* Starting new pen or updating one? */ + SDL_PenID penid = sdltool->penid; + + if (penid == 0) { + /* Found completely new pen? */ + penid = ++input->num_pens; + sdltool->penid = penid; + } + input->current_pen.builder = SDL_GetPenPtr(penid); + if (!input->current_pen.builder) { + /* Must register as new pen */ + input->current_pen.builder = SDL_PenModifyBegin(penid); + } + } + return input->current_pen.builder; +} + static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type) { - /* unimplemented */ + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: + pen->type = SDL_PEN_TYPE_ERASER; + break; + + case ZWP_TABLET_TOOL_V2_TYPE_PEN: + pen->type = SDL_PEN_TYPE_PEN; + break; + + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: + pen->type = SDL_PEN_TYPE_PENCIL; + break; + + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: + pen->type = SDL_PEN_TYPE_AIRBRUSH; + break; + + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: + pen->type = SDL_PEN_TYPE_BRUSH; + break; + + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + case ZWP_TABLET_TOOL_V2_TYPE_LENS: + default: + pen->type = SDL_PEN_TYPE_NONE; /* Mark for deregistration */ + } + + SDL_PenUpdateGUIDForType(&pen->guid, pen->type); } static void tablet_tool_handle_hardware_serial(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial_hi, uint32_t serial_lo) { - /* unimplemented */ +#if !(SDL_PEN_DEBUG_NOID) + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + + if (!input->current_pen.builder_guid_complete) { + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + SDL_PenUpdateGUIDForGeneric(&pen->guid, serial_hi, serial_lo); + if (serial_hi || serial_lo) { + input->current_pen.builder_guid_complete = SDL_TRUE; + } + } +#endif } static void tablet_tool_handle_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo) { - /* unimplemented */ +#if !(SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM) + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + Uint32 axis_flags; + +#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Check: have we disabled pen serial ID decoding for testing? */ + id_hi = 0; +#endif + + SDL_PenUpdateGUIDForWacom(&pen->guid, id_lo, id_hi); + if (id_hi) { /* Have a serial number? */ + input->current_pen.builder_guid_complete = SDL_TRUE; + } + + if (SDL_PenModifyForWacomID(pen, id_lo, &axis_flags)) { + SDL_PenModifyAddCapabilities(pen, axis_flags); + } +#endif } static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability) { - /* unimplemented */ + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + + switch (capability) { + case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); + break; + + case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); + break; + + case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_DISTANCE_MASK); + break; + + case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_ROTATION_MASK); + break; + + case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_SLIDER_MASK); + break; + + case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: + /* Presumably for tools other than pens? */ + break; + + default: + break; + } +} + +static void Wayland_tool_builder_reset(struct SDL_WaylandTabletInput *input) +{ + input->current_pen.builder = NULL; + input->current_pen.builder_guid_complete = SDL_FALSE; } static void tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *tool) { - /* unimplemented */ + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + + if (!input->current_pen.builder_guid_complete) { + /* No complete GUID? Use tablet and tool device index */ + SDL_PenUpdateGUIDForGeneric(&pen->guid, input->id, sdltool->penid); + } + + SDL_PenModifyEnd(pen, SDL_TRUE); + + Wayland_tool_builder_reset(input); } +static void Wayland_tool_destroy(struct zwp_tablet_tool_v2 *tool) +{ + if (tool) { + struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool); + if (waypen) { + SDL_free(waypen); + } + zwp_tablet_tool_v2_destroy(tool); + } +} + +static void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object); + static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *tool) { - /* unimplemented */ + struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool); + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + SDL_Pen *pen = Wayland_get_current_pen(data, tool); + if (pen) { + SDL_PenModifyEnd(pen, SDL_FALSE); + Wayland_tool_builder_reset(waypen->tablet); + Wayland_tool_destroy(tool); + } else { + zwp_tablet_tool_v2_destroy(tool); + } + + tablet_object_list_remove(input->tools, tool); } static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { - struct SDL_WaylandTabletInput *input = data; + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; SDL_WindowData *window; + SDL_PenID penid = Wayland_get_penid(data, tool); - if (surface == NULL) { + if (!surface) { return; } - if (!SDL_WAYLAND_own_surface(surface)) { - return; - } - - window = (SDL_WindowData *)wl_surface_get_user_data(surface); + window = Wayland_GetWindowDataForOwnedSurface(surface); if (window) { input->tool_focus = window; input->tool_prox_serial = serial; - input->is_down = SDL_FALSE; - - input->btn_stylus = SDL_FALSE; - input->btn_stylus2 = SDL_FALSE; - input->btn_stylus3 = SDL_FALSE; - - SDL_SetMouseFocus(window->sdlwindow); + if (penid) { + SDL_SendPenWindowEvent(0, penid, window->sdlwindow); + } else { + SDL_SetMouseFocus(window->sdlwindow); + } SDL_SetCursor(NULL); } } static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTabletInput *input = data; - + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + SDL_PenID penid = Wayland_get_penid(data, tool); if (input->tool_focus) { - SDL_SetMouseFocus(NULL); + if (penid) { + SDL_SendPenWindowEvent(0, penid, NULL); + } else { + SDL_SetMouseFocus(NULL); + } input->tool_focus = NULL; } } -static uint32_t tablet_tool_btn_to_sdl_button(struct SDL_WaylandTabletInput *input) -{ - unsigned int tool_btn = input->btn_stylus3 << 2 | input->btn_stylus2 << 1 | input->btn_stylus << 0; - switch (tool_btn) { - case 0b000: - return SDL_BUTTON_LEFT; - case 0b001: - return SDL_BUTTON_RIGHT; - case 0b010: - return SDL_BUTTON_MIDDLE; - case 0b100: - return SDL_BUTTON_X1; - default: - return SDL_BUTTON_LEFT; - } -} - static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial) { - struct SDL_WaylandTabletInput *input = data; - SDL_WindowData *window = input->tool_focus; - input->is_down = SDL_TRUE; - Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, serial); - if (window == NULL) { - /* tablet_tool_handle_proximity_out gets called when moving over the libdecoration csd. - * that sets input->tool_focus (window) to NULL, but handle_{down,up} events are still - * received. To prevent SIGSEGV this returns when this is the case. - */ - return; - } + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_SendMouseButton(0, window->sdlwindow, 0, SDL_PRESSED, tablet_tool_btn_to_sdl_button(input)); + input->current_pen.buttons_pressed |= SDL_PEN_DOWN_MASK; + + input->current_pen.serial = serial; } static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTabletInput *input = data; - SDL_WindowData *window = input->tool_focus; - - input->is_down = SDL_FALSE; - - if (window == NULL) { - /* tablet_tool_handle_proximity_out gets called when moving over the libdecoration csd. - * that sets input->tool_focus (window) to NULL, but handle_{down,up} events are still - * received. To prevent SIGSEGV this returns when this is the case. - */ - return; - } - - SDL_SendMouseButton(0, window->sdlwindow, 0, SDL_RELEASED, tablet_tool_btn_to_sdl_button(input)); + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + input->current_pen.buttons_released |= SDL_PEN_DOWN_MASK; } static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t sx_w, wl_fixed_t sy_w) { - struct SDL_WaylandTabletInput *input = data; + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; SDL_WindowData *window = input->tool_focus; + SDL_PenID penid = Wayland_get_penid(data, tool); input->sx_w = sx_w; input->sy_w = sy_w; + if (input->tool_focus) { - float sx = (float)(wl_fixed_to_double(sx_w) * window->pointer_scale_x); - float sy = (float)(wl_fixed_to_double(sy_w) * window->pointer_scale_y); - SDL_SendMouseMotion(0, window->sdlwindow, 0, 0, sx, sy); + const float sx_f = (float)wl_fixed_to_double(sx_w); + const float sy_f = (float)wl_fixed_to_double(sy_w); + const float sx = sx_f * window->pointer_scale_x; + const float sy = sy_f * window->pointer_scale_y; + + if (penid != SDL_PEN_INVALID) { + input->current_pen.update_status.x = sx; + input->current_pen.update_status.y = sy; + input->current_pen.update_window = window; + } else { + /* Plain mouse event */ + SDL_SendMouseMotion(0, window->sdlwindow, 0, 0, sx, sy); + } } } static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = pressure / 65535.0f; + if (pressure) { + input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = 0.0f; + } } static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = distance / 65535.0f; + if (distance) { + input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = 0.0f; + } } static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + + input->current_pen.update_status.axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt)); + input->current_pen.update_status.axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt)); } static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state) { - struct SDL_WaylandTabletInput *input = (struct SDL_WaylandTabletInput*)data; + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + Uint16 mask = 0; + SDL_bool pressed = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE; - if (input->is_down) { - tablet_tool_handle_up(data, tool); - input->is_down = SDL_TRUE; - } - - Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, serial); + /* record event serial number to report it later in tablet_tool_handle_frame() */ + input->current_pen.serial = serial; switch (button) { /* see %{_includedir}/linux/input-event-codes.h */ case 0x14b: /* BTN_STYLUS */ - input->btn_stylus = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE; + mask = SDL_BUTTON_LMASK; break; case 0x14c: /* BTN_STYLUS2 */ - input->btn_stylus2 = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE; + mask = SDL_BUTTON_MMASK; break; case 0x149: /* BTN_STYLUS3 */ - input->btn_stylus3 = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE; + mask = SDL_BUTTON_RMASK; break; } - if (input->is_down) { - tablet_tool_handle_down(data, tool, serial); + if (pressed) { + input->current_pen.buttons_pressed |= mask; + } else { + input->current_pen.buttons_released |= mask; } } static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + float rotation = (float)(wl_fixed_to_double(degrees)); + + /* map to -180.0f ... 179.0f range: */ + input->current_pen.update_status.axes[SDL_PEN_AXIS_ROTATION] = rotation > 180.0f ? rotation - 360.0f : rotation; } static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + input->current_pen.update_status.axes[SDL_PEN_AXIS_SLIDER] = position / 65535.0; } static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks) { - /* unimplemented */ + /* not supported at the moment */ } static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time) { - /* unimplemented */ + struct SDL_WaylandTool *sdltool = data; + struct SDL_WaylandTabletInput *input = sdltool->tablet; + SDL_PenID penid = Wayland_get_penid(data, tool); + SDL_WindowData *window = input->current_pen.update_window; + SDL_PenStatusInfo *status = &input->current_pen.update_status; + int button; + int button_mask; + Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + + if (penid == 0 || !window) { /* Not a pen, or event reported out of focus */ + return; + } + /* window == input->tool_focus */ + + /* All newly released buttons + PEN_UP event */ + button_mask = input->current_pen.buttons_released; + if (button_mask & SDL_PEN_DOWN_MASK) { + /* Perform hit test, if appropriate */ + if (!SDL_PenPerformHitTest() + || !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) { + SDL_SendPenTipEvent(timestamp, penid, SDL_RELEASED); + } + } + button_mask &= ~SDL_PEN_DOWN_MASK; + + for (button = 1; button_mask; ++button, button_mask >>= 1) { + if (button_mask & 1) { + SDL_SendPenButton(timestamp, penid, SDL_RELEASED, button); + } + } + + /* All newly pressed buttons + PEN_DOWN event */ + button_mask = input->current_pen.buttons_pressed; + if (button_mask & SDL_PEN_DOWN_MASK) { + /* Perform hit test, if appropriate */ + if (!SDL_PenPerformHitTest() + || !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) { + SDL_SendPenTipEvent(timestamp, penid, SDL_PRESSED); + } + } + button_mask &= ~SDL_PEN_DOWN_MASK; + + for (button = 1; button_mask; ++button, button_mask >>= 1) { + if (button_mask & 1) { + SDL_SendPenButton(timestamp, penid, SDL_PRESSED, button); + } + } + + SDL_SendPenMotion(timestamp, penid, SDL_TRUE, status); + + /* Wayland_UpdateImplicitGrabSerial will ignore serial 0, so it is safe to call with the default value */ + Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, input->current_pen.serial); + + /* Reset masks for next tool frame */ + input->current_pen.buttons_pressed = 0; + input->current_pen.buttons_released = 0; + input->current_pen.serial = 0; } static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { @@ -2592,7 +2845,7 @@ static struct SDL_WaylandTabletObjectListNode *tablet_object_list_new_node(void struct SDL_WaylandTabletObjectListNode *node; node = SDL_calloc(1, sizeof(*node)); - if (node == NULL) { + if (!node) { return NULL; } @@ -2604,7 +2857,7 @@ static struct SDL_WaylandTabletObjectListNode *tablet_object_list_new_node(void static void tablet_object_list_append(struct SDL_WaylandTabletObjectListNode *head, void *object) { - if (head->object == NULL) { + if (!head->object) { head->object = object; return; } @@ -2628,6 +2881,26 @@ static void tablet_object_list_destroy(struct SDL_WaylandTabletObjectListNode *h } } +void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object) +{ + struct SDL_WaylandTabletObjectListNode **head_p = &head; + while (*head_p && (*head_p)->object != object) { + head_p = &((*head_p)->next); + } + + if (*head_p) { + struct SDL_WaylandTabletObjectListNode *object_head = *head_p; + + if (object_head == head) { + /* Must not remove head node */ + head->object = NULL; + } else { + *head_p = object_head->next; + SDL_free(object_head); + } + } +} + static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_v2 *tablet) { struct SDL_WaylandTabletInput *input = data; @@ -2638,9 +2911,12 @@ static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_tool_v2 *tool) { struct SDL_WaylandTabletInput *input = data; + struct SDL_WaylandTool *sdltool = SDL_calloc(1, sizeof(struct SDL_WaylandTool)); - zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, data); - zwp_tablet_tool_v2_set_user_data(tool, data); + zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool); + zwp_tablet_tool_v2_set_user_data(tool, sdltool); + + sdltool->tablet = input; tablet_object_list_append(input->tools, tool); } @@ -2661,13 +2937,14 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager) { struct SDL_WaylandTabletInput *tablet_input; + static Uint32 num_tablets = 0; - if (tablet_manager == NULL || input == NULL || !input->seat) { + if (!tablet_manager || !input || !input->seat) { return; } tablet_input = SDL_calloc(1, sizeof(*tablet_input)); - if (tablet_input == NULL) { + if (!tablet_input) { return; } @@ -2679,6 +2956,7 @@ void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_Wayland tablet_input->tablets = tablet_object_list_new_node(NULL); tablet_input->tools = tablet_object_list_new_node(NULL); tablet_input->pads = tablet_object_list_new_node(NULL); + tablet_input->id = num_tablets++; zwp_tablet_seat_v2_add_listener((struct zwp_tablet_seat_v2 *)tablet_input->seat, &tablet_seat_listener, tablet_input); } @@ -2687,7 +2965,7 @@ void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_Wayland void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input) { tablet_object_list_destroy(input->tablet->pads, TABLET_OBJECT_LIST_DELETER(zwp_tablet_pad_v2_destroy)); - tablet_object_list_destroy(input->tablet->tools, TABLET_OBJECT_LIST_DELETER(zwp_tablet_tool_v2_destroy)); + tablet_object_list_destroy(input->tablet->tools, TABLET_OBJECT_LIST_DELETER(Wayland_tool_destroy)); tablet_object_list_destroy(input->tablet->tablets, TABLET_OBJECT_LIST_DELETER(zwp_tablet_v2_destroy)); zwp_tablet_seat_v2_destroy(input->tablet->seat); @@ -2701,10 +2979,12 @@ void Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version) struct SDL_WaylandInput *input; input = SDL_calloc(1, sizeof(*input)); - if (input == NULL) { + if (!input) { return; } + WAYLAND_wl_list_init(&touch_points); + input->display = d; input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version)); input->sx_w = wl_fixed_from_int(0); @@ -2712,13 +2992,13 @@ void Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version) input->xkb.current_group = XKB_GROUP_INVALID; d->input = input; - if (d->data_device_manager != NULL) { + if (d->data_device_manager) { Wayland_create_data_device(d); } - if (d->primary_selection_device_manager != NULL) { + if (d->primary_selection_device_manager) { Wayland_create_primary_selection_device(d); } - if (d->text_input_manager != NULL) { + if (d->text_input_manager) { Wayland_create_text_input(d); } @@ -2736,7 +3016,7 @@ void Wayland_display_destroy_input(SDL_VideoData *d) { struct SDL_WaylandInput *input = d->input; - if (input == NULL) { + if (!input) { return; } @@ -2750,34 +3030,38 @@ void Wayland_display_destroy_input(SDL_VideoData *d) zwp_input_timestamps_v1_destroy(input->touch_timestamps); } - if (input->data_device != NULL) { + if (input->data_device) { Wayland_data_device_clear_selection(input->data_device); - if (input->data_device->selection_offer != NULL) { + if (input->data_device->selection_offer) { Wayland_data_offer_destroy(input->data_device->selection_offer); } - if (input->data_device->drag_offer != NULL) { + if (input->data_device->drag_offer) { Wayland_data_offer_destroy(input->data_device->drag_offer); } - if (input->data_device->data_device != NULL) { - wl_data_device_release(input->data_device->data_device); + if (input->data_device->data_device) { + if (wl_data_device_get_version(input->data_device->data_device) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) { + wl_data_device_release(input->data_device->data_device); + } else { + wl_data_device_destroy(input->data_device->data_device); + } } SDL_free(input->data_device); } - if (input->primary_selection_device != NULL) { - if (input->primary_selection_device->selection_offer != NULL) { + if (input->primary_selection_device) { + if (input->primary_selection_device->selection_offer) { Wayland_primary_selection_offer_destroy(input->primary_selection_device->selection_offer); } - if (input->primary_selection_device->selection_source != NULL) { + if (input->primary_selection_device->selection_source) { Wayland_primary_selection_source_destroy(input->primary_selection_device->selection_source); } - if (input->primary_selection_device->primary_selection_device != NULL) { + if (input->primary_selection_device->primary_selection_device) { zwp_primary_selection_device_v1_destroy(input->primary_selection_device->primary_selection_device); } SDL_free(input->primary_selection_device); } - if (input->text_input != NULL) { + if (input->text_input) { zwp_text_input_v3_destroy(input->text_input->text_input); SDL_free(input->text_input); } @@ -2808,7 +3092,8 @@ void Wayland_display_destroy_input(SDL_VideoData *d) wl_touch_destroy(input->touch); } - wl_list_for_each_safe (tp, tmp, &touch_points, link) { + wl_list_for_each_safe(tp, tmp, &touch_points, link) + { WAYLAND_wl_list_remove(&tp->link); SDL_free(tp); } @@ -2846,35 +3131,6 @@ void Wayland_display_destroy_input(SDL_VideoData *d) d->input = NULL; } -/* !!! FIXME: just merge these into display_handle_global(). */ -void Wayland_display_add_relative_pointer_manager(SDL_VideoData *d, uint32_t id) -{ - d->relative_pointer_manager = - wl_registry_bind(d->registry, id, - &zwp_relative_pointer_manager_v1_interface, 1); -} - -void Wayland_display_destroy_relative_pointer_manager(SDL_VideoData *d) -{ - if (d->relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(d->relative_pointer_manager); - } -} - -void Wayland_display_add_pointer_constraints(SDL_VideoData *d, uint32_t id) -{ - d->pointer_constraints = - wl_registry_bind(d->registry, id, - &zwp_pointer_constraints_v1_interface, 1); -} - -void Wayland_display_destroy_pointer_constraints(SDL_VideoData *d) -{ - if (d->pointer_constraints) { - zwp_pointer_constraints_v1_destroy(d->pointer_constraints); - } -} - static void relative_pointer_handle_relative_motion(void *data, struct zwp_relative_pointer_v1 *pointer, uint32_t time_hi, @@ -2927,6 +3183,10 @@ static void lock_pointer_to_window(SDL_Window *window, SDL_VideoData *d = input->display; struct zwp_locked_pointer_v1 *locked_pointer; + if (!d->pointer_constraints || !input->pointer) { + return; + } + if (w->locked_pointer) { return; } @@ -3014,8 +3274,10 @@ int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input) w->locked_pointer = NULL; } - zwp_relative_pointer_v1_destroy(input->relative_pointer); - input->relative_pointer = NULL; + if (input->relative_pointer) { + zwp_relative_pointer_v1_destroy(input->relative_pointer); + input->relative_pointer = NULL; + } d->relative_mouse_mode = 0; @@ -3101,7 +3363,7 @@ int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *wi &confined_pointer_listener, window); - if (confine_rect != NULL) { + if (confine_rect) { wl_region_destroy(confine_rect); } diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 0a518932..5bd061a2 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -25,6 +25,7 @@ #define SDL_waylandevents_h_ #include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_pen_c.h" #include "SDL_waylandvideo.h" #include "SDL_waylandwindow.h" @@ -55,31 +56,38 @@ struct SDL_WaylandTabletInput struct SDL_WaylandTabletObjectListNode *tools; struct SDL_WaylandTabletObjectListNode *pads; + Uint32 id; + Uint32 num_pens; /* next pen ID is num_pens+1 */ + struct SDL_WaylandCurrentPen + { + SDL_Pen *builder; /* pen that is being defined or receiving updates, if any */ + SDL_bool builder_guid_complete; /* have complete/precise GUID information */ + SDL_PenStatusInfo update_status; /* collects pen update information before sending event */ + Uint16 buttons_pressed; /* Mask of newly pressed buttons, plus SDL_PEN_DOWN_MASK for PEN_DOWN */ + Uint16 buttons_released; /* Mask of newly pressed buttons, plus SDL_PEN_DOWN_MASK for PEN_UP */ + Uint32 serial; /* Most recent serial event number observed, or 0 */ + SDL_WindowData *update_window; /* NULL while no event is in progress, otherwise the affected window */ + } current_pen; + SDL_WindowData *tool_focus; uint32_t tool_prox_serial; - /* Last motion location */ + /* Last motion end location (kept separate from sx_w, sy_w for the mouse pointer) */ wl_fixed_t sx_w; wl_fixed_t sy_w; - - SDL_bool is_down; - - SDL_bool btn_stylus; - SDL_bool btn_stylus2; - SDL_bool btn_stylus3; }; typedef struct { - int32_t repeat_rate; /* Repeat rate in range of [1, 1000] character(s) per second */ - int32_t repeat_delay_ms; /* Time to first repeat event in milliseconds */ + int32_t repeat_rate; /* Repeat rate in range of [1, 1000] character(s) per second */ + int32_t repeat_delay_ms; /* Time to first repeat event in milliseconds */ SDL_bool is_initialized; SDL_bool is_key_down; uint32_t key; - Uint64 wl_press_time_ns; /* Key press time as reported by the Wayland API */ - Uint64 sdl_press_time_ns; /* Key press time expressed in SDL ticks */ - Uint64 next_repeat_ns; /* Next repeat event in nanoseconds */ + Uint64 wl_press_time_ns; /* Key press time as reported by the Wayland API */ + Uint64 sdl_press_time_ns; /* Key press time expressed in SDL ticks */ + Uint64 next_repeat_ns; /* Next repeat event in nanoseconds */ uint32_t scancode; char text[8]; } SDL_WaylandKeyboardRepeat; @@ -169,6 +177,12 @@ struct SDL_WaylandInput SDL_Keymod locked_modifiers; }; +struct SDL_WaylandTool +{ + SDL_PenID penid; + struct SDL_WaylandTabletInput *tablet; +}; + extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms); extern void Wayland_PumpEvents(SDL_VideoDevice *_this); @@ -182,18 +196,12 @@ extern void Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32 extern void Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version); extern void Wayland_display_destroy_input(SDL_VideoData *d); -extern void Wayland_display_add_pointer_constraints(SDL_VideoData *d, uint32_t id); -extern void Wayland_display_destroy_pointer_constraints(SDL_VideoData *d); - extern int Wayland_input_lock_pointer(struct SDL_WaylandInput *input); extern int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input); extern int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window); extern int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window); -extern void Wayland_display_add_relative_pointer_manager(SDL_VideoData *d, uint32_t id); -extern void Wayland_display_destroy_relative_pointer_manager(SDL_VideoData *d); - extern int Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input); extern int Wayland_input_ungrab_keyboard(SDL_Window *window); diff --git a/src/video/wayland/SDL_waylandkeyboard.c b/src/video/wayland/SDL_waylandkeyboard.c index 3aa52f38..0e903e21 100644 --- a/src/video/wayland/SDL_waylandkeyboard.c +++ b/src/video/wayland/SDL_waylandkeyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,7 +32,7 @@ int Wayland_InitKeyboard(SDL_VideoDevice *_this) { #ifdef SDL_USE_IME SDL_VideoData *driverdata = _this->driverdata; - if (driverdata->text_input_manager == NULL) { + if (!driverdata->text_input_manager) { SDL_IME_Init(); } #endif @@ -45,7 +45,7 @@ void Wayland_QuitKeyboard(SDL_VideoDevice *_this) { #ifdef SDL_USE_IME SDL_VideoData *driverdata = _this->driverdata; - if (driverdata->text_input_manager == NULL) { + if (!driverdata->text_input_manager) { SDL_IME_Quit(); } #endif @@ -57,7 +57,7 @@ void Wayland_StartTextInput(SDL_VideoDevice *_this) if (driverdata->text_input_manager) { struct SDL_WaylandInput *input = driverdata->input; - if (input != NULL && input->text_input) { + if (input && input->text_input) { const SDL_Rect *rect = &input->text_input->cursor_rect; /* Don't re-enable if we're already enabled. */ @@ -98,7 +98,7 @@ void Wayland_StopTextInput(SDL_VideoDevice *_this) if (driverdata->text_input_manager) { struct SDL_WaylandInput *input = driverdata->input; - if (input != NULL && input->text_input) { + if (input && input->text_input) { zwp_text_input_v3_disable(input->text_input->text_input); zwp_text_input_v3_commit(input->text_input->text_input); input->text_input->is_enabled = SDL_FALSE; @@ -117,7 +117,7 @@ int Wayland_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect) SDL_VideoData *driverdata = _this->driverdata; if (driverdata->text_input_manager) { struct SDL_WaylandInput *input = driverdata->input; - if (input != NULL && input->text_input) { + if (input && input->text_input) { if (!SDL_RectsEqual(rect, &input->text_input->cursor_rect)) { SDL_copyp(&input->text_input->cursor_rect, rect); zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input, diff --git a/src/video/wayland/SDL_waylandkeyboard.h b/src/video/wayland/SDL_waylandkeyboard.h index e8c3ded8..4a20ea26 100644 --- a/src/video/wayland/SDL_waylandkeyboard.h +++ b/src/video/wayland/SDL_waylandkeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylandmessagebox.c b/src/video/wayland/SDL_waylandmessagebox.c index 03149f82..436a8603 100644 --- a/src/video/wayland/SDL_waylandmessagebox.c +++ b/src/video/wayland/SDL_waylandmessagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,7 +36,8 @@ #define MAX_BUTTONS 8 /* Maximum number of buttons supported */ -static int run_zenity(const char **args, int fd_pipe[2]) { +static int run_zenity(const char **args, int fd_pipe[2]) +{ int status; pid_t pid1; @@ -47,6 +48,7 @@ static int run_zenity(const char **args, int fd_pipe[2]) { if (dup2(fd_pipe[1], STDOUT_FILENO) == -1) { _exit(128); } + close(fd_pipe[1]); /* const casting argv is fine: * https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html -> rational @@ -56,7 +58,6 @@ static int run_zenity(const char **args, int fd_pipe[2]) { } else if (pid1 < 0) { /* fork() failed */ return SDL_SetError("fork() failed: %s", strerror(errno)); } else { /* parent process */ - close(fd_pipe[1]); /* no writing to the pipe */ if (waitpid(pid1, &status, 0) != pid1) { return SDL_SetError("Waiting on zenity failed: %s", strerror(errno)); } @@ -73,7 +74,8 @@ static int run_zenity(const char **args, int fd_pipe[2]) { } } -static int get_zenity_version(int *major, int *minor) { +static int get_zenity_version(int *major, int *minor) +{ int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */ const char *argv[] = { "zenity", "--version", NULL }; @@ -87,8 +89,9 @@ static int get_zenity_version(int *major, int *minor) { char *version_ptr = NULL, *end_ptr = NULL; int tmp; + close(fd_pipe[1]); outputfp = fdopen(fd_pipe[0], "r"); - if (outputfp == NULL) { + if (!outputfp) { close(fd_pipe[0]); return SDL_SetError("failed to open pipe for reading: %s", strerror(errno)); } @@ -96,6 +99,10 @@ static int get_zenity_version(int *major, int *minor) { version_ptr = fgets(version_str, ZENITY_VERSION_LEN, outputfp); (void)fclose(outputfp); /* will close underlying fd */ + if (!version_ptr) { + return SDL_SetError("failed to read zenity version string"); + } + /* we expect the version string is in the form of MAJOR.MINOR.MICRO * as described in meson.build. We'll ignore everything after that. */ @@ -105,12 +112,16 @@ static int get_zenity_version(int *major, int *minor) { } *major = tmp; - version_ptr = end_ptr + 1; /* skip the dot */ - tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); - if (tmp == 0 && end_ptr == version_ptr) { - return SDL_SetError("failed to get zenity minor version number"); + if (*end_ptr == '.') { + version_ptr = end_ptr + 1; /* skip the dot */ + tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10); + if (tmp == 0 && end_ptr == version_ptr) { + return SDL_SetError("failed to get zenity minor version number"); + } + *minor = tmp; + } else { + *minor = 0; } - *minor = tmp; return 0; /* success */ } @@ -120,7 +131,8 @@ static int get_zenity_version(int *major, int *minor) { return -1; /* run_zenity should've called SDL_SetError() */ } -int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) { +int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) +{ int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */ int zenity_major = 0, zenity_minor = 0, output_len = 0; int argc = 5, i; @@ -128,6 +140,14 @@ int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *button "zenity", "--question", "--switch", "--no-wrap", "--no-markup" }; + /* Are we trying to connect to or are currently in a Wayland session? */ + if (!SDL_getenv("WAYLAND_DISPLAY")) { + const char *session = SDL_getenv("XDG_SESSION_TYPE"); + if (session && SDL_strcasecmp(session, "wayland") != 0) { + return SDL_SetError("Not on a wayland display"); + } + } + if (messageboxdata->numbuttons > MAX_BUTTONS) { return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS); } @@ -193,7 +213,7 @@ int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *button char *output = NULL; char *tmp = NULL; - if (buttonid == NULL) { + if (!buttonid) { /* if we don't need buttonid, we can return immediately */ close(fd_pipe[0]); return 0; /* success */ @@ -201,14 +221,14 @@ int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *button *buttonid = -1; output = SDL_malloc(output_len + 1); - if (output == NULL) { + if (!output) { close(fd_pipe[0]); - return SDL_OutOfMemory(); + return -1; } output[0] = '\0'; outputfp = fdopen(fd_pipe[0], "r"); - if (outputfp == NULL) { + if (!outputfp) { SDL_free(output); close(fd_pipe[0]); return SDL_SetError("Couldn't open pipe for reading: %s", strerror(errno)); @@ -216,20 +236,20 @@ int Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *button tmp = fgets(output, output_len + 1, outputfp); (void)fclose(outputfp); - if ((tmp == NULL) || (*tmp == '\0') || (*tmp == '\n')) { + if ((!tmp) || (*tmp == '\0') || (*tmp == '\n')) { SDL_free(output); return 0; /* User simply closed the dialog */ } /* It likes to add a newline... */ tmp = SDL_strrchr(output, '\n'); - if (tmp != NULL) { + if (tmp) { *tmp = '\0'; } /* Check which button got pressed */ for (i = 0; i < messageboxdata->numbuttons; i += 1) { - if (messageboxdata->buttons[i].text != NULL) { + if (messageboxdata->buttons[i].text) { if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) { *buttonid = messageboxdata->buttons[i].buttonid; break; diff --git a/src/video/wayland/SDL_waylandmessagebox.h b/src/video/wayland/SDL_waylandmessagebox.h index 5472c8e6..28817f12 100644 --- a/src/video/wayland/SDL_waylandmessagebox.h +++ b/src/video/wayland/SDL_waylandmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 815c5043..1efb4808 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -41,6 +41,8 @@ #include "../../SDL_hints_c.h" +static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1]; + static int Wayland_SetRelativeMouseMode(SDL_bool enabled); typedef struct @@ -215,7 +217,7 @@ static void Wayland_DBusInitCursorProperties(SDL_VideoData *vdata) SDL_DBusContext *dbus = SDL_DBus_GetContext(); SDL_bool add_filter = SDL_FALSE; - if (dbus == NULL) { + if (!dbus) { return; } @@ -279,7 +281,7 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa } /* First, find the appropriate theme based on the current scale... */ focus = SDL_GetMouse()->focus; - if (focus == NULL) { + if (!focus) { /* Nothing to see here, bail. */ return SDL_FALSE; } @@ -294,15 +296,15 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa break; } } - if (theme == NULL) { + if (!theme) { const char *xcursor_theme = dbus_cursor_theme; - vdata->cursor_themes = SDL_realloc(vdata->cursor_themes, - sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1)); - if (vdata->cursor_themes == NULL) { - SDL_OutOfMemory(); + SDL_WaylandCursorTheme *new_cursor_themes = SDL_realloc(vdata->cursor_themes, + sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1)); + if (!new_cursor_themes) { return SDL_FALSE; } + vdata->cursor_themes = new_cursor_themes; /* Fallback envvar if the DBus properties don't exist */ if (!xcursor_theme) { @@ -314,43 +316,82 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme; } - /* Next, find the cursor from the theme... */ + /* Next, find the cursor from the theme. Names taken from: */ + /* https://www.freedesktop.org/wiki/Specifications/cursor-spec/ */ switch (cdata->system_cursor) { case SDL_SYSTEM_CURSOR_ARROW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "default"); break; case SDL_SYSTEM_CURSOR_IBEAM: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "xterm"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "text"); break; case SDL_SYSTEM_CURSOR_WAIT: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "wait"); break; case SDL_SYSTEM_CURSOR_CROSSHAIR: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "tcross"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "crosshair"); break; case SDL_SYSTEM_CURSOR_WAITARROW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "progress"); break; case SDL_SYSTEM_CURSOR_SIZENWSE: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_left_corner"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "nwse-resize"); + if (!cursor) { + /* only a single arrow */ + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "nw-resize"); + } break; case SDL_SYSTEM_CURSOR_SIZENESW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_right_corner"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "nesw-resize"); + if (!cursor) { + /* only a single arrow */ + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "ne-resize"); + } break; case SDL_SYSTEM_CURSOR_SIZEWE: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_h_double_arrow"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "ew-resize"); + if (!cursor) { + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "col-resize"); + } break; case SDL_SYSTEM_CURSOR_SIZENS: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_v_double_arrow"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "ns-resize"); + if (!cursor) { + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "row-resize"); + } break; case SDL_SYSTEM_CURSOR_SIZEALL: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "fleur"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "all-scroll"); break; case SDL_SYSTEM_CURSOR_NO: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "pirate"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "not-allowed"); break; case SDL_SYSTEM_CURSOR_HAND: - cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand2"); + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "pointer"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "nw-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "n-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "ne-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "e-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "se-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "s-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sw-resize"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "w-resize"); break; default: SDL_assert(0); @@ -382,7 +423,7 @@ static int wayland_create_tmp_file(off_t size) int fd; xdg_path = SDL_getenv("XDG_RUNTIME_DIR"); - if (xdg_path == NULL) { + if (!xdg_path) { return -1; } @@ -462,15 +503,12 @@ static int create_buffer_from_shm(Wayland_CursorData *d, static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) { - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *wd = vd->driverdata; Wayland_CursorData *data = SDL_calloc(1, sizeof(Wayland_CursorData)); - if (data == NULL) { - SDL_OutOfMemory(); + if (!data) { SDL_free(cursor); return NULL; } @@ -498,8 +536,6 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot data->hot_y = hot_y; data->w = surface->w; data->h = surface->h; - } else { - SDL_OutOfMemory(); } return cursor; @@ -508,13 +544,10 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id) { SDL_VideoData *data = SDL_GetVideoDevice()->driverdata; - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { Wayland_CursorData *cdata = SDL_calloc(1, sizeof(Wayland_CursorData)); - if (cdata == NULL) { - SDL_OutOfMemory(); + if (!cdata) { SDL_free(cursor); return NULL; } @@ -527,8 +560,6 @@ static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id) * is output-specific. See wayland_get_system_cursor for the rest! */ cdata->system_cursor = id; - } else { - SDL_OutOfMemory(); } return cursor; @@ -556,7 +587,7 @@ static void Wayland_FreeCursorData(Wayland_CursorData *d) static void Wayland_FreeCursor(SDL_Cursor *cursor) { - if (cursor == NULL) { + if (!cursor) { return; } @@ -580,7 +611,7 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) struct wl_pointer *pointer = d->pointer; float scale = 1.0f; - if (pointer == NULL) { + if (!pointer) { return -1; } @@ -588,7 +619,7 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) Wayland_CursorData *data = cursor->driverdata; /* TODO: High-DPI custom cursors? -flibit */ - if (data->shm_data == NULL) { + if (!data->shm_data) { if (!wayland_get_system_cursor(d, data, &scale)) { return -1; } @@ -770,6 +801,23 @@ void Wayland_InitMouse(void) input->relative_mode_override = SDL_FALSE; input->cursor_visible = SDL_TRUE; + SDL_HitTestResult r = SDL_HITTEST_NORMAL; + while (r <= SDL_HITTEST_RESIZE_LEFT) { + switch (r) { + case SDL_HITTEST_NORMAL: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break; + case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break; + case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT); break; + case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOP); break; + case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT); break; + case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_RIGHT); break; + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT); break; + case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOM); break; + case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT); break; + case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_LEFT); break; + } + r++; + } + #ifdef SDL_USE_LIBDBUS Wayland_DBusInitCursorProperties(d); #endif @@ -783,6 +831,7 @@ void Wayland_InitMouse(void) void Wayland_FiniMouse(SDL_VideoData *data) { struct SDL_WaylandInput *input = data->input; + int i; Wayland_FreeCursorThemes(data); @@ -792,6 +841,20 @@ void Wayland_FiniMouse(SDL_VideoData *data) SDL_DelHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP, Wayland_EmulateMouseWarpChanged, input); + + for (i = 0; i < SDL_arraysize(sys_cursors); i++) { + Wayland_FreeCursor(sys_cursors[i]); + sys_cursors[i] = NULL; + } +} + +void Wayland_SetHitTestCursor(SDL_HitTestResult rc) +{ + if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { + SDL_SetCursor(NULL); + } else { + Wayland_ShowCursor(sys_cursors[rc]); + } } #endif /* SDL_VIDEO_DRIVER_WAYLAND */ diff --git a/src/video/wayland/SDL_waylandmouse.h b/src/video/wayland/SDL_waylandmouse.h index ac194af0..f912455c 100644 --- a/src/video/wayland/SDL_waylandmouse.h +++ b/src/video/wayland/SDL_waylandmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -26,6 +26,7 @@ extern void Wayland_InitMouse(void); extern void Wayland_FiniMouse(SDL_VideoData *data); +extern void Wayland_SetHitTestCursor(SDL_HitTestResult rc); #if 0 /* TODO RECONNECT: See waylandvideo.c for more information! */ extern void Wayland_RecreateCursors(void); #endif /* 0 */ diff --git a/src/video/wayland/SDL_waylandopengles.c b/src/video/wayland/SDL_waylandopengles.c index f67d9525..2b83fc3b 100644 --- a/src/video/wayland/SDL_waylandopengles.c +++ b/src/video/wayland/SDL_waylandopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylandopengles.h b/src/video/wayland/SDL_waylandopengles.h index 98968863..97dae93f 100644 --- a/src/video/wayland/SDL_waylandopengles.h +++ b/src/video/wayland/SDL_waylandopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h index 494104b1..4d815c63 100644 --- a/src/video/wayland/SDL_waylandsym.h +++ b/src/video/wayland/SDL_waylandsym.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -219,12 +219,12 @@ SDL_WAYLAND_SYM(bool, libdecor_configuration_get_window_state, (struct libdecor_ enum libdecor_window_state *)) SDL_WAYLAND_SYM(int, libdecor_dispatch, (struct libdecor *, int)) -#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || defined(SDL_HAVE_LIBDECOR_VER_0_1_2) +#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || defined(SDL_HAVE_LIBDECOR_VER_0_2_0) /* Only found in libdecor 0.1.1 or higher, so failure to load them is not fatal. */ -SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_min_content_size, (struct libdecor_frame *,\ +SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_min_content_size, (const struct libdecor_frame *,\ int *,\ int *)) -SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_max_content_size, (struct libdecor_frame *,\ +SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_max_content_size, (const struct libdecor_frame *,\ int *,\ int *)) #endif diff --git a/src/video/wayland/SDL_waylandtouch.c b/src/video/wayland/SDL_waylandtouch.c deleted file mode 100644 index e301fa50..00000000 --- a/src/video/wayland/SDL_waylandtouch.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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. -*/ - -/* Contributed by Thomas Perl */ - -#include "SDL_internal.h" - -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - -#include "SDL_waylandtouch.h" -#include "SDL_waylandevents_c.h" -#include "../../events/SDL_touch_c.h" - -struct SDL_WaylandTouch -{ - struct qt_touch_extension *touch_extension; -}; - -/** - * Qt TouchPointState - * adapted from qtbase/src/corelib/global/qnamespace.h - **/ -enum QtWaylandTouchPointState -{ - QtWaylandTouchPointPressed = 0x01, - QtWaylandTouchPointMoved = 0x02, - /* - Never sent by the server: - QtWaylandTouchPointStationary = 0x04, - */ - QtWaylandTouchPointReleased = 0x08, -}; - -static void touch_handle_touch(void *data, - struct qt_touch_extension *qt_touch_extension, - uint32_t time, - uint32_t id, - uint32_t state, - int32_t x, - int32_t y, - int32_t normalized_x, - int32_t normalized_y, - int32_t width, - int32_t height, - uint32_t pressure, - int32_t velocity_x, - int32_t velocity_y, - uint32_t flags, - struct wl_array *rawdata) -{ - /** - * Event is assembled in QtWayland in TouchExtensionGlobal::postTouchEvent - * (src/compositor/wayland_wrapper/qwltouch.cpp) - **/ - - SDL_VideoData *viddata = data; - - float FIXED_TO_FLOAT = 1. / 10000.; - float xf = FIXED_TO_FLOAT * normalized_x; - float yf = FIXED_TO_FLOAT * normalized_y; - - float PRESSURE_TO_FLOAT = 1. / 255.; - float pressuref = PRESSURE_TO_FLOAT * pressure; - - uint32_t touchState = state & 0xFFFF; - /* - Other fields that are sent by the server (qwltouch.cpp), - but not used at the moment can be decoded in this way: - - uint32_t sentPointCount = state >> 16; - uint32_t touchFlags = flags & 0xFFFF; - uint32_t capabilities = flags >> 16; - */ - - SDL_Window *window = NULL; - - SDL_TouchID deviceId = 1; - if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "qt_touch_extension") < 0) { - SDL_Log("error: can't add touch %s, %d", __FILE__, __LINE__); - } - - /* FIXME: This should be the window the given wayland surface is associated - * with, but how do we get the wayland surface? */ - window = SDL_GetMouseFocus(); - if (window == NULL) { - window = SDL_GetKeyboardFocus(); - } - - switch (touchState) { - case QtWaylandTouchPointPressed: - case QtWaylandTouchPointReleased: - SDL_SendTouch(Wayland_GetTouchTimestamp(viddata->input, time), deviceId, (SDL_FingerID)id, - window, (touchState == QtWaylandTouchPointPressed) ? SDL_TRUE : SDL_FALSE, xf, yf, pressuref); - break; - case QtWaylandTouchPointMoved: - SDL_SendTouchMotion(Wayland_GetTouchTimestamp(viddata->input, time), deviceId, (SDL_FingerID)id, - window, xf, yf, pressuref); - break; - default: - /* Should not happen */ - break; - } -} - -static void touch_handle_configure(void *data, - struct qt_touch_extension *qt_touch_extension, - uint32_t flags) -{ -} - -/* wayland-qt-touch-extension.c BEGINS */ - -static const struct qt_touch_extension_listener touch_listener = { - touch_handle_touch, - touch_handle_configure, -}; - -static const struct wl_interface *qt_touch_extension_types[] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, -}; - -static const struct wl_message qt_touch_extension_requests[] = { - { "dummy", "", qt_touch_extension_types + 0 }, -}; - -static const struct wl_message qt_touch_extension_events[] = { - { "touch", "uuuiiiiiiuiiua", qt_touch_extension_types + 0 }, - { "configure", "u", qt_touch_extension_types + 0 }, -}; - -const struct wl_interface qt_touch_extension_interface = { - "qt_touch_extension", - 1, - 1, - qt_touch_extension_requests, - 2, - qt_touch_extension_events, -}; - -/* wayland-qt-touch-extension.c ENDS */ - -/* wayland-qt-windowmanager.c BEGINS */ -static const struct wl_interface *qt_windowmanager_types[] = { - NULL, - NULL, -}; - -static const struct wl_message qt_windowmanager_requests[] = { - { "open_url", "us", qt_windowmanager_types + 0 }, -}; - -static const struct wl_message qt_windowmanager_events[] = { - { "hints", "i", qt_windowmanager_types + 0 }, - { "quit", "", qt_windowmanager_types + 0 }, -}; - -const struct wl_interface qt_windowmanager_interface = { - "qt_windowmanager", - 1, - 1, - qt_windowmanager_requests, - 2, - qt_windowmanager_events, -}; -/* wayland-qt-windowmanager.c ENDS */ - -/* wayland-qt-surface-extension.c BEGINS */ -#ifndef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC -extern const struct wl_interface wl_surface_interface; -#endif - -static const struct wl_interface *qt_surface_extension_types[] = { - NULL, - NULL, - &qt_extended_surface_interface, -#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC - /* FIXME: Set this dynamically to (*WAYLAND_wl_surface_interface) ? - * The value comes from auto generated code and does - * not appear to actually be used anywhere - */ - NULL, -#else - &wl_surface_interface, -#endif -}; - -static const struct wl_message qt_surface_extension_requests[] = { - { "get_extended_surface", "no", qt_surface_extension_types + 2 }, -}; - -const struct wl_interface qt_surface_extension_interface = { - "qt_surface_extension", - 1, - 1, - qt_surface_extension_requests, - 0, - NULL, -}; - -static const struct wl_message qt_extended_surface_requests[] = { - { "update_generic_property", "sa", qt_surface_extension_types + 0 }, - { "set_content_orientation", "i", qt_surface_extension_types + 0 }, - { "set_window_flags", "i", qt_surface_extension_types + 0 }, -}; - -static const struct wl_message qt_extended_surface_events[] = { - { "onscreen_visibility", "i", qt_surface_extension_types + 0 }, - { "set_generic_property", "sa", qt_surface_extension_types + 0 }, - { "close", "", qt_surface_extension_types + 0 }, -}; - -const struct wl_interface qt_extended_surface_interface = { - "qt_extended_surface", - 1, - 3, - qt_extended_surface_requests, - 3, - qt_extended_surface_events, -}; - -/* wayland-qt-surface-extension.c ENDS */ - -void Wayland_touch_create(SDL_VideoData *data, uint32_t id) -{ - struct SDL_WaylandTouch *touch; - - if (data->touch) { - Wayland_touch_destroy(data); - } - - /* !!! FIXME: check for failure, call SDL_OutOfMemory() */ - data->touch = SDL_malloc(sizeof(struct SDL_WaylandTouch)); - - touch = data->touch; - touch->touch_extension = wl_registry_bind(data->registry, id, &qt_touch_extension_interface, 1); - qt_touch_extension_add_listener(touch->touch_extension, &touch_listener, data); -} - -void Wayland_touch_destroy(SDL_VideoData *data) -{ - if (data->touch) { - struct SDL_WaylandTouch *touch = data->touch; - if (touch->touch_extension) { - qt_touch_extension_destroy(touch->touch_extension); - } - - SDL_free(data->touch); - data->touch = NULL; - } -} - -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ diff --git a/src/video/wayland/SDL_waylandtouch.h b/src/video/wayland/SDL_waylandtouch.h deleted file mode 100644 index 59150d8c..00000000 --- a/src/video/wayland/SDL_waylandtouch.h +++ /dev/null @@ -1,334 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_waylandtouch_h_ -#define SDL_waylandtouch_h_ - -#include "SDL_internal.h" - -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - -#include "SDL_waylandvideo.h" -#include -#include -#include "wayland-util.h" - -void Wayland_touch_create(SDL_VideoData *data, uint32_t id); -void Wayland_touch_destroy(SDL_VideoData *data); - -struct qt_touch_extension; - -/* Autogenerated QT headers */ - -/* - Support for Wayland with QmlCompositor as Server -================================================ - -The Wayland video driver has support for some additional features when -using QtWayland's "qmlcompositor" as Wayland server. This is needed for touch -input when running SDL applications under a qmlcompositor Wayland server. - -The files following headers have been -generated from the Wayland XML Protocol Definition in QtWayland as follows: - -Clone QtWayland from Git: - http://qt.gitorious.org/qt/qtwayland/ - -Generate headers and glue code: - for extension in touch-extension surface-extension windowmanager; do - wayland-scanner client-header < src/extensions/$extension.xml > wayland-qt-$extension.h - wayland-scanner code < src/extensions/$extension.xml > wayland-qt-$extension.c - done - -*/ - -/* wayland-qt-surface-extension.h */ - -struct wl_client; -struct wl_resource; - -struct qt_surface_extension; -struct qt_extended_surface; - -extern const struct wl_interface qt_surface_extension_interface; -extern const struct wl_interface qt_extended_surface_interface; - -#define QT_SURFACE_EXTENSION_GET_EXTENDED_SURFACE 0 - -static inline void qt_surface_extension_set_user_data(struct qt_surface_extension *qt_surface_extension, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *)qt_surface_extension, user_data); -} - -static inline void *qt_surface_extension_get_user_data(struct qt_surface_extension *qt_surface_extension) -{ - return wl_proxy_get_user_data((struct wl_proxy *)qt_surface_extension); -} - -static inline void qt_surface_extension_destroy(struct qt_surface_extension *qt_surface_extension) -{ - WAYLAND_wl_proxy_destroy((struct wl_proxy *)qt_surface_extension); -} - -static inline struct qt_extended_surface *qt_surface_extension_get_extended_surface(struct qt_surface_extension *qt_surface_extension, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_create((struct wl_proxy *)qt_surface_extension, - &qt_extended_surface_interface); - if (id == NULL) - return NULL; - - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_surface_extension, - QT_SURFACE_EXTENSION_GET_EXTENDED_SURFACE, id, surface); - - return (struct qt_extended_surface *)id; -} - -#ifndef QT_EXTENDED_SURFACE_ORIENTATION_ENUM -#define QT_EXTENDED_SURFACE_ORIENTATION_ENUM -enum qt_extended_surface_orientation -{ - QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION = 0, - QT_EXTENDED_SURFACE_ORIENTATION_PORTRAITORIENTATION = 1, - QT_EXTENDED_SURFACE_ORIENTATION_LANDSCAPEORIENTATION = 2, - QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDPORTRAITORIENTATION = 4, - QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDLANDSCAPEORIENTATION = 8, -}; -#endif /* QT_EXTENDED_SURFACE_ORIENTATION_ENUM */ - -#ifndef QT_EXTENDED_SURFACE_WINDOWFLAG_ENUM -#define QT_EXTENDED_SURFACE_WINDOWFLAG_ENUM -enum qt_extended_surface_windowflag -{ - QT_EXTENDED_SURFACE_WINDOWFLAG_OVERRIDESSYSTEMGESTURES = 1, - QT_EXTENDED_SURFACE_WINDOWFLAG_STAYSONTOP = 2, -}; -#endif /* QT_EXTENDED_SURFACE_WINDOWFLAG_ENUM */ - -struct qt_extended_surface_listener -{ - /** - * onscreen_visibility - (none) - * @visible: (none) - */ - void (*onscreen_visibility)(void *data, - struct qt_extended_surface *qt_extended_surface, - int32_t visible); - /** - * set_generic_property - (none) - * @name: (none) - * @value: (none) - */ - void (*set_generic_property)(void *data, - struct qt_extended_surface *qt_extended_surface, - const char *name, - struct wl_array *value); - /** - * close - (none) - */ - void (*close)(void *data, - struct qt_extended_surface *qt_extended_surface); -}; - -static inline int qt_extended_surface_add_listener(struct qt_extended_surface *qt_extended_surface, - const struct qt_extended_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *)qt_extended_surface, - (void (**)(void))listener, data); -} - -#define QT_EXTENDED_SURFACE_UPDATE_GENERIC_PROPERTY 0 -#define QT_EXTENDED_SURFACE_SET_CONTENT_ORIENTATION 1 -#define QT_EXTENDED_SURFACE_SET_WINDOW_FLAGS 2 - -static inline void qt_extended_surface_set_user_data(struct qt_extended_surface *qt_extended_surface, void *user_data) -{ - WAYLAND_wl_proxy_set_user_data((struct wl_proxy *)qt_extended_surface, user_data); -} - -static inline void *qt_extended_surface_get_user_data(struct qt_extended_surface *qt_extended_surface) -{ - return WAYLAND_wl_proxy_get_user_data((struct wl_proxy *)qt_extended_surface); -} - -static inline void qt_extended_surface_destroy(struct qt_extended_surface *qt_extended_surface) -{ - WAYLAND_wl_proxy_destroy((struct wl_proxy *)qt_extended_surface); -} - -static inline void qt_extended_surface_update_generic_property(struct qt_extended_surface *qt_extended_surface, const char *name, struct wl_array *value) -{ - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_extended_surface, - QT_EXTENDED_SURFACE_UPDATE_GENERIC_PROPERTY, name, value); -} - -static inline void qt_extended_surface_set_content_orientation(struct qt_extended_surface *qt_extended_surface, int32_t orientation) -{ - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_extended_surface, - QT_EXTENDED_SURFACE_SET_CONTENT_ORIENTATION, orientation); -} - -static inline void qt_extended_surface_set_window_flags(struct qt_extended_surface *qt_extended_surface, int32_t flags) -{ - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_extended_surface, - QT_EXTENDED_SURFACE_SET_WINDOW_FLAGS, flags); -} - -/* wayland-qt-touch-extension.h */ - -extern const struct wl_interface qt_touch_extension_interface; - -#ifndef QT_TOUCH_EXTENSION_FLAGS_ENUM -#define QT_TOUCH_EXTENSION_FLAGS_ENUM -enum qt_touch_extension_flags -{ - QT_TOUCH_EXTENSION_FLAGS_MOUSE_FROM_TOUCH = 0x1, -}; -#endif /* QT_TOUCH_EXTENSION_FLAGS_ENUM */ - -struct qt_touch_extension_listener -{ - /** - * touch - (none) - * @time: (none) - * @id: (none) - * @state: (none) - * @x: (none) - * @y: (none) - * @normalized_x: (none) - * @normalized_y: (none) - * @width: (none) - * @height: (none) - * @pressure: (none) - * @velocity_x: (none) - * @velocity_y: (none) - * @flags: (none) - * @rawdata: (none) - */ - void (*touch)(void *data, - struct qt_touch_extension *qt_touch_extension, - uint32_t time, - uint32_t id, - uint32_t state, - int32_t x, - int32_t y, - int32_t normalized_x, - int32_t normalized_y, - int32_t width, - int32_t height, - uint32_t pressure, - int32_t velocity_x, - int32_t velocity_y, - uint32_t flags, - struct wl_array *rawdata); - /** - * configure - (none) - * @flags: (none) - */ - void (*configure)(void *data, - struct qt_touch_extension *qt_touch_extension, - uint32_t flags); -}; - -static inline int qt_touch_extension_add_listener(struct qt_touch_extension *qt_touch_extension, - const struct qt_touch_extension_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *)qt_touch_extension, - (void (**)(void))listener, data); -} - -#define QT_TOUCH_EXTENSION_DUMMY 0 - -static inline void qt_touch_extension_set_user_data(struct qt_touch_extension *qt_touch_extension, void *user_data) -{ - WAYLAND_wl_proxy_set_user_data((struct wl_proxy *)qt_touch_extension, user_data); -} - -static inline void *qt_touch_extension_get_user_data(struct qt_touch_extension *qt_touch_extension) -{ - return WAYLAND_wl_proxy_get_user_data((struct wl_proxy *)qt_touch_extension); -} - -static inline void qt_touch_extension_destroy(struct qt_touch_extension *qt_touch_extension) -{ - WAYLAND_wl_proxy_destroy((struct wl_proxy *)qt_touch_extension); -} - -static inline void qt_touch_extension_dummy(struct qt_touch_extension *qt_touch_extension) -{ - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_touch_extension, - QT_TOUCH_EXTENSION_DUMMY); -} - -/* wayland-qt-windowmanager.h */ - -extern const struct wl_interface qt_windowmanager_interface; - -struct qt_windowmanager_listener -{ - /** - * hints - (none) - * @show_is_fullscreen: (none) - */ - void (*hints)(void *data, - struct qt_windowmanager *qt_windowmanager, - int32_t show_is_fullscreen); - /** - * quit - (none) - */ - void (*quit)(void *data, - struct qt_windowmanager *qt_windowmanager); -}; - -static inline int qt_windowmanager_add_listener(struct qt_windowmanager *qt_windowmanager, - const struct qt_windowmanager_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *)qt_windowmanager, - (void (**)(void))listener, data); -} - -#define QT_WINDOWMANAGER_OPEN_URL 0 - -static inline void qt_windowmanager_set_user_data(struct qt_windowmanager *qt_windowmanager, void *user_data) -{ - WAYLAND_wl_proxy_set_user_data((struct wl_proxy *)qt_windowmanager, user_data); -} - -static inline void *qt_windowmanager_get_user_data(struct qt_windowmanager *qt_windowmanager) -{ - return WAYLAND_wl_proxy_get_user_data((struct wl_proxy *)qt_windowmanager); -} - -static inline void qt_windowmanager_destroy(struct qt_windowmanager *qt_windowmanager) -{ - WAYLAND_wl_proxy_destroy((struct wl_proxy *)qt_windowmanager); -} - -static inline void qt_windowmanager_open_url(struct qt_windowmanager *qt_windowmanager, uint32_t remaining, const char *url) -{ - WAYLAND_wl_proxy_marshal((struct wl_proxy *)qt_windowmanager, - QT_WINDOWMANAGER_OPEN_URL, remaining, url); -} - -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - -#endif /* SDL_waylandtouch_h_ */ diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 4d1b3d10..728d7b98 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,7 +32,6 @@ #include "SDL_waylandopengles.h" #include "SDL_waylandmouse.h" #include "SDL_waylandkeyboard.h" -#include "SDL_waylandtouch.h" #include "SDL_waylandclipboard.h" #include "SDL_waylandvulkan.h" @@ -55,6 +54,8 @@ #include "primary-selection-unstable-v1-client-protocol.h" #include "fractional-scale-v1-client-protocol.h" #include "input-timestamps-unstable-v1-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -105,12 +106,45 @@ SDL_bool SDL_WAYLAND_own_output(struct wl_output *output) return wl_proxy_get_tag((struct wl_proxy *)output) == &SDL_WAYLAND_output_tag; } +/* External surfaces may have their own user data attached, the modification of which + * can cause problems with external toolkits. Instead, external windows are kept in + * their own list, and a search is conducted to find a matching surface. + */ +static struct wl_list external_window_list; + +void Wayland_AddWindowDataToExternalList(SDL_WindowData *data) +{ + WAYLAND_wl_list_insert(&external_window_list, &data->external_window_list_link); +} + +void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data) +{ + WAYLAND_wl_list_remove(&data->external_window_list_link); +} + +SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface) +{ + if (SDL_WAYLAND_own_surface(surface)) { + return (SDL_WindowData *)wl_surface_get_user_data(surface); + } else if (!WAYLAND_wl_list_empty(&external_window_list)) { + SDL_WindowData *p; + wl_list_for_each (p, &external_window_list, external_window_list_link) { + if (p->surface == surface) { + return p; + } + } + } + + return NULL; +} + static void Wayland_DeleteDevice(SDL_VideoDevice *device) { SDL_VideoData *data = device->driverdata; - if (data->display) { + if (data->display && !data->display_externally_owned) { WAYLAND_wl_display_flush(data->display); WAYLAND_wl_display_disconnect(data->display); + SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER); } if (device->wakeup_lock) { SDL_DestroyMutex(device->wakeup_lock); @@ -124,40 +158,57 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) { SDL_VideoDevice *device; SDL_VideoData *data; - struct wl_display *display; + struct wl_display *display = SDL_GetProperty(SDL_GetGlobalProperties(), + SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL); + SDL_bool display_is_external = !!display; + + /* Are we trying to connect to or are currently in a Wayland session? */ + if (!SDL_getenv("WAYLAND_DISPLAY")) { + const char *session = SDL_getenv("XDG_SESSION_TYPE"); + if (session && SDL_strcasecmp(session, "wayland") != 0) { + return NULL; + } + } if (!SDL_WAYLAND_LoadSymbols()) { return NULL; } - display = WAYLAND_wl_display_connect(NULL); - if (display == NULL) { - SDL_WAYLAND_UnloadSymbols(); - return NULL; + if (!display) { + display = WAYLAND_wl_display_connect(NULL); + if (!display) { + SDL_WAYLAND_UnloadSymbols(); + return NULL; + } } data = SDL_calloc(1, sizeof(*data)); - if (data == NULL) { + if (!data) { WAYLAND_wl_display_disconnect(display); SDL_WAYLAND_UnloadSymbols(); - SDL_OutOfMemory(); return NULL; } data->initializing = SDL_TRUE; data->display = display; + data->display_externally_owned = display_is_external; WAYLAND_wl_list_init(&data->output_list); + WAYLAND_wl_list_init(&external_window_list); /* Initialize all variables that we clean on shutdown */ device = SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { + if (!device) { SDL_free(data); WAYLAND_wl_display_disconnect(display); SDL_WAYLAND_UnloadSymbols(); - SDL_OutOfMemory(); return NULL; } + if (!display_is_external) { + SDL_SetProperty(SDL_GetGlobalProperties(), + SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, display); + } + device->driverdata = data; device->wakeup_lock = SDL_CreateMutex(); @@ -165,7 +216,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->VideoInit = Wayland_VideoInit; device->VideoQuit = Wayland_VideoQuit; device->GetDisplayBounds = Wayland_GetDisplayBounds; - device->GetWindowWMInfo = Wayland_GetWindowWMInfo; device->SuspendScreenSaver = Wayland_SuspendScreenSaver; device->PumpEvents = Wayland_PumpEvents; @@ -210,6 +260,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->FlashWindow = Wayland_FlashWindow; device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; + device->SyncWindow = Wayland_SyncWindow; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) @@ -220,9 +271,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->SetClipboardData = Wayland_SetClipboardData; device->GetClipboardData = Wayland_GetClipboardData; device->HasClipboardData = Wayland_HasClipboardData; - device->SetPrimarySelectionText = Wayland_SetPrimarySelectionText; - device->GetPrimarySelectionText = Wayland_GetPrimarySelectionText; - device->HasPrimarySelectionText = Wayland_HasPrimarySelectionText; device->StartTextInput = Wayland_StartTextInput; device->StopTextInput = Wayland_StopTextInput; device->SetTextInputRect = Wayland_SetTextInputRect; @@ -236,9 +284,9 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->free = Wayland_DeleteDevice; - device->quirk_flags = VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED | - VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE | - VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; + device->device_caps = VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | + VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; return device; } @@ -409,7 +457,7 @@ static void display_handle_geometry(void *data, driverdata->physical_height = physical_height; /* The model is only used for the output name if wl_output or xdg-output haven't provided a description. */ - if (driverdata->display == 0 && driverdata->placeholder.name == NULL) { + if (driverdata->display == 0 && !driverdata->placeholder.name) { driverdata->placeholder.name = SDL_strdup(model); } @@ -568,7 +616,7 @@ static void display_handle_done(void *data, SDL_SetDesktopDisplayMode(dpy, &desktop_mode); /* Expose the unscaled, native resolution if the scale is 1.0 or viewports are available... */ - if (driverdata->scale_factor == 1.0f || video->viewporter != NULL) { + if (driverdata->scale_factor == 1.0f || video->viewporter) { SDL_AddFullscreenDisplayMode(dpy, &native_mode); } else { /* ...otherwise expose the integer scaled variants of the desktop resolution down to 1. */ @@ -591,7 +639,7 @@ static void display_handle_done(void *data, if (driverdata->display == 0) { /* First time getting display info, create the VideoDisplay */ - SDL_bool send_event = driverdata->videodata->initializing ? SDL_FALSE : SDL_TRUE; + SDL_bool send_event = !driverdata->videodata->initializing; if (driverdata->physical_width >= driverdata->physical_height) { driverdata->placeholder.natural_orientation = SDL_ORIENTATION_LANDSCAPE; } else { @@ -645,7 +693,7 @@ static int Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version) SDL_DisplayData *data; output = wl_registry_bind(d->registry, id, &wl_output_interface, version); - if (output == NULL) { + if (!output) { return SDL_SetError("Failed to retrieve output."); } data = (SDL_DisplayData *)SDL_calloc(1, sizeof(*data)); @@ -704,23 +752,6 @@ static void Wayland_init_xdg_output(SDL_VideoData *d) } } -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH -static void windowmanager_hints(void *data, struct qt_windowmanager *qt_windowmanager, - int32_t show_is_fullscreen) -{ -} - -static void windowmanager_quit(void *data, struct qt_windowmanager *qt_windowmanager) -{ - SDL_SendQuit(); -} - -static const struct qt_windowmanager_listener windowmanager_listener = { - windowmanager_hints, - windowmanager_quit, -}; -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial) { xdg_wm_base_pong(xdg, serial); @@ -762,9 +793,9 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint } else if (SDL_strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { - Wayland_display_add_relative_pointer_manager(d, id); + d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1); } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) { - Wayland_display_add_pointer_constraints(d, id); + d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1); } else if (SDL_strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) { d->key_inhibitor_manager = wl_registry_bind(d->registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); } else if (SDL_strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) { @@ -797,17 +828,6 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint if (d->input) { Wayland_RegisterTimestampListeners(d->input); } -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - } else if (SDL_strcmp(interface, "qt_touch_extension") == 0) { - Wayland_touch_create(d, id); - } else if (SDL_strcmp(interface, "qt_surface_extension") == 0) { - d->surface_extension = wl_registry_bind(registry, id, - &qt_surface_extension_interface, 1); - } else if (SDL_strcmp(interface, "qt_windowmanager") == 0) { - d->windowmanager = wl_registry_bind(registry, id, - &qt_windowmanager_interface, 1); - qt_windowmanager_add_listener(d->windowmanager, &windowmanager_listener, d); -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ } } @@ -881,7 +901,7 @@ int Wayland_VideoInit(SDL_VideoDevice *_this) } data->registry = wl_display_get_registry(data->display); - if (data->registry == NULL) { + if (!data->registry) { return SDL_SetError("Failed to get the Wayland registry"); } @@ -902,6 +922,12 @@ int Wayland_VideoInit(SDL_VideoDevice *_this) Wayland_InitKeyboard(_this); + if (data->primary_selection_device_manager) { + _this->SetPrimarySelectionText = Wayland_SetPrimarySelectionText; + _this->GetPrimarySelectionText = Wayland_GetPrimarySelectionText; + _this->HasPrimarySelectionText = Wayland_HasPrimarySelectionText; + } + data->initializing = SDL_FALSE; return 0; @@ -941,8 +967,16 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) } Wayland_display_destroy_input(data); - Wayland_display_destroy_pointer_constraints(data); - Wayland_display_destroy_relative_pointer_manager(data); + + if (data->pointer_constraints) { + zwp_pointer_constraints_v1_destroy(data->pointer_constraints); + data->pointer_constraints = NULL; + } + + if (data->relative_pointer_manager) { + zwp_relative_pointer_manager_v1_destroy(data->relative_pointer_manager); + data->relative_pointer_manager = NULL; + } if (data->activation_manager) { xdg_activation_v1_destroy(data->activation_manager); @@ -970,19 +1004,6 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) WAYLAND_xkb_context_unref(data->xkb_context); data->xkb_context = NULL; } -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - if (data->windowmanager) { - qt_windowmanager_destroy(data->windowmanager); - data->windowmanager = NULL; - } - - if (data->surface_extension) { - qt_surface_extension_destroy(data->surface_extension); - data->surface_extension = NULL; - } - - Wayland_touch_destroy(data); -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ if (data->tablet_manager) { zwp_tablet_manager_v2_destroy((struct zwp_tablet_manager_v2 *)data->tablet_manager); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index bc55ee3e..25a4b3c0 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,12 +35,6 @@ struct xkb_context; struct SDL_WaylandInput; struct SDL_WaylandTabletManager; -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH -struct SDL_WaylandTouch; -struct qt_surface_extension; -struct qt_windowmanager; -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - typedef struct { struct wl_cursor_theme *theme; @@ -84,13 +78,8 @@ struct SDL_VideoData struct SDL_WaylandTabletManager *tablet_manager; struct wl_list output_list; -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - struct SDL_WaylandTouch *touch; - struct qt_surface_extension *surface_extension; - struct qt_windowmanager *windowmanager; -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - int relative_mouse_mode; + SDL_bool display_externally_owned; }; struct SDL_DisplayData @@ -119,6 +108,10 @@ extern void SDL_WAYLAND_register_output(struct wl_output *output); extern SDL_bool SDL_WAYLAND_own_surface(struct wl_surface *surface); extern SDL_bool SDL_WAYLAND_own_output(struct wl_output *output); +extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface); +void Wayland_AddWindowDataToExternalList(SDL_WindowData *data); +void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data); + extern SDL_bool Wayland_LoadLibdecor(SDL_VideoData *data, SDL_bool ignore_xdg); extern SDL_bool Wayland_VideoReconnect(SDL_VideoDevice *_this); diff --git a/src/video/wayland/SDL_waylandvulkan.c b/src/video/wayland/SDL_waylandvulkan.c index 1f667512..7debe29f 100644 --- a/src/video/wayland/SDL_waylandvulkan.c +++ b/src/video/wayland/SDL_waylandvulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -28,16 +28,12 @@ #if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WAYLAND) -#include "../SDL_vulkan_internal.h" - #include "SDL_waylandvideo.h" #include "SDL_waylandwindow.h" #include "../SDL_vulkan_internal.h" #include "SDL_waylandvulkan.h" -#include - #ifdef __OpenBSD__ #define DEFAULT_VULKAN "libvulkan.so" #else @@ -56,10 +52,10 @@ int Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan loader library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { path = DEFAULT_VULKAN; } _this->vulkan_config.loader_handle = SDL_LoadObject(path); @@ -84,7 +80,7 @@ int Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) (PFN_vkEnumerateInstanceExtensionProperties) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } for (i = 0; i < extensionCount; i++) { @@ -118,25 +114,23 @@ void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count) { static const char *const extensionsForWayland[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; + + if (count) { + *count = SDL_arraysize(extensionsForWayland); } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForWayland), - extensionsForWayland); + + return extensionsForWayland; } SDL_bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { SDL_WindowData *windowData = window->driverdata; @@ -165,8 +159,7 @@ SDL_bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.display = windowData->waylandData->display; createInfo.surface = windowData->surface; - result = vkCreateWaylandSurfaceKHR(instance, &createInfo, - NULL, surface); + result = vkCreateWaylandSurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateWaylandSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); diff --git a/src/video/wayland/SDL_waylandvulkan.h b/src/video/wayland/SDL_waylandvulkan.h index 18aa3ed5..e154d1a0 100644 --- a/src/video/wayland/SDL_waylandvulkan.h +++ b/src/video/wayland/SDL_waylandvulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 88e0682c..5637ea3c 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,7 +30,6 @@ #include "SDL_waylandevents_c.h" #include "SDL_waylandwindow.h" #include "SDL_waylandvideo.h" -#include "SDL_waylandtouch.h" #include "../../SDL_hints_c.h" #include "xdg-shell-client-protocol.h" @@ -115,7 +114,7 @@ static SDL_bool WindowNeedsViewport(SDL_Window *window) * - The surface scale is fractional. * - An exclusive fullscreen mode is being emulated and the mode does not match the requested output size. */ - if (video->viewporter != NULL) { + if (video->viewporter) { if (SurfaceScaleIsFractional(window)) { return SDL_TRUE; } else if (window->fullscreen_exclusive) { @@ -158,7 +157,7 @@ static void SetDrawSurfaceViewport(SDL_Window *window, int src_width, int src_he SDL_VideoData *video = wind->waylandData; if (video->viewporter) { - if (wind->draw_viewport == NULL) { + if (!wind->draw_viewport) { wind->draw_viewport = wp_viewporter_get_viewport(video->viewporter, wind->surface); } @@ -180,10 +179,9 @@ static void UnsetDrawSurfaceViewport(SDL_Window *window) static void SetMinMaxDimensions(SDL_Window *window) { SDL_WindowData *wind = window->driverdata; - SDL_VideoData *viddata = wind->waylandData; int min_width, min_height, max_width, max_height; - if (window->flags & SDL_WINDOW_FULLSCREEN) { + if ((window->flags & SDL_WINDOW_FULLSCREEN) || wind->fullscreen_deadline_count) { min_width = 0; min_height = 0; max_width = 0; @@ -202,7 +200,7 @@ static void SetMinMaxDimensions(SDL_Window *window) #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (!wind->shell_surface.libdecor.initial_configure_seen || wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.initial_configure_seen || !wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } /* No need to change these values if the window is non-resizable, @@ -218,7 +216,7 @@ static void SetMinMaxDimensions(SDL_Window *window) } } else #endif - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && viddata->shell.xdg) { + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { return; /* Can't do anything yet, wait for ShowWindow */ } @@ -231,7 +229,7 @@ static void SetMinMaxDimensions(SDL_Window *window) } } -static void EnsurePopupPositionIsValid(SDL_Window *window) +static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y) { int adj_count = 0; @@ -242,20 +240,20 @@ static void EnsurePopupPositionIsValid(SDL_Window *window) * can result in behavior ranging from the window being spuriously closed * to a protocol violation. */ - if (window->x + window->driverdata->wl_window_width < 0) { - window->x = -window->w; + if (*x + window->driverdata->wl_window_width < 0) { + *x = -window->w; ++adj_count; } - if (window->y + window->driverdata->wl_window_height < 0) { - window->y = -window->h; + if (*y + window->driverdata->wl_window_height < 0) { + *y = -window->h; ++adj_count; } - if (window->x > window->parent->driverdata->wl_window_width) { - window->x = window->parent->driverdata->wl_window_width; + if (*x > window->parent->driverdata->wl_window_width) { + *x = window->parent->driverdata->wl_window_width; ++adj_count; } - if (window->y > window->parent->driverdata->wl_window_height) { - window->y = window->parent->driverdata->wl_window_height; + if (*y > window->parent->driverdata->wl_window_height) { + *y = window->parent->driverdata->wl_window_height; ++adj_count; } @@ -264,7 +262,7 @@ static void EnsurePopupPositionIsValid(SDL_Window *window) * must be nudged by 1 to be considered adjacent. */ if (adj_count > 1) { - window->x += window->x < 0 ? 1 : -1; + *x += *x < 0 ? 1 : -1; } } @@ -283,17 +281,19 @@ static void GetPopupPosition(SDL_Window *popup, int x, int y, int *adj_x, int *a } } -static void RepositionPopup(SDL_Window *window) +static void RepositionPopup(SDL_Window *window, SDL_bool use_current_position) { SDL_WindowData *wind = window->driverdata; if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP && wind->shell_surface.xdg.roleobj.popup.positioner && xdg_popup_get_version(wind->shell_surface.xdg.roleobj.popup.popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) { + int orig_x = use_current_position ? window->x : window->floating.x; + int orig_y = use_current_position ? window->y : window->floating.y; int x, y; - EnsurePopupPositionIsValid(window); - GetPopupPosition(window, window->x, window->y, &x, &y); + EnsurePopupPositionIsValid(window, &orig_x, &orig_y); + GetPopupPosition(window, orig_x, orig_y, &x, &y); xdg_positioner_set_anchor_rect(wind->shell_surface.xdg.roleobj.popup.positioner, 0, 0, window->parent->driverdata->wl_window_width, window->parent->driverdata->wl_window_height); xdg_positioner_set_size(wind->shell_surface.xdg.roleobj.popup.positioner, wind->wl_window_width, wind->wl_window_height); xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner, x, y); @@ -395,8 +395,10 @@ static void ConfigureWindowGeometry(SDL_Window *window) wl_surface_set_buffer_scale(data->surface, 1); SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height, window_width, window_height); - } else { + } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { UnsetDrawSurfaceViewport(window); + + /* Don't change this if DPI awareness flag is unset, as an application may have set this manually. */ wl_surface_set_buffer_scale(data->surface, (int32_t)data->windowed_scale_factor); } @@ -417,8 +419,7 @@ static void ConfigureWindowGeometry(SDL_Window *window) struct wl_region *region; /* libdecor does this internally on frame commits, so it's only needed for xdg surfaces. */ - if (data->shell_surface_type != WAYLAND_SURFACE_LIBDECOR && - viddata->shell.xdg && data->shell_surface.xdg.surface != NULL) { + if (data->shell_surface_type != WAYLAND_SURFACE_LIBDECOR && data->shell_surface.xdg.surface) { xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->wl_window_width, data->wl_window_height); } @@ -431,8 +432,8 @@ static void ConfigureWindowGeometry(SDL_Window *window) } /* Ensure that child popup windows are still in bounds. */ - for (SDL_Window *child = window->first_child; child != NULL; child = child->next_sibling) { - RepositionPopup(child); + for (SDL_Window *child = window->first_child; child; child = child->next_sibling) { + RepositionPopup(child, SDL_TRUE); } if (data->confined_pointer) { @@ -480,6 +481,31 @@ static void CommitLibdecorFrame(SDL_Window *window) #endif } +static void fullscreen_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + /* Get the window from the ID as it may have been destroyed */ + SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); + SDL_Window *window = SDL_GetWindowFromID(windowID); + + if (window && window->driverdata) { + window->driverdata->fullscreen_deadline_count--; + SetMinMaxDimensions(window); + } + + wl_callback_destroy(callback); +} + +static struct wl_callback_listener fullscreen_deadline_listener = { + fullscreen_deadline_handler +}; + +static void FlushFullscreenEvents(SDL_Window *window) +{ + while (window->driverdata->fullscreen_deadline_count) { + WAYLAND_wl_display_roundtrip(window->driverdata->waylandData->display); + } +} + static void SetFullscreen(SDL_Window *window, struct wl_output *output) { SDL_WindowData *wind = window->driverdata; @@ -487,9 +513,11 @@ static void SetFullscreen(SDL_Window *window, struct wl_output *output) #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } + + ++wind->fullscreen_deadline_count; if (output) { Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, SDL_TRUE); wl_surface_commit(wind->surface); @@ -500,11 +528,12 @@ static void SetFullscreen(SDL_Window *window, struct wl_output *output) } } else #endif - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && viddata->shell.xdg) { + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { return; /* Can't do anything yet, wait for ShowWindow */ } + ++wind->fullscreen_deadline_count; if (output) { Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, SDL_TRUE); wl_surface_commit(wind->surface); @@ -515,8 +544,9 @@ static void SetFullscreen(SDL_Window *window, struct wl_output *output) } } - /* Roundtrip to apply the new state. */ - WAYLAND_wl_display_roundtrip(viddata->display); + /* Queue a deadline event */ + struct wl_callback *cb = wl_display_sync(viddata->display); + wl_callback_add_listener(cb, &fullscreen_deadline_listener, (void *)((uintptr_t)window->id)); } static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen) @@ -525,26 +555,30 @@ static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen) wind->is_fullscreen = fullscreen; - /* If this configure event is coming from a roundtrip after explicitly - * changing the fullscreen state, don't call back into the - * SDL_SetWindowFullscreen() function. - */ - if (wind->in_fullscreen_transition) { - return; - } - if (fullscreen) { if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - wind->in_fullscreen_transition = SDL_TRUE; - SDL_SetWindowFullscreen(window, SDL_TRUE); - wind->in_fullscreen_transition = SDL_FALSE; + SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_FALSE); + + /* Unconditionally set the output for exclusive fullscreen windows when entering + * fullscreen from a compositor event, as where the compositor will actually + * place the fullscreen window is unknown. + */ + if (window->fullscreen_exclusive && !wind->fullscreen_was_positioned) { + SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID); + if (disp) { + wind->fullscreen_was_positioned = SDL_TRUE; + SetFullscreen(window, disp->driverdata->output); + } + } } } else { /* Don't change the fullscreen flags if the window is hidden or being hidden. */ if ((window->flags & SDL_WINDOW_FULLSCREEN) && !window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) { - wind->in_fullscreen_transition = SDL_TRUE; - SDL_SetWindowFullscreen(window, SDL_FALSE); - wind->in_fullscreen_transition = SDL_FALSE; + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE); + wind->fullscreen_was_positioned = SDL_FALSE; } } } @@ -571,7 +605,7 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time wind->surface_status = WAYLAND_SURFACE_STATUS_SHOWN; /* If any child windows are waiting on this window to be shown, show them now */ - for (SDL_Window *w = wind->sdlwindow->first_child; w != NULL; w = w->next_sibling) { + for (SDL_Window *w = wind->sdlwindow->first_child; w; w = w->next_sibling) { if (w->driverdata->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING) { Wayland_ShowWindow(SDL_GetVideoDevice(), w); } @@ -639,6 +673,7 @@ static void handle_configure_xdg_toplevel(void *data, SDL_bool fullscreen = SDL_FALSE; SDL_bool maximized = SDL_FALSE; SDL_bool floating = SDL_TRUE; + SDL_bool tiled = SDL_FALSE; SDL_bool active = SDL_FALSE; SDL_bool suspended = SDL_FALSE; wl_array_for_each (state, states) { @@ -658,10 +693,12 @@ static void handle_configure_xdg_toplevel(void *data, case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + tiled = SDL_TRUE; floating = SDL_FALSE; break; case XDG_TOPLEVEL_STATE_SUSPENDED: suspended = SDL_TRUE; + break; default: break; } @@ -669,24 +706,48 @@ static void handle_configure_xdg_toplevel(void *data, UpdateWindowFullscreen(window, fullscreen); + /* Always send a maximized/restore event; if the event is redundant it will + * automatically be discarded (see src/events/SDL_windowevents.c) + * + * No, we do not get minimize events from xdg-shell, however, the minimized + * state can be programmatically set. The meaning of 'minimized' is compositor + * dependent, but in general, we can assume that the flag should remain set until + * the next focused configure event occurs. + */ + if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { + if (window->flags & SDL_WINDOW_MINIMIZED) { + /* If we were minimized, send a restored event before possibly sending maximized. */ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + SDL_SendWindowEvent(window, + (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, + 0, 0); + } + if (!fullscreen) { /* xdg_toplevel spec states that this is a suggestion. * Ignore if less than or greater than max/min size. */ if (window->flags & SDL_WINDOW_RESIZABLE) { - if ((floating && !wind->floating) || width == 0 || height == 0) { + if ((floating && !wind->floating) || + width == 0 || height == 0) { /* This happens when we're being restored from a * non-floating state, so use the cached floating size here. */ - width = wind->floating_width; - height = wind->floating_height; + width = window->floating.w; + height = window->floating.h; } } else { /* If we're a fixed-size window, we know our size for sure. * Always assume the configure is wrong. */ - width = window->windowed.w; - height = window->windowed.h; + if (floating) { + width = window->floating.w; + height = window->floating.h; + } else { + width = window->windowed.w; + height = window->windowed.h; + } } /* The content limits are only a hint, which the compositor is free to ignore, @@ -706,50 +767,28 @@ static void handle_configure_xdg_toplevel(void *data, } height = SDL_max(height, window->min_h); } - - /* Always send a maximized/restore event; if the event is redundant it will - * automatically be discarded (see src/events/SDL_windowevents.c) - * - * No, we do not get minimize events from xdg-shell, however, the minimized - * state can be programmatically set. The meaning of 'minimized' is compositor - * dependent, but in general, we can assume that the flag should remain set until - * the next focused configure event occurs. - */ - if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { - SDL_SendWindowEvent(window, - maximized ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, - 0, 0); - } - - /* Store current floating dimensions for restoring */ - if (floating) { - wind->floating_width = width; - wind->floating_height = height; - } } else { - /* Unconditionally set the output for exclusive fullscreen windows when entering - * fullscreen from a compositor event, as where the compositor will actually - * place the fullscreen window is unknown. - */ - if (window->fullscreen_exclusive && !wind->fullscreen_was_positioned) { - SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID); - if (disp) { - wind->fullscreen_was_positioned = SDL_TRUE; - xdg_toplevel_set_fullscreen(xdg_toplevel, disp->driverdata->output); - } - } - if (width == 0 || height == 0) { width = wind->requested_window_width; height = wind->requested_window_height; } } - wind->requested_window_width = width; - wind->requested_window_height = height; + /* Don't update the dimensions if they haven't changed, or they could overwrite + * a new size set programmatically with old dimensions. + */ + if (width != wind->last_configure_width || height != wind->last_configure_height) { + wind->requested_window_width = width; + wind->requested_window_height = height; + } + + wind->last_configure_width = width; + wind->last_configure_height = height; wind->floating = floating; wind->suspended = suspended; wind->active = active; + window->state_not_floating = tiled; + if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { wind->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME; } @@ -877,10 +916,10 @@ static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { static void OverrideLibdecorLimits(SDL_Window *window) { #ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR - if (libdecor_frame_get_min_content_size == NULL) { + if (!libdecor_frame_get_min_content_size) { libdecor_frame_set_min_content_size(window->driverdata->shell_surface.libdecor.frame, window->min_w, window->min_h); } -#elif !defined(SDL_HAVE_LIBDECOR_VER_0_1_2) +#elif !defined(SDL_HAVE_LIBDECOR_VER_0_2_0) libdecor_frame_set_min_content_size(window->driverdata->shell_surface.libdecor.frame, window->min_w, window->min_h); #endif } @@ -899,7 +938,7 @@ static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w, if (libdecor_frame_get_min_content_size != NULL) { libdecor_frame_get_min_content_size(frame, min_w, min_h); } -#elif defined(SDL_HAVE_LIBDECOR_VER_0_1_2) +#elif defined(SDL_HAVE_LIBDECOR_VER_0_2_0) libdecor_frame_get_min_content_size(frame, min_w, min_h); #endif } @@ -932,7 +971,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame, maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0; active = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0; tiled = (window_state & tiled_states) != 0; -#ifdef SDL_HAVE_LIBDECOR_VER_0_1_2 +#ifdef SDL_HAVE_LIBDECOR_VER_0_2_0 suspended = (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) != 0; #endif } @@ -940,38 +979,28 @@ static void decoration_frame_configure(struct libdecor_frame *frame, UpdateWindowFullscreen(window, fullscreen); - if (!fullscreen) { - /* Always send a maximized/restore event; if the event is redundant it will - * automatically be discarded (see src/events/SDL_windowevents.c) - * - * No, we do not get minimize events from libdecor, however, the minimized - * state can be programmatically set. The meaning of 'minimized' is compositor - * dependent, but in general, we can assume that the flag should remain set until - * the next focused configure event occurs. - */ - if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { - SDL_SendWindowEvent(window, - maximized ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, - 0, 0); + /* Always send a maximized/restore event; if the event is redundant it will + * automatically be discarded (see src/events/SDL_windowevents.c) + * + * No, we do not get minimize events from libdecor, however, the minimized + * state can be programmatically set. The meaning of 'minimized' is compositor + * dependent, but in general, we can assume that the flag should remain set until + * the next focused configure event occurs. + */ + if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { + if (window->flags & SDL_WINDOW_MINIMIZED) { + /* If we were minimized, send a restored event before possibly sending maximized. */ + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); } + SDL_SendWindowEvent(window, + (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, + 0, 0); } /* For fullscreen or fixed-size windows we know our size. * Always assume the configure is wrong. */ if (fullscreen) { - /* Unconditionally set the output for exclusive fullscreen windows when entering - * fullscreen from a compositor event, as where the compositor will actually - * place the fullscreen window is unknown. - */ - if (window->fullscreen_exclusive && !wind->fullscreen_was_positioned) { - SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID); - if (disp) { - wind->fullscreen_was_positioned = SDL_TRUE; - libdecor_frame_set_fullscreen(frame, disp->driverdata->output); - } - } - /* FIXME: We have been explicitly told to respect the fullscreen size * parameters here, even though they are known to be wrong on GNOME at * bare minimum. If this is wrong, don't blame us, we were explicitly @@ -983,8 +1012,13 @@ static void decoration_frame_configure(struct libdecor_frame *frame, } } else { if (!(window->flags & SDL_WINDOW_RESIZABLE)) { - width = window->windowed.w; - height = window->windowed.h; + if (floating) { + width = window->floating.w; + height = window->floating.h; + } else { + width = window->windowed.w; + height = window->windowed.h; + } OverrideLibdecorLimits(window); } else { @@ -1008,11 +1042,11 @@ static void decoration_frame_configure(struct libdecor_frame *frame, /* This usually happens when we're being restored from a * non-floating state, so use the cached floating size here. */ - width = wind->floating_width; - height = wind->floating_height; + width = window->floating.w; + height = window->floating.h; } else { - width = window->w; - height = window->h; + width = window->windowed.w; + height = window->windowed.h; } } } @@ -1036,20 +1070,23 @@ static void decoration_frame_configure(struct libdecor_frame *frame, } } - /* Store current floating dimensions for restoring */ - if (floating) { - wind->floating_width = width; - wind->floating_height = height; + /* Don't update the dimensions if they haven't changed, or they could overwrite + * a new size set programmatically with old dimensions. + */ + if (width != wind->last_configure_width || height != wind->last_configure_height) { + wind->requested_window_width = width; + wind->requested_window_height = height; } /* Store the new state. */ + wind->last_configure_width = width; + wind->last_configure_height = height; wind->floating = floating; wind->suspended = suspended; wind->active = active; + window->state_not_floating = tiled; /* Calculate the new window geometry */ - wind->requested_window_width = width; - wind->requested_window_height = height; ConfigureWindowGeometry(window); /* ... then commit the changes on the libdecor side. */ @@ -1100,31 +1137,6 @@ static struct libdecor_frame_interface libdecor_frame_interface = { }; #endif -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH -static void handle_onscreen_visibility(void *data, - struct qt_extended_surface *qt_extended_surface, int32_t visible) -{ -} - -static void handle_set_generic_property(void *data, - struct qt_extended_surface *qt_extended_surface, const char *name, - struct wl_array *value) -{ -} - -static void handle_close(void *data, struct qt_extended_surface *qt_extended_surface) -{ - SDL_WindowData *window = (SDL_WindowData *)data; - SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); -} - -static const struct qt_extended_surface_listener extended_surface_listener = { - handle_onscreen_visibility, - handle_set_generic_property, - handle_close, -}; -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, float factor) { const float old_factor = window_data->windowed_scale_factor; @@ -1203,10 +1215,16 @@ static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata) * -flibit */ SDL_Rect bounds; - SDL_GetDisplayBounds(displays[i], &bounds); wind->last_displayID = displays[i]; if (wind->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP) { + /* Need to catch up on fullscreen state here, as the video core may try to update + * the fullscreen window, which on Wayland involves a set fullscreen call, which + * can overwrite older pending state. + */ + FlushFullscreenEvents(window); + + SDL_GetDisplayBounds(wind->last_displayID, &bounds); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, bounds.x, bounds.y); } break; @@ -1221,13 +1239,18 @@ static void handle_surface_enter(void *data, struct wl_surface *surface, { SDL_WindowData *window = data; SDL_DisplayData *driverdata = wl_output_get_user_data(output); + SDL_DisplayData **new_outputs; if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) { return; } - window->outputs = SDL_realloc(window->outputs, - sizeof(SDL_DisplayData *) * (window->num_outputs + 1)); + new_outputs = SDL_realloc(window->outputs, + sizeof(SDL_DisplayData *) * (window->num_outputs + 1)); + if (!new_outputs) { + return; + } + window->outputs = new_outputs; window->outputs[window->num_outputs++] = driverdata; /* Update the scale factor after the move so that fullscreen outputs are updated. */ @@ -1313,7 +1336,7 @@ static void SetKeyboardFocus(SDL_Window *window) SDL_Window *topmost = window; /* Find the topmost parent */ - while (topmost->parent != NULL) { + while (topmost->parent) { topmost = topmost->parent; } @@ -1327,37 +1350,6 @@ static void SetKeyboardFocus(SDL_Window *window) SDL_SetKeyboardFocus(window); } -int Wayland_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - SDL_VideoData *viddata = _this->driverdata; - SDL_WindowData *data = window->driverdata; - - info->subsystem = SDL_SYSWM_WAYLAND; - info->info.wl.display = data->waylandData->display; - info->info.wl.surface = data->surface; - info->info.wl.egl_window = data->egl_window; - -#ifdef HAVE_LIBDECOR_H - if (data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (data->shell_surface.libdecor.frame != NULL) { - info->info.wl.xdg_surface = libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame); - info->info.wl.xdg_toplevel = libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame); - } - } else -#endif - if (viddata->shell.xdg && data->shell_surface.xdg.surface != NULL) { - SDL_bool popup = (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) ? SDL_TRUE : SDL_FALSE; - info->info.wl.xdg_surface = data->shell_surface.xdg.surface; - info->info.wl.xdg_toplevel = popup ? NULL : data->shell_surface.xdg.roleobj.toplevel; - if (popup) { - info->info.wl.xdg_popup = data->shell_surface.xdg.roleobj.popup.popup; - info->info.wl.xdg_positioner = data->shell_surface.xdg.roleobj.popup.positioner; - } - } - - return 0; -} - int Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled) { return 0; /* just succeed, the real work is done elsewhere. */ @@ -1375,10 +1367,10 @@ int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, #ifdef HAVE_LIBDECOR_H if (viddata->shell.libdecor) { - if (modal_data->shell_surface.libdecor.frame == NULL) { + if (!modal_data->shell_surface.libdecor.frame) { return SDL_SetError("Modal window was hidden"); } - if (parent_data->shell_surface.libdecor.frame == NULL) { + if (!parent_data->shell_surface.libdecor.frame) { return SDL_SetError("Parent window was hidden"); } libdecor_frame_set_parent(modal_data->shell_surface.libdecor.frame, @@ -1398,14 +1390,37 @@ int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, return SDL_Unsupported(); } - WAYLAND_wl_display_flush(viddata->display); return 0; } +static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + /* Get the window from the ID as it may have been destroyed */ + SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); + SDL_Window *window = SDL_GetWindowFromID(windowID); + + if (window && window->driverdata) { + SDL_WindowData *wind = window->driverdata; + wind->show_hide_sync_required = SDL_FALSE; + } + + wl_callback_destroy(callback); +} + +static struct wl_callback_listener show_hide_sync_listener = { + show_hide_sync_handler +}; + void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_VideoData *c = _this->driverdata; SDL_WindowData *data = window->driverdata; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + + /* Custom surfaces don't get toplevels and are always considered 'shown'; nothing to do here. */ + if (data->shell_surface_type == WAYLAND_SURFACE_CUSTOM) { + return; + } /* If this is a child window, the parent *must* be in the final shown state, * meaning that it has received a configure event, followed by a frame callback. @@ -1422,6 +1437,13 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } + /* The window was hidden, but the sync point hasn't yet been reached. + * Pump events to avoid a possible protocol violation. + */ + if (data->show_hide_sync_required) { + WAYLAND_wl_display_roundtrip(c->display); + } + data->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE; /* Detach any previous buffers before resetting everything, otherwise when @@ -1448,18 +1470,22 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) data->surface, &libdecor_frame_interface, data); - if (data->shell_surface.libdecor.frame == NULL) { + if (!data->shell_surface.libdecor.frame) { SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!"); } else { libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id); libdecor_frame_map(data->shell_surface.libdecor.frame); + + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame)); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame)); } } else #endif - if ((data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) && c->shell.xdg) { + if (data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface); xdg_surface_set_user_data(data->shell_surface.xdg.surface, data); xdg_surface_add_listener(data->shell_surface.xdg.surface, &shell_surface_listener_xdg, data); + SDL_SetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, data->shell_surface.xdg.surface); if (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { SDL_Window *parent = window->parent; @@ -1488,7 +1514,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) xdg_positioner_set_size(data->shell_surface.xdg.roleobj.popup.positioner, window->w, window->h); /* Set the popup initial position */ - EnsurePopupPositionIsValid(window); + EnsurePopupPositionIsValid(window, &window->x, &window->y); GetPopupPosition(window, window->x, window->y, &position_x, &position_y); xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner, position_x, position_y); @@ -1511,10 +1537,14 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) SetKeyboardFocus(window); } } + + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.roleobj.popup.popup); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, data->shell_surface.xdg.roleobj.popup.positioner); } else { data->shell_surface.xdg.roleobj.toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface); xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, data->app_id); xdg_toplevel_add_listener(data->shell_surface.xdg.roleobj.toplevel, &toplevel_listener_xdg, data); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, data->shell_surface.xdg.roleobj.toplevel); } } @@ -1534,7 +1564,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } else #endif - if (c->shell.xdg) { + if (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP || data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { /* Unlike libdecor we need to call this explicitly to prevent a deadlock. * libdecor will call this as part of their configure event! * -flibit @@ -1603,11 +1633,9 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - /* - * Roundtrip required to avoid a possible protocol violation when - * HideWindow was called immediately before ShowWindow. - */ - WAYLAND_wl_display_roundtrip(c->display); + data->show_hide_sync_required = SDL_TRUE; + struct wl_callback *cb = wl_display_sync(_this->driverdata->display); + wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); /* Send an exposure event to signal that the client should draw. */ if (data->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME) { @@ -1620,11 +1648,11 @@ static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) SDL_WindowData *popupdata; /* Basic sanity checks to weed out the weird popup closures */ - if (popup == NULL || popup->magic != &_this->window_magic) { + if (!popup || popup->magic != &_this->window_magic) { return; } popupdata = popup->driverdata; - if (popupdata == NULL) { + if (!popupdata) { return; } @@ -1638,7 +1666,7 @@ static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) SDL_Window *new_focus = popup->parent; /* Find the highest level window that isn't being hidden or destroyed. */ - while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + while (new_focus->parent && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } @@ -1650,12 +1678,29 @@ static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) xdg_positioner_destroy(popupdata->shell_surface.xdg.roleobj.popup.positioner); popupdata->shell_surface.xdg.roleobj.popup.popup = NULL; popupdata->shell_surface.xdg.roleobj.popup.positioner = NULL; + + SDL_PropertiesID props = SDL_GetWindowProperties(popup); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_POPUP_POINTER, NULL); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, NULL); } void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_VideoData *data = _this->driverdata; SDL_WindowData *wind = window->driverdata; + SDL_PropertiesID props = SDL_GetWindowProperties(window); + + /* Custom surfaces have nothing to destroy and are always considered to be 'shown'; nothing to do here. */ + if (wind->shell_surface_type == WAYLAND_SURFACE_CUSTOM) { + return; + } + + /* The window was shown, but the sync point hasn't yet been reached. + * Pump events to avoid a possible protocol violation. + */ + if (wind->show_hide_sync_required) { + WAYLAND_wl_display_roundtrip(data->display); + } wind->surface_status = WAYLAND_SURFACE_STATUS_HIDDEN; @@ -1675,27 +1720,28 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) if (wind->shell_surface.libdecor.frame) { libdecor_frame_unref(wind->shell_surface.libdecor.frame); wind->shell_surface.libdecor.frame = NULL; + + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); } } else #endif - if (data->shell.xdg) { if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - Wayland_ReleasePopup(_this, window); - } else if (wind->shell_surface.xdg.roleobj.toplevel) { - xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel); - wind->shell_surface.xdg.roleobj.toplevel = NULL; - } - if (wind->shell_surface.xdg.surface) { - xdg_surface_destroy(wind->shell_surface.xdg.surface); - wind->shell_surface.xdg.surface = NULL; - } + Wayland_ReleasePopup(_this, window); + } else if (wind->shell_surface.xdg.roleobj.toplevel) { + xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel); + wind->shell_surface.xdg.roleobj.toplevel = NULL; + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); + } + if (wind->shell_surface.xdg.surface) { + xdg_surface_destroy(wind->shell_surface.xdg.surface); + wind->shell_surface.xdg.surface = NULL; + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); } - /* - * Roundtrip required to avoid a possible protocol violation when - * ShowWindow is called immediately after HideWindow. - */ - WAYLAND_wl_display_roundtrip(data->display); + wind->show_hide_sync_required = SDL_TRUE; + struct wl_callback *cb = wl_display_sync(_this->driverdata->display); + wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); } static void handle_xdg_activation_done(void *data, @@ -1743,7 +1789,7 @@ static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_ struct wl_surface *requesting_surface = focus ? focus->driverdata->surface : NULL; if (data->activation_manager) { - if (target_wind->activation_token != NULL) { + if (target_wind->activation_token) { /* We're about to overwrite this with a new request */ xdg_activation_token_v1_destroy(target_wind->activation_token); } @@ -1760,7 +1806,7 @@ static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_ * * -flibit */ - if (requesting_surface != NULL) { + if (requesting_surface) { /* This specifies the surface from which the activation request is originating, not the activation target surface. */ xdg_activation_token_v1_set_surface(target_wind->activation_token, requesting_surface); } @@ -1785,116 +1831,51 @@ int Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOpe return 0; } -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH -static void SDLCALL QtExtendedSurface_OnHintChanged(void *userdata, const char *name, - const char *oldValue, const char *newValue) +static void fullscreen_configure_handler(void *data, struct wl_callback *callback, uint32_t callback_data) { - struct qt_extended_surface *qt_extended_surface = userdata; - int i; + /* Get the window from the ID as it may have been destroyed */ + SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); + SDL_Window *window = SDL_GetWindowFromID(windowID); - static struct - { - const char *name; - int32_t value; - } orientations[] = { - { "portrait", QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION }, - { "landscape", QT_EXTENDED_SURFACE_ORIENTATION_LANDSCAPEORIENTATION }, - { "inverted-portrait", QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDPORTRAITORIENTATION }, - { "inverted-landscape", QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDLANDSCAPEORIENTATION } - }; - - if (name == NULL) { - return; + if (window && window->driverdata && window->driverdata->is_fullscreen) { + ConfigureWindowGeometry(window); + CommitLibdecorFrame(window); } - if (SDL_strcmp(name, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION) == 0) { - int32_t orientation = QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION; - - if (newValue != NULL) { - const char *value_attempt = newValue; - - orientation = 0; - while (value_attempt != NULL && *value_attempt != 0) { - const char *value_attempt_end = SDL_strchr(value_attempt, ','); - size_t value_attempt_len = (value_attempt_end != NULL) ? (value_attempt_end - value_attempt) - : SDL_strlen(value_attempt); - - for (i = 0; i < SDL_arraysize(orientations); i += 1) { - if ((value_attempt_len == SDL_strlen(orientations[i].name)) && - (SDL_strncasecmp(orientations[i].name, value_attempt, value_attempt_len) == 0)) { - orientation |= orientations[i].value; - break; - } - } - - value_attempt = (value_attempt_end != NULL) ? (value_attempt_end + 1) : NULL; - } - } - - qt_extended_surface_set_content_orientation(qt_extended_surface, orientation); - } else if (SDL_strcmp(name, SDL_HINT_QTWAYLAND_WINDOW_FLAGS) == 0) { - uint32_t flags = 0; - - if (newValue != NULL) { - char *tmp = SDL_strdup(newValue); - char *saveptr = NULL; - - char *flag = SDL_strtok_r(tmp, " ", &saveptr); - while (flag) { - if (SDL_strcmp(flag, "OverridesSystemGestures") == 0) { - flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_OVERRIDESSYSTEMGESTURES; - } else if (SDL_strcmp(flag, "StaysOnTop") == 0) { - flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_STAYSONTOP; - } else if (SDL_strcmp(flag, "BypassWindowManager") == 0) { - // See https://github.com/qtproject/qtwayland/commit/fb4267103d - flags |= 4 /* QT_EXTENDED_SURFACE_WINDOWFLAG_BYPASSWINDOWMANAGER */; - } - - flag = SDL_strtok_r(NULL, " ", &saveptr); - } - - SDL_free(tmp); - } - - qt_extended_surface_set_window_flags(qt_extended_surface, flags); - } + wl_callback_destroy(callback); } -static void QtExtendedSurface_Subscribe(struct qt_extended_surface *surface, const char *name) -{ - SDL_AddHintCallback(name, QtExtendedSurface_OnHintChanged, surface); -} +static struct wl_callback_listener fullscreen_configure_listener = { + fullscreen_configure_handler +}; -static void QtExtendedSurface_Unsubscribe(struct qt_extended_surface *surface, const char *name) -{ - SDL_DelHintCallback(name, QtExtendedSurface_OnHintChanged, surface); -} -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - -void Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, +int Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { SDL_WindowData *wind = window->driverdata; struct wl_output *output = display->driverdata->output; - /* Called from within a configure event or the window is a popup, drop it. */ - if (wind->in_fullscreen_transition || wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - if (!fullscreen) { - /* Clear the fullscreen positioned flag. */ - wind->fullscreen_was_positioned = SDL_FALSE; - } - return; + /* Custom surfaces have no toplevel to make fullscreen. */ + if (wind->shell_surface_type == WAYLAND_SURFACE_CUSTOM) { + return -1; } - /* If we're here, this was called from a higher-level video subsystem function. - * Set the flag to avoid recursively re-entering these functions while changing the - * fullscreen state. - */ - wind->in_fullscreen_transition = SDL_TRUE; + if (wind->show_hide_sync_required) { + WAYLAND_wl_display_roundtrip(_this->driverdata->display); + } + + /* Flushing old events pending a new one, ignore this request. */ + if (wind->drop_fullscreen_requests) { + return 0; + } + + wind->drop_fullscreen_requests = SDL_TRUE; + FlushFullscreenEvents(window); + wind->drop_fullscreen_requests = SDL_FALSE; /* Don't send redundant fullscreen set/unset events. */ - if (wind->is_fullscreen != fullscreen) { - wind->fullscreen_was_positioned = fullscreen ? SDL_TRUE : SDL_FALSE; + if (fullscreen != wind->is_fullscreen) { + wind->fullscreen_was_positioned = fullscreen; SetFullscreen(window, fullscreen ? output : NULL); } else if (wind->is_fullscreen) { /* @@ -1908,40 +1889,34 @@ void Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, wind->fullscreen_was_positioned = SDL_TRUE; SetFullscreen(window, output); } else { - ConfigureWindowGeometry(window); - CommitLibdecorFrame(window); + /* Queue a configure event */ + struct wl_callback *cb = wl_display_sync(_this->driverdata->display); + wl_callback_add_listener(cb, &fullscreen_configure_listener, (void *)((uintptr_t)window->id)); } } - wind->in_fullscreen_transition = SDL_FALSE; + return 1; } void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_VideoData *viddata = _this->driverdata; SDL_WindowData *wind = window->driverdata; - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - return; - } - #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame); } else #endif /* Note that xdg-shell does NOT provide a way to unset minimize! */ - if (viddata->shell.xdg) { + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { return; /* Can't do anything yet, wait for ShowWindow */ } xdg_toplevel_unset_maximized(wind->shell_surface.xdg.roleobj.toplevel); } - - WAYLAND_wl_display_roundtrip(viddata->display); } void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered) @@ -1949,10 +1924,6 @@ void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_b SDL_WindowData *wind = window->driverdata; const SDL_VideoData *viddata = (const SDL_VideoData *)_this->driverdata; - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - return; - } - #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { if (wind->shell_surface.libdecor.frame) { @@ -1960,9 +1931,11 @@ void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_b } } else #endif + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if ((viddata->decoration_manager) && (wind->server_decoration)) { - const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; - zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode); + const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode); + } } } @@ -1972,7 +1945,7 @@ void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_ const SDL_WindowData *wind = window->driverdata; if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) { @@ -1994,67 +1967,48 @@ void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_ void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_VideoData *viddata = _this->driverdata; SDL_WindowData *wind = window->driverdata; - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - return; - } - - if (!(window->flags & SDL_WINDOW_RESIZABLE)) { - return; + if (wind->show_hide_sync_required) { + WAYLAND_wl_display_roundtrip(_this->driverdata->display); } #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame); } else #endif - if (viddata->shell.xdg) { + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { return; /* Can't do anything yet, wait for ShowWindow */ } xdg_toplevel_set_maximized(wind->shell_surface.xdg.roleobj.toplevel); } - - /* Don't roundtrip if this is being called to set the initial state during window creation. */ - if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME || - wind->surface_status == WAYLAND_SURFACE_STATUS_SHOWN) { - WAYLAND_wl_display_roundtrip(viddata->display); - } } void Wayland_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_VideoData *viddata = _this->driverdata; SDL_WindowData *wind = window->driverdata; - /* Maximized and minimized flags are mutually exclusive */ - window->flags &= ~SDL_WINDOW_MAXIMIZED; - window->flags |= SDL_WINDOW_MINIMIZED; - + /* TODO: Check compositor capabilities to see if minimizing is supported */ #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { + if (!wind->shell_surface.libdecor.frame) { return; /* Can't do anything yet, wait for ShowWindow */ } libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); } else #endif - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && viddata->shell.xdg) { + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { return; /* Can't do anything yet, wait for ShowWindow */ } xdg_toplevel_set_minimized(wind->shell_surface.xdg.roleobj.toplevel); - } - - /* Don't roundtrip if this is being called to set the initial state during window creation. */ - if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME || - wind->surface_status == WAYLAND_SURFACE_STATUS_SHOWN) { - WAYLAND_wl_display_roundtrip(viddata->display); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); } } @@ -2099,14 +2053,20 @@ void Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, S } } -int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *data; SDL_VideoData *c; + struct wl_surface *external_surface = (struct wl_surface *)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, + (struct wl_surface *)SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL)); + const SDL_bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_FALSE); + const SDL_bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) || + SDL_GetBooleanProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, SDL_FALSE); + data = SDL_calloc(1, sizeof(*data)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } c = _this->driverdata; @@ -2120,7 +2080,7 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } if (SDL_WINDOW_IS_POPUP(window)) { - EnsurePopupPositionIsValid(window); + EnsurePopupPositionIsValid(window, &window->x, &window->y); } data->waylandData = c; @@ -2144,14 +2104,21 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) data->requested_window_width = window->w; data->requested_window_height = window->h; - data->floating_width = window->windowed.w; - data->floating_height = window->windowed.h; - data->surface = - wl_compositor_create_surface(c->compositor); - wl_surface_add_listener(data->surface, &surface_listener, data); + if (!external_surface) { + data->surface = wl_compositor_create_surface(c->compositor); + wl_surface_add_listener(data->surface, &surface_listener, data); + wl_surface_set_user_data(data->surface, data); + SDL_WAYLAND_register_surface(data->surface); + } else { + window->flags |= SDL_WINDOW_EXTERNAL; + data->surface = external_surface; - SDL_WAYLAND_register_surface(data->surface); + /* External surfaces are registered by being put in a list, as changing tags or userdata + * can cause problems with external toolkits. + */ + Wayland_AddWindowDataToExternalList(data); + } /* Must be called before EGL configuration to set the drawable backbuffer size. */ ConfigureWindowGeometry(window); @@ -2169,19 +2136,12 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); } - /* Fire a callback when the compositor wants a new frame to set the surface damage region. */ - data->surface_frame_callback = wl_surface_frame(data->surface); - wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data); - -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - if (c->surface_extension) { - data->extended_surface = qt_surface_extension_get_extended_surface( - c->surface_extension, data->surface); - - QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION); - QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS); + /* No frame callback on external surfaces as it may already have one attached. */ + if (!external_surface) { + /* Fire a callback when the compositor wants a new frame to set the surface damage region. */ + data->surface_frame_callback = wl_surface_frame(data->surface); + wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data); } -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ if (window->flags & SDL_WINDOW_TRANSPARENT) { if (_this->gl_config.alpha_size == 0) { @@ -2189,55 +2149,65 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - if (window->flags & SDL_WINDOW_OPENGL) { + if (create_egl_window) { data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->drawable_width, data->drawable_height); + } #ifdef SDL_VIDEO_OPENGL_EGL + if (window->flags & SDL_WINDOW_OPENGL) { /* 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 -1; /* SDL_EGL_CreateSurface should have set error */ } + } #endif - } - -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - if (data->extended_surface) { - qt_extended_surface_set_user_data(data->extended_surface, data); - qt_extended_surface_add_listener(data->extended_surface, - &extended_surface_listener, data); - } -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ if (c->relative_mouse_mode) { Wayland_input_lock_pointer(c->input); } - if (c->fractional_scale_manager) { + /* Don't attach a fractional scale manager to surfaces unless they are + * flagged as DPI-aware. Under non-scaled operation, the scale will + * always be 1.0, and external/custom surfaces may already have, or + * will try to attach, their own fractional scale manager, which will + * result in a protocol violation. + */ + if (c->fractional_scale_manager && (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface); wp_fractional_scale_v1_add_listener(data->fractional_scale, &fractional_scale_listener, data); } - /* Moved this call to ShowWindow: wl_surface_commit(data->surface); */ - WAYLAND_wl_display_flush(c->display); - /* We may need to create an idle inhibitor for this new window */ Wayland_SuspendScreenSaver(_this); + if (!custom_surface_role) { #ifdef HAVE_LIBDECOR_H - if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) { - data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR; - } else + if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) { + data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR; + } else #endif - if (c->shell.xdg) { - if (SDL_WINDOW_IS_POPUP(window)) { - data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP; - } else { - data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL; - } - } /* All other cases will be WAYLAND_SURFACE_UNKNOWN */ + if (c->shell.xdg) { + if (SDL_WINDOW_IS_POPUP(window)) { + data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP; + } else { + data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL; + } + } /* All other cases will be WAYLAND_SURFACE_UNKNOWN */ + } else { + /* Roleless and external surfaces are always considered to be in the shown state by the backend. */ + data->shell_surface_type = WAYLAND_SURFACE_CUSTOM; + data->surface_status = WAYLAND_SURFACE_STATUS_SHOWN; + } + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER, data->waylandData->display); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER, data->surface); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window); + + data->hit_test_result = SDL_HITTEST_NORMAL; return 0; } @@ -2265,35 +2235,93 @@ int Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) return SDL_Unsupported(); } - RepositionPopup(window); + RepositionPopup(window, SDL_FALSE); return 0; + } else if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR || wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { + const int x = window->floating.x; + const int y = window->floating.y; + + /* Catch up on any pending state before attempting to change the fullscreen window + * display via a set fullscreen call to make sure the window doesn't have a pending + * leave fullscreen event that it might override. + */ + FlushFullscreenEvents(window); + + /* XXX: Need to restore this after the roundtrip, as the requested coordinates might + * have been overwritten by the 'real' coordinates if a display enter/leave event + * occurred. + * + * The common pattern: + * + * SDL_SetWindowPosition(); + * SDL_SetWindowFullscreen(); + * + * for positioning a desktop fullscreen window won't work without this. + */ + window->floating.x = x; + window->floating.y = y; + + if (wind->is_fullscreen) { + SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window); + if (display && wind->last_displayID != display->id) { + struct wl_output *output = display->driverdata->output; + SetFullscreen(window, output); + + return 0; + } + } } return SDL_SetError("wayland cannot position non-popup windows"); } +static void size_event_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + /* Get the window from the ID as it may have been destroyed */ + SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); + SDL_Window *window = SDL_GetWindowFromID(windowID); + + if (window && window->driverdata) { + SDL_WindowData *wind = window->driverdata; + + /* Fullscreen windows do not get explicitly resized, and not strictly + * obeying the size of maximized windows is a protocol violation. + */ + if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) { + wind->requested_window_width = wind->pending_size_event.width; + wind->requested_window_height = wind->pending_size_event.height; + + ConfigureWindowGeometry(window); + } + + /* Always commit, as this may be in response to a min/max limit change. */ + CommitLibdecorFrame(window); + } + + wl_callback_destroy(callback); +} + +static struct wl_callback_listener size_event_listener = { + size_event_handler +}; + void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->driverdata; - /* - * Unconditionally store the floating size, as it will need - * to be applied when returning from a non-floating state. - */ - wind->floating_width = window->windowed.w; - wind->floating_height = window->windowed.h; + if (wind->shell_surface_type != WAYLAND_SURFACE_CUSTOM) { + /* Queue an event to send the window size. */ + struct wl_callback *cb = wl_display_sync(_this->driverdata->display); - /* Fullscreen windows do not get explicitly resized, and not strictly - * obeying the size of maximized windows is a protocol violation. - */ - if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) { - wind->requested_window_width = window->windowed.w; - wind->requested_window_height = window->windowed.h; + wind->pending_size_event.width = window->floating.w; + wind->pending_size_event.height = window->floating.h; + wl_callback_add_listener(cb, &size_event_listener, (void *)((uintptr_t)window->id)); + } else { + /* We are being informed of a size change on a custom surface, just configure. */ + wind->requested_window_width = window->floating.w; + wind->requested_window_height = window->floating.h; ConfigureWindowGeometry(window); } - - /* Always commit, as this may be in response to a min/max limit change. */ - CommitLibdecorFrame(window); } void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) @@ -2309,29 +2337,22 @@ void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, i void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->driverdata; - SDL_VideoData *viddata = _this->driverdata; const char *title = window->title ? window->title : ""; - if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { - return; - } - #ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame == NULL) { - return; /* Can't do anything yet, wait for ShowWindow */ - } + if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) { libdecor_frame_set_title(wind->shell_surface.libdecor.frame, title); } else #endif - if (viddata->shell.xdg) { - if (wind->shell_surface.xdg.roleobj.toplevel == NULL) { - return; /* Can't do anything yet, wait for ShowWindow */ - } + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && wind->shell_surface.xdg.roleobj.toplevel) { xdg_toplevel_set_title(wind->shell_surface.xdg.roleobj.toplevel, title); } +} - WAYLAND_wl_display_flush(viddata->display); +int Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + WAYLAND_wl_display_roundtrip(_this->driverdata->display); + return 0; } void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) @@ -2434,14 +2455,11 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) wl_callback_destroy(wind->surface_frame_callback); } -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - if (wind->extended_surface) { - QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION); - QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS); - qt_extended_surface_destroy(wind->extended_surface); + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { + wl_surface_destroy(wind->surface); + } else { + Wayland_RemoveWindowDataFromExternalList(wind); } -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - wl_surface_destroy(wind->surface); SDL_free(wind); WAYLAND_wl_display_flush(data->display); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index dc13561a..0925ac6d 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,8 +27,6 @@ #include "../SDL_sysvideo.h" #include "../../events/SDL_touch_c.h" -#include - #include "SDL_waylandvideo.h" struct SDL_WaylandInput; @@ -72,7 +70,8 @@ struct SDL_WindowData WAYLAND_SURFACE_UNKNOWN = 0, WAYLAND_SURFACE_XDG_TOPLEVEL, WAYLAND_SURFACE_XDG_POPUP, - WAYLAND_SURFACE_LIBDECOR + WAYLAND_SURFACE_LIBDECOR, + WAYLAND_SURFACE_CUSTOM } shell_surface_type; enum { @@ -97,15 +96,8 @@ struct SDL_WindowData struct wp_viewport *draw_viewport; struct wp_fractional_scale_v1 *fractional_scale; - /* floating dimensions for restoring from maximized and fullscreen */ - int floating_width, floating_height; - SDL_AtomicInt swap_interval_ready; -#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH - struct qt_extended_surface *extended_surface; -#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - SDL_DisplayData **outputs; int num_outputs; @@ -115,24 +107,37 @@ struct SDL_WindowData float windowed_scale_factor; float pointer_scale_x; float pointer_scale_y; + + struct + { + int width, height; + } pending_size_event; + + int last_configure_width, last_configure_height; int requested_window_width, requested_window_height; int drawable_width, drawable_height; int wl_window_width, wl_window_height; int system_min_required_width; int system_min_required_height; SDL_DisplayID last_displayID; + int fullscreen_deadline_count; SDL_bool floating; SDL_bool suspended; SDL_bool active; SDL_bool is_fullscreen; - SDL_bool in_fullscreen_transition; + SDL_bool drop_fullscreen_requests; SDL_bool fullscreen_was_positioned; + SDL_bool show_hide_sync_required; + + SDL_HitTestResult hit_test_result; + + struct wl_list external_window_list_link; }; extern void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern void Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, +extern int Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen); extern void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window); @@ -143,7 +148,7 @@ extern void Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *wi extern void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); extern void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); -extern int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern int Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window); @@ -155,8 +160,8 @@ extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int Wayland_SuspendScreenSaver(SDL_VideoDevice *_this); -extern int Wayland_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info); extern int Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern int Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); +extern int Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); #endif /* SDL_waylandwindow_h_ */ diff --git a/src/video/windows/SDL_msctf.h b/src/video/windows/SDL_msctf.h index 5f6829a0..738696f0 100644 --- a/src/video/windows/SDL_msctf.h +++ b/src/video/windows/SDL_msctf.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_vkeys.h b/src/video/windows/SDL_vkeys.h deleted file mode 100644 index b15a39f0..00000000 --- a/src/video/windows/SDL_vkeys.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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 VK_0 -#define VK_0 '0' -#define VK_1 '1' -#define VK_2 '2' -#define VK_3 '3' -#define VK_4 '4' -#define VK_5 '5' -#define VK_6 '6' -#define VK_7 '7' -#define VK_8 '8' -#define VK_9 '9' -#define VK_A 'A' -#define VK_B 'B' -#define VK_C 'C' -#define VK_D 'D' -#define VK_E 'E' -#define VK_F 'F' -#define VK_G 'G' -#define VK_H 'H' -#define VK_I 'I' -#define VK_J 'J' -#define VK_K 'K' -#define VK_L 'L' -#define VK_M 'M' -#define VK_N 'N' -#define VK_O 'O' -#define VK_P 'P' -#define VK_Q 'Q' -#define VK_R 'R' -#define VK_S 'S' -#define VK_T 'T' -#define VK_U 'U' -#define VK_V 'V' -#define VK_W 'W' -#define VK_X 'X' -#define VK_Y 'Y' -#define VK_Z 'Z' -#endif /* VK_0 */ - -/* These keys haven't been defined, but were experimentally determined */ -#define VK_SEMICOLON 0xBA -#define VK_EQUALS 0xBB -#define VK_COMMA 0xBC -#define VK_MINUS 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_GRAVE 0xC0 -#define VK_LBRACKET 0xDB -#define VK_BACKSLASH 0xDC -#define VK_RBRACKET 0xDD -#define VK_APOSTROPHE 0xDE -#define VK_BACKTICK 0xDF -#define VK_OEM_102 0xE2 diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c index fabb3d6b..b9c788af 100644 --- a/src/video/windows/SDL_windowsclipboard.c +++ b/src/video/windows/SDL_windowsclipboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,12 +35,11 @@ #define IMAGE_FORMAT CF_DIB #define IMAGE_MIME_TYPE "image/bmp" +#define BFT_BITMAP 0x4d42 /* 'BM' */ /* Assume we can directly read and write BMP fields without byte swapping */ SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN); -static const char bmp_magic[2] = { 'B', 'M' }; - static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this) { /* Retry to open the clipboard in case another application has it open */ @@ -69,7 +68,7 @@ static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size) { HANDLE hMem = NULL; - if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && SDL_memcmp(bmp, bmp_magic, sizeof(bmp_magic)) == 0) { + if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && ((BITMAPFILEHEADER *)bmp)->bfType == BFT_BITMAP) { BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)((Uint8 *)bmp + sizeof(BITMAPFILEHEADER)); size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD); @@ -119,15 +118,13 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size) bmp = SDL_malloc(bmp_size); if (bmp) { BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; - pbfh->bfType = 0x4d42; /* bmp_magic */ + pbfh->bfType = BFT_BITMAP; pbfh->bfSize = (DWORD)bmp_size; pbfh->bfReserved1 = 0; pbfh->bfReserved2 = 0; pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + bih_size); SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size); *size = bmp_size; - } else { - SDL_OutOfMemory(); } } else { SDL_SetError("Invalid BMP data"); @@ -288,7 +285,7 @@ void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t WIN_CloseClipboard(); } } - if (text == NULL) { + if (!text) { text = SDL_strdup(""); } data = text; diff --git a/src/video/windows/SDL_windowsclipboard.h b/src/video/windows/SDL_windowsclipboard.h index 17039806..5606ef86 100644 --- a/src/video/windows/SDL_windowsclipboard.h +++ b/src/video/windows/SDL_windowsclipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 5ab0e68f..179a7ad6 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,13 +23,10 @@ #ifdef SDL_VIDEO_DRIVER_WINDOWS #include "SDL_windowsvideo.h" -#include "SDL_windowsshape.h" -#include "SDL_vkeys.h" #include "../../events/SDL_events_c.h" #include "../../events/SDL_touch_c.h" #include "../../events/scancodes_windows.h" - -#include +#include "../../main/SDL_main_callbacks.h" /* Dropfile support */ #include @@ -54,15 +51,6 @@ /* #define HIGHDPI_DEBUG */ -/* Masks for processing the windows KEYDOWN and KEYUP messages */ -#define REPEATED_KEYMASK (1 << 30) -#define EXTENDED_KEYMASK (1 << 24) - -#define VK_ENTER 10 /* Keypad Enter ... no VKEY defined? */ -#ifndef VK_OEM_NEC_EQUAL -#define VK_OEM_NEC_EQUAL 0x92 -#endif - /* Make sure XBUTTON stuff is defined that isn't in older Platform SDKs... */ #ifndef WM_XBUTTONDOWN #define WM_XBUTTONDOWN 0x020B @@ -98,19 +86,29 @@ #define TOUCHEVENTF_PEN 0x0040 #endif +#ifndef MAPVK_VK_TO_VSC_EX +#define MAPVK_VK_TO_VSC_EX 4 +#endif + +#ifndef WC_ERR_INVALID_CHARS +#define WC_ERR_INVALID_CHARS 0x00000080 +#endif + #ifndef IS_HIGH_SURROGATE #define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff)) #endif -#ifndef IS_LOW_SURROGATE -#define IS_LOW_SURROGATE(x) (((x) >= 0xdc00) && ((x) <= 0xdfff)) -#endif -#ifndef IS_SURROGATE_PAIR -#define IS_SURROGATE_PAIR(h, l) (IS_HIGH_SURROGATE(h) && IS_LOW_SURROGATE(l)) + +#ifndef USER_TIMER_MINIMUM +#define USER_TIMER_MINIMUM 0x0000000A #endif /* Used to compare Windows message timestamps */ #define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) +#ifdef _WIN64 +typedef Uint64 QWORD; // Needed for NEXTRAWINPUTBLOCK() +#endif + static SDL_bool SDL_processing_messages; static DWORD message_tick; static Uint64 timestamp_offset; @@ -150,202 +148,43 @@ static Uint64 WIN_GetEventTimestamp() return timestamp; } -static SDL_Scancode VKeytoScancodeFallback(WPARAM vkey) -{ - switch (vkey) { - case VK_LEFT: - return SDL_SCANCODE_LEFT; - case VK_UP: - return SDL_SCANCODE_UP; - case VK_RIGHT: - return SDL_SCANCODE_RIGHT; - case VK_DOWN: - return SDL_SCANCODE_DOWN; - - default: - return SDL_SCANCODE_UNKNOWN; - } -} - -static SDL_Scancode VKeytoScancode(WPARAM vkey) -{ - switch (vkey) { - case VK_MODECHANGE: - return SDL_SCANCODE_MODE; - case VK_SELECT: - return SDL_SCANCODE_SELECT; - case VK_EXECUTE: - return SDL_SCANCODE_EXECUTE; - case VK_HELP: - return SDL_SCANCODE_HELP; - case VK_PAUSE: - return SDL_SCANCODE_PAUSE; - case VK_NUMLOCK: - return SDL_SCANCODE_NUMLOCKCLEAR; - - case VK_F13: - return SDL_SCANCODE_F13; - case VK_F14: - return SDL_SCANCODE_F14; - case VK_F15: - return SDL_SCANCODE_F15; - case VK_F16: - return SDL_SCANCODE_F16; - case VK_F17: - return SDL_SCANCODE_F17; - case VK_F18: - return SDL_SCANCODE_F18; - case VK_F19: - return SDL_SCANCODE_F19; - case VK_F20: - return SDL_SCANCODE_F20; - case VK_F21: - return SDL_SCANCODE_F21; - case VK_F22: - return SDL_SCANCODE_F22; - case VK_F23: - return SDL_SCANCODE_F23; - case VK_F24: - return SDL_SCANCODE_F24; - - case VK_OEM_NEC_EQUAL: - return SDL_SCANCODE_KP_EQUALS; - case VK_BROWSER_BACK: - return SDL_SCANCODE_AC_BACK; - case VK_BROWSER_FORWARD: - return SDL_SCANCODE_AC_FORWARD; - case VK_BROWSER_REFRESH: - return SDL_SCANCODE_AC_REFRESH; - case VK_BROWSER_STOP: - return SDL_SCANCODE_AC_STOP; - case VK_BROWSER_SEARCH: - return SDL_SCANCODE_AC_SEARCH; - case VK_BROWSER_FAVORITES: - return SDL_SCANCODE_AC_BOOKMARKS; - case VK_BROWSER_HOME: - return SDL_SCANCODE_AC_HOME; - case VK_VOLUME_MUTE: - return SDL_SCANCODE_AUDIOMUTE; - case VK_VOLUME_DOWN: - return SDL_SCANCODE_VOLUMEDOWN; - case VK_VOLUME_UP: - return SDL_SCANCODE_VOLUMEUP; - - case VK_MEDIA_NEXT_TRACK: - return SDL_SCANCODE_AUDIONEXT; - case VK_MEDIA_PREV_TRACK: - return SDL_SCANCODE_AUDIOPREV; - case VK_MEDIA_STOP: - return SDL_SCANCODE_AUDIOSTOP; - case VK_MEDIA_PLAY_PAUSE: - return SDL_SCANCODE_AUDIOPLAY; - case VK_LAUNCH_MAIL: - return SDL_SCANCODE_MAIL; - case VK_LAUNCH_MEDIA_SELECT: - return SDL_SCANCODE_MEDIASELECT; - - case VK_OEM_102: - return SDL_SCANCODE_NONUSBACKSLASH; - - case VK_ATTN: - return SDL_SCANCODE_SYSREQ; - case VK_CRSEL: - return SDL_SCANCODE_CRSEL; - case VK_EXSEL: - return SDL_SCANCODE_EXSEL; - case VK_OEM_CLEAR: - return SDL_SCANCODE_CLEAR; - - case VK_LAUNCH_APP1: - return SDL_SCANCODE_APP1; - case VK_LAUNCH_APP2: - return SDL_SCANCODE_APP2; - - default: - return SDL_SCANCODE_UNKNOWN; - } -} - static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam) { SDL_Scancode code; - int nScanCode = (lParam >> 16) & 0xFF; - SDL_bool bIsExtended = (lParam & (1 << 24)) != 0; + Uint8 index; + Uint16 keyFlags = HIWORD(lParam); + Uint16 scanCode = LOBYTE(keyFlags); - code = VKeytoScancode(wParam); + /* On-Screen Keyboard can send wrong scan codes with high-order bit set (key break code). + * Strip high-order bit. */ + scanCode &= ~0x80; - if (code == SDL_SCANCODE_UNKNOWN && nScanCode <= 127) { - code = windows_scancode_table[nScanCode]; + if (scanCode != 0) { + if ((keyFlags & KF_EXTENDED) == KF_EXTENDED) { + scanCode = MAKEWORD(scanCode, 0xe0); + } else if (scanCode == 0x45) { + /* Pause */ + scanCode = 0xe046; + } + } else { + Uint16 vkCode = LOWORD(wParam); - if (bIsExtended) { - switch (code) { - case SDL_SCANCODE_RETURN: - code = SDL_SCANCODE_KP_ENTER; - break; - case SDL_SCANCODE_LALT: - code = SDL_SCANCODE_RALT; - break; - case SDL_SCANCODE_LCTRL: - code = SDL_SCANCODE_RCTRL; - break; - case SDL_SCANCODE_SLASH: - code = SDL_SCANCODE_KP_DIVIDE; - break; - case SDL_SCANCODE_CAPSLOCK: - code = SDL_SCANCODE_KP_PLUS; - break; - default: - break; - } - } else { - switch (code) { - case SDL_SCANCODE_HOME: - code = SDL_SCANCODE_KP_7; - break; - case SDL_SCANCODE_UP: - code = SDL_SCANCODE_KP_8; - break; - case SDL_SCANCODE_PAGEUP: - code = SDL_SCANCODE_KP_9; - break; - case SDL_SCANCODE_LEFT: - code = SDL_SCANCODE_KP_4; - break; - case SDL_SCANCODE_RIGHT: - code = SDL_SCANCODE_KP_6; - break; - case SDL_SCANCODE_END: - code = SDL_SCANCODE_KP_1; - break; - case SDL_SCANCODE_DOWN: - code = SDL_SCANCODE_KP_2; - break; - case SDL_SCANCODE_PAGEDOWN: - code = SDL_SCANCODE_KP_3; - break; - case SDL_SCANCODE_INSERT: - code = SDL_SCANCODE_KP_0; - break; - case SDL_SCANCODE_DELETE: - code = SDL_SCANCODE_KP_PERIOD; - break; - case SDL_SCANCODE_PRINTSCREEN: - code = SDL_SCANCODE_KP_MULTIPLY; - break; - default: - break; - } +#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) + /* Windows may not report scan codes for some buttons (multimedia buttons etc). + * Get scan code from the VK code.*/ + scanCode = LOWORD(MapVirtualKey(vkCode, MAPVK_VK_TO_VSC_EX)); +#endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ + + /* Pause/Break key have a special scan code with 0xe1 prefix. + * Use Pause scan code that is used in Win32. */ + if (scanCode == 0xe11d) { + scanCode = 0xe046; } } - /* The on-screen keyboard can generate VK_LEFT and VK_RIGHT events without a scancode - * value set, however we cannot simply map these in VKeytoScancode() or we will be - * incorrectly handling the arrow keys on the number pad when NumLock is disabled - * (which also generate VK_LEFT, VK_RIGHT, etc in that scenario). Instead, we'll only - * map them if none of the above special number pad mappings applied. */ - if (code == SDL_SCANCODE_UNKNOWN) { - code = VKeytoScancodeFallback(wParam); - } + /* Pack scan code into one byte to make the index. */ + index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00); + code = windows_scancode_table[index]; return code; } @@ -357,7 +196,7 @@ static SDL_bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data) !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); } -static void WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mouseFlags, SDL_bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID) +static void WIN_CheckWParamMouseButton(Uint64 timestamp, SDL_bool bwParamMousePressed, Uint32 mouseFlags, SDL_bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID) { if (bSwapButtons) { if (button == SDL_BUTTON_LEFT) { @@ -379,9 +218,9 @@ static void WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mous } if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON(button))) { - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, SDL_PRESSED, button); + SDL_SendMouseButton(timestamp, data->window, mouseID, SDL_PRESSED, button); } else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON(button))) { - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, SDL_RELEASED, button); + SDL_SendMouseButton(timestamp, data->window, mouseID, SDL_RELEASED, button); } } @@ -389,23 +228,23 @@ static void WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mous * Some windows systems fail to send a WM_LBUTTONDOWN sometimes, but each mouse move contains the current button state also * so this function reconciles our view of the world with the current buttons reported by windows */ -static void WIN_CheckWParamMouseButtons(WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID) +static void WIN_CheckWParamMouseButtons(Uint64 timestamp, WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID) { if (wParam != data->mouse_button_flags) { Uint32 mouseFlags = SDL_GetMouseState(NULL, NULL); /* WM_LBUTTONDOWN and friends handle button swapping for us. No need to check SM_SWAPBUTTON here. */ - WIN_CheckWParamMouseButton((wParam & MK_LBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_LEFT, mouseID); - WIN_CheckWParamMouseButton((wParam & MK_MBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_MIDDLE, mouseID); - WIN_CheckWParamMouseButton((wParam & MK_RBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_RIGHT, mouseID); - WIN_CheckWParamMouseButton((wParam & MK_XBUTTON1), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X1, mouseID); - WIN_CheckWParamMouseButton((wParam & MK_XBUTTON2), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X2, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_LBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_LEFT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_MBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_MIDDLE, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_RBUTTON), mouseFlags, SDL_FALSE, data, SDL_BUTTON_RIGHT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON1), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X1, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON2), mouseFlags, SDL_FALSE, data, SDL_BUTTON_X2, mouseID); data->mouse_button_flags = wParam; } } -static void WIN_CheckRawMouseButtons(ULONG rawButtons, SDL_WindowData *data, SDL_MouseID mouseID) +static void WIN_CheckRawMouseButtons(Uint64 timestamp, HANDLE hDevice, ULONG rawButtons, SDL_WindowData *data, SDL_MouseID mouseID) { // Add a flag to distinguish raw mouse buttons from wParam above rawButtons |= 0x8000000; @@ -413,41 +252,45 @@ static void WIN_CheckRawMouseButtons(ULONG rawButtons, SDL_WindowData *data, SDL if (rawButtons != data->mouse_button_flags) { Uint32 mouseFlags = SDL_GetMouseState(NULL, NULL); SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + if (swapButtons && hDevice == NULL) { + /* Touchpad, already has buttons swapped */ + swapButtons = SDL_FALSE; + } if (rawButtons & RI_MOUSE_BUTTON_1_DOWN) { - WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_1_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (rawButtons & RI_MOUSE_BUTTON_1_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_1_UP) { - WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_1_UP), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID); + WIN_CheckWParamMouseButton(timestamp, !(rawButtons & RI_MOUSE_BUTTON_1_UP), mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_2_DOWN) { - WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_2_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (rawButtons & RI_MOUSE_BUTTON_2_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_2_UP) { - WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_2_UP), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID); + WIN_CheckWParamMouseButton(timestamp, !(rawButtons & RI_MOUSE_BUTTON_2_UP), mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_3_DOWN) { - WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_3_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID); + WIN_CheckWParamMouseButton(timestamp, (rawButtons & RI_MOUSE_BUTTON_3_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_3_UP) { - WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_3_UP), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID); + WIN_CheckWParamMouseButton(timestamp, !(rawButtons & RI_MOUSE_BUTTON_3_UP), mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_4_DOWN) { - WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_4_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID); + WIN_CheckWParamMouseButton(timestamp, (rawButtons & RI_MOUSE_BUTTON_4_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_4_UP) { - WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_4_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID); + WIN_CheckWParamMouseButton(timestamp, !(rawButtons & RI_MOUSE_BUTTON_4_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_5_DOWN) { - WIN_CheckWParamMouseButton((rawButtons & RI_MOUSE_BUTTON_5_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID); + WIN_CheckWParamMouseButton(timestamp, (rawButtons & RI_MOUSE_BUTTON_5_DOWN), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID); } if (rawButtons & RI_MOUSE_BUTTON_5_UP) { - WIN_CheckWParamMouseButton(!(rawButtons & RI_MOUSE_BUTTON_5_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID); + WIN_CheckWParamMouseButton(timestamp, !(rawButtons & RI_MOUSE_BUTTON_5_UP), mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID); } data->mouse_button_flags = rawButtons; } } -static void WIN_CheckAsyncMouseRelease(SDL_WindowData *data) +static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data) { Uint32 mouseFlags; SHORT keyState; @@ -461,23 +304,23 @@ static void WIN_CheckAsyncMouseRelease(SDL_WindowData *data) keyState = GetAsyncKeyState(VK_LBUTTON); if (!(keyState & 0x8000)) { - WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, 0); + WIN_CheckWParamMouseButton(timestamp, SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, 0); } keyState = GetAsyncKeyState(VK_RBUTTON); if (!(keyState & 0x8000)) { - WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, 0); + WIN_CheckWParamMouseButton(timestamp, SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, 0); } keyState = GetAsyncKeyState(VK_MBUTTON); if (!(keyState & 0x8000)) { - WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, 0); + WIN_CheckWParamMouseButton(timestamp, SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, 0); } keyState = GetAsyncKeyState(VK_XBUTTON1); if (!(keyState & 0x8000)) { - WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X1, 0); + WIN_CheckWParamMouseButton(timestamp, SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X1, 0); } keyState = GetAsyncKeyState(VK_XBUTTON2); if (!(keyState & 0x8000)) { - WIN_CheckWParamMouseButton(SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X2, 0); + WIN_CheckWParamMouseButton(timestamp, SDL_FALSE, mouseFlags, swapButtons, data, SDL_BUTTON_X2, 0); } data->mouse_button_flags = (WPARAM)-1; } @@ -486,8 +329,8 @@ static void WIN_UpdateFocus(SDL_Window *window, SDL_bool expect_focus) { SDL_WindowData *data = window->driverdata; HWND hwnd = data->hwnd; - SDL_bool had_focus = (SDL_GetKeyboardFocus() == window) ? SDL_TRUE : SDL_FALSE; - SDL_bool has_focus = (GetForegroundWindow() == hwnd) ? SDL_TRUE : SDL_FALSE; + SDL_bool had_focus = (SDL_GetKeyboardFocus() == window); + SDL_bool has_focus = (GetForegroundWindow() == hwnd); if (had_focus == has_focus || has_focus != expect_focus) { return; @@ -522,7 +365,7 @@ static void WIN_UpdateFocus(SDL_Window *window, SDL_bool expect_focus) SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, 0, 0, (float)cursorPos.x, (float)cursorPos.y); } - WIN_CheckAsyncMouseRelease(data); + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); WIN_UpdateClipCursor(window); /* @@ -557,39 +400,6 @@ static void WIN_UpdateFocus(SDL_Window *window, SDL_bool expect_focus) } #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ -static BOOL WIN_ConvertUTF32toUTF8(UINT32 codepoint, char *text) -{ - if (codepoint <= 0x7F) { - text[0] = (char)codepoint; - text[1] = '\0'; - } else if (codepoint <= 0x7FF) { - text[0] = 0xC0 | (char)((codepoint >> 6) & 0x1F); - text[1] = 0x80 | (char)(codepoint & 0x3F); - text[2] = '\0'; - } else if (codepoint <= 0xFFFF) { - text[0] = 0xE0 | (char)((codepoint >> 12) & 0x0F); - text[1] = 0x80 | (char)((codepoint >> 6) & 0x3F); - text[2] = 0x80 | (char)(codepoint & 0x3F); - text[3] = '\0'; - } else if (codepoint <= 0x10FFFF) { - text[0] = 0xF0 | (char)((codepoint >> 18) & 0x0F); - text[1] = 0x80 | (char)((codepoint >> 12) & 0x3F); - text[2] = 0x80 | (char)((codepoint >> 6) & 0x3F); - text[3] = 0x80 | (char)(codepoint & 0x3F); - text[4] = '\0'; - } else { - return SDL_FALSE; - } - return SDL_TRUE; -} - -static BOOL WIN_ConvertUTF16toUTF8(UINT32 high_surrogate, UINT32 low_surrogate, char *text) -{ - const UINT32 SURROGATE_OFFSET = 0x10000U - (0xD800 << 10) - 0xDC00; - const UINT32 codepoint = (high_surrogate << 10) + low_surrogate + SURROGATE_OFFSET; - return WIN_ConvertUTF32toUTF8(codepoint, text); -} - static SDL_bool ShouldGenerateWindowCloseOnAltF4(void) { return !SDL_GetHintBoolean(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, SDL_FALSE); @@ -609,12 +419,11 @@ typedef enum SDL_MOUSE_EVENT_SOURCE_PEN, } SDL_MOUSE_EVENT_SOURCE; -static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource() +static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo) { - LPARAM extrainfo = GetMessageExtraInfo(); /* Mouse data (ignoring synthetic mouse events generated for touchscreens) */ /* Versions below Vista will set the low 7 bits to the Mouse ID and don't use bit 7: - Check bits 8-32 for the signature (which will indicate a Tablet PC Pen or Touch Device). + Check bits 8-31 for the signature (which will indicate a Tablet PC Pen or Touch Device). Only check bit 7 when Vista and up(Cleared=Pen, Set=Touch(which we need to filter out)), when the signature is set. The Mouse ID will be zero for an actual mouse. */ if (IsTouchEvent(extrainfo)) { @@ -624,6 +433,11 @@ static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource() return SDL_MOUSE_EVENT_SOURCE_PEN; } } + /* Sometimes WM_INPUT events won't have the correct touch signature, + so we have to rely purely on the touch bit being set. */ + if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) { + return SDL_MOUSE_EVENT_SOURCE_TOUCH; + } return SDL_MOUSE_EVENT_SOURCE_MOUSE; } #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ @@ -707,6 +521,181 @@ WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) return 1; } +static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_WindowData *data, HANDLE hDevice, RAWMOUSE *rawmouse) +{ + SDL_MouseID mouseID; + + if (GetMouseMessageSource(rawmouse->ulExtraInformation) == SDL_MOUSE_EVENT_SOURCE_TOUCH) { + return; + } + + /* We do all of our mouse state checking against mouse ID 0 + * We would only use the actual hDevice if we were tracking + * all mouse motion independently, and never using mouse ID 0. + */ + mouseID = 0; /* (SDL_MouseID)(uintptr_t)inp.header.hDevice; */ + + if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) { + if (rawmouse->lLastX || rawmouse->lLastY) { + SDL_SendMouseMotion(timestamp, data->window, mouseID, 1, (float)rawmouse->lLastX, (float)rawmouse->lLastY); + } + } else if (rawmouse->lLastX || rawmouse->lLastY) { + /* This is absolute motion, either using a tablet or mouse over RDP + + Notes on how RDP appears to work, as of Windows 10 2004: + - SetCursorPos() calls are cached, with multiple calls coalesced into a single call that's sent to the RDP client. If the last call to SetCursorPos() has the same value as the last one that was sent to the client, it appears to be ignored and not sent. This means that we need to jitter the SetCursorPos() position slightly in order for the recentering to work correctly. + - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the position we requested with SetCursorPos(). + - SetCursorPos() outside of the bounds of the focus window appears not to do anything. + - SetCursorPos() while the cursor is NULL doesn't do anything + + We handle this by creating a safe area within the application window, and when the mouse leaves that safe area, we warp back to the opposite side. Any single motion > 50% of the safe area is assumed to be a warp and ignored. + */ + SDL_bool remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? SDL_TRUE : SDL_FALSE; + SDL_bool virtual_desktop = (rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) ? SDL_TRUE : SDL_FALSE; + SDL_bool normalized_coordinates = !(rawmouse->usFlags & 0x40) ? SDL_TRUE : SDL_FALSE; + int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); + int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); + int x = normalized_coordinates ? (int)(((float)rawmouse->lLastX / 65535.0f) * w) : (int)rawmouse->lLastX; + int y = normalized_coordinates ? (int)(((float)rawmouse->lLastY / 65535.0f) * h) : (int)rawmouse->lLastY; + int relX, relY; + + /* Calculate relative motion */ + if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) { + data->last_raw_mouse_position.x = x; + data->last_raw_mouse_position.y = y; + } + relX = x - data->last_raw_mouse_position.x; + relY = y - data->last_raw_mouse_position.y; + + if (remote_desktop) { + if (!data->in_title_click && !data->focus_click_pending) { + static int wobble; + float floatX = (float)x / w; + float floatY = (float)y / h; + + /* See if the mouse is at the edge of the screen, or in the RDP title bar area */ + if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) { + /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */ + RECT rect = data->cursor_clipped_rect; + int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble; + int warpY = rect.top + ((rect.bottom - rect.top) / 2); + + WIN_SetCursorPos(warpX, warpY); + + ++wobble; + if (wobble > 1) { + wobble = -1; + } + } else { + /* Send relative motion if we didn't warp last frame (had good position data) + We also sometimes get large deltas due to coalesced mouse motion and warping, + so ignore those. + */ + const int MAX_RELATIVE_MOTION = (h / 6); + if (SDL_abs(relX) < MAX_RELATIVE_MOTION && + SDL_abs(relY) < MAX_RELATIVE_MOTION) { + SDL_SendMouseMotion(timestamp, data->window, mouseID, 1, (float)relX, (float)relY); + } + } + } + } else { + const int MAXIMUM_TABLET_RELATIVE_MOTION = 32; + if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION || + SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) { + /* Ignore this motion, probably a pen lift and drop */ + } else { + SDL_SendMouseMotion(timestamp, data->window, mouseID, 1, (float)relX, (float)relY); + } + } + + data->last_raw_mouse_position.x = x; + data->last_raw_mouse_position.y = y; + } + WIN_CheckRawMouseButtons(timestamp, hDevice, rawmouse->usButtonFlags, data, mouseID); +} + +void WIN_PollRawMouseInput(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Window *window; + SDL_WindowData *data; + UINT size, count, i, total = 0; + RAWINPUT *input; + Uint64 now; + + /* We only use raw mouse input in relative mode */ + if (!mouse->relative_mode || mouse->relative_mode_warp) { + return; + } + + /* Relative mouse motion is delivered to the window with keyboard focus */ + window = SDL_GetKeyboardFocus(); + if (!window) { + return; + } + data = window->driverdata; + + if (data->rawinput_size == 0) { + BOOL isWow64; + + data->rawinput_offset = sizeof(RAWINPUTHEADER); + if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { + /* We're going to get 64-bit data, so use the 64-bit RAWINPUTHEADER size */ + data->rawinput_offset += 8; + } + + if (GetRawInputBuffer(NULL, &data->rawinput_size, sizeof(RAWINPUTHEADER)) == (UINT)-1) { + return; + } + if (data->rawinput_size == 0) { + return; + } + } + + /* Get all available events */ + for (;;) { + if (total == data->rawinput_count) { + count = total + 8; + input = (RAWINPUT *)SDL_realloc(data->rawinput, count * data->rawinput_size); + if (!input) { + return; + } + data->rawinput = input; + data->rawinput_count = count; + } + + size = (data->rawinput_count - total) * data->rawinput_size; + count = GetRawInputBuffer((RAWINPUT *)((BYTE *)data->rawinput + (total * data->rawinput_size)), &size, sizeof(RAWINPUTHEADER)); + if (count == (UINT)-1 || count == 0) { + break; + } + total += count; + } + + now = SDL_GetTicksNS(); + if (total > 0) { + Uint64 timestamp, increment; + Uint64 delta = (now - data->last_rawinput_poll); + if (total > 1 && delta <= SDL_MS_TO_NS(100)) { + /* We'll spread these events over the time since the last poll */ + timestamp = data->last_rawinput_poll; + increment = delta / total; + } else { + /* Do we want to track the update rate per device? */ + timestamp = now; + increment = 0; + } + for (i = 0, input = data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) { + timestamp += increment; + if (input->header.dwType == RIM_TYPEMOUSE) { + RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset); + WIN_HandleRawMouseInput(timestamp, window->driverdata, input->header.hDevice, rawmouse); + } + } + } + data->last_rawinput_poll = now; +} + #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ LRESULT CALLBACK @@ -715,28 +704,15 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) SDL_WindowData *data; LRESULT returnCode = -1; - /* Send a SDL_EVENT_SYSWM if the application wants them */ - if (SDL_EventEnabled(SDL_EVENT_SYSWM)) { - SDL_SysWMmsg wmmsg; - - wmmsg.version = SDL_SYSWM_CURRENT_VERSION; - wmmsg.subsystem = SDL_SYSWM_WINDOWS; - wmmsg.msg.win.hwnd = hwnd; - wmmsg.msg.win.msg = msg; - wmmsg.msg.win.wParam = wParam; - wmmsg.msg.win.lParam = lParam; - SDL_SendSysWMEvent(&wmmsg); - } - /* Get the window data for the window */ data = WIN_GetWindowDataFromHWND(hwnd); #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) - if (data == NULL) { + if (!data) { /* Fallback */ data = (SDL_WindowData *)GetProp(hwnd, TEXT("SDL_WindowData")); } #endif - if (data == NULL) { + if (!data) { return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam); } @@ -831,7 +807,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (!mouse->relative_mode || mouse->relative_mode_warp) { /* Only generate mouse events for real mouse */ - if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH && + if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) != SDL_MOUSE_EVENT_SOURCE_TOUCH && lParam != data->last_pointer_update) { SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, 0, 0, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)); } @@ -853,13 +829,14 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { SDL_Mouse *mouse = SDL_GetMouse(); if (!mouse->relative_mode || mouse->relative_mode_warp) { - if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH && + if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) != SDL_MOUSE_EVENT_SOURCE_TOUCH && lParam != data->last_pointer_update) { - WIN_CheckWParamMouseButtons(wParam, data, 0); + WIN_CheckWParamMouseButtons(WIN_GetEventTimestamp(), wParam, data, 0); } } } break; +#if 0 /* We handle raw input all at once instead of using a syscall for each mouse event */ case WM_INPUT: { SDL_Mouse *mouse = SDL_GetMouse(); @@ -881,96 +858,10 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) /* Mouse data (ignoring synthetic mouse events generated for touchscreens) */ if (inp.header.dwType == RIM_TYPEMOUSE) { - SDL_MouseID mouseID; - RAWMOUSE *rawmouse; - if (SDL_GetNumTouchDevices() > 0 && - (GetMouseMessageSource() == SDL_MOUSE_EVENT_SOURCE_TOUCH || (GetMessageExtraInfo() & 0x82) == 0x82)) { - break; - } - /* We do all of our mouse state checking against mouse ID 0 - * We would only use the actual hDevice if we were tracking - * all mouse motion independently, and never using mouse ID 0. - */ - mouseID = 0; /* (SDL_MouseID)(uintptr_t)inp.header.hDevice; */ - rawmouse = &inp.data.mouse; - - if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) { - SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, 1, (float)rawmouse->lLastX, (float)rawmouse->lLastY); - } else if (rawmouse->lLastX || rawmouse->lLastY) { - /* This is absolute motion, either using a tablet or mouse over RDP - - Notes on how RDP appears to work, as of Windows 10 2004: - - SetCursorPos() calls are cached, with multiple calls coalesced into a single call that's sent to the RDP client. If the last call to SetCursorPos() has the same value as the last one that was sent to the client, it appears to be ignored and not sent. This means that we need to jitter the SetCursorPos() position slightly in order for the recentering to work correctly. - - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the position we requested with SetCursorPos(). - - SetCursorPos() outside of the bounds of the focus window appears not to do anything. - - SetCursorPos() while the cursor is NULL doesn't do anything - - We handle this by creating a safe area within the application window, and when the mouse leaves that safe area, we warp back to the opposite side. Any single motion > 50% of the safe area is assumed to be a warp and ignored. - */ - SDL_bool remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? SDL_TRUE : SDL_FALSE; - SDL_bool virtual_desktop = (rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) ? SDL_TRUE : SDL_FALSE; - SDL_bool normalized_coordinates = !(rawmouse->usFlags & 0x40) ? SDL_TRUE : SDL_FALSE; - int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); - int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); - int x = normalized_coordinates ? (int)(((float)rawmouse->lLastX / 65535.0f) * w) : (int)rawmouse->lLastX; - int y = normalized_coordinates ? (int)(((float)rawmouse->lLastY / 65535.0f) * h) : (int)rawmouse->lLastY; - int relX, relY; - - /* Calculate relative motion */ - if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) { - data->last_raw_mouse_position.x = x; - data->last_raw_mouse_position.y = y; - } - relX = x - data->last_raw_mouse_position.x; - relY = y - data->last_raw_mouse_position.y; - - if (remote_desktop) { - if (!data->in_title_click && !data->focus_click_pending) { - static int wobble; - float floatX = (float)x / w; - float floatY = (float)y / h; - - /* See if the mouse is at the edge of the screen, or in the RDP title bar area */ - if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) { - /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */ - RECT rect = data->cursor_clipped_rect; - int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble; - int warpY = rect.top + ((rect.bottom - rect.top) / 2); - - WIN_SetCursorPos(warpX, warpY); - - ++wobble; - if (wobble > 1) { - wobble = -1; - } - } else { - /* Send relative motion if we didn't warp last frame (had good position data) - We also sometimes get large deltas due to coalesced mouse motion and warping, - so ignore those. - */ - const int MAX_RELATIVE_MOTION = (h / 6); - if (SDL_abs(relX) < MAX_RELATIVE_MOTION && - SDL_abs(relY) < MAX_RELATIVE_MOTION) { - SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, 1, (float)relX, (float)relY); - } - } - } - } else { - const int MAXIMUM_TABLET_RELATIVE_MOTION = 32; - if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION || - SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) { - /* Ignore this motion, probably a pen lift and drop */ - } else { - SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, 1, (float)relX, (float)relY); - } - } - - data->last_raw_mouse_position.x = x; - data->last_raw_mouse_position.y = y; - } - WIN_CheckRawMouseButtons(rawmouse->usButtonFlags, data, mouseID); + WIN_HandleRawMouseInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.mouse); } } break; +#endif case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: @@ -1061,7 +952,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) returnCode = 1; } else { char text[5]; - if (WIN_ConvertUTF32toUTF8((UINT32)wParam, text)) { + if (SDL_UCS4ToUTF8((Uint32)wParam, text) != text) { SDL_SendKeyboardText(text); } returnCode = 0; @@ -1069,31 +960,27 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) break; case WM_CHAR: - /* When a user enters a Unicode code point defined in the Basic Multilingual Plane, Windows sends a WM_CHAR - message with the code point encoded as UTF-16. When a user enters a Unicode code point from a Supplementary - Plane, Windows sends the code point in two separate WM_CHAR messages: The first message includes the UTF-16 - High Surrogate and the second the UTF-16 Low Surrogate. The High and Low Surrogates cannot be individually - converted to valid UTF-8, therefore, we must save the High Surrogate from the first WM_CHAR message and - concatenate it with the Low Surrogate from the second WM_CHAR message. At that point, we have a valid - UTF-16 surrogate pair ready to re-encode as UTF-8. */ + /* Characters outside Unicode Basic Multilingual Plane (BMP) + * are coded as so called "surrogate pair" in two separate UTF-16 character events. + * Cache high surrogate until next character event. */ if (IS_HIGH_SURROGATE(wParam)) { data->high_surrogate = (WCHAR)wParam; - } else if (IS_SURROGATE_PAIR(data->high_surrogate, wParam)) { - /* The code point is in a Supplementary Plane. - Here wParam is the Low Surrogate. */ - char text[5]; - if (WIN_ConvertUTF16toUTF8((UINT32)data->high_surrogate, (UINT32)wParam, text)) { - SDL_SendKeyboardText(text); - } - data->high_surrogate = 0; } else { - /* The code point is in the Basic Multilingual Plane. - It's numerically equal to UTF-32. */ - char text[5]; - if (WIN_ConvertUTF32toUTF8((UINT32)wParam, text)) { - SDL_SendKeyboardText(text); + WCHAR utf16[] = { + data->high_surrogate ? data->high_surrogate : (WCHAR)wParam, + data->high_surrogate ? (WCHAR)wParam : L'\0', + L'\0' + }; + + char utf8[5]; + int result = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL); + if (result > 0) { + SDL_SendKeyboardText(utf8); } + + data->high_surrogate = L'\0'; } + returnCode = 0; break; @@ -1117,7 +1004,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) data->in_title_click = SDL_FALSE; /* The mouse may have been released during a modal loop */ - WIN_CheckAsyncMouseRelease(data); + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); } break; #ifdef WM_GETMINMAXINFO @@ -1159,27 +1046,11 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) { - LONG style = GetWindowLong(hwnd, GWL_STYLE); - /* DJM - according to the docs for GetMenu(), the - return value is undefined if hwnd is a child window. - Apparently it's too difficult for MS to check - inside their function, so I have to do it here. - */ - BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); - UINT dpi; - - dpi = 96; size.top = 0; size.left = 0; size.bottom = h; size.right = w; - - if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) { - dpi = data->videodata->GetDpiForWindow(hwnd); - data->videodata->AdjustWindowRectExForDpi(&size, style, menu, 0, dpi); - } else { - AdjustWindowRectEx(&size, style, menu, 0); - } + WIN_AdjustWindowRectForHWND(hwnd, &size, 0); w = size.right - size.left; h = size.bottom - size.top; #ifdef HIGHDPI_DEBUG @@ -1220,66 +1091,91 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) #endif /* WM_GETMINMAXINFO */ case WM_WINDOWPOSCHANGING: - + { if (data->expected_resize) { returnCode = 0; } - break; + + if (data->floating_rect_pending && + !IsIconic(hwnd) && + !IsZoomed(hwnd) && + (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) && + !(data->window->flags & SDL_WINDOW_FULLSCREEN)) { + /* If a new floating size is pending, apply it if moving from a fixed-size to floating state. */ + WINDOWPOS *windowpos = (WINDOWPOS*)lParam; + int fx, fy, fw, fh; + + WIN_AdjustWindowRect(data->window, &fx, &fy, &fw, &fh, SDL_WINDOWRECT_FLOATING); + windowpos->x = fx; + windowpos->y = fy; + windowpos->cx = fw; + windowpos->cy = fh; + windowpos->flags &= ~(SWP_NOSIZE | SWP_NOMOVE); + + data->floating_rect_pending = SDL_FALSE; + } + } break; case WM_WINDOWPOSCHANGED: { SDL_Window *win; + const SDL_DisplayID original_displayID = data->last_displayID; + const WINDOWPOS *windowpos = (WINDOWPOS *)lParam; + const SDL_bool iconic = IsIconic(hwnd); + const SDL_bool zoomed = IsZoomed(hwnd); RECT rect; int x, y; int w, h; - const SDL_DisplayID original_displayID = data->last_displayID; - if (data->initializing || data->in_border_change) { - break; + if (windowpos->flags & SWP_SHOWWINDOW) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + } + + if (iconic) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + } else if (zoomed) { + if (data->window->flags & SDL_WINDOW_MINIMIZED) { + /* If going from minimized to maximized, send the restored event first. */ + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + } else if (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + + if (windowpos->flags & SWP_HIDEWINDOW) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); } /* When the window is minimized it's resized to the dock icon size, ignore this */ - if (IsIconic(hwnd)) { + if (iconic) { break; } - if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) { + if (data->initializing) { break; } - ClientToScreen(hwnd, (LPPOINT)&rect); - ClientToScreen(hwnd, (LPPOINT)&rect + 1); - WIN_UpdateClipCursor(data->window); - - x = rect.left; - y = rect.top; - - SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); - - // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds - if (GetClientRect(hwnd, &rect)) { - ClientToScreen(hwnd, (LPPOINT)&rect); - ClientToScreen(hwnd, (LPPOINT)&rect + 1); - - WIN_UpdateClipCursor(data->window); + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + ClientToScreen(hwnd, (LPPOINT) &rect); + ClientToScreen(hwnd, (LPPOINT) &rect + 1); x = rect.left; y = rect.top; + + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); } - w = rect.right - rect.left; - h = rect.bottom - rect.top; - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h); + /* Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds */ + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + w = rect.right; + h = rect.bottom; -#ifdef HIGHDPI_DEBUG - SDL_Log("WM_WINDOWPOSCHANGED: Windows client rect (pixels): (%d, %d) (%d x %d)\tSDL client rect (points): (%d, %d) (%d x %d) windows reported dpi %d", - rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, - x, y, w, h, data->videodata->GetDpiForWindow ? data->videodata->GetDpiForWindow(data->hwnd) : 0); -#endif + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h); + } - /* Forces a WM_PAINT event */ - InvalidateRect(hwnd, NULL, FALSE); + WIN_UpdateClipCursor(data->window); /* Update the window display position */ data->last_displayID = SDL_GetDisplayForWindow(data->window); @@ -1290,34 +1186,41 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } /* Update the position of any child windows */ - for (win = data->window->first_child; win != NULL; win = win->next_sibling) { + for (win = data->window->first_child; win; win = win->next_sibling) { /* Don't update hidden child windows, their relative position doesn't change */ if (!(win->flags & SDL_WINDOW_HIDDEN)) { - WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); } } + + /* Forces a WM_PAINT event */ + InvalidateRect(hwnd, NULL, FALSE); + + } break; + + case WM_ENTERSIZEMOVE: + case WM_ENTERMENULOOP: + { + SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL); + } break; + + case WM_TIMER: + { + if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) { + if (SDL_HasMainCallbacks()) { + SDL_IterateMainCallbacks(SDL_FALSE); + } else { + // Send an expose event so the application can redraw + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } + return 0; + } } break; - case WM_SIZE: + case WM_EXITSIZEMOVE: + case WM_EXITMENULOOP: { - switch (wParam) { - case SIZE_MAXIMIZED: - SDL_SendWindowEvent(data->window, - SDL_EVENT_WINDOW_RESTORED, 0, 0); - SDL_SendWindowEvent(data->window, - SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); - break; - case SIZE_MINIMIZED: - SDL_SendWindowEvent(data->window, - SDL_EVENT_WINDOW_MINIMIZED, 0, 0); - break; - case SIZE_RESTORED: - SDL_SendWindowEvent(data->window, - SDL_EVENT_WINDOW_RESTORED, 0, 0); - break; - default: - break; - } + KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks); } break; case WM_SETCURSOR: @@ -1419,6 +1322,8 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) for (i = 0; i < num_inputs; ++i) { PTOUCHINPUT input = &inputs[i]; + const int w = (rect.right - rect.left); + const int h = (rect.bottom - rect.top); const SDL_TouchID touchId = (SDL_TouchID)((size_t)input->hSource); @@ -1430,8 +1335,16 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } /* Get the normalized coordinates for the window */ - x = (float)(input->x - rect.left) / (rect.right - rect.left); - y = (float)(input->y - rect.top) / (rect.bottom - rect.top); + if (w <= 1) { + x = 0.5f; + } else { + x = (float)(input->x - rect.left) / (w - 1); + } + if (h <= 1) { + y = 0.5f; + } else { + y = (float)(input->y - rect.top) / (h - 1); + } /* FIXME: Should we use the input->dwTime field for the tick source of the timestamp? */ if (input->dwFlags & TOUCHEVENTF_DOWN) { @@ -1472,16 +1385,15 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) HDROP drop = (HDROP)wParam; UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); for (i = 0; i < count; ++i) { - SDL_bool isstack; UINT size = DragQueryFile(drop, i, NULL, 0) + 1; - LPTSTR buffer = SDL_small_alloc(TCHAR, size, &isstack); + LPTSTR buffer = (LPTSTR)SDL_malloc(sizeof(TCHAR) * size); if (buffer) { if (DragQueryFile(drop, i, buffer, size)) { char *file = WIN_StringToUTF8(buffer); - SDL_SendDropFile(data->window, file); + SDL_SendDropFile(data->window, NULL, file); SDL_free(file); } - SDL_small_free(buffer, isstack); + SDL_free(buffer); } } SDL_SendDropComplete(data->window); @@ -1503,8 +1415,8 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (!(window_flags & SDL_WINDOW_RESIZABLE)) { int w, h; NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam; - w = data->window->windowed.w; - h = data->window->windowed.h; + w = data->window->floating.w; + h = data->window->floating.h; params->rgrc[0].right = params->rgrc[0].left + w; params->rgrc[0].bottom = params->rgrc[0].top + h; } @@ -1588,9 +1500,6 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) int frame_w, frame_h; int query_client_w_win, query_client_h_win; - const DWORD style = GetWindowLong(hwnd, GWL_STYLE); - const BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); - #ifdef HIGHDPI_DEBUG SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)", prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy); @@ -1601,7 +1510,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) RECT rect = { 0 }; if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { - data->videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, prevDPI); + WIN_AdjustWindowRectForHWND(hwnd, &rect, prevDPI); } frame_w = -rect.left + rect.right; @@ -1618,7 +1527,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) rect.bottom = query_client_h_win; if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { - data->videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, nextDPI); + WIN_AdjustWindowRectForHWND(hwnd, &rect, nextDPI); } /* This is supposed to control the suggested rect param of WM_DPICHANGED */ @@ -1659,19 +1568,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { /* Calculate the new frame w/h such that the client area size is maintained. */ - const DWORD style = GetWindowLong(hwnd, GWL_STYLE); - const BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); - RECT rect = { 0 }; rect.right = data->window->w; rect.bottom = data->window->h; if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { - if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) { - data->videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, newDPI); - } else { - AdjustWindowRectEx(&rect, style, menu, 0); - } + WIN_AdjustWindowRectForHWND(hwnd, &rect, newDPI); } w = rect.right - rect.left; @@ -1758,8 +1660,10 @@ static void WIN_UpdateMouseCapture() SDL_MouseID mouseID = SDL_GetMouse()->mouseID; SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, 0, (float)cursorPos.x, (float)cursorPos.y); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT); - SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, + !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, + !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT); SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE); SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1); SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2); @@ -1795,11 +1699,13 @@ int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) message_result = GetMessage(&msg, 0, 0, 0); } if (message_result) { - if (msg.message == WM_TIMER && msg.hwnd == NULL && msg.wParam == timer_id) { + if (msg.message == WM_TIMER && !msg.hwnd && msg.wParam == timer_id) { return 0; } if (g_WindowsMessageHook) { - g_WindowsMessageHook(g_WindowsMessageHookData, msg.hwnd, msg.message, msg.wParam, msg.lParam); + if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) { + return 1; + } } /* Always translate the message in case it's a non-SDL window (e.g. with Qt integration) */ TranslateMessage(&msg); @@ -1823,7 +1729,14 @@ void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window) void WIN_PumpEvents(SDL_VideoDevice *_this) { MSG msg; +#ifdef _MSC_VER /* We explicitly want to use GetTickCount(), not GetTickCount64() */ +#pragma warning(push) +#pragma warning(disable : 28159) +#endif DWORD end_ticks = GetTickCount() + 1; +#ifdef _MSC_VER +#pragma warning(pop) +#endif int new_messages = 0; #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) const Uint8 *keystate; @@ -1835,7 +1748,9 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (g_WindowsMessageHook) { - g_WindowsMessageHook(g_WindowsMessageHookData, msg.hwnd, msg.message, msg.wParam, msg.lParam); + if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) { + continue; + } } #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) @@ -1890,7 +1805,7 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState() will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */ focusWindow = SDL_GetKeyboardFocus(); - if (focusWindow == NULL || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) { + if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) { if ((keystate[SDL_SCANCODE_LGUI] == SDL_PRESSED) && !(GetKeyState(VK_LWIN) & 0x8000)) { SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LGUI); } @@ -1944,8 +1859,8 @@ int SDL_RegisterApp(const char *name, Uint32 style, void *hInst) ++app_registered; return 0; } - SDL_assert(SDL_Appname == NULL); - if (name == NULL) { + SDL_assert(!SDL_Appname); + if (!name) { name = "SDL_app"; #if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC) style = (CS_BYTEALIGNCLIENT | CS_OWNDC); diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h index 12475a3e..77f1dd46 100644 --- a/src/video/windows/SDL_windowsevents.h +++ b/src/video/windows/SDL_windowsevents.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -30,6 +30,7 @@ extern HINSTANCE SDL_Instance; extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +extern void WIN_PollRawMouseInput(void); extern void WIN_PumpEvents(SDL_VideoDevice *_this); extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); extern int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS); diff --git a/src/video/windows/SDL_windowsframebuffer.c b/src/video/windows/SDL_windowsframebuffer.c index aab98603..bdc7fe0f 100644 --- a/src/video/windows/SDL_windowsframebuffer.c +++ b/src/video/windows/SDL_windowsframebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -47,7 +47,7 @@ int WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD); info = (LPBITMAPINFO)SDL_small_alloc(Uint8, size, &isstack); if (!info) { - return SDL_OutOfMemory(); + return -1; } SDL_memset(info, 0, size); @@ -114,7 +114,7 @@ void WIN_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; - if (data == NULL) { + if (!data) { /* The window wasn't fully initialized */ return; } diff --git a/src/video/windows/SDL_windowsframebuffer.h b/src/video/windows/SDL_windowsframebuffer.h index e9ed4410..5de8e8fc 100644 --- a/src/video/windows/SDL_windowsframebuffer.h +++ b/src/video/windows/SDL_windowsframebuffer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowskeyboard.c b/src/video/windows/SDL_windowskeyboard.c index 50321876..633fe676 100644 --- a/src/video/windows/SDL_windowskeyboard.c +++ b/src/video/windows/SDL_windowskeyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -44,9 +44,6 @@ static SDL_bool IME_IsTextInputShown(SDL_VideoData *videodata); #ifndef MAPVK_VSC_TO_VK #define MAPVK_VSC_TO_VK 1 #endif -#ifndef MAPVK_VK_TO_CHAR -#define MAPVK_VK_TO_CHAR 2 -#endif /* Alphabetic scancodes for PC keyboards */ void WIN_InitKeyboard(SDL_VideoDevice *_this) @@ -120,9 +117,12 @@ void WIN_UpdateKeymap(SDL_bool send_event) SDL_Keycode keymap[SDL_NUM_SCANCODES]; SDL_GetDefaultKeymap(keymap); + WIN_ResetDeadKeys(); for (i = 0; i < SDL_arraysize(windows_scancode_table); i++) { - int vk; + Uint8 vk; + Uint16 sc; + /* Make sure this scancode is a valid character scancode */ scancode = windows_scancode_table[i]; if (scancode == SDL_SCANCODE_UNKNOWN) { @@ -130,20 +130,46 @@ void WIN_UpdateKeymap(SDL_bool send_event) } /* If this key is one of the non-mappable keys, ignore it */ - /* Uncomment the second part re-enable the behavior of not mapping the "`"(grave) key to the users actual keyboard layout */ - if ((keymap[scancode] & SDLK_SCANCODE_MASK) /*|| scancode == SDL_SCANCODE_GRAVE*/) { + /* Uncomment the third part to re-enable the behavior of not mapping the "`"(grave) key to the users actual keyboard layout */ + if ((keymap[scancode] & SDLK_SCANCODE_MASK) || scancode == SDL_SCANCODE_DELETE /*|| scancode == SDL_SCANCODE_GRAVE*/) { continue; } - vk = MapVirtualKey(i, MAPVK_VSC_TO_VK); - if (vk) { - int ch = (MapVirtualKey(vk, MAPVK_VK_TO_CHAR) & 0x7FFF); + /* Unpack the single byte index to make the scan code. */ + sc = MAKEWORD(i & 0x7f, (i & 0x80) ? 0xe0 : 0x00); + vk = LOBYTE(MapVirtualKey(sc, MAPVK_VSC_TO_VK)); + if (!vk) { + continue; + } + + /* Always map VK_A..VK_Z to SDLK_a..SDLK_z codes. + * This was behavior with MapVirtualKey(MAPVK_VK_TO_CHAR). */ + //if (vk >= 'A' && vk <= 'Z') { + // keymap[scancode] = SDLK_a + (vk - 'A'); + //} else { + { + BYTE keyboardState[256] = { 0 }; + WCHAR buffer[16] = { 0 }; + Uint32 *ch = 0; + int result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0); + buffer[SDL_abs(result)] = 0; + + /* Convert UTF-16 to UTF-32 code points */ + ch = (Uint32 *)SDL_iconv_string("UTF-32LE", "UTF-16LE", (const char *)buffer, (SDL_abs(result) + 1) * sizeof(WCHAR)); if (ch) { - if (ch >= 'A' && ch <= 'Z') { - keymap[scancode] = SDLK_a + (ch - 'A'); + if (ch[0] != 0 && ch[1] != 0) { + /* We have several UTF-32 code points on a single key press. + * Cannot fit into single SDL_Keycode in keymap. + * See https://kbdlayout.info/features/ligatures */ + keymap[scancode] = 0xfffd; /* U+FFFD REPLACEMENT CHARACTER */ } else { - keymap[scancode] = ch; + keymap[scancode] = ch[0]; } + SDL_free(ch); + } + + if (result < 0) { + WIN_ResetDeadKeys(); } } } @@ -174,19 +200,21 @@ void WIN_ResetDeadKeys() */ BYTE keyboardState[256]; WCHAR buffer[16]; - int keycode, scancode, result, i; + int vk, sc, result, i; - GetKeyboardState(keyboardState); + if (!GetKeyboardState(keyboardState)) { + return; + } - keycode = VK_SPACE; - scancode = MapVirtualKey(keycode, MAPVK_VK_TO_VSC); - if (scancode == 0) { + vk = VK_SPACE; + sc = MapVirtualKey(vk, MAPVK_VK_TO_VSC); + if (sc == 0) { /* the keyboard doesn't have this key */ return; } for (i = 0; i < 5; i++) { - result = ToUnicode(keycode, scancode, keyboardState, (LPWSTR)buffer, 16, 0); + result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0); if (result > 0) { /* success */ return; @@ -693,7 +721,7 @@ static void IME_SetupAPI(SDL_VideoData *videodata) } hime = SDL_LoadObject(ime_file); - if (hime == NULL) { + if (!hime) { return; } @@ -735,7 +763,7 @@ static void IME_UpdateInputLocale(SDL_VideoData *videodata) } videodata->ime_hkl = hklnext; - videodata->ime_candvertical = (PRIMLANG() == LANG_KOREAN || LANG() == LANG_CHS) ? SDL_FALSE : SDL_TRUE; + videodata->ime_candvertical = (PRIMLANG() != LANG_KOREAN && LANG() != LANG_CHS); } static void IME_ClearComposition(SDL_VideoData *videodata) @@ -766,7 +794,7 @@ static SDL_bool IME_IsTextInputShown(SDL_VideoData *videodata) return SDL_FALSE; } - return videodata->ime_uicontext != 0 ? SDL_TRUE : SDL_FALSE; + return videodata->ime_uicontext != 0; } static void IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string) @@ -776,7 +804,7 @@ static void IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD length = ImmGetCompositionStringW(himc, string, NULL, 0); if (length > 0 && videodata->ime_composition_length < length) { - if (videodata->ime_composition != NULL) { + if (videodata->ime_composition) { SDL_free(videodata->ime_composition); } @@ -968,11 +996,11 @@ static int IME_ShowCandidateList(SDL_VideoData *videodata) videodata->ime_candcount = 0; candidates = SDL_realloc(videodata->ime_candidates, MAX_CANDSIZE); - if (candidates != NULL) { + if (candidates) { videodata->ime_candidates = (WCHAR *)candidates; } - if (videodata->ime_candidates == NULL) { + if (!videodata->ime_candidates) { return -1; } @@ -1186,7 +1214,7 @@ TSFSink_Release(TSFSink *sink) STDMETHODIMP UIElementSink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv) { - if (ppv == NULL) { + if (!ppv) { return E_INVALIDARG; } @@ -1223,7 +1251,7 @@ STDMETHODIMP UIElementSink_BeginUIElement(TSFSink *sink, DWORD dwUIElementId, BO ITfReadingInformationUIElement *preading = 0; ITfCandidateListUIElement *pcandlist = 0; SDL_VideoData *videodata = (SDL_VideoData *)sink->data; - if (element == NULL) { + if (!element) { return E_INVALIDARG; } @@ -1248,7 +1276,7 @@ STDMETHODIMP UIElementSink_UpdateUIElement(TSFSink *sink, DWORD dwUIElementId) ITfReadingInformationUIElement *preading = 0; ITfCandidateListUIElement *pcandlist = 0; SDL_VideoData *videodata = (SDL_VideoData *)sink->data; - if (element == NULL) { + if (!element) { return E_INVALIDARG; } @@ -1274,7 +1302,7 @@ STDMETHODIMP UIElementSink_EndUIElement(TSFSink *sink, DWORD dwUIElementId) ITfReadingInformationUIElement *preading = 0; ITfCandidateListUIElement *pcandlist = 0; SDL_VideoData *videodata = (SDL_VideoData *)sink->data; - if (element == NULL) { + if (!element) { return E_INVALIDARG; } @@ -1296,7 +1324,7 @@ STDMETHODIMP UIElementSink_EndUIElement(TSFSink *sink, DWORD dwUIElementId) STDMETHODIMP IPPASink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv) { - if (ppv == NULL) { + if (!ppv) { return E_INVALIDARG; } @@ -1462,7 +1490,7 @@ static void StopDrawToBitmap(HDC hdc, HBITMAP *hhbm) static void DrawRect(HDC hdc, int left, int top, int right, int bottom, int pensize) { /* The case of no pen (PenSize = 0) is automatically taken care of. */ - const int penadjust = (int)SDL_floor(pensize / 2.0f - 0.5f); + const int penadjust = (int)SDL_floorf(pensize / 2.0f - 0.5f); left += pensize / 2; top += pensize / 2; right -= penadjust; diff --git a/src/video/windows/SDL_windowskeyboard.h b/src/video/windows/SDL_windowskeyboard.h index 777bc40e..76f8233e 100644 --- a/src/video/windows/SDL_windowskeyboard.h +++ b/src/video/windows/SDL_windowskeyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsmessagebox.c b/src/video/windows/SDL_windowsmessagebox.c index b5459da5..82aca552 100644 --- a/src/video/windows/SDL_windowsmessagebox.c +++ b/src/video/windows/SDL_windowsmessagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -265,7 +265,7 @@ static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wP if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { /* Focus on the first default return-key button */ HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex)); - if (buttonctl == NULL) { + if (!buttonctl) { EndDialog(hDlg, IDINVALPTRDLGITEM); } PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE); @@ -276,7 +276,7 @@ static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wP return FALSE; case WM_SETFOCUS: messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); - if (messageboxdata == NULL) { + if (!messageboxdata) { EndDialog(hDlg, IDINVALPTRSETFOCUS); return TRUE; } @@ -288,7 +288,7 @@ static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wP return TRUE; case WM_COMMAND: messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); - if (messageboxdata == NULL) { + if (!messageboxdata) { EndDialog(hDlg, IDINVALPTRCOMMAND); return TRUE; } @@ -344,8 +344,7 @@ static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space) if (size > dialog->size) { void *data = SDL_realloc(dialog->data, size); - if (data == NULL) { - SDL_OutOfMemory(); + if (!data) { return SDL_FALSE; } dialog->data = data; @@ -387,12 +386,12 @@ static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string) size_t count; SDL_bool status; - if (string == NULL) { + if (!string) { string = ""; } wstring = WIN_UTF8ToStringW(string); - if (wstring == NULL) { + if (!wstring) { return SDL_FALSE; } @@ -448,7 +447,7 @@ static SDL_bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, if (!AddDialogData(dialog, &type, sizeof(type))) { return SDL_FALSE; } - if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption != NULL)) { + if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption)) { if (!AddDialogString(dialog, caption)) { return SDL_FALSE; } @@ -521,7 +520,7 @@ static WIN_DialogData *CreateDialogData(int w, int h, const char *caption) Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy); dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog)); - if (dialog == NULL) { + if (!dialog) { return NULL; } @@ -623,7 +622,7 @@ static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src) size_t ampcount = 0; size_t srclen = 0; - if (src == NULL) { + if (!src) { return NULL; } @@ -642,7 +641,7 @@ static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src) if (SIZE_MAX - srclen < ampcount) { return NULL; } - if (*dst == NULL || *dstlen < srclen + ampcount) { + if (!*dst || *dstlen < srclen + ampcount) { /* Allocating extra space in case the next strings are a bit longer. */ size_t extraspace = SIZE_MAX - (srclen + ampcount); if (extraspace > 512) { @@ -652,7 +651,7 @@ static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src) SDL_free(*dst); *dst = NULL; newdst = SDL_malloc(*dstlen); - if (newdst == NULL) { + if (!newdst) { return NULL; } *dst = newdst; @@ -817,7 +816,7 @@ static int WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int * Size.cy += ButtonHeight + TextMargin; dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title); - if (dialog == NULL) { + if (!dialog) { return -1; } @@ -858,7 +857,7 @@ static int WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int * buttontext = EscapeAmpersands(&escape, &escapesize, sdlButton->text); /* Make sure to provide the correct ID to keep buttons indexed in the * same order as how they are in messageboxdata. */ - if (buttontext == NULL || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) { + if (!buttontext || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) { FreeDialogData(dialog); SDL_free(ampescape); return -1; @@ -932,7 +931,7 @@ int WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) /* If we cannot load comctl32.dll use the old messagebox! */ hComctl32 = LoadLibrary(TEXT("comctl32.dll")); - if (hComctl32 == NULL) { + if (!hComctl32) { return WIN_ShowOldMessageBox(messageboxdata, buttonid); } @@ -944,7 +943,7 @@ int WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") */ pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect"); - if (pfnTaskDialogIndirect == NULL) { + if (!pfnTaskDialogIndirect) { FreeLibrary(hComctl32); return WIN_ShowOldMessageBox(messageboxdata, buttonid); } @@ -992,7 +991,7 @@ int WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) pButton->nButtonID = IDBUTTONINDEX0 + i; } buttontext = EscapeAmpersands(&escape, &escapesize, messageboxdata->buttons[i].text); - if (buttontext == NULL) { + if (!buttontext) { int j; FreeLibrary(hComctl32); SDL_free(ampescape); diff --git a/src/video/windows/SDL_windowsmessagebox.h b/src/video/windows/SDL_windowsmessagebox.h index 41b5cc72..fa2330e2 100644 --- a/src/video/windows/SDL_windowsmessagebox.h +++ b/src/video/windows/SDL_windowsmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index df80ba3f..80483590 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -186,9 +186,9 @@ static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) } if (dpi == 0) { /* Safe default */ - dpi = 96; + dpi = USER_DEFAULT_SCREEN_DPI; } - return dpi / 96.0f; + return dpi / (float)USER_DEFAULT_SCREEN_DPI; } static SDL_bool WIN_GetDisplayMode(SDL_VideoDevice *_this, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation) @@ -203,7 +203,7 @@ static SDL_bool WIN_GetDisplayMode(SDL_VideoDevice *_this, HMONITOR hMonitor, LP } data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data)); - if (data == NULL) { + if (!data) { return SDL_FALSE; } @@ -251,7 +251,7 @@ static char *WIN_GetDisplayNameVista(const WCHAR *deviceName) LONG rc; dll = SDL_LoadObject("USER32.DLL"); - if (dll == NULL) { + if (!dll) { return NULL; } @@ -259,7 +259,7 @@ static char *WIN_GetDisplayNameVista(const WCHAR *deviceName) pQueryDisplayConfig = (SDL_WIN32PROC_QueryDisplayConfig)SDL_LoadFunction(dll, "QueryDisplayConfig"); pDisplayConfigGetDeviceInfo = (SDL_WIN32PROC_DisplayConfigGetDeviceInfo)SDL_LoadFunction(dll, "DisplayConfigGetDeviceInfo"); - if (pGetDisplayConfigBufferSizes == NULL || pQueryDisplayConfig == NULL || pDisplayConfigGetDeviceInfo == NULL) { + if (!pGetDisplayConfigBufferSizes || !pQueryDisplayConfig || !pDisplayConfigGetDeviceInfo) { goto WIN_GetDisplayNameVista_failed; } @@ -274,7 +274,7 @@ static char *WIN_GetDisplayNameVista(const WCHAR *deviceName) paths = (DISPLAYCONFIG_PATH_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_PATH_INFO) * pathCount); modes = (DISPLAYCONFIG_MODE_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_MODE_INFO) * modeCount); - if ((paths == NULL) || (modes == NULL)) { + if ((!paths) || (!modes)) { goto WIN_GetDisplayNameVista_failed; } @@ -401,7 +401,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI } displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); - if (displaydata == NULL) { + if (!displaydata) { return; } SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName)); @@ -410,7 +410,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI SDL_zero(display); display.name = WIN_GetDisplayNameVista(info->szDevice); - if (display.name == NULL) { + if (!display.name) { DISPLAY_DEVICEW device; SDL_zero(device); device.cb = sizeof(device); @@ -677,7 +677,7 @@ void WIN_RefreshDisplays(SDL_VideoDevice *_this) SDL_VideoDisplay *display = _this->displays[i]; SDL_DisplayData *driverdata = display->driverdata; if (driverdata->state == DisplayAdded) { - SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_CONNECTED, 0); + SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ADDED, 0); } } } diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h index 31a76026..8e2a3a2c 100644 --- a/src/video/windows/SDL_windowsmodes.h +++ b/src/video/windows/SDL_windowsmodes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c index 46d39136..07625887 100644 --- a/src/video/windows/SDL_windowsmouse.c +++ b/src/video/windows/SDL_windowsmouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -23,8 +23,11 @@ #if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__) #include "SDL_windowsvideo.h" +#include "SDL_windowsevents.h" +#include "../SDL_video_c.h" #include "../../events/SDL_mouse_c.h" +#include "../../joystick/usb_ids.h" DWORD SDL_last_warp_time = 0; HCURSOR SDL_cursor = NULL; @@ -32,9 +35,87 @@ static SDL_Cursor *SDL_blank_cursor = NULL; static int rawInputEnableCount = 0; +typedef struct +{ + HANDLE ready_event; + HANDLE done_event; + HANDLE thread; +} RawMouseThreadData; + +static RawMouseThreadData thread_data = { + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE +}; + +static DWORD WINAPI WIN_RawMouseThread(LPVOID param) +{ + RAWINPUTDEVICE rawMouse; + HWND window; + + window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!window) { + return 0; + } + + rawMouse.usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; + rawMouse.usUsage = USB_USAGE_GENERIC_MOUSE; + rawMouse.dwFlags = 0; + rawMouse.hwndTarget = window; + + if (!RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse))) { + DestroyWindow(window); + return 0; + } + + /* Make sure we get mouse events as soon as possible */ + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + /* Tell the parent we're ready to go! */ + SetEvent(thread_data.ready_event); + + for ( ; ; ) { + if (MsgWaitForMultipleObjects(1, &thread_data.done_event, 0, INFINITE, QS_RAWINPUT) != WAIT_OBJECT_0 + 1) { + break; + } + + /* Clear the queue status so MsgWaitForMultipleObjects() will wait again */ + (void)GetQueueStatus(QS_RAWINPUT); + + WIN_PollRawMouseInput(); + } + + rawMouse.dwFlags |= RIDEV_REMOVE; + RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse)); + + DestroyWindow(window); + + return 0; +} + +static void CleanupRawMouseThreadData(void) +{ + if (thread_data.thread != INVALID_HANDLE_VALUE) { + SetEvent(thread_data.done_event); + WaitForSingleObject(thread_data.thread, 500); + CloseHandle(thread_data.thread); + thread_data.thread = INVALID_HANDLE_VALUE; + } + + if (thread_data.ready_event != INVALID_HANDLE_VALUE) { + CloseHandle(thread_data.ready_event); + thread_data.ready_event = INVALID_HANDLE_VALUE; + } + + if (thread_data.done_event != INVALID_HANDLE_VALUE) { + CloseHandle(thread_data.done_event); + thread_data.done_event = INVALID_HANDLE_VALUE; + } +} + static int ToggleRawInput(SDL_bool enabled) { - RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */ + int result = -1; if (enabled) { rawInputEnableCount++; @@ -51,108 +132,198 @@ static int ToggleRawInput(SDL_bool enabled) } } - if (!enabled) { - rawMouse.dwFlags |= RIDEV_REMOVE; - } + if (enabled) { + HANDLE handles[2]; - /* (Un)register raw input for mice */ - if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) { - /* Reset the enable count, otherwise subsequent enable calls will - believe raw input is enabled */ - rawInputEnableCount = 0; - - /* Only return an error when registering. If we unregister and fail, - then it's probably that we unregistered twice. That's OK. */ - if (enabled) { - return SDL_Unsupported(); + thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_data.ready_event == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateEvent"); + goto done; } + + thread_data.done_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_data.done_event == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateEvent"); + goto done; + } + + thread_data.thread = CreateThread(NULL, 0, WIN_RawMouseThread, &thread_data, 0, NULL); + if (thread_data.thread == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateThread"); + goto done; + } + + /* Wait for the thread to signal ready or exit */ + handles[0] = thread_data.ready_event; + handles[1] = thread_data.thread; + if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0) { + SDL_SetError("Couldn't set up raw input handling"); + goto done; + } + result = 0; + } else { + CleanupRawMouseThreadData(); + result = 0; } - return 0; + +done: + if (enabled && result < 0) { + CleanupRawMouseThreadData(); + + /* Reset rawInputEnableCount so we can try again */ + rawInputEnableCount = 0; + } + return result; } static SDL_Cursor *WIN_CreateDefaultCursor() { - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { cursor->driverdata = LoadCursor(NULL, IDC_ARROW); - } else { - SDL_OutOfMemory(); } return cursor; } -static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) +static SDL_bool IsMonochromeSurface(SDL_Surface *surface) { - /* msdn says cursor mask has to be padded out to word alignment. Not sure - if that means machine word or WORD, but this handles either case. */ - const size_t pad = (sizeof(size_t) * 8); /* 32 or 64, or whatever. */ - SDL_Cursor *cursor; - HICON hicon; - HICON hcursor; - HDC hdc; - BITMAPV4HEADER bmh; - LPVOID pixels; - LPVOID maskbits; - size_t maskbitslen; - SDL_bool isstack; - ICONINFO ii; + int x, y; + Uint8 r, g, b, a; - SDL_zero(bmh); - bmh.bV4Size = sizeof(bmh); - bmh.bV4Width = surface->w; - bmh.bV4Height = -surface->h; /* Invert the image */ - bmh.bV4Planes = 1; - bmh.bV4BitCount = 32; - bmh.bV4V4Compression = BI_BITFIELDS; - bmh.bV4AlphaMask = 0xFF000000; - bmh.bV4RedMask = 0x00FF0000; - bmh.bV4GreenMask = 0x0000FF00; - bmh.bV4BlueMask = 0x000000FF; + SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); - maskbitslen = ((surface->w + (pad - (surface->w % pad))) / 8) * surface->h; - maskbits = SDL_small_alloc(Uint8, maskbitslen, &isstack); - if (maskbits == NULL) { - SDL_OutOfMemory(); + for (y = 0; y < surface->h; y++) { + for (x = 0; x < surface->w; x++) { + SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a); + + /* Black or white pixel. */ + if (!((r == 0x00 && g == 0x00 && b == 0x00) || (r == 0xff && g == 0xff && b == 0xff))) { + return SDL_FALSE; + } + + /* Transparent or opaque pixel. */ + if (!(a == 0x00 || a == 0xff)) { + return SDL_FALSE; + } + } + } + + return SDL_TRUE; +} + +static HBITMAP CreateColorBitmap(SDL_Surface *surface) +{ + HBITMAP bitmap; + BITMAPINFO bi; + void *pixels; + + SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); + + SDL_zero(bi); + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = surface->w; + bi.bmiHeader.biHeight = -surface->h; /* Invert height to make the top-down DIB. */ + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pixels, NULL, 0); + if (!bitmap || !pixels) { + WIN_SetError("CreateDIBSection()"); return NULL; } - /* AND the cursor against full bits: no change. We already have alpha. */ - SDL_memset(maskbits, 0xFF, maskbitslen); + SDL_memcpy(pixels, surface->pixels, surface->pitch * surface->h); + + return bitmap; +} + +/* Generate bitmap with a mask and optional monochrome image data. + * + * For info on the expected mask format see: + * https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 + */ +static HBITMAP CreateMaskBitmap(SDL_Surface *surface, SDL_bool is_monochrome) +{ + HBITMAP bitmap; + SDL_bool isstack; + void *pixels; + int x, y; + Uint8 r, g, b, a; + Uint8 *dst; + const int pitch = ((surface->w + 15) & ~15) / 8; + const int size = pitch * surface->h; + static const unsigned char masks[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; + + SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); + + pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack); + if (!pixels) { + return NULL; + } + + dst = pixels; + + /* Make the mask completely transparent. */ + SDL_memset(dst, 0xff, size); + if (is_monochrome) { + SDL_memset(dst + size, 0x00, size); + } + + for (y = 0; y < surface->h; y++, dst += pitch) { + for (x = 0; x < surface->w; x++) { + SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a); + + if (a != 0) { + /* Reset bit of an opaque pixel. */ + dst[x >> 3] &= ~masks[x & 7]; + } + + if (is_monochrome && !(r == 0x00 && g == 0x00 && b == 0x00)) { + /* Set bit of white or inverted pixel. */ + dst[size + (x >> 3)] |= masks[x & 7]; + } + } + } + + bitmap = CreateBitmap(surface->w, surface->h * (is_monochrome ? 2 : 1), 1, 1, pixels); + SDL_small_free(pixels, isstack); + if (!bitmap) { + WIN_SetError("CreateBitmap()"); + return NULL; + } + + return bitmap; +} + +static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + HCURSOR hcursor; + SDL_Cursor *cursor; + ICONINFO ii; + SDL_bool is_monochrome = IsMonochromeSurface(surface); - hdc = GetDC(NULL); SDL_zero(ii); ii.fIcon = FALSE; ii.xHotspot = (DWORD)hot_x; ii.yHotspot = (DWORD)hot_y; - ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO *)&bmh, DIB_RGB_COLORS, &pixels, NULL, 0); - ii.hbmMask = CreateBitmap(surface->w, surface->h, 1, 1, maskbits); - ReleaseDC(NULL, hdc); - SDL_small_free(maskbits, isstack); + ii.hbmMask = CreateMaskBitmap(surface, is_monochrome); + ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface); - SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); - SDL_assert(surface->pitch == surface->w * 4); - SDL_memcpy(pixels, surface->pixels, (size_t)surface->h * surface->pitch); - - hicon = CreateIconIndirect(&ii); - - DeleteObject(ii.hbmColor); - DeleteObject(ii.hbmMask); - - if (!hicon) { - WIN_SetError("CreateIconIndirect()"); + if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) { return NULL; } - /* The cursor returned by CreateIconIndirect does not respect system cursor size - preference, use CopyImage to duplicate the cursor with desired sizes */ - hcursor = CopyImage(hicon, IMAGE_CURSOR, surface->w, surface->h, 0); - DestroyIcon(hicon); + hcursor = CreateIconIndirect(&ii); + + DeleteObject(ii.hbmMask); + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } if (!hcursor) { - WIN_SetError("CopyImage()"); + WIN_SetError("CreateIconIndirect()"); return NULL; } @@ -160,8 +331,7 @@ static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) if (cursor) { cursor->driverdata = hcursor; } else { - DestroyIcon(hcursor); - SDL_OutOfMemory(); + DestroyCursor(hcursor); } return cursor; @@ -223,17 +393,39 @@ static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_HAND: name = IDC_HAND; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + name = IDC_SIZENWSE; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + name = IDC_SIZENS; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + name = IDC_SIZENESW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + name = IDC_SIZEWE; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + name = IDC_SIZENWSE; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + name = IDC_SIZENS; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + name = IDC_SIZENESW; + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + name = IDC_SIZEWE; + break; } cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { - HICON hicon; + HCURSOR hcursor; - hicon = LoadCursor(NULL, name); + hcursor = LoadCursor(NULL, name); - cursor->driverdata = hicon; - } else { - SDL_OutOfMemory(); + cursor->driverdata = hcursor; } return cursor; @@ -241,15 +433,15 @@ static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id) static void WIN_FreeCursor(SDL_Cursor *cursor) { - HICON hicon = (HICON)cursor->driverdata; + HCURSOR hcursor = (HCURSOR)cursor->driverdata; - DestroyIcon(hicon); + DestroyCursor(hcursor); SDL_free(cursor); } static int WIN_ShowCursor(SDL_Cursor *cursor) { - if (cursor == NULL) { + if (!cursor) { cursor = SDL_blank_cursor; } if (cursor) { @@ -271,10 +463,17 @@ void WIN_SetCursorPos(int x, int y) SetCursorPos(x, y); /* Flush any mouse motion prior to or associated with this warp */ +#ifdef _MSC_VER /* We explicitly want to use GetTickCount(), not GetTickCount64() */ +#pragma warning(push) +#pragma warning(disable : 28159) +#endif SDL_last_warp_time = GetTickCount(); if (!SDL_last_warp_time) { SDL_last_warp_time = 1; } +#ifdef _MSC_VER +#pragma warning(pop) +#endif } static int WIN_WarpMouse(SDL_Window *window, float x, float y) @@ -414,7 +613,7 @@ static void WIN_SetEnhancedMouseScale(int mouse_speed) float xpoints[5]; float ypoints[5]; float scale_points[10]; - const int dpi = 96; // FIXME, how do we handle different monitors with different DPI? + const int dpi = USER_DEFAULT_SCREEN_DPI; // FIXME, how do we handle different monitors with different DPI? const float display_factor = 3.5f * (150.0f / dpi); if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { diff --git a/src/video/windows/SDL_windowsmouse.h b/src/video/windows/SDL_windowsmouse.h index 3c378823..50c50327 100644 --- a/src/video/windows/SDL_windowsmouse.h +++ b/src/video/windows/SDL_windowsmouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsopengl.c b/src/video/windows/SDL_windowsopengl.c index acc7af9d..d7bc3146 100644 --- a/src/video/windows/SDL_windowsopengl.c +++ b/src/video/windows/SDL_windowsopengl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -125,7 +125,7 @@ int WIN_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) /* Allocate OpenGL memory */ _this->gl_data = (struct SDL_GLDriverData *)SDL_calloc(1, sizeof(struct SDL_GLDriverData)); if (!_this->gl_data) { - return SDL_OutOfMemory(); + return -1; } /* Load function pointers */ @@ -218,7 +218,7 @@ SDL_FunctionPointer WIN_GL_GetProcAddress(SDL_VideoDevice *_this, const char *pr /* This is to pick up extensions */ func = _this->gl_data->wglGetProcAddress(proc); - if (func == NULL) { + if (!func) { /* This is probably a normal GL function */ func = GetProcAddress(_this->gl_config.dll_handle, proc); } @@ -380,7 +380,7 @@ static SDL_bool HasExtension(const char *extension, const char *extensions) return SDL_FALSE; } - if (extensions == NULL) { + if (!extensions) { return SDL_FALSE; } @@ -392,7 +392,7 @@ static SDL_bool HasExtension(const char *extension, const char *extensions) for (;;) { where = SDL_strstr(start, extension); - if (where == NULL) { + if (!where) { break; } @@ -526,6 +526,9 @@ static int WIN_GL_ChoosePixelFormatARB(SDL_VideoDevice *_this, int *iAttribs, fl int pixel_format = 0; unsigned int matching; + int qAttrib = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; + int srgb = 0; + hwnd = CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0, 10, 10, NULL, NULL, SDL_Instance, NULL); @@ -547,6 +550,10 @@ static int WIN_GL_ChoosePixelFormatARB(SDL_VideoDevice *_this, int *iAttribs, fl &matching); } + /* Check whether we actually got an SRGB capable buffer */ + _this->gl_data->wglGetPixelFormatAttribivARB(hdc, pixel_format, 0, 1, &qAttrib, &srgb); + _this->gl_config.framebuffer_srgb_capable = srgb; + _this->gl_data->wglMakeCurrent(hdc, NULL); _this->gl_data->wglDeleteContext(hglrc); } @@ -835,9 +842,9 @@ int WIN_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext NULL, against spec. Since hdc is _supposed_ to be ignored if context is NULL, we either use the current GL window, or do nothing if we already have no current context. */ - if (window == NULL) { + if (!window) { window = SDL_GL_GetCurrentWindow(); - if (window == NULL) { + if (!window) { SDL_assert(SDL_GL_GetCurrentContext() == NULL); return 0; /* already done. */ } @@ -893,24 +900,6 @@ int WIN_GL_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context) return 0; } -SDL_bool WIN_GL_SetPixelFormatFrom(SDL_VideoDevice *_this, SDL_Window *fromWindow, SDL_Window *toWindow) -{ - HDC hfromdc = fromWindow->driverdata->hdc; - HDC htodc = toWindow->driverdata->hdc; - BOOL result; - - /* get the pixel format of the fromWindow */ - int pixel_format = GetPixelFormat(hfromdc); - PIXELFORMATDESCRIPTOR pfd; - SDL_memset(&pfd, 0, sizeof(pfd)); - DescribePixelFormat(hfromdc, pixel_format, sizeof(pfd), &pfd); - - /* set the pixel format of the toWindow */ - result = SetPixelFormat(htodc, pixel_format, &pfd); - - return result ? SDL_TRUE : SDL_FALSE; -} - #endif /* SDL_VIDEO_OPENGL_WGL */ #endif /* SDL_VIDEO_DRIVER_WINDOWS */ diff --git a/src/video/windows/SDL_windowsopengl.h b/src/video/windows/SDL_windowsopengl.h index dc1d140d..9780f664 100644 --- a/src/video/windows/SDL_windowsopengl.h +++ b/src/video/windows/SDL_windowsopengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -115,7 +115,6 @@ extern int WIN_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval); extern int WIN_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int WIN_GL_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context); extern void WIN_GL_InitExtensions(SDL_VideoDevice *_this); -extern SDL_bool WIN_GL_SetPixelFormatFrom(SDL_VideoDevice *_this, SDL_Window *fromWindow, SDL_Window *toWindow); #ifndef WGL_ARB_pixel_format #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 diff --git a/src/video/windows/SDL_windowsopengles.c b/src/video/windows/SDL_windowsopengles.c index d956eaf2..7847b904 100644 --- a/src/video/windows/SDL_windowsopengles.c +++ b/src/video/windows/SDL_windowsopengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -53,7 +53,7 @@ int WIN_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path) #endif } - if (_this->egl_data == NULL) { + if (!_this->egl_data) { return SDL_EGL_LoadLibrary(_this, NULL, EGL_DEFAULT_DISPLAY, _this->gl_config.egl_platform); } @@ -111,7 +111,7 @@ int WIN_GLES_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_Window *current_win = SDL_GL_GetCurrentWindow(); SDL_GLContext current_ctx = SDL_GL_GetCurrentContext(); - if (_this->egl_data == NULL) { + if (!_this->egl_data) { /* !!! FIXME: commenting out this assertion is (I think) incorrect; figure out why driver_loaded is wrong for ANGLE instead. --ryan. */ #if 0 /* When hint SDL_HINT_OPENGL_ES_DRIVER is set to "1" (e.g. for ANGLE support), _this->gl_config.driver_loaded can be 1, while the below lines function. */ SDL_assert(!_this->gl_config.driver_loaded); diff --git a/src/video/windows/SDL_windowsopengles.h b/src/video/windows/SDL_windowsopengles.h index 4cd8af41..5a40f777 100644 --- a/src/video/windows/SDL_windowsopengles.h +++ b/src/video/windows/SDL_windowsopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/windows/SDL_windowsshape.c b/src/video/windows/SDL_windowsshape.c deleted file mode 100644 index 25c3f5e0..00000000 --- a/src/video/windows/SDL_windowsshape.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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" - -#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__) - -#include "SDL_windowsshape.h" -#include "SDL_windowsvideo.h" - -SDL_WindowShaper *Win32_CreateShaper(SDL_Window *window) -{ - SDL_WindowShaper *result = (SDL_WindowShaper *)SDL_malloc(sizeof(SDL_WindowShaper)); - if (result == NULL) { - SDL_OutOfMemory(); - return NULL; - } - result->window = window; - result->mode.mode = ShapeModeDefault; - result->mode.parameters.binarizationCutoff = 1; - result->hasshape = SDL_FALSE; - result->driverdata = (SDL_ShapeData *)SDL_calloc(1, sizeof(SDL_ShapeData)); - if (!result->driverdata) { - SDL_free(result); - SDL_OutOfMemory(); - return NULL; - } - window->shaper = result; - - return result; -} - -static void CombineRectRegions(SDL_ShapeTree *node, void *closure) -{ - HRGN mask_region = *((HRGN *)closure), temp_region = NULL; - if (node->kind == OpaqueShape) { - /* Win32 API regions exclude their outline, so we widen the region by one pixel in each direction to include the real outline. */ - temp_region = CreateRectRgn(node->data.shape.x, node->data.shape.y, node->data.shape.x + node->data.shape.w + 1, node->data.shape.y + node->data.shape.h + 1); - if (mask_region != NULL) { - CombineRgn(mask_region, mask_region, temp_region, RGN_OR); - DeleteObject(temp_region); - } else { - *((HRGN *)closure) = temp_region; - } - } -} - -int Win32_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode) -{ - SDL_ShapeData *data; - HRGN mask_region = NULL; - - if ((shaper == NULL) || - (shape == NULL) || - ((shape->format->Amask == 0) && (shape_mode->mode != ShapeModeColorKey))) { - return SDL_INVALID_SHAPE_ARGUMENT; - } - - data = (SDL_ShapeData *)shaper->driverdata; - if (data->mask_tree != NULL) { - SDL_FreeShapeTree(&data->mask_tree); - } - data->mask_tree = SDL_CalculateShapeTree(*shape_mode, shape); - - SDL_TraverseShapeTree(data->mask_tree, &CombineRectRegions, &mask_region); - SDL_assert(mask_region != NULL); - - SetWindowRgn(shaper->window->driverdata->hwnd, mask_region, TRUE); - - return 0; -} - -#endif /* SDL_VIDEO_DRIVER_WINDOWS */ diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 66ad8e9b..1080a6a7 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,7 +31,6 @@ #include "SDL_windowsvideo.h" #include "SDL_windowsframebuffer.h" -#include "SDL_windowsshape.h" #include "SDL_windowsvulkan.h" #ifdef SDL_GDK_TEXTINPUT @@ -118,7 +117,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) } if (!data) { SDL_free(device); - SDL_OutOfMemory(); return NULL; } device->driverdata = data; @@ -176,7 +174,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) #endif device->CreateSDLWindow = WIN_CreateWindow; - device->CreateSDLWindowFrom = WIN_CreateWindowFrom; device->SetWindowTitle = WIN_SetWindowTitle; device->SetWindowIcon = WIN_SetWindowIcon; device->SetWindowPosition = WIN_SetWindowPosition; @@ -201,7 +198,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->SetWindowKeyboardGrab = WIN_SetWindowKeyboardGrab; #endif device->DestroyWindow = WIN_DestroyWindow; - device->GetWindowWMInfo = WIN_GetWindowWMInfo; #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer; @@ -212,9 +208,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->FlashWindow = WIN_FlashWindow; device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; device->SetWindowFocusable = WIN_SetWindowFocusable; - - device->shape_driver.CreateShaper = Win32_CreateShaper; - device->shape_driver.SetWindowShape = Win32_SetWindowShape; #endif #ifdef SDL_VIDEO_OPENGL_WGL @@ -284,7 +277,8 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->free = WIN_DeleteDevice; - device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; return device; } @@ -414,7 +408,7 @@ static void WIN_InitDPIAwareness(SDL_VideoDevice *_this) { const char *hint = SDL_GetHint("SDL_WINDOWS_DPI_AWARENESS"); - if (hint == NULL || SDL_strcmp(hint, "permonitorv2") == 0) { + if (!hint || SDL_strcmp(hint, "permonitorv2") == 0) { WIN_DeclareDPIAwarePerMonitorV2(_this); } else if (SDL_strcmp(hint, "permonitor") == 0) { WIN_DeclareDPIAwarePerMonitor(_this); @@ -550,7 +544,7 @@ int SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID) SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID); int adapterIndex = D3DADAPTER_DEFAULT; - if (pData == NULL) { + if (!pData) { SDL_SetError("Invalid display index"); adapterIndex = -1; /* make sure we return something invalid */ } else { @@ -633,12 +627,12 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * IDXGIAdapter *pDXGIAdapter; IDXGIOutput *pDXGIOutput; - if (adapterIndex == NULL) { + if (!adapterIndex) { SDL_InvalidParamError("adapterIndex"); return SDL_FALSE; } - if (outputIndex == NULL) { + if (!outputIndex) { SDL_InvalidParamError("outputIndex"); return SDL_FALSE; } @@ -646,7 +640,7 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * *adapterIndex = -1; *outputIndex = -1; - if (pData == NULL) { + if (!pData) { SDL_SetError("Invalid display index"); return SDL_FALSE; } @@ -717,8 +711,7 @@ SDL_bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) if (data->AreDpiAwarenessContextsEqual && data->GetThreadDpiAwarenessContext) { /* Windows 10, version 1607 */ - return (SDL_bool)data->AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, - data->GetThreadDpiAwarenessContext()); + return data->AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, data->GetThreadDpiAwarenessContext()); } #endif return SDL_FALSE; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 2be8eba3..925e4b2e 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -52,6 +52,10 @@ #include "SDL_windowswindow.h" +#ifndef USER_DEFAULT_SCREEN_DPI +#define USER_DEFAULT_SCREEN_DPI 96 +#endif + #if WINVER < 0x0601 /* Touch input definitions */ #define TWF_FINETOUCH 1 diff --git a/src/video/windows/SDL_windowsvulkan.c b/src/video/windows/SDL_windowsvulkan.c index f0b288b4..b2eee2b5 100644 --- a/src/video/windows/SDL_windowsvulkan.c +++ b/src/video/windows/SDL_windowsvulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,8 +35,6 @@ #include "SDL_windowsvulkan.h" -#include - int WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) { VkExtensionProperties *extensions = NULL; @@ -50,10 +48,10 @@ int WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan loader library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { path = "vulkan-1.dll"; } _this->vulkan_config.loader_handle = SDL_LoadObject(path); @@ -78,7 +76,7 @@ int WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) (PFN_vkEnumerateInstanceExtensionProperties) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } for (i = 0; i < extensionCount; i++) { @@ -112,25 +110,20 @@ void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { static const char *const extensionsForWin32[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME }; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; - } - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForWin32), - extensionsForWin32); + if(count) { *count = SDL_arraysize(extensionsForWin32); } + return extensionsForWin32; } SDL_bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { SDL_WindowData *windowData = window->driverdata; @@ -158,8 +151,7 @@ SDL_bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.flags = 0; createInfo.hinstance = windowData->hinstance; createInfo.hwnd = windowData->hwnd; - result = vkCreateWin32SurfaceKHR(instance, &createInfo, - NULL, surface); + result = vkCreateWin32SurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateWin32SurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); diff --git a/src/video/windows/SDL_windowsvulkan.h b/src/video/windows/SDL_windowsvulkan.h index 17b69687..088e2041 100644 --- a/src/video/windows/SDL_windowsvulkan.h +++ b/src/video/windows/SDL_windowsvulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -36,12 +36,12 @@ int WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 159a7abd..2cab61dc 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -33,13 +33,10 @@ #include "SDL_windowsvideo.h" #include "SDL_windowswindow.h" -#include "SDL_windowsshape.h" /* Dropfile support */ #include -#include - /* Dark mode support */ #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -157,21 +154,32 @@ static DWORD GetWindowStyleEx(SDL_Window *window) * Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates. * Can be called before we have a HWND. */ -static int WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x, int *y, int *width, int *height, SDL_bool use_current) +static int WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, DWORD styleEx, BOOL menu, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type) { SDL_VideoData *videodata = SDL_GetVideoDevice() ? SDL_GetVideoDevice()->driverdata : NULL; RECT rect; -#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) - UINT frame_dpi; -#endif /* Client rect, in points */ - SDL_RelativeToGlobalForWindow(window, - (use_current ? window->x : window->windowed.x), - (use_current ? window->y : window->windowed.y), - x, y); - *width = (use_current ? window->w : window->windowed.w); - *height = (use_current ? window->h : window->windowed.h); + switch (rect_type) { + case SDL_WINDOWRECT_CURRENT: + SDL_RelativeToGlobalForWindow(window, window->x, window->y, x, y); + *width = window->w; + *height = window->h; + break; + case SDL_WINDOWRECT_WINDOWED: + SDL_RelativeToGlobalForWindow(window, window->windowed.x, window->windowed.y, x, y); + *width = window->windowed.w; + *height = window->windowed.h; + break; + case SDL_WINDOWRECT_FLOATING: + SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, x, y); + *width = window->floating.w; + *height = window->floating.h; + break; + default: + /* Should never be here */ + SDL_assert_release(SDL_FALSE); + } /* Copy the client size in pixels into this rect structure, which we'll then adjust with AdjustWindowRectEx */ @@ -190,15 +198,16 @@ static int WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL m if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) { /* With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of AdjustWindowRectEx. */ - if (videodata != NULL) { + if (videodata) { + UINT frame_dpi; SDL_WindowData *data = window->driverdata; - frame_dpi = (data && videodata->GetDpiForWindow) ? videodata->GetDpiForWindow(data->hwnd) : 96; - if (videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, frame_dpi) == 0) { + frame_dpi = (data && videodata->GetDpiForWindow) ? videodata->GetDpiForWindow(data->hwnd) : USER_DEFAULT_SCREEN_DPI; + if (videodata->AdjustWindowRectExForDpi(&rect, style, menu, styleEx, frame_dpi) == 0) { return WIN_SetError("AdjustWindowRectExForDpi()"); } } } else { - if (AdjustWindowRectEx(&rect, style, menu, 0) == 0) { + if (AdjustWindowRectEx(&rect, style, menu, styleEx) == 0) { return WIN_SetError("AdjustWindowRectEx()"); } } @@ -213,32 +222,68 @@ static int WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL m #ifdef HIGHDPI_DEBUG SDL_Log("WIN_AdjustWindowRectWithStyle: in: %d, %d, %dx%d, returning: %d, %d, %dx%d, used dpi %d for frame calculation", - (use_current ? window->x : window->windowed.x), - (use_current ? window->y : window->windowed.y), - (use_current ? window->w : window->windowed.w), - (use_current ? window->h : window->windowed.h), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.x : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.x : window->x), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.y : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.y : window->y), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.w : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.w : window->w), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.h : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.h : window->h), *x, *y, *width, *height, frame_dpi); #endif return 0; } -static void WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_bool use_current) +int WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type) { SDL_WindowData *data = window->driverdata; HWND hwnd = data->hwnd; - DWORD style; + DWORD style, styleEx; BOOL menu; style = GetWindowLong(hwnd, GWL_STYLE); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); #if defined(__XBOXONE__) || defined(__XBOXSERIES__) menu = FALSE; #else menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); #endif - WIN_AdjustWindowRectWithStyle(window, style, menu, x, y, width, height, use_current); + return WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu, x, y, width, height, rect_type); } -int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags) +int WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi) +{ + SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + SDL_VideoData *videodata = videodevice ? videodevice->driverdata : NULL; + DWORD style, styleEx; + BOOL menu; + + style = GetWindowLong(hwnd, GWL_STYLE); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); +#if defined(__XBOXONE__) || defined(__XBOXSERIES__) + menu = FALSE; +#else + menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); +#endif + +#if defined(__XBOXONE__) || defined(__XBOXSERIES__) + AdjustWindowRectEx(lpRect, style, menu, styleEx); +#else + if (WIN_IsPerMonitorV2DPIAware(videodevice)) { + /* With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of AdjustWindowRectEx. */ + if (!frame_dpi) { + frame_dpi = videodata->GetDpiForWindow ? videodata->GetDpiForWindow(hwnd) : USER_DEFAULT_SCREEN_DPI; + } + if (!videodata->AdjustWindowRectExForDpi(lpRect, style, menu, styleEx, frame_dpi)) { + return WIN_SetError("AdjustWindowRectExForDpi()"); + } + } else { + if (!AdjustWindowRectEx(lpRect, style, menu, styleEx)) { + return WIN_SetError("AdjustWindowRectEx()"); + } + } +#endif + return 0; +} + +int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type) { SDL_Window *child_window; SDL_WindowData *data = window->driverdata; @@ -255,7 +300,7 @@ int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags) top = HWND_NOTOPMOST; } - WIN_AdjustWindowRect(window, &x, &y, &w, &h, SDL_TRUE); + WIN_AdjustWindowRect(window, &x, &y, &w, &h, rect_type); data->expected_resize = SDL_TRUE; if (SetWindowPos(hwnd, top, x, y, w, h, flags) == 0) { @@ -264,8 +309,8 @@ int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags) data->expected_resize = SDL_FALSE; /* Update any child windows */ - for (child_window = window->first_child; child_window != NULL; child_window = child_window->next_sibling) { - if (WIN_SetWindowPositionInternal(child_window, flags) < 0) { + for (child_window = window->first_child; child_window; child_window = child_window->next_sibling) { + if (WIN_SetWindowPositionInternal(child_window, flags, SDL_WINDOWRECT_CURRENT) < 0) { result = -1; } } @@ -278,15 +323,15 @@ static void SDLCALL WIN_MouseRelativeModeCenterChanged(void *userdata, const cha data->mouse_relative_mode_center = SDL_GetStringBoolean(hint, SDL_TRUE); } -static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd, HWND parent, SDL_bool created) +static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd, HWND parent) { SDL_VideoData *videodata = _this->driverdata; SDL_WindowData *data; /* Allocate the window data */ data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } data->window = window; data->hwnd = hwnd; @@ -297,8 +342,6 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd data->hdc = GetDC(hwnd); #endif data->hinstance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE); - data->created = created; - data->high_surrogate = 0; data->mouse_button_flags = (WPARAM)-1; data->last_pointer_update = (LPARAM)-1; data->videodata = videodata; @@ -351,11 +394,14 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd int w = rect.right; int h = rect.bottom; - if ((window->windowed.w && window->windowed.w != w) || (window->windowed.h && window->windowed.h != h)) { + if (window->flags & SDL_WINDOW_EXTERNAL) { + window->floating.w = window->windowed.w = window->w = w; + window->floating.h = window->windowed.h = window->h = h; + } else if ((window->windowed.w && window->windowed.w != w) || (window->windowed.h && window->windowed.h != h)) { /* We tried to create a window larger than the desktop and Windows didn't allow it. Override! */ int x, y; /* Figure out what the window area will be */ - WIN_AdjustWindowRect(window, &x, &y, &w, &h, SDL_FALSE); + WIN_AdjustWindowRect(window, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING); data->expected_resize = SDL_TRUE; SetWindowPos(hwnd, NULL, x, y, w, h, data->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); data->expected_resize = SDL_FALSE; @@ -371,6 +417,10 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd point.x = 0; point.y = 0; if (ClientToScreen(hwnd, &point)) { + if (window->flags & SDL_WINDOW_EXTERNAL) { + window->floating.x = window->windowed.x = point.x; + window->floating.y = window->windowed.y = point.y; + } window->x = point.x; window->y = point.y; } @@ -440,6 +490,34 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd data->initializing = SDL_FALSE; +#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) + if (window->flags & SDL_WINDOW_EXTERNAL) { + /* Query the title from the existing window */ + LPTSTR title; + int titleLen; + SDL_bool isstack; + + titleLen = GetWindowTextLength(hwnd); + title = SDL_small_alloc(TCHAR, titleLen + 1, &isstack); + if (title) { + titleLen = GetWindowText(hwnd, title, titleLen + 1); + } else { + titleLen = 0; + } + if (titleLen > 0) { + window->title = WIN_StringToUTF8(title); + } + if (title) { + SDL_small_free(title, isstack); + } + } +#endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WIN32_HWND_POINTER, data->hwnd); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WIN32_HDC_POINTER, data->hdc); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WIN32_INSTANCE_POINTER, data->hinstance); + /* All done! */ return 0; } @@ -461,14 +539,14 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window) ReleaseDC(data->hwnd, data->hdc); RemoveProp(data->hwnd, TEXT("SDL_WindowData")); #endif - if (data->created) { + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { DestroyWindow(data->hwnd); if (data->destroy_parent_with_window && data->parent) { DestroyWindow(data->parent); } } else { /* Restore any original event handler... */ - if (data->wndproc != NULL) { + if (data->wndproc) { #ifdef GWLP_WNDPROC SetWindowLongPtr(data->hwnd, GWLP_WNDPROC, (LONG_PTR)data->wndproc); @@ -478,6 +556,9 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window) #endif } } +#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) + SDL_free(data->rawinput); +#endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ SDL_free(data); } window->driverdata = NULL; @@ -490,12 +571,12 @@ static void WIN_ConstrainPopup(SDL_Window *window) SDL_Window *w; SDL_DisplayID displayID; SDL_Rect rect; - int abs_x = window->x; - int abs_y = window->y; + int abs_x = window->floating.x; + int abs_y = window->floating.y; int offset_x = 0, offset_y = 0; /* Calculate the total offset from the parents */ - for (w = window->parent; w->parent != NULL; w = w->parent) { + for (w = window->parent; w->parent; w = w->parent) { offset_x += w->x; offset_y += w->y; } @@ -508,17 +589,17 @@ static void WIN_ConstrainPopup(SDL_Window *window) /* Constrain the popup window to the display of the toplevel parent */ displayID = SDL_GetDisplayForWindow(w); SDL_GetDisplayBounds(displayID, &rect); - if (abs_x + window->w > rect.x + rect.w) { - abs_x -= (abs_x + window->w) - (rect.x + rect.w); + if (abs_x + window->floating.w > rect.x + rect.w) { + abs_x -= (abs_x + window->floating.w) - (rect.x + rect.w); } - if (abs_y + window->h > rect.y + rect.h) { - abs_y -= (abs_y + window->h) - (rect.y + rect.h); + if (abs_y + window->floating.h > rect.y + rect.h) { + abs_y -= (abs_y + window->floating.h) - (rect.y + rect.h); } abs_x = SDL_max(abs_x, rect.x); abs_y = SDL_max(abs_y, rect.y); - window->x = window->windowed.x = abs_x - offset_x; - window->y = window->windowed.y = abs_y - offset_y; + window->floating.x = abs_x - offset_x; + window->floating.y = abs_y - offset_y; } } @@ -527,7 +608,7 @@ static void WIN_SetKeyboardFocus(SDL_Window *window) SDL_Window *topmost = window; /* Find the topmost parent */ - while (topmost->parent != NULL) { + while (topmost->parent) { topmost = topmost->parent; } @@ -535,57 +616,67 @@ static void WIN_SetKeyboardFocus(SDL_Window *window) SDL_SetKeyboardFocus(window); } -int WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { - HWND hwnd, parent = NULL; - DWORD style = STYLE_BASIC; - DWORD styleEx = 0; - int x, y; - int w, h; + HWND hwnd = (HWND)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER, SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL)); + HWND parent = NULL; + if (hwnd) { + window->flags |= SDL_WINDOW_EXTERNAL; - if (SDL_WINDOW_IS_POPUP(window)) { - parent = window->parent->driverdata->hwnd; - } else if (window->flags & SDL_WINDOW_UTILITY) { - parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL); - } - - style |= GetWindowStyle(window); - styleEx |= GetWindowStyleEx(window); - - /* Figure out what the window area will be */ - WIN_ConstrainPopup(window); - WIN_AdjustWindowRectWithStyle(window, style, FALSE, &x, &y, &w, &h, SDL_FALSE); - - hwnd = CreateWindowEx(styleEx, SDL_Appname, TEXT(""), style, - x, y, w, h, parent, NULL, SDL_Instance, NULL); - if (!hwnd) { - return WIN_SetError("Couldn't create window"); - } - - WIN_UpdateDarkModeForHWND(hwnd); - - WIN_PumpEvents(_this); - - if (SetupWindowData(_this, window, hwnd, parent, SDL_TRUE) < 0) { - DestroyWindow(hwnd); - if (parent) { - DestroyWindow(parent); + if (SetupWindowData(_this, window, hwnd, parent) < 0) { + return -1; + } + } else { + DWORD style = STYLE_BASIC; + DWORD styleEx = 0; + int x, y; + int w, h; + + if (SDL_WINDOW_IS_POPUP(window)) { + parent = window->parent->driverdata->hwnd; + } else if (window->flags & SDL_WINDOW_UTILITY) { + parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL); + } + + style |= GetWindowStyle(window); + styleEx |= GetWindowStyleEx(window); + + /* Figure out what the window area will be */ + WIN_ConstrainPopup(window); + WIN_AdjustWindowRectWithStyle(window, style, styleEx, FALSE, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING); + + hwnd = CreateWindowEx(styleEx, SDL_Appname, TEXT(""), style, + x, y, w, h, parent, NULL, SDL_Instance, NULL); + if (!hwnd) { + return WIN_SetError("Couldn't create window"); + } + + WIN_UpdateDarkModeForHWND(hwnd); + + WIN_PumpEvents(_this); + + if (SetupWindowData(_this, window, hwnd, parent) < 0) { + DestroyWindow(hwnd); + if (parent) { + DestroyWindow(parent); + } + return -1; + } + + /* 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); + + if (window->flags & SDL_WINDOW_MINIMIZED) { + /* TODO: We have to clear SDL_WINDOW_HIDDEN here to ensure the window flags match the window state. The + window is already shown after this and windows with WS_MINIMIZE do not generate a WM_SHOWWINDOW. This + means you can't currently create a window that is initially hidden and is minimized when shown. + */ + window->flags &= ~SDL_WINDOW_HIDDEN; + ShowWindow(hwnd, SW_SHOWMINNOACTIVE); } - return -1; - } - - /* 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); - - if (window->flags & SDL_WINDOW_MINIMIZED) { - /* TODO: We have to clear SDL_WINDOW_HIDDEN here to ensure the window flags match the window state. The - window is already shown after this and windows with WS_MINIMIZE do not generate a WM_SHOWWINDOW. This - means you can't currently create a window that is initially hidden and is minimized when shown. - */ - window->flags &= ~SDL_WINDOW_HIDDEN; - ShowWindow(hwnd, SW_SHOWMINNOACTIVE); } +#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) /* FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs) */ if (window->flags & SDL_WINDOW_TRANSPARENT) { void *handle = SDL_LoadObject("dwmapi.dll"); @@ -605,99 +696,57 @@ int WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - if (!(window->flags & SDL_WINDOW_OPENGL)) { - return 0; - } + HWND share_hwnd = (HWND)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER, NULL); + if (share_hwnd) { + HDC hdc = GetDC(share_hwnd); + int pixel_format = GetPixelFormat(hdc); + PIXELFORMATDESCRIPTOR pfd; - /* The rest of this macro mess is for OpenGL or OpenGL ES windows */ -#ifdef SDL_VIDEO_OPENGL_ES2 - if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES || - SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, SDL_FALSE)) && -#ifdef SDL_VIDEO_OPENGL_WGL - (!_this->gl_data || WIN_GL_UseEGL(_this)) -#endif /* SDL_VIDEO_OPENGL_WGL */ - ) { -#ifdef SDL_VIDEO_OPENGL_EGL - if (WIN_GLES_SetupWindow(_this, window) < 0) { + SDL_zero(pfd); + DescribePixelFormat(hdc, pixel_format, sizeof(pfd), &pfd); + ReleaseDC(share_hwnd, hdc); + + if (!SetPixelFormat(window->driverdata->hdc, pixel_format, &pfd)) { WIN_DestroyWindow(_this, window); - return -1; + return WIN_SetError("SetPixelFormat()"); } - return 0; + } else { + if (!(window->flags & SDL_WINDOW_OPENGL)) { + return 0; + } + + /* The rest of this macro mess is for OpenGL or OpenGL ES windows */ +#ifdef SDL_VIDEO_OPENGL_ES2 + if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES || + SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, SDL_FALSE)) && +#ifdef SDL_VIDEO_OPENGL_WGL + (!_this->gl_data || WIN_GL_UseEGL(_this)) +#endif /* SDL_VIDEO_OPENGL_WGL */ + ) { +#ifdef SDL_VIDEO_OPENGL_EGL + if (WIN_GLES_SetupWindow(_this, window) < 0) { + WIN_DestroyWindow(_this, window); + return -1; + } + return 0; #else - return SDL_SetError("Could not create GLES window surface (EGL support not configured)"); + return SDL_SetError("Could not create GLES window surface (EGL support not configured)"); #endif /* SDL_VIDEO_OPENGL_EGL */ - } + } #endif /* SDL_VIDEO_OPENGL_ES2 */ #ifdef SDL_VIDEO_OPENGL_WGL - if (WIN_GL_SetupWindow(_this, window) < 0) { - WIN_DestroyWindow(_this, window); - return -1; - } -#else - return SDL_SetError("Could not create GL window (WGL support not configured)"); -#endif - - return 0; -} - -int WIN_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ -#if defined(__XBOXONE__) || defined(__XBOXSERIES__) - return -1; -#else - HWND hwnd = (HWND)data; - LPTSTR title; - int titleLen; - SDL_bool isstack; - - /* Query the title from the existing window */ - titleLen = GetWindowTextLength(hwnd); - title = SDL_small_alloc(TCHAR, titleLen + 1, &isstack); - if (title) { - titleLen = GetWindowText(hwnd, title, titleLen + 1); - } else { - titleLen = 0; - } - if (titleLen > 0) { - window->title = WIN_StringToUTF8(title); - } - if (title) { - SDL_small_free(title, isstack); - } - - if (SetupWindowData(_this, window, hwnd, GetParent(hwnd), SDL_FALSE) < 0) { - return -1; - } - -#ifdef SDL_VIDEO_OPENGL_WGL - { - const char *hint = SDL_GetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT); - if (hint) { - /* This hint is a pointer (in string form) of the address of - the window to share a pixel format with - */ - SDL_Window *otherWindow = NULL; - (void)SDL_sscanf(hint, "%p", (void **)&otherWindow); - - /* Do some error checking on the pointer */ - if (otherWindow != NULL && otherWindow->magic == &_this->window_magic) { - /* If the otherWindow has SDL_WINDOW_OPENGL set, set it for the new window as well */ - if (otherWindow->flags & SDL_WINDOW_OPENGL) { - window->flags |= SDL_WINDOW_OPENGL; - if (!WIN_GL_SetPixelFormatFrom(_this, otherWindow, window)) { - return -1; - } - } - } - } else if (window->flags & SDL_WINDOW_OPENGL) { - /* Try to set up the pixel format, if it hasn't been set by the application */ - WIN_GL_SetupWindow(_this, window); + if (WIN_GL_SetupWindow(_this, window) < 0) { + WIN_DestroyWindow(_this, window); + return -1; } - } +#else + return SDL_SetError("Could not create GL window (WGL support not configured)"); #endif - return 0; + } #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ + + return 0; } void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) @@ -760,7 +809,7 @@ int WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i SDL_small_free(icon_bmp, isstack); - if (hicon == NULL) { + if (!hicon) { retval = SDL_SetError("SetWindowIcon() failed, error %08X", (unsigned int)GetLastError()); } @@ -780,13 +829,29 @@ int WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) /* HighDPI support: removed SWP_NOSIZE. If the move results in a DPI change, we need to allow * the window to resize (e.g. AdjustWindowRectExForDpi frame sizes are different). */ - WIN_ConstrainPopup(window); - return WIN_SetWindowPositionInternal(window, window->driverdata->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (!(window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { + WIN_ConstrainPopup(window); + return WIN_SetWindowPositionInternal(window, + window->driverdata->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | + SWP_NOACTIVATE, SDL_WINDOWRECT_FLOATING); + } else { + window->driverdata->floating_rect_pending = SDL_TRUE; + } + } else { + return SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_TRUE); + } + + return 0; } void WIN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { - WIN_SetWindowPositionInternal(window, window->driverdata->copybits_flag | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) { + WIN_SetWindowPositionInternal(window, window->driverdata->copybits_flag | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_FLOATING); + } else { + window->driverdata->floating_rect_pending = SDL_TRUE; + } } int WIN_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right) @@ -913,7 +978,7 @@ void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_Window *new_focus = window->parent; /* Find the highest level window that isn't being hidden or destroyed. */ - while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + while (new_focus->parent && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } @@ -973,14 +1038,24 @@ void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) void WIN_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) { - /* Other platforms refuse to maximize a non-resizable window, and with win32, - the OS resizes the window weirdly (covering the taskbar) if you don't have - the STYLE_RESIZABLE flag set. So just forbid it for now. */ - if (window->flags & SDL_WINDOW_RESIZABLE) { - SDL_WindowData *data = window->driverdata; - HWND hwnd = data->hwnd; + SDL_WindowData *data = window->driverdata; + HWND hwnd = data->hwnd; + data->expected_resize = SDL_TRUE; + ShowWindow(hwnd, SW_MAXIMIZE); + data->expected_resize = SDL_FALSE; + + /* Clamp the maximized window size to the max window size. + * This is automatic if maximizing from the window controls. + */ + if (window->max_w || window->max_h) { + int fx, fy, fw, fh; + + window->windowed.w = window->max_w ? SDL_min(window->w, window->max_w) : window->windowed.w; + window->windowed.h = window->max_h ? SDL_min(window->h, window->max_h) : window->windowed.h; + WIN_AdjustWindowRect(window, &fx, &fy, &fw, &fh, SDL_WINDOWRECT_WINDOWED); + data->expected_resize = SDL_TRUE; - ShowWindow(hwnd, SW_MAXIMIZE); + SetWindowPos(hwnd, HWND_TOP, fx, fy, fw, fh, data->copybits_flag | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOACTIVATE); data->expected_resize = SDL_FALSE; } } @@ -1003,7 +1078,7 @@ void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool data->in_border_change = SDL_TRUE; SetWindowLong(hwnd, GWL_STYLE, style); - WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); data->in_border_change = SDL_FALSE; } @@ -1022,7 +1097,7 @@ void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top) { - WIN_SetWindowPositionInternal(window, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); } void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -1037,48 +1112,50 @@ void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) /** * Reconfigures the window to fill the given display, if fullscreen is true, otherwise restores the window. */ -void WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +int WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) SDL_DisplayData *displaydata = display->driverdata; SDL_WindowData *data = window->driverdata; HWND hwnd = data->hwnd; MONITORINFO minfo; - DWORD style; + DWORD style, styleEx; HWND top; int x, y; int w, h; - if (!fullscreen && (window->flags & SDL_WINDOW_FULLSCREEN)) { - /* Resizing the window on hide causes problems restoring it in Wine, and it's unnecessary. - * Also, Windows would preview the minimized window with the wrong size. - */ - return; - } - #ifdef HIGHDPI_DEBUG SDL_Log("WIN_SetWindowFullscreen: %d", (int)fullscreen); #endif + /* Early out if already not in fullscreen, or the styling on + * external windows may end up being overridden. + */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && !fullscreen) { + return 0; + } + if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) { top = HWND_TOPMOST; } else { top = HWND_NOTOPMOST; } - style = GetWindowLong(hwnd, GWL_STYLE); - style &= ~STYLE_MASK; - style |= GetWindowStyle(window); - /* Use GetMonitorInfo instead of WIN_GetDisplayBounds because we want the monitor bounds in Windows coordinates (pixels) rather than SDL coordinates (points). */ SDL_zero(minfo); minfo.cbSize = sizeof(MONITORINFO); if (!GetMonitorInfo(displaydata->MonitorHandle, &minfo)) { SDL_SetError("GetMonitorInfo failed"); - return; + return -1; } + SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); + if (fullscreen) { x = minfo.rcMonitor.left; y = minfo.rcMonitor.top; @@ -1103,11 +1180,14 @@ void WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_Vid */ if (data->windowed_mode_was_maximized && !data->in_window_deactivation) { style |= WS_MAXIMIZE; - data->windowed_mode_was_maximized = SDL_FALSE; } menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); - WIN_AdjustWindowRectWithStyle(window, style, menu, &x, &y, &w, &h, SDL_FALSE); + WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu, + &x, &y, + &w, &h, + data->windowed_mode_was_maximized ? SDL_WINDOWRECT_WINDOWED : SDL_WINDOWRECT_FLOATING); + data->windowed_mode_was_maximized = SDL_FALSE; } SetWindowLong(hwnd, GWL_STYLE, style); data->expected_resize = SDL_TRUE; @@ -1119,6 +1199,7 @@ void WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_Vid #endif #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ + return 0; } #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) @@ -1159,12 +1240,10 @@ void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t filename_utf8 = WIN_StringToUTF8(data->ICMFileName); if (filename_utf8) { iccProfileData = SDL_LoadFile(filename_utf8, size); - if (iccProfileData == NULL) { + if (!iccProfileData) { SDL_SetError("Could not open ICC profile"); } SDL_free(filename_utf8); - } else { - SDL_OutOfMemory(); } return iccProfileData; } @@ -1235,33 +1314,9 @@ void WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_b void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { - if (window->shaper) { - SDL_ShapeData *shapedata = (SDL_ShapeData *)window->shaper->driverdata; - if (shapedata) { - if (shapedata->mask_tree) { - SDL_FreeShapeTree(&shapedata->mask_tree); - } - SDL_free(shapedata); - } - SDL_free(window->shaper); - window->shaper = NULL; - } - CleanupWindowData(_this, window); } -int WIN_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - const SDL_WindowData *data = (const SDL_WindowData *)window->driverdata; - - info->subsystem = SDL_SYSWM_WINDOWS; - info->info.win.window = data->hwnd; - info->info.win.hdc = data->hdc; - info->info.win.hinstance = data->hinstance; - - return 0; -} - /* * Creates a HelperWindow used for DirectInput. */ @@ -1294,7 +1349,7 @@ int SDL_HelperWindowCreate(void) CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, hInstance, NULL); - if (SDL_HelperWindow == NULL) { + if (!SDL_HelperWindow) { UnregisterClass(SDL_HelperWindowClassName, hInstance); return WIN_SetError("Unable to create Helper Window"); } @@ -1333,13 +1388,13 @@ void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; - if (data == NULL || !data->hwnd) { + if (!data || !data->hwnd) { /* The window wasn't fully initialized */ return; } if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { - WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_NOSIZE | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); } } diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 527fee78..49659a10 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,6 +34,13 @@ extern "C" { #endif +typedef enum SDL_WindowRect +{ + SDL_WINDOWRECT_CURRENT, + SDL_WINDOWRECT_WINDOWED, + SDL_WINDOWRECT_FLOATING +} SDL_WindowRect; + struct SDL_WindowData { SDL_Window *window; @@ -45,7 +52,6 @@ struct SDL_WindowData HBITMAP hbm; WNDPROC wndproc; HHOOK keyboard_hook; - SDL_bool created; WPARAM mouse_button_flags; LPARAM last_pointer_update; WCHAR high_surrogate; @@ -53,6 +59,7 @@ struct SDL_WindowData SDL_bool expected_resize; SDL_bool in_border_change; SDL_bool in_title_click; + SDL_bool floating_rect_pending; Uint8 focus_click_pending; SDL_bool skip_update_clipcursor; Uint64 last_updated_clipcursor; @@ -60,6 +67,13 @@ struct SDL_WindowData SDL_bool windowed_mode_was_maximized; SDL_bool in_window_deactivation; RECT cursor_clipped_rect; +#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) + RAWINPUT *rawinput; + UINT rawinput_offset; + UINT rawinput_size; + UINT rawinput_count; + Uint64 last_rawinput_poll; +#endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ SDL_Point last_raw_mouse_position; SDL_bool mouse_tracked; SDL_bool destroy_parent_with_window; @@ -75,8 +89,7 @@ struct SDL_WindowData UINT copybits_flag; }; -extern int WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int WIN_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +extern int WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern int WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); extern int WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); @@ -93,23 +106,24 @@ extern void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); extern void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); extern void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top); -extern void WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void WIN_UpdateWindowICCProfile(SDL_Window *window, SDL_bool send_event); extern void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); extern void WIN_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window); extern void WIN_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int WIN_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); extern void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window); extern void WIN_UpdateClipCursor(SDL_Window *window); extern int WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern void WIN_UpdateDarkModeForHWND(HWND hwnd); -extern int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags); +extern int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type); extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern int WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); +extern int WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type); +extern int WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/video/windows/wmmsg.h b/src/video/windows/wmmsg.h index cefe9109..2e1571dc 100644 --- a/src/video/windows/wmmsg.h +++ b/src/video/windows/wmmsg.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtevents.cpp b/src/video/winrt/SDL_winrtevents.cpp index 2b7e3ff8..0a4cdaab 100644 --- a/src/video/winrt/SDL_winrtevents.cpp +++ b/src/video/winrt/SDL_winrtevents.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtevents_c.h b/src/video/winrt/SDL_winrtevents_c.h index f5871caf..e77e37d9 100644 --- a/src/video/winrt/SDL_winrtevents_c.h +++ b/src/video/winrt/SDL_winrtevents_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -63,9 +63,8 @@ extern void WINRT_ProcessPointerWheelChangedEvent(SDL_Window *window, Windows::U extern void WINRT_ProcessMouseMovedEvent(SDL_Window *window, Windows::Devices::Input::MouseEventArgs ^ args); /* Keyboard */ -extern void WINRT_ProcessKeyDownEvent(Windows::UI::Core::KeyEventArgs ^ args); -extern void WINRT_ProcessKeyUpEvent(Windows::UI::Core::KeyEventArgs ^ args); -extern void WINRT_ProcessCharacterReceivedEvent(Windows::UI::Core::CharacterReceivedEventArgs ^ args); +extern void WINRT_ProcessAcceleratorKeyActivated(Windows::UI::Core::AcceleratorKeyEventArgs ^ args); +extern void WINRT_ProcessCharacterReceivedEvent(SDL_Window *window, Windows::UI::Core::CharacterReceivedEventArgs ^ args); #if NTDDI_VERSION >= NTDDI_WIN10 extern void WINTRT_InitialiseInputPaneEvents(SDL_VideoDevice *_this); diff --git a/src/video/winrt/SDL_winrtgamebar.cpp b/src/video/winrt/SDL_winrtgamebar.cpp index f9ffa2c5..63d27b6e 100644 --- a/src/video/winrt/SDL_winrtgamebar.cpp +++ b/src/video/winrt/SDL_winrtgamebar.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -115,7 +115,7 @@ static void WINRT_HandleGameBarIsInputRedirected_MainThread() return; } gameBar = WINRT_GetGameBar(); - if (gameBar == NULL) { + if (!gameBar) { /* Shouldn't happen, but just in case... */ return; } @@ -166,11 +166,11 @@ void WINRT_QuitGameBar(SDL_VideoDevice *_this) { SDL_VideoData *driverdata; IGameBarStatics_ *gameBar; - if (_this == NULL || _this->driverdata == NULL) { + if (!_this || !_this->driverdata) { return; } gameBar = WINRT_GetGameBar(); - if (gameBar == NULL) { + if (!gameBar) { return; } driverdata = _this->driverdata; diff --git a/src/video/winrt/SDL_winrtgamebar_cpp.h b/src/video/winrt/SDL_winrtgamebar_cpp.h index 6cadd11c..51c54628 100644 --- a/src/video/winrt/SDL_winrtgamebar_cpp.h +++ b/src/video/winrt/SDL_winrtgamebar_cpp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtkeyboard.cpp b/src/video/winrt/SDL_winrtkeyboard.cpp index bf82e8f9..b1aa6b03 100644 --- a/src/video/winrt/SDL_winrtkeyboard.cpp +++ b/src/video/winrt/SDL_winrtkeyboard.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -34,365 +34,79 @@ extern "C" { #include "../../events/SDL_keyboard_c.h" } -static SDL_Scancode WinRT_Official_Keycodes[] = { - SDL_SCANCODE_UNKNOWN, /* VirtualKey.None -- 0 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.LeftButton -- 1 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.RightButton -- 2 */ - SDL_SCANCODE_CANCEL, /* VirtualKey.Cancel -- 3 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.MiddleButton -- 4 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.XButton1 -- 5 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.XButton2 -- 6 */ - SDL_SCANCODE_UNKNOWN, /* -- 7 */ - SDL_SCANCODE_BACKSPACE, /* VirtualKey.Back -- 8 */ - SDL_SCANCODE_TAB, /* VirtualKey.Tab -- 9 */ - SDL_SCANCODE_UNKNOWN, /* -- 10 */ - SDL_SCANCODE_UNKNOWN, /* -- 11 */ - SDL_SCANCODE_CLEAR, /* VirtualKey.Clear -- 12 */ - SDL_SCANCODE_RETURN, /* VirtualKey.Enter -- 13 */ - SDL_SCANCODE_UNKNOWN, /* -- 14 */ - SDL_SCANCODE_UNKNOWN, /* -- 15 */ - SDL_SCANCODE_LSHIFT, /* VirtualKey.Shift -- 16 */ - SDL_SCANCODE_LCTRL, /* VirtualKey.Control -- 17 */ - SDL_SCANCODE_MENU, /* VirtualKey.Menu -- 18 */ - SDL_SCANCODE_PAUSE, /* VirtualKey.Pause -- 19 */ - SDL_SCANCODE_CAPSLOCK, /* VirtualKey.CapitalLock -- 20 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Kana or VirtualKey.Hangul -- 21 */ - SDL_SCANCODE_UNKNOWN, /* -- 22 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Junja -- 23 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Final -- 24 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Hanja or VirtualKey.Kanji -- 25 */ - SDL_SCANCODE_UNKNOWN, /* -- 26 */ - SDL_SCANCODE_ESCAPE, /* VirtualKey.Escape -- 27 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Convert -- 28 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.NonConvert -- 29 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Accept -- 30 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.ModeChange -- 31 (maybe SDL_SCANCODE_MODE ?) */ - SDL_SCANCODE_SPACE, /* VirtualKey.Space -- 32 */ - SDL_SCANCODE_PAGEUP, /* VirtualKey.PageUp -- 33 */ - SDL_SCANCODE_PAGEDOWN, /* VirtualKey.PageDown -- 34 */ - SDL_SCANCODE_END, /* VirtualKey.End -- 35 */ - SDL_SCANCODE_HOME, /* VirtualKey.Home -- 36 */ - SDL_SCANCODE_LEFT, /* VirtualKey.Left -- 37 */ - SDL_SCANCODE_UP, /* VirtualKey.Up -- 38 */ - SDL_SCANCODE_RIGHT, /* VirtualKey.Right -- 39 */ - SDL_SCANCODE_DOWN, /* VirtualKey.Down -- 40 */ - SDL_SCANCODE_SELECT, /* VirtualKey.Select -- 41 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Print -- 42 (maybe SDL_SCANCODE_PRINTSCREEN ?) */ - SDL_SCANCODE_EXECUTE, /* VirtualKey.Execute -- 43 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Snapshot -- 44 */ - SDL_SCANCODE_INSERT, /* VirtualKey.Insert -- 45 */ - SDL_SCANCODE_DELETE, /* VirtualKey.Delete -- 46 */ - SDL_SCANCODE_HELP, /* VirtualKey.Help -- 47 */ - SDL_SCANCODE_0, /* VirtualKey.Number0 -- 48 */ - SDL_SCANCODE_1, /* VirtualKey.Number1 -- 49 */ - SDL_SCANCODE_2, /* VirtualKey.Number2 -- 50 */ - SDL_SCANCODE_3, /* VirtualKey.Number3 -- 51 */ - SDL_SCANCODE_4, /* VirtualKey.Number4 -- 52 */ - SDL_SCANCODE_5, /* VirtualKey.Number5 -- 53 */ - SDL_SCANCODE_6, /* VirtualKey.Number6 -- 54 */ - SDL_SCANCODE_7, /* VirtualKey.Number7 -- 55 */ - SDL_SCANCODE_8, /* VirtualKey.Number8 -- 56 */ - SDL_SCANCODE_9, /* VirtualKey.Number9 -- 57 */ - SDL_SCANCODE_UNKNOWN, /* -- 58 */ - SDL_SCANCODE_UNKNOWN, /* -- 59 */ - SDL_SCANCODE_UNKNOWN, /* -- 60 */ - SDL_SCANCODE_UNKNOWN, /* -- 61 */ - SDL_SCANCODE_UNKNOWN, /* -- 62 */ - SDL_SCANCODE_UNKNOWN, /* -- 63 */ - SDL_SCANCODE_UNKNOWN, /* -- 64 */ - SDL_SCANCODE_A, /* VirtualKey.A -- 65 */ - SDL_SCANCODE_B, /* VirtualKey.B -- 66 */ - SDL_SCANCODE_C, /* VirtualKey.C -- 67 */ - SDL_SCANCODE_D, /* VirtualKey.D -- 68 */ - SDL_SCANCODE_E, /* VirtualKey.E -- 69 */ - SDL_SCANCODE_F, /* VirtualKey.F -- 70 */ - SDL_SCANCODE_G, /* VirtualKey.G -- 71 */ - SDL_SCANCODE_H, /* VirtualKey.H -- 72 */ - SDL_SCANCODE_I, /* VirtualKey.I -- 73 */ - SDL_SCANCODE_J, /* VirtualKey.J -- 74 */ - SDL_SCANCODE_K, /* VirtualKey.K -- 75 */ - SDL_SCANCODE_L, /* VirtualKey.L -- 76 */ - SDL_SCANCODE_M, /* VirtualKey.M -- 77 */ - SDL_SCANCODE_N, /* VirtualKey.N -- 78 */ - SDL_SCANCODE_O, /* VirtualKey.O -- 79 */ - SDL_SCANCODE_P, /* VirtualKey.P -- 80 */ - SDL_SCANCODE_Q, /* VirtualKey.Q -- 81 */ - SDL_SCANCODE_R, /* VirtualKey.R -- 82 */ - SDL_SCANCODE_S, /* VirtualKey.S -- 83 */ - SDL_SCANCODE_T, /* VirtualKey.T -- 84 */ - SDL_SCANCODE_U, /* VirtualKey.U -- 85 */ - SDL_SCANCODE_V, /* VirtualKey.V -- 86 */ - SDL_SCANCODE_W, /* VirtualKey.W -- 87 */ - SDL_SCANCODE_X, /* VirtualKey.X -- 88 */ - SDL_SCANCODE_Y, /* VirtualKey.Y -- 89 */ - SDL_SCANCODE_Z, /* VirtualKey.Z -- 90 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.LeftWindows -- 91 (maybe SDL_SCANCODE_APPLICATION or SDL_SCANCODE_LGUI ?) */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.RightWindows -- 92 (maybe SDL_SCANCODE_APPLICATION or SDL_SCANCODE_RGUI ?) */ - SDL_SCANCODE_APPLICATION, /* VirtualKey.Application -- 93 */ - SDL_SCANCODE_UNKNOWN, /* -- 94 */ - SDL_SCANCODE_SLEEP, /* VirtualKey.Sleep -- 95 */ - SDL_SCANCODE_KP_0, /* VirtualKey.NumberPad0 -- 96 */ - SDL_SCANCODE_KP_1, /* VirtualKey.NumberPad1 -- 97 */ - SDL_SCANCODE_KP_2, /* VirtualKey.NumberPad2 -- 98 */ - SDL_SCANCODE_KP_3, /* VirtualKey.NumberPad3 -- 99 */ - SDL_SCANCODE_KP_4, /* VirtualKey.NumberPad4 -- 100 */ - SDL_SCANCODE_KP_5, /* VirtualKey.NumberPad5 -- 101 */ - SDL_SCANCODE_KP_6, /* VirtualKey.NumberPad6 -- 102 */ - SDL_SCANCODE_KP_7, /* VirtualKey.NumberPad7 -- 103 */ - SDL_SCANCODE_KP_8, /* VirtualKey.NumberPad8 -- 104 */ - SDL_SCANCODE_KP_9, /* VirtualKey.NumberPad9 -- 105 */ - SDL_SCANCODE_KP_MULTIPLY, /* VirtualKey.Multiply -- 106 */ - SDL_SCANCODE_KP_PLUS, /* VirtualKey.Add -- 107 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Separator -- 108 */ - SDL_SCANCODE_KP_MINUS, /* VirtualKey.Subtract -- 109 */ - SDL_SCANCODE_UNKNOWN, /* VirtualKey.Decimal -- 110 (maybe SDL_SCANCODE_DECIMALSEPARATOR, SDL_SCANCODE_KP_DECIMAL, or SDL_SCANCODE_KP_PERIOD ?) */ - SDL_SCANCODE_KP_DIVIDE, /* VirtualKey.Divide -- 111 */ - SDL_SCANCODE_F1, /* VirtualKey.F1 -- 112 */ - SDL_SCANCODE_F2, /* VirtualKey.F2 -- 113 */ - SDL_SCANCODE_F3, /* VirtualKey.F3 -- 114 */ - SDL_SCANCODE_F4, /* VirtualKey.F4 -- 115 */ - SDL_SCANCODE_F5, /* VirtualKey.F5 -- 116 */ - SDL_SCANCODE_F6, /* VirtualKey.F6 -- 117 */ - SDL_SCANCODE_F7, /* VirtualKey.F7 -- 118 */ - SDL_SCANCODE_F8, /* VirtualKey.F8 -- 119 */ - SDL_SCANCODE_F9, /* VirtualKey.F9 -- 120 */ - SDL_SCANCODE_F10, /* VirtualKey.F10 -- 121 */ - SDL_SCANCODE_F11, /* VirtualKey.F11 -- 122 */ - SDL_SCANCODE_F12, /* VirtualKey.F12 -- 123 */ - SDL_SCANCODE_F13, /* VirtualKey.F13 -- 124 */ - SDL_SCANCODE_F14, /* VirtualKey.F14 -- 125 */ - SDL_SCANCODE_F15, /* VirtualKey.F15 -- 126 */ - SDL_SCANCODE_F16, /* VirtualKey.F16 -- 127 */ - SDL_SCANCODE_F17, /* VirtualKey.F17 -- 128 */ - SDL_SCANCODE_F18, /* VirtualKey.F18 -- 129 */ - SDL_SCANCODE_F19, /* VirtualKey.F19 -- 130 */ - SDL_SCANCODE_F20, /* VirtualKey.F20 -- 131 */ - SDL_SCANCODE_F21, /* VirtualKey.F21 -- 132 */ - SDL_SCANCODE_F22, /* VirtualKey.F22 -- 133 */ - SDL_SCANCODE_F23, /* VirtualKey.F23 -- 134 */ - SDL_SCANCODE_F24, /* VirtualKey.F24 -- 135 */ - SDL_SCANCODE_UNKNOWN, /* -- 136 */ - SDL_SCANCODE_UNKNOWN, /* -- 137 */ - SDL_SCANCODE_UNKNOWN, /* -- 138 */ - SDL_SCANCODE_UNKNOWN, /* -- 139 */ - SDL_SCANCODE_UNKNOWN, /* -- 140 */ - SDL_SCANCODE_UNKNOWN, /* -- 141 */ - SDL_SCANCODE_UNKNOWN, /* -- 142 */ - SDL_SCANCODE_UNKNOWN, /* -- 143 */ - SDL_SCANCODE_NUMLOCKCLEAR, /* VirtualKey.NumberKeyLock -- 144 */ - SDL_SCANCODE_SCROLLLOCK, /* VirtualKey.Scroll -- 145 */ - SDL_SCANCODE_UNKNOWN, /* -- 146 */ - SDL_SCANCODE_UNKNOWN, /* -- 147 */ - SDL_SCANCODE_UNKNOWN, /* -- 148 */ - SDL_SCANCODE_UNKNOWN, /* -- 149 */ - SDL_SCANCODE_UNKNOWN, /* -- 150 */ - SDL_SCANCODE_UNKNOWN, /* -- 151 */ - SDL_SCANCODE_UNKNOWN, /* -- 152 */ - SDL_SCANCODE_UNKNOWN, /* -- 153 */ - SDL_SCANCODE_UNKNOWN, /* -- 154 */ - SDL_SCANCODE_UNKNOWN, /* -- 155 */ - SDL_SCANCODE_UNKNOWN, /* -- 156 */ - SDL_SCANCODE_UNKNOWN, /* -- 157 */ - SDL_SCANCODE_UNKNOWN, /* -- 158 */ - SDL_SCANCODE_UNKNOWN, /* -- 159 */ - SDL_SCANCODE_LSHIFT, /* VirtualKey.LeftShift -- 160 */ - SDL_SCANCODE_RSHIFT, /* VirtualKey.RightShift -- 161 */ - SDL_SCANCODE_LCTRL, /* VirtualKey.LeftControl -- 162 */ - SDL_SCANCODE_RCTRL, /* VirtualKey.RightControl -- 163 */ - SDL_SCANCODE_MENU, /* VirtualKey.LeftMenu -- 164 */ - SDL_SCANCODE_MENU, /* VirtualKey.RightMenu -- 165 */ - SDL_SCANCODE_AC_BACK, /* VirtualKey.GoBack -- 166 : The go back key. */ - SDL_SCANCODE_AC_FORWARD, /* VirtualKey.GoForward -- 167 : The go forward key. */ - SDL_SCANCODE_AC_REFRESH, /* VirtualKey.Refresh -- 168 : The refresh key. */ - SDL_SCANCODE_AC_STOP, /* VirtualKey.Stop -- 169 : The stop key. */ - SDL_SCANCODE_AC_SEARCH, /* VirtualKey.Search -- 170 : The search key. */ - SDL_SCANCODE_AC_BOOKMARKS, /* VirtualKey.Favorites -- 171 : The favorites key. */ - SDL_SCANCODE_AC_HOME /* VirtualKey.GoHome -- 172 : The go home key. */ -}; +#include "SDL_winrtvideo_cpp.h" -/* Attempt to translate a keycode that isn't listed in WinRT's VirtualKey enum. - */ -static SDL_Scancode WINRT_TranslateUnofficialKeycode(int keycode) +static SDL_Scancode WINRT_TranslateKeycode(Windows::System::VirtualKey virtualKey, const Windows::UI::Core::CorePhysicalKeyStatus& keyStatus) { - switch (keycode) { - case 173: - return SDL_SCANCODE_MUTE; /* VK_VOLUME_MUTE */ - case 174: - return SDL_SCANCODE_VOLUMEDOWN; /* VK_VOLUME_DOWN */ - case 175: - return SDL_SCANCODE_VOLUMEUP; /* VK_VOLUME_UP */ - case 176: - return SDL_SCANCODE_AUDIONEXT; /* VK_MEDIA_NEXT_TRACK */ - case 177: - return SDL_SCANCODE_AUDIOPREV; /* VK_MEDIA_PREV_TRACK */ - // case 178: return ; /* VK_MEDIA_STOP */ - case 179: - return SDL_SCANCODE_AUDIOPLAY; /* VK_MEDIA_PLAY_PAUSE */ - case 180: - return SDL_SCANCODE_MAIL; /* VK_LAUNCH_MAIL */ - case 181: - return SDL_SCANCODE_MEDIASELECT; /* VK_LAUNCH_MEDIA_SELECT */ - // case 182: return ; /* VK_LAUNCH_APP1 */ - case 183: - return SDL_SCANCODE_CALCULATOR; /* VK_LAUNCH_APP2 */ - // case 184: return ; /* ... reserved ... */ - // case 185: return ; /* ... reserved ... */ - case 186: - return SDL_SCANCODE_SEMICOLON; /* VK_OEM_1, ';:' key on US standard keyboards */ - case 187: - return SDL_SCANCODE_EQUALS; /* VK_OEM_PLUS */ - case 188: - return SDL_SCANCODE_COMMA; /* VK_OEM_COMMA */ - case 189: - return SDL_SCANCODE_MINUS; /* VK_OEM_MINUS */ - case 190: - return SDL_SCANCODE_PERIOD; /* VK_OEM_PERIOD */ - case 191: - return SDL_SCANCODE_SLASH; /* VK_OEM_2, '/?' key on US standard keyboards */ - case 192: - return SDL_SCANCODE_GRAVE; /* VK_OEM_3, '`~' key on US standard keyboards */ - // ? - // ... reserved or unassigned ... - // ? - case 219: - return SDL_SCANCODE_LEFTBRACKET; /* VK_OEM_4, '[{' key on US standard keyboards */ - case 220: - return SDL_SCANCODE_BACKSLASH; /* VK_OEM_5, '\|' key on US standard keyboards */ - case 221: - return SDL_SCANCODE_RIGHTBRACKET; /* VK_OEM_6, ']}' key on US standard keyboards */ - case 222: - return SDL_SCANCODE_APOSTROPHE; /* VK_OEM_7, 'single/double quote' on US standard keyboards */ - default: + SDL_Scancode code; + Uint8 index; + Uint16 scanCode = keyStatus.ScanCode | (keyStatus.IsExtendedKey ? 0xe000 : 0); + + /* Pause/Break key have a special scan code with 0xe1 prefix + * that is not properly reported under UWP. + * Use Pause scan code that is used in Win32. */ + if (virtualKey == Windows::System::VirtualKey::Pause) { + scanCode = 0xe046; + } + + /* Pack scan code into one byte to make the index. */ + index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00); + code = windows_scancode_table[index]; + + return code; +} + +void WINRT_ProcessAcceleratorKeyActivated(Windows::UI::Core::AcceleratorKeyEventArgs ^ args) +{ + using namespace Windows::UI::Core; + + Uint8 state; + SDL_Scancode code; + + switch (args->EventType) { + case CoreAcceleratorKeyEventType::SystemKeyDown: + case CoreAcceleratorKeyEventType::KeyDown: + state = SDL_PRESSED; break; + case CoreAcceleratorKeyEventType::SystemKeyUp: + case CoreAcceleratorKeyEventType::KeyUp: + state = SDL_RELEASED; + break; + default: + return; } - return SDL_SCANCODE_UNKNOWN; + + code = WINRT_TranslateKeycode(args->VirtualKey, args->KeyStatus); + SDL_SendKeyboardKey(0, state, code); } -static SDL_Scancode WINRT_TranslateKeycode(int keycode, unsigned int nativeScancode) +void WINRT_ProcessCharacterReceivedEvent(SDL_Window *window, Windows::UI::Core::CharacterReceivedEventArgs ^ args) { - // TODO, WinRT: try filling out the WinRT keycode table as much as possible, using the Win32 table for interpretation hints + if (!window) { + return; + } - SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN; + SDL_WindowData *data = window->driverdata; - /* HACK ALERT: At least one VirtualKey constant (Shift) with a left/right - * designation might not get reported with its correct handedness, however - * its hardware scan code can fill in the gaps. If this is detected, - * use the hardware scan code to try telling if the left, or the right - * side's key was used. - * - * If Microsoft ever allows MapVirtualKey or MapVirtualKeyEx to be used - * in WinRT apps, or something similar to these (it doesn't appear to be, - * at least not for Windows [Phone] 8/8.1, as of Oct 24, 2014), then this - * hack might become deprecated, or obsolete. - */ - if (nativeScancode < SDL_arraysize(windows_scancode_table)) { - switch (keycode) { - case 16: // VirtualKey.Shift - switch (windows_scancode_table[nativeScancode]) { - case SDL_SCANCODE_LSHIFT: - case SDL_SCANCODE_RSHIFT: - return windows_scancode_table[nativeScancode]; - } - break; + /* Characters outside Unicode Basic Multilingual Plane (BMP) + * are coded as so called "surrogate pair" in two separate UTF-16 character events. + * Cache high surrogate until next character event. */ + if (IS_HIGH_SURROGATE(args->KeyCode)) { + data->high_surrogate = (WCHAR)args->KeyCode; + } else { + WCHAR utf16[] = { + data->high_surrogate ? data->high_surrogate : (WCHAR)args->KeyCode, + data->high_surrogate ? (WCHAR)args->KeyCode : L'\0', + L'\0' + }; - // Add others, as necessary. - // - // Unfortunately, this hack doesn't seem to work in determining - // handedness with Control keys. - - default: - break; + char utf8[5]; + int result = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL); + if (result > 0) { + SDL_SendKeyboardText(utf8); } - } - /* Try to get a documented, WinRT, 'VirtualKey' next (as documented at - http://msdn.microsoft.com/en-us/library/windows/apps/windows.system.virtualkey.aspx ). - If that fails, fall back to a Win32 virtual key. - If that fails, attempt to fall back to a scancode-derived key. - */ - if (keycode < SDL_arraysize(WinRT_Official_Keycodes)) { - scancode = WinRT_Official_Keycodes[keycode]; - } - if (scancode == SDL_SCANCODE_UNKNOWN) { - scancode = WINRT_TranslateUnofficialKeycode(keycode); - } - if (scancode == SDL_SCANCODE_UNKNOWN) { - if (nativeScancode < SDL_arraysize(windows_scancode_table)) { - scancode = windows_scancode_table[nativeScancode]; - } - } - /* - if (scancode == SDL_SCANCODE_UNKNOWN) { - SDL_Log("WinRT TranslateKeycode, unknown keycode=%d\n", (int)keycode); - } - */ - return scancode; -} - -void WINRT_ProcessKeyDownEvent(Windows::UI::Core::KeyEventArgs ^ args) -{ - SDL_Scancode sdlScancode = WINRT_TranslateKeycode((int)args->VirtualKey, args->KeyStatus.ScanCode); -#if 0 - SDL_Keycode keycode = SDL_GetKeyFromScancode(sdlScancode); - SDL_Log("key down, handled=%s, ext?=%s, released?=%s, menu key down?=%s, " - "repeat count=%d, native scan code=0x%x, was down?=%s, vkey=%d, " - "sdl scan code=%d (%s), sdl key code=%d (%s)\n", - (args->Handled ? "1" : "0"), - (args->KeyStatus.IsExtendedKey ? "1" : "0"), - (args->KeyStatus.IsKeyReleased ? "1" : "0"), - (args->KeyStatus.IsMenuKeyDown ? "1" : "0"), - args->KeyStatus.RepeatCount, - args->KeyStatus.ScanCode, - (args->KeyStatus.WasKeyDown ? "1" : "0"), - args->VirtualKey, - sdlScancode, - SDL_GetScancodeName(sdlScancode), - keycode, - SDL_GetKeyName(keycode)); - //args->Handled = true; -#endif - SDL_SendKeyboardKey(0, SDL_PRESSED, sdlScancode); -} - -void WINRT_ProcessKeyUpEvent(Windows::UI::Core::KeyEventArgs ^ args) -{ - SDL_Scancode sdlScancode = WINRT_TranslateKeycode((int)args->VirtualKey, args->KeyStatus.ScanCode); -#if 0 - SDL_Keycode keycode = SDL_GetKeyFromScancode(sdlScancode); - SDL_Log("key up, handled=%s, ext?=%s, released?=%s, menu key down?=%s, " - "repeat count=%d, native scan code=0x%x, was down?=%s, vkey=%d, " - "sdl scan code=%d (%s), sdl key code=%d (%s)\n", - (args->Handled ? "1" : "0"), - (args->KeyStatus.IsExtendedKey ? "1" : "0"), - (args->KeyStatus.IsKeyReleased ? "1" : "0"), - (args->KeyStatus.IsMenuKeyDown ? "1" : "0"), - args->KeyStatus.RepeatCount, - args->KeyStatus.ScanCode, - (args->KeyStatus.WasKeyDown ? "1" : "0"), - args->VirtualKey, - sdlScancode, - SDL_GetScancodeName(sdlScancode), - keycode, - SDL_GetKeyName(keycode)); - //args->Handled = true; -#endif - SDL_SendKeyboardKey(0, SDL_RELEASED, sdlScancode); -} - -void WINRT_ProcessCharacterReceivedEvent(Windows::UI::Core::CharacterReceivedEventArgs ^ args) -{ - wchar_t src_ucs2[2]; - char dest_utf8[16]; - int result; - - /* Setup src */ - src_ucs2[0] = args->KeyCode; - src_ucs2[1] = L'\0'; - - /* Convert the text, then send an SDL_EVENT_TEXT_INPUT event. */ - result = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&src_ucs2, -1, (LPSTR)dest_utf8, sizeof(dest_utf8), NULL, NULL); - if (result > 0) { - SDL_SendKeyboardText(dest_utf8); + data->high_surrogate = L'\0'; } } diff --git a/src/video/winrt/SDL_winrtmessagebox.cpp b/src/video/winrt/SDL_winrtmessagebox.cpp index aeedc92d..bee89009 100644 --- a/src/video/winrt/SDL_winrtmessagebox.cpp +++ b/src/video/winrt/SDL_winrtmessagebox.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtmessagebox.h b/src/video/winrt/SDL_winrtmessagebox.h index 8786eb26..865ce329 100644 --- a/src/video/winrt/SDL_winrtmessagebox.h +++ b/src/video/winrt/SDL_winrtmessagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtmouse.cpp b/src/video/winrt/SDL_winrtmouse.cpp index 826a75bc..d16aa01d 100644 --- a/src/video/winrt/SDL_winrtmouse.cpp +++ b/src/video/winrt/SDL_winrtmouse.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -90,6 +90,30 @@ static SDL_Cursor *WINRT_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_HAND: cursorType = CoreCursorType::Hand; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + cursorType = CoreCursorType::SizeNorthwestSoutheast; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + cursorType = CoreCursorType::SizeNorthSouth; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + cursorType = CoreCursorType::SizeNortheastSouthwest; + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + cursorType = CoreCursorType::SizeWestEast; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + cursorType = CoreCursorType::SizeNorthwestSoutheast; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + cursorType = CoreCursorType::SizeNorthSouth; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + cursorType = CoreCursorType::SizeNortheastSouthwest; + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + cursorType = CoreCursorType::SizeWestEast; + break; } cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); @@ -102,8 +126,6 @@ static SDL_Cursor *WINRT_CreateSystemCursor(SDL_SystemCursor id) CoreCursor ^ *theCursor = new CoreCursor ^ (nullptr); *theCursor = ref new CoreCursor(cursorType, 0); cursor->driverdata = (void *)theCursor; - } else { - SDL_OutOfMemory(); } return cursor; diff --git a/src/video/winrt/SDL_winrtmouse_c.h b/src/video/winrt/SDL_winrtmouse_c.h index 1a543841..61deedf3 100644 --- a/src/video/winrt/SDL_winrtmouse_c.h +++ b/src/video/winrt/SDL_winrtmouse_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtopengles.cpp b/src/video/winrt/SDL_winrtopengles.cpp index 52a1ef5b..4dfba50e 100644 --- a/src/video/winrt/SDL_winrtopengles.cpp +++ b/src/video/winrt/SDL_winrtopengles.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtopengles.h b/src/video/winrt/SDL_winrtopengles.h index eaebadf5..0b00f176 100644 --- a/src/video/winrt/SDL_winrtopengles.h +++ b/src/video/winrt/SDL_winrtopengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtpointerinput.cpp b/src/video/winrt/SDL_winrtpointerinput.cpp index ade322aa..47dd3c25 100644 --- a/src/video/winrt/SDL_winrtpointerinput.cpp +++ b/src/video/winrt/SDL_winrtpointerinput.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/winrt/SDL_winrtvideo.cpp b/src/video/winrt/SDL_winrtvideo.cpp index b98f6752..973730e1 100644 --- a/src/video/winrt/SDL_winrtvideo.cpp +++ b/src/video/winrt/SDL_winrtvideo.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -67,8 +67,6 @@ extern "C" { #include "SDL_winrtmouse_c.h" #include "SDL_winrtvideo_cpp.h" -#include - /* Initialization/Query functions */ static int WINRT_VideoInit(SDL_VideoDevice *_this); static int WINRT_InitModes(SDL_VideoDevice *_this); @@ -76,11 +74,10 @@ static int WINRT_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *displa static void WINRT_VideoQuit(SDL_VideoDevice *_this); /* Window functions */ -static int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); +static int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); static void WINRT_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); -static void WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +static int WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); static void WINRT_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -static int WINRT_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info); /* Misc functions */ static ABI::Windows::System::Display::IDisplayRequest *WINRT_CreateDisplayRequest(SDL_VideoDevice *_this); @@ -111,14 +108,12 @@ static SDL_VideoDevice *WINRT_CreateDevice(void) /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return 0; } data = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (data == NULL) { - SDL_OutOfMemory(); + if (!data) { SDL_free(device); return 0; } @@ -133,7 +128,6 @@ static SDL_VideoDevice *WINRT_CreateDevice(void) device->DestroyWindow = WINRT_DestroyWindow; device->SetDisplayMode = WINRT_SetDisplayMode; device->PumpEvents = WINRT_PumpEvents; - device->GetWindowWMInfo = WINRT_GetWindowWMInfo; device->SuspendScreenSaver = WINRT_SuspendScreenSaver; #if NTDDI_VERSION >= NTDDI_WIN10 @@ -177,7 +171,7 @@ static void SDLCALL WINRT_SetDisplayOrientationsPreference(void *userdata, const * * TODO, WinRT: consider reading in an app's .appxmanifest file, and apply its orientation when 'newValue == NULL'. */ - if ((oldValue == NULL) && (newValue == NULL)) { + if ((!oldValue) && (!newValue)) { return; } @@ -324,8 +318,7 @@ static int WINRT_AddDisplaysForOutput(SDL_VideoDevice *_this, IDXGIAdapter1 *dxg } dxgiModes = (DXGI_MODE_DESC *)SDL_calloc(numModes, sizeof(DXGI_MODE_DESC)); - if (dxgiModes == NULL) { - SDL_OutOfMemory(); + if (!dxgiModes) { goto done; } @@ -585,7 +578,7 @@ static bool WINRT_IsCoreWindowActive(CoreWindow ^ coreWindow) return true; } -int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { // Make sure that only one window gets created, at least until multimonitor // support is added. @@ -599,6 +592,7 @@ int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) } window->driverdata = data; data->sdlWindow = window; + data->high_surrogate = L'\0'; /* To note, when XAML support is enabled, access to the CoreWindow will not be possible, at least not via the SDL/XAML thread. Attempts to access it @@ -612,6 +606,7 @@ int WINRT_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) data->appView = ApplicationView::GetForCurrentView(); #endif } + SDL_SetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WINRT_WINDOW_POINTER, reinterpret_cast(data->coreWindow.Get())); /* Make note of the requested window flags, before they start getting changed. */ const Uint32 requestedFlags = window->flags; @@ -739,12 +734,14 @@ void WINRT_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { #if NTDDI_VERSION >= NTDDI_WIN10 SDL_WindowData *data = window->driverdata; - const Windows::Foundation::Size size((float)window->w, (float)window->h); - data->appView->TryResizeView(size); // TODO, WinRT: return failure (to caller?) from TryResizeView() + const Windows::Foundation::Size size((float)window->floating.w, (float)window->floating.h); + if (data->appView->TryResizeView(size)) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->floating.w, window->floating.h); + } #endif } -void WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) +int WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen) { #if NTDDI_VERSION >= NTDDI_WIN10 SDL_WindowData *data = window->driverdata; @@ -752,7 +749,7 @@ void WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V if (isWindowActive) { if (fullscreen) { if (!data->appView->IsFullScreenMode) { - data->appView->TryEnterFullScreenMode(); // TODO, WinRT: return failure (to caller?) from TryEnterFullScreenMode() + return data->appView->TryEnterFullScreenMode() ? 0 : -1; } } else { if (data->appView->IsFullScreenMode) { @@ -761,6 +758,8 @@ void WINRT_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_V } } #endif + + return 0; } void WINRT_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -779,15 +778,6 @@ void WINRT_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } } -int WINRT_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - SDL_WindowData *data = window->driverdata; - - info->subsystem = SDL_SYSWM_WINRT; - info->info.winrt.window = reinterpret_cast(data->coreWindow.Get()); - return 0; -} - static ABI::Windows::System::Display::IDisplayRequest *WINRT_CreateDisplayRequest(SDL_VideoDevice *_this) { /* Setup a WinRT DisplayRequest object, usable for enabling/disabling screensaver requests */ diff --git a/src/video/winrt/SDL_winrtvideo_cpp.h b/src/video/winrt/SDL_winrtvideo_cpp.h index 069fff4f..55edc9a6 100644 --- a/src/video/winrt/SDL_winrtvideo_cpp.h +++ b/src/video/winrt/SDL_winrtvideo_cpp.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -100,6 +100,7 @@ struct SDL_WindowData #if SDL_WINRT_USE_APPLICATIONVIEW Windows::UI::ViewManagement::ApplicationView ^ appView; #endif + WCHAR high_surrogate; }; #endif // ifdef __cplusplus_winrt diff --git a/src/video/x11/SDL_x11clipboard.c b/src/video/x11/SDL_x11clipboard.c index 3bec6709..4ebeb783 100644 --- a/src/video/x11/SDL_x11clipboard.c +++ b/src/video/x11/SDL_x11clipboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -99,11 +99,9 @@ static int SetSelectionData(SDL_VideoDevice *_this, Atom selection, SDL_Clipboar static void *CloneDataBuffer(const void *buffer, size_t *len) { void *clone = NULL; - if (*len > 0 && buffer != NULL) { + if (*len > 0 && buffer) { clone = SDL_malloc((*len)+sizeof(Uint32)); - if (clone == NULL) { - SDL_OutOfMemory(); - } else { + if (clone) { SDL_memcpy(clone, buffer, *len); SDL_memset((Uint8 *)clone + *len, 0, sizeof(Uint32)); } @@ -228,7 +226,7 @@ SDL_bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) size_t length; void *data; data = X11_GetClipboardData(_this, mime_type, &length); - if (data != NULL) { + if (data) { SDL_free(data); } return length > 0; diff --git a/src/video/x11/SDL_x11clipboard.h b/src/video/x11/SDL_x11clipboard.h index 1a7ce11e..2d9c0f15 100644 --- a/src/video/x11/SDL_x11clipboard.h +++ b/src/video/x11/SDL_x11clipboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11dyn.c b/src/video/x11/SDL_x11dyn.c index 18174d12..0ae1c5a9 100644 --- a/src/video/x11/SDL_x11dyn.c +++ b/src/video/x11/SDL_x11dyn.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -72,22 +72,22 @@ static void *X11_GetSym(const char *fnname, int *pHasModule) int i; void *fn = NULL; for (i = 0; i < SDL_TABLESIZE(x11libs); i++) { - if (x11libs[i].lib != NULL) { + if (x11libs[i].lib) { fn = SDL_LoadFunction(x11libs[i].lib, fnname); - if (fn != NULL) { + if (fn) { break; } } } #if DEBUG_DYNAMIC_X11 - if (fn != NULL) + if (fn) printf("X11: Found '%s' in %s (%p)\n", fnname, x11libs[i].libname, fn); else printf("X11: Symbol '%s' NOT FOUND!\n", fnname); #endif - if (fn == NULL) { + if (!fn) { *pHasModule = 0; /* kill this module. */ } @@ -133,7 +133,7 @@ void SDL_X11_UnloadSymbols(void) #ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC for (i = 0; i < SDL_TABLESIZE(x11libs); i++) { - if (x11libs[i].lib != NULL) { + if (x11libs[i].lib) { SDL_UnloadObject(x11libs[i].lib); x11libs[i].lib = NULL; } @@ -154,7 +154,7 @@ int SDL_X11_LoadSymbols(void) int i; int *thismod = NULL; for (i = 0; i < SDL_TABLESIZE(x11libs); i++) { - if (x11libs[i].libname != NULL) { + if (x11libs[i].libname) { x11libs[i].lib = SDL_LoadObject(x11libs[i].libname); } } diff --git a/src/video/x11/SDL_x11dyn.h b/src/video/x11/SDL_x11dyn.h index 610e12bc..73d8953e 100644 --- a/src/video/x11/SDL_x11dyn.h +++ b/src/video/x11/SDL_x11dyn.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index d414ee66..56a78c7f 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -39,8 +39,7 @@ #include "../../events/SDL_touch_c.h" #include "../../core/linux/SDL_system_theme.h" #include "../../SDL_utils_c.h" - -#include +#include "../SDL_sysvideo.h" #include @@ -102,7 +101,7 @@ static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop) int bytes_fetch = 0; do { - if (ret != NULL) { + if (ret) { X11_XFree(ret); } X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); @@ -181,14 +180,14 @@ static SDL_bool X11_KeyRepeat(Display *display, XEvent *event) return d.found; } -static SDL_bool X11_IsWheelEvent(Display *display, XEvent *event, int *xticks, int *yticks) +static SDL_bool X11_IsWheelEvent(Display *display, int button, int *xticks, int *yticks) { /* according to the xlib docs, no specific mouse wheel events exist. However, the defacto standard is that the vertical wheel is X buttons 4 (up) and 5 (down) and a horizontal wheel is 6 (left) and 7 (right). */ /* Xlib defines "Button1" through 5, so we just use literals here. */ - switch (event->xbutton.button) { + switch (button) { case 4: *yticks = 1; return SDL_TRUE; @@ -224,7 +223,7 @@ static int X11_URIDecode(char *buf, int len) { int ri, wi, di; char decode = '\0'; - if (buf == NULL || len < 0) { + if (!buf || len < 0) { errno = EINVAL; return -1; } @@ -300,7 +299,7 @@ static char *X11_URIToLocal(char *uri) /* got a hostname? */ if (!local && uri[0] == '/' && uri[2] != '/') { char *hostname_end = SDL_strchr(uri + 1, '/'); - if (hostname_end != NULL) { + if (hostname_end) { char hostname[257]; if (gethostname(hostname, 255) == 0) { hostname[256] = '\0'; @@ -324,27 +323,27 @@ static char *X11_URIToLocal(char *uri) return file; } -#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS -static void X11_HandleGenericEvent(SDL_VideoData *videodata, XEvent *xev) +/* An X11 event hook */ +static SDL_X11EventHook g_X11EventHook = NULL; +static void *g_X11EventHookData = NULL; + +void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) { + g_X11EventHook = callback; + g_X11EventHookData = userdata; +} + +#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS +static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev) +{ + SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + /* event is a union, so cookie == &event, but this is type safe. */ XGenericEventCookie *cookie = &xev->xcookie; if (X11_XGetEventData(videodata->display, cookie)) { - X11_HandleXinput2Event(videodata, cookie); - - /* Send a SDL_EVENT_SYSWM if the application wants them. - * Since event data is only available until XFreeEventData is called, - * the *only* way for an application to access it is to register an event filter/watcher - * and do all the processing on the SDL_EVENT_SYSWM inside the callback. */ - if (SDL_EventEnabled(SDL_EVENT_SYSWM)) { - SDL_SysWMmsg wmmsg; - - wmmsg.version = SDL_SYSWM_CURRENT_VERSION; - wmmsg.subsystem = SDL_SYSWM_X11; - wmmsg.msg.x11.event = *xev; - SDL_SendSysWMEvent(&wmmsg); + if (!g_X11EventHook || g_X11EventHook(g_X11EventHookData, xev)) { + X11_HandleXinput2Event(_this, cookie); } - X11_XFreeEventData(videodata->display, cookie); } } @@ -561,13 +560,26 @@ static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *d X11_XSync(display, 0); } -static SDL_bool ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const XEvent *xev) +SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result) +{ + SDL_Window *window = data->window; + if (!window->hit_test) return SDL_FALSE; + const SDL_Point point = { x, y }; + SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + if (!force_new_result && rc == data->hit_test_result) { + return SDL_TRUE; + } + X11_SetHitTestCursor(rc); + data->hit_test_result = rc; + return SDL_TRUE; +} + +SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y) { SDL_Window *window = data->window; if (window->hit_test) { - const SDL_Point point = { xev->xbutton.x, xev->xbutton.y }; - const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + const SDL_Point point = { x, y }; static const int directions[] = { _NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP, _NET_WM_MOVERESIZE_SIZE_TOPRIGHT, _NET_WM_MOVERESIZE_SIZE_RIGHT, @@ -575,7 +587,7 @@ static SDL_bool ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *dat _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT, _NET_WM_MOVERESIZE_SIZE_LEFT }; - switch (rc) { + switch (data->hit_test_result) { case SDL_HITTEST_DRAGGABLE: InitiateWindowMove(_this, data, &point); return SDL_TRUE; @@ -588,7 +600,7 @@ static SDL_bool ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *dat case SDL_HITTEST_RESIZE_BOTTOM: case SDL_HITTEST_RESIZE_BOTTOMLEFT: case SDL_HITTEST_RESIZE_LEFT: - InitiateWindowResize(_this, data, &point, directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]); + InitiateWindowResize(_this, data, &point, directions[data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]); return SDL_TRUE; default: @@ -689,7 +701,7 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven /* FIXME: We don't support the X11 INCR protocol for large clipboards. Do we want that? - Yes, yes we do. */ /* This is a safe cast, XChangeProperty() doesn't take a const value, but it doesn't modify the data */ seln_data = (unsigned char *)clipboard->callback(clipboard->userdata, mime_type, &seln_length); - if (seln_data != NULL) { + if (seln_data) { X11_XChangeProperty(display, req->requestor, req->property, req->target, 8, PropModeReplace, seln_data, seln_length); @@ -765,21 +777,21 @@ static Bool isReparentNotify(Display *display, XEvent *ev, XPointer arg) static SDL_bool IsHighLatin1(const char *string, int length) { - while (length-- > 0) { - Uint8 ch = (Uint8)*string; - if (ch >= 0x80) { - return SDL_TRUE; - } - ++string; - } - return SDL_FALSE; + while (length-- > 0) { + Uint8 ch = (Uint8)*string; + if (ch >= 0x80) { + return SDL_TRUE; + } + ++string; + } + return SDL_FALSE; } static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out) { int result = X11_XLookupString(event_struct, buffer_return, bytes_buffer, keysym_return, status_in_out); if (IsHighLatin1(buffer_return, result)) { - char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", buffer_return, result); + char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", buffer_return, result + 1); if (utf8_text) { SDL_strlcpy(buffer_return, utf8_text, bytes_buffer); SDL_free(utf8_text); @@ -791,6 +803,78 @@ static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int return result; } +SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window) +{ + const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + int i; + + if (videodata && videodata->windowlist) { + for (i = 0; i < videodata->numwindows; ++i) { + if ((videodata->windowlist[i] != NULL) && + (videodata->windowlist[i]->xwindow == window)) { + return videodata->windowlist[i]; + } + } + } + return NULL; +} + +void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, int button, const float x, const float y, const unsigned long time) +{ + SDL_Window *window = windowdata->window; + const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + Display *display = videodata->display; + int xticks = 0, yticks = 0; +#ifdef DEBUG_XEVENTS + printf("window %p: ButtonPress (X11 button = %d)\n", window, button); +#endif + if (X11_IsWheelEvent(display, button, &xticks, &yticks)) { + SDL_SendMouseWheel(0, window, 0, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL); + } else { + SDL_bool ignore_click = SDL_FALSE; + if (button == Button1) { + if (X11_TriggerHitTestAction(_this, windowdata, x, y)) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); + return; /* don't pass this event on to app. */ + } + } else if (button > 7) { + /* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ... + => subtract (8-SDL_BUTTON_X1) to get value SDL expects */ + button -= (8 - SDL_BUTTON_X1); + } + if (windowdata->last_focus_event_time) { + const int X11_FOCUS_CLICK_TIMEOUT = 10; + if (SDL_GetTicks() < (windowdata->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) { + ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); + } + windowdata->last_focus_event_time = 0; + } + if (!ignore_click) { + SDL_SendMouseButton(0, window, 0, SDL_PRESSED, button); + } + } + X11_UpdateUserTime(windowdata, time); +} + +void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, int button) +{ + SDL_Window *window = windowdata->window; + const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + Display *display = videodata->display; + /* The X server sends a Release event for each Press for wheels. Ignore them. */ + int xticks = 0, yticks = 0; +#ifdef DEBUG_XEVENTS + printf("window %p: ButtonRelease (X11 button = %d)\n", data, xevent->xbutton.button); +#endif + if (!X11_IsWheelEvent(display, button, &xticks, &yticks)) { + if (button > 7) { + /* see explanation at case ButtonPress */ + button -= (8 - SDL_BUTTON_X1); + } + SDL_SendMouseButton(0, window, 0, SDL_RELEASED, button); + } +} + void X11_GetBorderValues(SDL_WindowData *data) { SDL_VideoData *videodata = data->videodata; @@ -800,18 +884,24 @@ void X11_GetBorderValues(SDL_WindowData *data) int format; unsigned long nitems, bytes_after; unsigned char *property; - if (X11_XGetWindowProperty(display, data->xwindow, videodata->_NET_FRAME_EXTENTS, 0, 16, 0, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &property) == Success) { - if (type != None && nitems == 4) { - data->border_left = (int)((long *)property)[0]; - data->border_right = (int)((long *)property)[1]; - data->border_top = (int)((long *)property)[2]; - data->border_bottom = (int)((long *)property)[3]; - } - X11_XFree(property); + + /* Some compositors will send extents even when the border hint is turned off. Ignore them in this case. */ + if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { + if (X11_XGetWindowProperty(display, data->xwindow, videodata->_NET_FRAME_EXTENTS, 0, 16, 0, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &property) == Success) { + if (type != None && nitems == 4) { + data->border_left = (int)((long *)property)[0]; + data->border_right = (int)((long *)property)[1]; + data->border_top = (int)((long *)property)[2]; + data->border_bottom = (int)((long *)property)[3]; + } + X11_XFree(property); #ifdef DEBUG_XEVENTS - printf("New _NET_FRAME_EXTENTS: left=%d right=%d, top=%d, bottom=%d\n", data->border_left, data->border_right, data->border_top, data->border_bottom); + printf("New _NET_FRAME_EXTENTS: left=%d right=%d, top=%d, bottom=%d\n", data->border_left, data->border_right, data->border_top, data->border_bottom); #endif + } + } else { + data->border_left = data->border_top = data->border_right = data->border_bottom = 0; } } @@ -865,27 +955,24 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) #ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS if (xevent->type == GenericEvent) { - X11_HandleGenericEvent(videodata, xevent); + X11_HandleGenericEvent(_this, xevent); return; } #endif + /* Calling the event hook for generic events happens in X11_HandleGenericEvent(), where the event data is available */ + if (g_X11EventHook) { + if (!g_X11EventHook(g_X11EventHookData, xevent)) { + return; + } + } + #ifdef SDL_VIDEO_DRIVER_X11_XRANDR if (videodata->xrandr_event_base && (xevent->type == (videodata->xrandr_event_base + RRNotify))) { X11_HandleXRandREvent(_this, xevent); } #endif - /* Send a SDL_EVENT_SYSWM if the application wants them */ - if (SDL_EventEnabled(SDL_EVENT_SYSWM)) { - SDL_SysWMmsg wmmsg; - - wmmsg.version = SDL_SYSWM_CURRENT_VERSION; - wmmsg.subsystem = SDL_SYSWM_X11; - wmmsg.msg.x11.event = *xevent; - SDL_SendSysWMEvent(&wmmsg); - } - #if 0 printf("type = %d display = %d window = %d\n", xevent->type, xevent->xany.display, xevent->xany.window); @@ -893,19 +980,19 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) #ifdef SDL_VIDEO_DRIVER_X11_XFIXES if (SDL_X11_HAVE_XFIXES && - xevent->type == X11_GetXFixesSelectionNotifyEvent()) { - XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *) xevent; + xevent->type == X11_GetXFixesSelectionNotifyEvent()) { + XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *)xevent; /* !!! FIXME: cache atoms */ Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0); #ifdef DEBUG_XEVENTS printf("window CLIPBOARD: XFixesSelectionNotify (selection = %s)\n", - X11_XGetAtomName(display, ev->selection)); + X11_XGetAtomName(display, ev->selection)); #endif if (ev->selection == XA_PRIMARY || - (XA_CLIPBOARD != None && ev->selection == XA_CLIPBOARD)) { + (XA_CLIPBOARD != None && ev->selection == XA_CLIPBOARD)) { SDL_SendClipboardUpdate(); return; } @@ -918,17 +1005,9 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) return; } - data = NULL; - if (videodata && videodata->windowlist) { - for (i = 0; i < videodata->numwindows; ++i) { - if ((videodata->windowlist[i] != NULL) && - (videodata->windowlist[i]->xwindow == xevent->xany.window)) { - data = videodata->windowlist[i]; - break; - } - } - } - if (data == NULL) { + data = X11_FindWindow(_this, xevent->xany.window); + + if (!data) { /* The window for KeymapNotify, etc events is 0 */ if (xevent->type == KeymapNotify) { #ifdef DEBUG_XEVENTS @@ -1019,6 +1098,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) /* We ungrab in LeaveNotify, so we may need to grab again here */ SDL_UpdateWindowGrab(data->window); + + X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, SDL_TRUE); } break; /* Losing mouse coverage? */ case LeaveNotify: @@ -1234,8 +1315,9 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) xevent->xconfigure.x, xevent->xconfigure.y, xevent->xconfigure.width, xevent->xconfigure.height); #endif - /* Real configure notify events are relative to the parent, synthetic events are absolute. */ - if (!xevent->xconfigure.send_event) { + /* Real configure notify events are relative to the parent, synthetic events are absolute. */ + if (!xevent->xconfigure.send_event) + { unsigned int NumChildren; Window ChildReturn, Root, Parent; Window *Children; @@ -1250,33 +1332,39 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (xevent->xconfigure.x != data->last_xconfigure.x || xevent->xconfigure.y != data->last_xconfigure.y) { - SDL_Window *w; - int x = xevent->xconfigure.x; - int y = xevent->xconfigure.y; + if (!data->disable_size_position_events) { + SDL_Window *w; + int x = xevent->xconfigure.x; + int y = xevent->xconfigure.y; - SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, - x, y); + data->pending_operation &= ~X11_PENDING_OP_MOVE; + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); #ifdef SDL_USE_IME - if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { - /* Update IME candidate list position */ - SDL_IME_UpdateTextRect(NULL); - } + if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { + /* Update IME candidate list position */ + SDL_IME_UpdateTextRect(NULL); + } #endif - for (w = data->window->first_child; w != NULL; w = w->next_sibling) { - /* Don't update hidden child windows, their relative position doesn't change */ - if (!(w->flags & SDL_WINDOW_HIDDEN)) { - X11_UpdateWindowPosition(w); + for (w = data->window->first_child; w; w = w->next_sibling) { + /* Don't update hidden child windows, their relative position doesn't change */ + if (!(w->flags & SDL_WINDOW_HIDDEN)) { + X11_UpdateWindowPosition(w, SDL_TRUE); + } } } } if (xevent->xconfigure.width != data->last_xconfigure.width || xevent->xconfigure.height != data->last_xconfigure.height) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, - xevent->xconfigure.width, - xevent->xconfigure.height); + if (!data->disable_size_position_events) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, + xevent->xconfigure.width, + xevent->xconfigure.height); + } } + data->last_xconfigure = xevent->xconfigure; } break; @@ -1325,9 +1413,9 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) root_y = xevent->xclient.data.l[2] & 0xffff; /* Translate from root to current window position */ X11_XTranslateCoordinates(display, DefaultRootWindow(display), data->xwindow, - root_x, root_y, &window_x, &window_y, &ChildReturn); + root_x, root_y, &window_x, &window_y, &ChildReturn); - SDL_SendDropPosition(data->window, NULL, (float)window_x, (float)window_y); /* FIXME, can we get the filename ? */ + SDL_SendDropPosition(data->window, (float)window_x, (float)window_y); } /* reply with status */ @@ -1409,6 +1497,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); } break; + /* Use XInput2 instead of the xevents API if possible, for: + - MotionNotify + - ButtonPress + - ButtonRelease + XInput2 has more precise information, e.g., to distinguish different input devices. */ +#ifndef SDL_VIDEO_DRIVER_X11_XINPUT2 case MotionNotify: { SDL_Mouse *mouse = SDL_GetMouse(); @@ -1417,61 +1511,22 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) printf("window %p: X11 motion: %d,%d\n", data, xevent->xmotion.x, xevent->xmotion.y); #endif + X11_ProcessHitTest(_this, data, (float)xevent->xmotion.x, (float)xevent->xmotion.y, SDL_FALSE); SDL_SendMouseMotion(0, data->window, 0, 0, (float)xevent->xmotion.x, (float)xevent->xmotion.y); } } break; case ButtonPress: { - int xticks = 0, yticks = 0; -#ifdef DEBUG_XEVENTS - printf("window %p: ButtonPress (X11 button = %d)\n", data, xevent->xbutton.button); -#endif - if (X11_IsWheelEvent(display, xevent, &xticks, &yticks)) { - SDL_SendMouseWheel(0, data->window, 0, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL); - } else { - SDL_bool ignore_click = SDL_FALSE; - int button = xevent->xbutton.button; - if (button == Button1) { - if (ProcessHitTest(_this, data, xevent)) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); - break; /* don't pass this event on to app. */ - } - } else if (button > 7) { - /* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ... - => subtract (8-SDL_BUTTON_X1) to get value SDL expects */ - button -= (8 - SDL_BUTTON_X1); - } - if (data->last_focus_event_time) { - const int X11_FOCUS_CLICK_TIMEOUT = 10; - if (SDL_GetTicks() < (data->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) { - ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); - } - data->last_focus_event_time = 0; - } - if (!ignore_click) { - SDL_SendMouseButton(0, data->window, 0, SDL_PRESSED, button); - } - } - X11_UpdateUserTime(data, xevent->xbutton.time); + X11_HandleButtonPress(_this, data, xevent->xbutton.button, + xevent->xbutton.x, xevent->xbutton.y, xevent->xbutton.time); } break; case ButtonRelease: { - int button = xevent->xbutton.button; - /* The X server sends a Release event for each Press for wheels. Ignore them. */ - int xticks = 0, yticks = 0; -#ifdef DEBUG_XEVENTS - printf("window %p: ButtonRelease (X11 button = %d)\n", data, xevent->xbutton.button); -#endif - if (!X11_IsWheelEvent(display, xevent, &xticks, &yticks)) { - if (button > 7) { - /* see explanation at case ButtonPress */ - button -= (8 - SDL_BUTTON_X1); - } - SDL_SendMouseButton(0, data->window, 0, SDL_RELEASED, button); - } + X11_HandleButtonRelease(_this, data, xevent->xbutton.button); } break; +#endif /* !SDL_VIDEO_DRIVER_X11_XINPUT2 */ case PropertyNotify: { @@ -1579,17 +1634,92 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) } } - if ((changed & SDL_WINDOW_MAXIMIZED) && ((flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); - } - if ((changed & SDL_WINDOW_MINIMIZED) && (flags & SDL_WINDOW_MINIMIZED)) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); - } - if (((changed & SDL_WINDOW_MAXIMIZED) || (changed & SDL_WINDOW_MINIMIZED)) && - (!(flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); - } + if (!SDL_WINDOW_IS_POPUP(data->window)) { + if (changed & SDL_WINDOW_FULLSCREEN) { + data->pending_operation &= ~X11_PENDING_OP_FULLSCREEN; + if (flags & SDL_WINDOW_FULLSCREEN) { + if (!(flags & SDL_WINDOW_MINIMIZED)) { + const SDL_bool commit = data->requested_fullscreen_mode.displayID == 0 || + SDL_memcmp(&data->window->current_fullscreen_mode, &data->requested_fullscreen_mode, sizeof(SDL_DisplayMode)) != 0; + + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); + if (commit) { + /* This was initiated by the compositor, or the mode was changed between the request and the window + * becoming fullscreen. Switch to the application requested mode if necessary. + */ + SDL_copyp(&data->window->current_fullscreen_mode, &data->window->requested_fullscreen_mode); + SDL_UpdateFullscreenMode(data->window, SDL_TRUE, SDL_TRUE); + } else { + SDL_UpdateFullscreenMode(data->window, SDL_TRUE, SDL_FALSE); + } + } + } else { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + SDL_UpdateFullscreenMode(data->window, SDL_FALSE, SDL_FALSE); + + /* Need to restore or update any limits changed while the window was fullscreen. */ + X11_SetWindowMinMax(data->window, !!(flags & SDL_WINDOW_MAXIMIZED)); + } + + if ((flags & SDL_WINDOW_FULLSCREEN) && + (data->border_top || data->border_left || data->border_bottom || data->border_right)) { + /* If the window is entering fullscreen and the borders are + * non-zero sized, turn off size events until the borders are + * shut off to avoid bogus window sizes and positions, and + * note that the old borders were non-zero for restoration. + */ + data->disable_size_position_events = SDL_TRUE; + data->previous_borders_nonzero = SDL_TRUE; + } else if (!(flags & SDL_WINDOW_FULLSCREEN) && + data->previous_borders_nonzero && + (!data->border_top && !data->border_left && !data->border_bottom && !data->border_right)) { + /* If the window is leaving fullscreen and the current borders + * are zero sized, but weren't when entering fullscreen, turn + * off size events until the borders come back to avoid bogus + * window sizes and positions. + */ + data->disable_size_position_events = SDL_TRUE; + data->previous_borders_nonzero = SDL_FALSE; + } else { + data->disable_size_position_events = SDL_FALSE; + data->previous_borders_nonzero = SDL_FALSE; + + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { + data->toggle_borders = SDL_FALSE; + X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS)); + } + } + } + if ((changed & SDL_WINDOW_MAXIMIZED) && ((flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) { + data->pending_operation &= ~X11_PENDING_OP_MAXIMIZE; + if ((changed & SDL_WINDOW_MINIMIZED)) { + data->pending_operation &= ~X11_PENDING_OP_RESTORE; + /* If coming out of minimized, send a restore event before sending maximized. */ + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + } + if ((changed & SDL_WINDOW_MINIMIZED) && (flags & SDL_WINDOW_MINIMIZED)) { + data->pending_operation &= ~X11_PENDING_OP_MINIMIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + } + if (!(flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { + data->pending_operation &= ~X11_PENDING_OP_RESTORE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + + /* Restore the last known floating state if leaving maximized mode */ + if (!(flags & SDL_WINDOW_FULLSCREEN)) { + data->pending_operation |= X11_PENDING_OP_MOVE | X11_PENDING_OP_RESIZE; + data->expected.x = data->window->floating.x - data->border_left; + data->expected.y = data->window->floating.y - data->border_top; + data->expected.w = data->window->floating.w; + data->expected.h = data->window->floating.h; + X11_XMoveWindow(display, data->xwindow, data->window->floating.x - data->border_left, data->window->floating.y - data->border_top); + X11_XResizeWindow(display, data->xwindow, data->window->floating.w, data->window->floating.h); + } + } + } if (changed & SDL_WINDOW_OCCLUDED) { SDL_SendWindowEvent(data->window, (flags & SDL_WINDOW_OCCLUDED) ? SDL_EVENT_WINDOW_OCCLUDED : SDL_EVENT_WINDOW_EXPOSED, 0, 0); } @@ -1602,7 +1732,30 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) right approach, but it seems to work. */ X11_UpdateKeymap(_this, SDL_TRUE); } else if (xevent->xproperty.atom == videodata->_NET_FRAME_EXTENTS) { + /* Re-enable size events if they were turned off waiting for the borders to come back + * when leaving fullscreen. + */ + data->disable_size_position_events = SDL_FALSE; X11_GetBorderValues(data); + if (data->border_top != 0 || data->border_left != 0 || data->border_right != 0 || data->border_bottom != 0) { + /* Adjust if the window size/position changed to accommodate the borders. */ + if (data->window->flags & SDL_WINDOW_MAXIMIZED) { + data->pending_operation |= X11_PENDING_OP_RESIZE; + data->expected.w = data->window->windowed.w; + data->expected.h = data->window->windowed.h; + X11_XResizeWindow(display, data->xwindow, data->window->windowed.w, data->window->windowed.h); + } else { + data->pending_operation |= X11_PENDING_OP_RESIZE | X11_PENDING_OP_MOVE; + data->expected.w = data->window->floating.w; + data->expected.h = data->window->floating.h; + X11_XMoveWindow(display, data->xwindow, data->window->floating.x - data->border_left, data->window->floating.y - data->border_top); + X11_XResizeWindow(display, data->xwindow, data->window->floating.w, data->window->floating.h); + } + } + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { + data->toggle_borders = SDL_FALSE; + X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS)); + } } } break; @@ -1623,13 +1776,13 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) char *name = X11_XGetAtomName(display, target); if (name) { char *token = SDL_strtok_r((char *)p.data, "\r\n", &saveptr); - while (token != NULL) { + while (token) { if (SDL_strcmp("text/plain", name) == 0) { SDL_SendDropText(data->window, token); } else if (SDL_strcmp("text/uri-list", name) == 0) { char *fn = X11_URIToLocal(token); if (fn) { - SDL_SendDropFile(data->window, fn); + SDL_SendDropFile(data->window, NULL, fn); } } token = SDL_strtok_r(NULL, "\r\n", &saveptr); @@ -1782,6 +1935,22 @@ void X11_PumpEvents(SDL_VideoDevice *_this) XEvent xevent; int i; + /* Check if a display had the mode changed and is waiting for a window to asynchronously become + * fullscreen. If there is no fullscreen window past the elapsed timeout, revert the mode switch. + */ + for (i = 0; i < _this->num_displays; ++i) { + if (_this->displays[i]->driverdata->mode_switch_deadline_ns && + SDL_GetTicksNS() >= _this->displays[i]->driverdata->mode_switch_deadline_ns) { + if (_this->displays[i]->fullscreen_window) { + _this->displays[i]->driverdata->mode_switch_deadline_ns = 0; + } else { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "Time out elapsed after mode switch on display %" SDL_PRIu32 " with no window becoming fullscreen; reverting", _this->displays[i]->id); + SDL_SetDisplayModeForDisplay(_this->displays[i], NULL); + } + } + } + if (data->last_mode_change_deadline) { if (SDL_GetTicks() >= data->last_mode_change_deadline) { data->last_mode_change_deadline = 0; /* assume we're done. */ diff --git a/src/video/x11/SDL_x11events.h b/src/video/x11/SDL_x11events.h index 58cdb8da..4863a243 100644 --- a/src/video/x11/SDL_x11events.h +++ b/src/video/x11/SDL_x11events.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,5 +29,10 @@ extern void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); extern int X11_SuspendScreenSaver(SDL_VideoDevice *_this); extern void X11_ReconcileKeyboardState(SDL_VideoDevice *_this); extern void X11_GetBorderValues(SDL_WindowData *data); +extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button, const float x, const float y, const unsigned long time); +extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button); +extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window); +extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result); +extern SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y); #endif /* SDL_x11events_h_ */ diff --git a/src/video/x11/SDL_x11framebuffer.c b/src/video/x11/SDL_x11framebuffer.c index 9e8da1dd..1d3a3e66 100644 --- a/src/video/x11/SDL_x11framebuffer.c +++ b/src/video/x11/SDL_x11framebuffer.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -127,8 +127,8 @@ int X11_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint #endif /* not NO_SHARED_MEMORY */ *pixels = SDL_malloc((size_t)h * (*pitch)); - if (*pixels == NULL) { - return SDL_OutOfMemory(); + if (!*pixels) { + return -1; } data->ximage = X11_XCreateImage(display, data->visual, @@ -226,7 +226,7 @@ void X11_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) SDL_WindowData *data = window->driverdata; Display *display; - if (data == NULL) { + if (!data) { /* The window wasn't fully initialized */ return; } diff --git a/src/video/x11/SDL_x11framebuffer.h b/src/video/x11/SDL_x11framebuffer.h index 83623462..582cbddb 100644 --- a/src/video/x11/SDL_x11framebuffer.h +++ b/src/video/x11/SDL_x11framebuffer.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c index cf2d4c7e..6f4851f5 100644 --- a/src/video/x11/SDL_x11keyboard.c +++ b/src/video/x11/SDL_x11keyboard.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11keyboard.h b/src/video/x11/SDL_x11keyboard.h index 45ab01dc..9164e04e 100644 --- a/src/video/x11/SDL_x11keyboard.h +++ b/src/video/x11/SDL_x11keyboard.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11messagebox.c b/src/video/x11/SDL_x11messagebox.c index cc197227..b1f8ab04 100644 --- a/src/video/x11/SDL_x11messagebox.c +++ b/src/video/x11/SDL_x11messagebox.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -190,15 +190,15 @@ static int X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBoxD int num_missing = 0; data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont, &missing, &num_missing, NULL); - if (missing != NULL) { + if (missing) { X11_XFreeStringList(missing); } - if (data->font_set == NULL) { + if (!data->font_set) { return SDL_SetError("Couldn't load font %s", g_MessageBoxFont); } } else { data->font_struct = X11_XLoadQueryFont(data->display, g_MessageBoxFontLatin1); - if (data->font_struct == NULL) { + if (!data->font_struct) { return SDL_SetError("Couldn't load font %s", g_MessageBoxFontLatin1); } } @@ -239,13 +239,13 @@ static int X11_MessageBoxInitPositions(SDL_MessageBoxDataX11 *data) const SDL_MessageBoxData *messageboxdata = data->messageboxdata; /* Go over text and break linefeeds into separate lines. */ - if (messageboxdata != NULL && messageboxdata->message[0]) { + if (messageboxdata && messageboxdata->message[0]) { const char *text = messageboxdata->message; const int linecount = CountLinesOfText(text); TextLineData *plinedata = (TextLineData *)SDL_malloc(sizeof(TextLineData) * linecount); - if (plinedata == NULL) { - return SDL_OutOfMemory(); + if (!plinedata) { + return -1; } data->linedata = plinedata; @@ -272,7 +272,7 @@ static int X11_MessageBoxInitPositions(SDL_MessageBoxDataX11 *data) text += length + 1; /* Break if there are no more linefeeds. */ - if (lf == NULL) { + if (!lf) { break; } } @@ -361,12 +361,12 @@ static int X11_MessageBoxInitPositions(SDL_MessageBoxDataX11 *data) /* Free SDL_MessageBoxData data. */ static void X11_MessageBoxShutdown(SDL_MessageBoxDataX11 *data) { - if (data->font_set != NULL) { + if (data->font_set) { X11_XFreeFontSet(data->display, data->font_set); data->font_set = NULL; } - if (data->font_struct != NULL) { + if (data->font_struct) { X11_XFreeFont(data->display, data->font_struct); data->font_struct = NULL; } @@ -760,10 +760,10 @@ static int X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int #if SDL_SET_LOCALE origlocale = setlocale(LC_ALL, NULL); - if (origlocale != NULL) { + if (origlocale) { origlocale = SDL_strdup(origlocale); - if (origlocale == NULL) { - return SDL_OutOfMemory(); + if (!origlocale) { + return -1; } (void)setlocale(LC_ALL, ""); } diff --git a/src/video/x11/SDL_x11messagebox.h b/src/video/x11/SDL_x11messagebox.h index 05802ed3..f0ecce0f 100644 --- a/src/video/x11/SDL_x11messagebox.h +++ b/src/video/x11/SDL_x11messagebox.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c index e8c0ad33..3ba4f604 100644 --- a/src/video/x11/SDL_x11modes.c +++ b/src/video/x11/SDL_x11modes.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -27,6 +27,11 @@ /* #define X11MODES_DEBUG */ +/* Timeout and revert mode switches if the timespan has elapsed without the window becoming fullscreen. + * 5 seconds seems good from testing. + */ +#define MODE_SWITCH_TIMEOUT_NS SDL_NS_PER_SECOND * 5 + /* I'm becoming more and more convinced that the application should never * use XRandR, and it's the window manager's responsibility to track and * manage display modes for fullscreen windows. Right now XRandR is completely @@ -536,7 +541,7 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen } output_info = X11_XRRGetOutputInfo(dpy, res, outputid); - if (output_info == NULL || !output_info->crtc || output_info->connection == RR_Disconnected) { + if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) { X11_XRRFreeOutputInfo(output_info); return 0; /* ignore this one. */ } @@ -548,7 +553,7 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen X11_XRRFreeOutputInfo(output_info); crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc); - if (crtc == NULL) { + if (!crtc) { return 0; /* oh well, ignore it. */ } @@ -564,14 +569,14 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen X11_XRRFreeCrtcInfo(crtc); displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); - if (displaydata == NULL) { - return SDL_OutOfMemory(); + if (!displaydata) { + return -1; } modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); - if (modedata == NULL) { + if (!modedata) { SDL_free(displaydata); - return SDL_OutOfMemory(); + return -1; } modedata->xrandr_mode = modeID; @@ -626,11 +631,11 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput } if (ev->connection == RR_Disconnected) { /* output is going away */ - if (display != NULL) { + if (display) { SDL_DelVideoDisplay(display->id, SDL_TRUE); } } else if (ev->connection == RR_Connected) { /* output is coming online */ - if (display != NULL) { + if (display) { /* !!! FIXME: update rotation or current mode of existing display? */ } else { Display *dpy = ev->display; @@ -638,7 +643,7 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput XVisualInfo vinfo; if (get_visualinfo(dpy, screen, &vinfo) == 0) { XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen)); - if (res == NULL || res->noutput == 0) { + if (!res || res->noutput == 0) { if (res) { X11_XRRFreeScreenResources(res); } @@ -694,13 +699,13 @@ static int X11_InitModes_XRandR(SDL_VideoDevice *_this) } res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen)); - if (res == NULL || res->noutput == 0) { + if (!res || res->noutput == 0) { if (res) { X11_XRRFreeScreenResources(res); } res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen)); - if (res == NULL) { + if (!res) { continue; } } @@ -767,14 +772,14 @@ static int X11_InitModes_StdXlib(SDL_VideoDevice *_this) mode.format = pixelformat; displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); - if (displaydata == NULL) { - return SDL_OutOfMemory(); + if (!displaydata) { + return -1; } modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); - if (modedata == NULL) { + if (!modedata) { SDL_free(displaydata); - return SDL_OutOfMemory(); + return -1; } mode.driverdata = modedata; @@ -861,7 +866,7 @@ int X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display) if (output_info && output_info->connection != RR_Disconnected) { for (i = 0; i < output_info->nmode; ++i) { modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); - if (modedata == NULL) { + if (!modedata) { continue; } mode.driverdata = modedata; @@ -903,6 +908,12 @@ int X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SD viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2); + if (mode != &sdl_display->desktop_mode) { + data->mode_switch_deadline_ns = SDL_GetTicksNS() + MODE_SWITCH_TIMEOUT_NS; + } else { + data->mode_switch_deadline_ns = 0; + } + #ifdef SDL_VIDEO_DRIVER_X11_XRANDR if (data->use_xrandr) { Display *display = viddata->display; @@ -914,18 +925,18 @@ int X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SD Status status; res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen)); - if (res == NULL) { + if (!res) { return SDL_SetError("Couldn't get XRandR screen resources"); } output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output); - if (output_info == NULL || output_info->connection == RR_Disconnected) { + if (!output_info || output_info->connection == RR_Disconnected) { X11_XRRFreeScreenResources(res); return SDL_SetError("Couldn't get XRandR output info"); } crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc); - if (crtc == NULL) { + if (!crtc) { X11_XRRFreeOutputInfo(output_info); X11_XRRFreeScreenResources(res); return SDL_SetError("Couldn't get XRandR crtc info"); diff --git a/src/video/x11/SDL_x11modes.h b/src/video/x11/SDL_x11modes.h index 9d78cb1e..070af280 100644 --- a/src/video/x11/SDL_x11modes.h +++ b/src/video/x11/SDL_x11modes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,6 +32,8 @@ struct SDL_DisplayData int x; int y; + Uint64 mode_switch_deadline_ns; + SDL_bool use_xrandr; #ifdef SDL_VIDEO_DRIVER_X11_XRANDR diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c index 51eda30f..b87416ad 100644 --- a/src/video/x11/SDL_x11mouse.c +++ b/src/video/x11/SDL_x11mouse.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,6 +31,8 @@ /* FIXME: Find a better place to put this... */ static Cursor x11_empty_cursor = None; +static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1]; + static Display *GetDisplay(void) { return SDL_GetVideoDevice()->driverdata->display; @@ -67,14 +69,10 @@ static void X11_DestroyEmptyCursor(void) static SDL_Cursor *X11_CreateDefaultCursor(void) { - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { /* None is used to indicate the default cursor */ cursor->driverdata = (void *)(uintptr_t)None; - } else { - SDL_OutOfMemory(); } return cursor; @@ -88,7 +86,7 @@ static Cursor X11_CreateXCursorCursor(SDL_Surface *surface, int hot_x, int hot_y XcursorImage *image; image = X11_XcursorImageCreate(surface->w, surface->h); - if (image == NULL) { + if (!image) { SDL_OutOfMemory(); return None; } @@ -121,15 +119,13 @@ static Cursor X11_CreatePixmapCursor(SDL_Surface *surface, int hot_x, int hot_y) size_t width_bytes = ((surface->w + 7) & ~((size_t)7)) / 8; data_bits = SDL_calloc(1, surface->h * width_bytes); - if (data_bits == NULL) { - SDL_OutOfMemory(); + if (!data_bits) { return None; } mask_bits = SDL_calloc(1, surface->h * width_bytes); - if (mask_bits == NULL) { + if (!mask_bits) { SDL_free(data_bits); - SDL_OutOfMemory(); return None; } @@ -198,9 +194,7 @@ static Cursor X11_CreatePixmapCursor(SDL_Surface *surface, int hot_x, int hot_y) static SDL_Cursor *X11_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) { - SDL_Cursor *cursor; - - cursor = SDL_calloc(1, sizeof(*cursor)); + SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { Cursor x11_cursor = None; @@ -213,8 +207,6 @@ static SDL_Cursor *X11_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) x11_cursor = X11_CreatePixmapCursor(surface, hot_x, hot_y); } cursor->driverdata = (void *)(uintptr_t)x11_cursor; - } else { - SDL_OutOfMemory(); } return cursor; @@ -267,6 +259,30 @@ static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id) case SDL_SYSTEM_CURSOR_HAND: shape = XC_hand2; break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + shape = XC_top_left_corner; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + shape = XC_top_side; + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + shape = XC_top_right_corner; + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + shape = XC_right_side; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + shape = XC_bottom_right_corner; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + shape = XC_bottom_side; + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + shape = XC_bottom_left_corner; + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + shape = XC_left_side; + break; } cursor = SDL_calloc(1, sizeof(*cursor)); @@ -276,8 +292,6 @@ static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id) x11_cursor = X11_XCreateFontCursor(GetDisplay(), shape); cursor->driverdata = (void *)(uintptr_t)x11_cursor; - } else { - SDL_OutOfMemory(); } return cursor; @@ -330,13 +344,16 @@ static void X11_WarpMouseInternal(Window xwindow, float x, float y) Display *display = videodata->display; #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 int deviceid = 0; - /* It seems XIWarpPointer() doesn't work correctly on multi-head setups: - * https://developer.blender.org/rB165caafb99c6846e53d11c4e966990aaffc06cea - */ - if (ScreenCount(display) == 1) { - X11_XIGetClientPointer(display, None, &deviceid); + if (X11_Xinput2IsInitialized()) { + /* It seems XIWarpPointer() doesn't work correctly on multi-head setups: + * https://developer.blender.org/rB165caafb99c6846e53d11c4e966990aaffc06cea + */ + if (ScreenCount(display) == 1) { + X11_XIGetClientPointer(display, None, &deviceid); + } } if (deviceid != 0) { + SDL_assert(SDL_X11_HAVE_XINPUT2); X11_XIWarpPointer(display, deviceid, None, xwindow, 0.0, 0.0, 0, 0, x, y); } else #endif @@ -370,12 +387,7 @@ static int X11_WarpMouseGlobal(float x, float y) static int X11_SetRelativeMouseMode(SDL_bool enabled) { -#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 - if (X11_Xinput2IsInitialized()) { - return 0; - } -#endif - return SDL_Unsupported(); + return X11_Xinput2IsInitialized() ? 0 : SDL_Unsupported(); } static int X11_CaptureMouse(SDL_Window *window) @@ -413,12 +425,9 @@ static Uint32 X11_GetGlobalMouseState(float *x, float *y) /* !!! FIXME: should we XSync() here first? */ -#ifndef SDL_VIDEO_DRIVER_X11_XINPUT2 - videodata->global_mouse_changed = SDL_TRUE; -#else - if (!SDL_X11_HAVE_XINPUT2) + if (!X11_Xinput2IsInitialized()) { videodata->global_mouse_changed = SDL_TRUE; -#endif + } /* check if we have this cached since XInput last saw the mouse move. */ /* !!! FIXME: can we just calculate this from XInput's events? */ @@ -427,7 +436,7 @@ static Uint32 X11_GetGlobalMouseState(float *x, float *y) if (displays) { for (i = 0; displays[i]; ++i) { SDL_DisplayData *data = SDL_GetDisplayDriverData(displays[i]); - if (data != NULL) { + if (data) { Window root, child; int rootx, rooty, winx, winy; unsigned int mask; @@ -477,6 +486,23 @@ void X11_InitMouse(SDL_VideoDevice *_this) mouse->CaptureMouse = X11_CaptureMouse; mouse->GetGlobalMouseState = X11_GetGlobalMouseState; + SDL_HitTestResult r = SDL_HITTEST_NORMAL; + while (r <= SDL_HITTEST_RESIZE_LEFT) { + switch (r) { + case SDL_HITTEST_NORMAL: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break; + case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break; + case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT); break; + case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOP); break; + case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT); break; + case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_RIGHT); break; + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT); break; + case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOM); break; + case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT); break; + case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_WINDOW_LEFT); break; + } + r++; + } + SDL_SetDefaultCursor(X11_CreateDefaultCursor()); } @@ -485,8 +511,14 @@ void X11_QuitMouse(SDL_VideoDevice *_this) SDL_VideoData *data = _this->driverdata; SDL_XInput2DeviceInfo *i; SDL_XInput2DeviceInfo *next; + int j; - for (i = data->mouse_device_info; i != NULL; i = next) { + for (j = 0; j < SDL_arraysize(sys_cursors); j++) { + X11_FreeCursor(sys_cursors[j]); + sys_cursors[j] = NULL; + } + + for (i = data->mouse_device_info; i; i = next) { next = i->next; SDL_free(i); } @@ -495,4 +527,13 @@ void X11_QuitMouse(SDL_VideoDevice *_this) X11_DestroyEmptyCursor(); } +void X11_SetHitTestCursor(SDL_HitTestResult rc) +{ + if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { + SDL_SetCursor(NULL); + } else { + X11_ShowCursor(sys_cursors[rc]); + } +} + #endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11mouse.h b/src/video/x11/SDL_x11mouse.h index 96e0f931..bbe61eb5 100644 --- a/src/video/x11/SDL_x11mouse.h +++ b/src/video/x11/SDL_x11mouse.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -35,5 +35,6 @@ typedef struct SDL_XInput2DeviceInfo extern void X11_InitMouse(SDL_VideoDevice *_this); extern void X11_QuitMouse(SDL_VideoDevice *_this); +extern void X11_SetHitTestCursor(SDL_HitTestResult rc); #endif /* SDL_x11mouse_h_ */ diff --git a/src/video/x11/SDL_x11opengl.c b/src/video/x11/SDL_x11opengl.c index 1a24358b..f4abb695 100644 --- a/src/video/x11/SDL_x11opengl.c +++ b/src/video/x11/SDL_x11opengl.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2021 NVIDIA Corporation This software is provided 'as-is', without any express or implied @@ -193,7 +193,7 @@ int X11_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) sizeof(struct SDL_GLDriverData)); if (!_this->gl_data) { - return SDL_OutOfMemory(); + return -1; } /* Load function pointers */ @@ -237,6 +237,8 @@ int X11_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) return SDL_SetError("GLX is not supported"); } + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNTESTED; + /* Initialize extensions */ /* See lengthy comment about the inc/dec in ../windows/SDL_windowsopengl.c. */ @@ -902,7 +904,6 @@ int X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) X11_GL_GetSwapInterval(_this, ¤tInterval); _this->gl_data->glXSwapIntervalEXT(display, drawable, currentInterval); _this->gl_data->glXSwapIntervalEXT(display, drawable, interval); - status = 0; swapinterval = interval; } else if (_this->gl_data->glXSwapIntervalMESA) { @@ -925,6 +926,53 @@ int X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) return status; } +static SDL_GLSwapIntervalTearBehavior CheckSwapIntervalTearBehavior(SDL_VideoDevice *_this, Window drawable, unsigned int current_val, unsigned int current_allow_late) +{ + /* Mesa and Nvidia interpret GLX_EXT_swap_control_tear differently, as of this writing, so + figure out which behavior we have. + Technical details: https://github.com/libsdl-org/SDL/issues/8004#issuecomment-1819603282 */ + if (_this->gl_data->swap_interval_tear_behavior == SDL_SWAPINTERVALTEAR_UNTESTED) { + if (!_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } else { + Display *display = _this->driverdata->display; + unsigned int allow_late_swap_tearing = 22; + int original_val = (int) current_val; + + /* + * This is a workaround for a bug in NVIDIA drivers. Bug has been reported + * and will be fixed in a future release (probably 319.xx). + * + * There's a bug where glXSetSwapIntervalEXT ignores updates because + * it has the wrong value cached. To work around it, we just run a no-op + * update to the current value. + */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, current_val); + + /* set it to no swap interval and see how it affects GLX_LATE_SWAPS_TEAR_EXT... */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, 0); + _this->gl_data->glXQueryDrawable(display, drawable, GLX_LATE_SWAPS_TEAR_EXT, &allow_late_swap_tearing); + + if (allow_late_swap_tearing == 0) { /* GLX_LATE_SWAPS_TEAR_EXT says whether late swapping is currently in use */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_NVIDIA; + if (current_allow_late) { + original_val = -original_val; + } + } else if (allow_late_swap_tearing == 1) { /* GLX_LATE_SWAPS_TEAR_EXT says whether the Drawable can use late swapping at all */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_MESA; + } else { /* unexpected outcome! */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } + + /* set us back to what it was originally... */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, original_val); + } + } + + return _this->gl_data->swap_interval_tear_behavior; +} + + int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) { if (_this->gl_data->glXSwapIntervalEXT) { @@ -935,6 +983,7 @@ int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) unsigned int val = 0; if (_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + allow_late_swap_tearing = 22; /* set this to nonsense. */ _this->gl_data->glXQueryDrawable(display, drawable, GLX_LATE_SWAPS_TEAR_EXT, &allow_late_swap_tearing); @@ -943,12 +992,21 @@ int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) _this->gl_data->glXQueryDrawable(display, drawable, GLX_SWAP_INTERVAL_EXT, &val); - if ((allow_late_swap_tearing) && (val > 0)) { - *interval = -((int)val); - return 0; + *interval = (int)val; + + switch (CheckSwapIntervalTearBehavior(_this, drawable, val, allow_late_swap_tearing)) { + case SDL_SWAPINTERVALTEAR_MESA: + *interval = (int)val; /* unsigned int cast to signed that generates negative value if necessary. */ + break; + + case SDL_SWAPINTERVALTEAR_NVIDIA: + default: + if ((allow_late_swap_tearing) && (val > 0)) { + *interval = -((int)val); + } + break; } - *interval = (int)val; return 0; } else if (_this->gl_data->glXGetSwapIntervalMESA) { int val = _this->gl_data->glXGetSwapIntervalMESA(); diff --git a/src/video/x11/SDL_x11opengl.h b/src/video/x11/SDL_x11opengl.h index f49f5566..240081f7 100644 --- a/src/video/x11/SDL_x11opengl.h +++ b/src/video/x11/SDL_x11opengl.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -29,6 +29,14 @@ typedef void (*__GLXextFuncPtr)(void); +typedef enum SDL_GLSwapIntervalTearBehavior +{ + SDL_SWAPINTERVALTEAR_UNTESTED, + SDL_SWAPINTERVALTEAR_UNKNOWN, + SDL_SWAPINTERVALTEAR_MESA, + SDL_SWAPINTERVALTEAR_NVIDIA +} SDL_GLSwapIntervalTearBehavior; + struct SDL_GLDriverData { int errorBase, eventBase; @@ -50,6 +58,8 @@ struct SDL_GLDriverData int minor; } es_profile_max_supported_version; + SDL_GLSwapIntervalTearBehavior swap_interval_tear_behavior; + Bool (*glXQueryExtension)(Display *, int *, int *); __GLXextFuncPtr (*glXGetProcAddress)(const GLubyte *); XVisualInfo *(*glXChooseVisual)(Display *, int, int *); diff --git a/src/video/x11/SDL_x11opengles.c b/src/video/x11/SDL_x11opengles.c index 0a2b5831..5bd277d5 100644 --- a/src/video/x11/SDL_x11opengles.c +++ b/src/video/x11/SDL_x11opengles.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11opengles.h b/src/video/x11/SDL_x11opengles.h index a4237b5e..d6158581 100644 --- a/src/video/x11/SDL_x11opengles.h +++ b/src/video/x11/SDL_x11opengles.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11pen.c b/src/video/x11/SDL_x11pen.c new file mode 100644 index 00000000..61ad579a --- /dev/null +++ b/src/video/x11/SDL_x11pen.c @@ -0,0 +1,696 @@ +/* + Simple DirectMedia Layer + 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 + 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_XINPUT2 + +#include "../../events/SDL_pen_c.h" +#include "../SDL_sysvideo.h" +#include "SDL_x11pen.h" +#include "SDL_x11video.h" +#include "SDL_x11xinput2.h" + +#define PEN_ERASER_ID_MAXLEN 256 /* Max # characters of device name to scan */ +#define PEN_ERASER_NAME_TAG "eraser" /* String constant to identify erasers */ + +#define DEBUG_PEN (SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM | SDL_PEN_DEBUG_UNKNOWN_WACOM | SDL_PEN_DEBUG_NOSERIAL_WACOM) + +#define SDL_PEN_AXIS_VALUATOR_MISSING -1 + +/* X11-specific information attached to each pen */ +typedef struct xinput2_pen +{ + float axis_min[SDL_PEN_NUM_AXES]; + float axis_max[SDL_PEN_NUM_AXES]; + float slider_bias; /* shift value to add to PEN_AXIS_SLIDER (before normalisation) */ + float rotation_bias; /* rotation to add to PEN_AXIS_ROTATION (after normalisation) */ + Sint8 valuator_for_axis[SDL_PEN_NUM_AXES]; /* SDL_PEN_AXIS_VALUATOR_MISSING if not supported */ +} xinput2_pen; + +/* X11 atoms */ +static struct +{ + int initialized; /* initialised to 0 */ + Atom device_product_id; + Atom abs_pressure; + Atom abs_tilt_x; + Atom abs_tilt_y; + Atom wacom_serial_ids; + Atom wacom_tool_type; +} pen_atoms; + +/* + * Mapping from X11 device IDs to pen IDs + * + * In X11, the same device ID may represent any number of pens. We + * thus cannot directly use device IDs as pen IDs. + */ +static struct +{ + int num_pens_known; /* Number of currently known pens (based on their GUID); used to give pen ID to new pens */ + int num_entries; /* Number of X11 device IDs that correspond to pens */ + + struct pen_device_id_mapping + { + Uint32 deviceid; + Uint32 pen_id; + } * entries; /* Current pen to device ID mappings */ +} pen_map; + +typedef enum +{ + SDL_PEN_VENDOR_UNKNOWN = 0, + SDL_PEN_VENDOR_WACOM +} sdl_pen_vendor; + +/* Information to identify pens during discovery */ +typedef struct +{ + sdl_pen_vendor vendor; + SDL_GUID guid; + SDL_PenSubtype heuristic_type; /* Distinguish pen+eraser devices with shared bus ID */ + Uint32 devicetype_id, serial; /* used by PEN_VENDOR_WACOM */ + Uint32 deviceid; +} pen_identity; + +int X11_PenIDFromDeviceID(int deviceid) +{ + int i; + for (i = 0; i < pen_map.num_entries; ++i) { + if (pen_map.entries[i].deviceid == deviceid) { + return pen_map.entries[i].pen_id; + } + } + return SDL_PEN_INVALID; +} + +static void pen_atoms_ensure_initialized(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = (SDL_VideoData *)_this->driverdata; + + if (pen_atoms.initialized) { + return; + } + /* Create atoms if they don't exist yet to pre-empt hotplugging updates */ + pen_atoms.device_product_id = X11_XInternAtom(data->display, "Device Product ID", False); + pen_atoms.wacom_serial_ids = X11_XInternAtom(data->display, "Wacom Serial IDs", False); + pen_atoms.wacom_tool_type = X11_XInternAtom(data->display, "Wacom Tool Type", False); + pen_atoms.abs_pressure = X11_XInternAtom(data->display, "Abs Pressure", False); + pen_atoms.abs_tilt_x = X11_XInternAtom(data->display, "Abs Tilt X", False); + pen_atoms.abs_tilt_y = X11_XInternAtom(data->display, "Abs Tilt Y", False); + + pen_atoms.initialized = 1; +} + +/* Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably. + Returns number of Sint32s written (<= max_words), or 0 on error. */ +static size_t xinput2_pen_get_int_property(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) +{ + const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + unsigned char *output; + + if (property == None) { + return 0; + } + + if (Success != X11_XIGetProperty(data->display, deviceid, + property, + 0, max_words, False, + XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, + &output) || + num_items_return == 0 || output == NULL) { + return 0; + } + + if (type_return == XA_INTEGER) { + int k; + const int to_copy = SDL_min(max_words, num_items_return); + + if (format_return == 8) { + Sint8 *numdata = (Sint8 *)output; + for (k = 0; k < to_copy; ++k) { + dest[k] = numdata[k]; + } + } else if (format_return == 16) { + Sint16 *numdata = (Sint16 *)output; + for (k = 0; k < to_copy; ++k) { + dest[k] = numdata[k]; + } + } else { + SDL_memcpy(dest, output, sizeof(Sint32) * to_copy); + } + X11_XFree(output); + return to_copy; + } + return 0; /* type mismatch */ +} + +/* 32 bit vendor + device ID from evdev */ +static Uint32 xinput2_pen_evdevid(SDL_VideoDevice *_this, int deviceid) +{ +#if !(SDL_PEN_DEBUG_NOID) + Sint32 ids[2]; + + pen_atoms_ensure_initialized(_this); + + if (2 != xinput2_pen_get_int_property(_this, deviceid, pen_atoms.device_product_id, ids, 2)) { + return 0; + } + return ((ids[0] << 16) | (ids[1] & 0xffff)); +#else /* Testing: pretend that we have no ID (not sure if this can happen IRL) */ + return 0; +#endif +} + +/* Gets reasonably-unique GUID for the device */ +static void xinput2_pen_update_generic_guid(SDL_VideoDevice *_this, pen_identity *pident, int deviceid) +{ + Uint32 evdevid = xinput2_pen_evdevid(_this, deviceid); /* also initialises pen_atoms */ + + if (!evdevid) { + /* Fallback: if no evdevid is available; try to at least distinguish devices within the + current session. This is a poor GUID and our last resort. */ + evdevid = deviceid; + } + SDL_PenUpdateGUIDForGeneric(&pident->guid, 0, evdevid); +} + +/* Identify Wacom devices (if SDL_TRUE is returned) and extract their device type and serial IDs */ +static SDL_bool xinput2_wacom_deviceid(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) +{ +#if !(SDL_PEN_DEBUG_NONWACOM) /* Can be disabled for testing */ + Sint32 serial_id_buf[3]; + int result; + + pen_atoms_ensure_initialized(_this); + + if ((result = xinput2_pen_get_int_property(_this, deviceid, pen_atoms.wacom_serial_ids, serial_id_buf, 3)) == 3) { + *wacom_devicetype_id = serial_id_buf[2]; + *wacom_serial = serial_id_buf[1]; +#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Disabled for testing? */ + *wacom_serial = 0; +#endif + return SDL_TRUE; + } +#endif + return SDL_FALSE; +} + +/* Heuristically determines if device is an eraser */ +static SDL_bool xinput2_pen_is_eraser(SDL_VideoDevice *_this, int deviceid, char *devicename) +{ + SDL_VideoData *data = (SDL_VideoData *)_this->driverdata; + char dev_name[PEN_ERASER_ID_MAXLEN]; + int k; + + pen_atoms_ensure_initialized(_this); + + if (pen_atoms.wacom_tool_type != None) { + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + unsigned char *tooltype_name_info = NULL; + + /* Try Wacom-specific method */ + if (Success == X11_XIGetProperty(data->display, deviceid, + pen_atoms.wacom_tool_type, + 0, 32, False, + AnyPropertyType, &type_return, &format_return, + &num_items_return, &bytes_after_return, + &tooltype_name_info) && + tooltype_name_info != NULL && num_items_return > 0) { + + SDL_bool result = SDL_FALSE; + char *tooltype_name = NULL; + + if (type_return == XA_ATOM) { + /* Atom instead of string? Un-intern */ + Atom atom = *((Atom *)tooltype_name_info); + if (atom != None) { + tooltype_name = X11_XGetAtomName(data->display, atom); + } + } else if (type_return == XA_STRING && format_return == 8) { + tooltype_name = (char *)tooltype_name_info; + } + + if (tooltype_name) { + if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) { + result = SDL_TRUE; + } + X11_XFree(tooltype_name_info); + + return result; + } + } + } + /* Non-Wacom device? */ + + /* We assume that a device is an eraser if its name contains the string "eraser". + * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */ + + SDL_strlcpy(dev_name, devicename, PEN_ERASER_ID_MAXLEN); + /* lowercase device name string so we can use strstr() */ + for (k = 0; dev_name[k]; ++k) { + dev_name[k] = tolower(dev_name[k]); + } + + return (SDL_strstr(dev_name, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE; +} + +/* Gets GUID and other identifying information for the device using the best known method */ +static pen_identity xinput2_identify_pen(SDL_VideoDevice *_this, int deviceid, char *name) +{ + pen_identity pident; + + pident.devicetype_id = 0ul; + pident.serial = 0ul; + pident.deviceid = deviceid; + pident.heuristic_type = SDL_PEN_TYPE_PEN; + SDL_memset(pident.guid.data, 0, sizeof(pident.guid.data)); + + if (xinput2_pen_is_eraser(_this, deviceid, name)) { + pident.heuristic_type = SDL_PEN_TYPE_ERASER; + } + + if (xinput2_wacom_deviceid(_this, deviceid, &pident.devicetype_id, &pident.serial)) { + pident.vendor = SDL_PEN_VENDOR_WACOM; + SDL_PenUpdateGUIDForWacom(&pident.guid, pident.devicetype_id, pident.serial); + +#if DEBUG_PEN + printf("[pen] Pen %d reports Wacom device_id %x\n", + deviceid, pident.devicetype_id); +#endif + + } else { + pident.vendor = SDL_PEN_VENDOR_UNKNOWN; + } + if (!pident.serial) { + /* If the pen has a serial number, we can move it across tablets and retain its identity. + Otherwise, we use the evdev ID as part of its GUID, which may mean that we identify it with the tablet. */ + xinput2_pen_update_generic_guid(_this, &pident, deviceid); + } + SDL_PenUpdateGUIDForType(&pident.guid, pident.heuristic_type); + return pident; +} + +static void xinput2_pen_free_deviceinfo(Uint32 deviceid, void *x11_peninfo, void *context) +{ + SDL_free(x11_peninfo); +} + +static void xinput2_merge_deviceinfo(xinput2_pen *dest, xinput2_pen *src) +{ + *dest = *src; +} + +/** + * Fill in vendor-specific device information, if available + * + * For Wacom pens: identify number of buttons and extra axis (if present) + * + * \param _this global state + * \param dev The device to analyse + * \param pen The pen to initialise + * \param pident Pen identity information + * \param[out] valuator_5 Meaning of the valuator with offset 5, if any + * (written only if known and if the device has a 6th axis, + * e.g., for the Wacom Art Pen and Wacom Airbrush Pen) + * \param[out] axes Bitmask of all possibly supported axes + * + * This function identifies Wacom device types through a Wacom-specific device ID. + * It then fills in pen details from an internal database. + * If the device seems to be a Wacom pen/eraser but can't be identified, the function + * leaves "axes" untouched and sets the other outputs to common defaults. + * + * There is no explicit support for other vendors, though vendors that + * emulate the Wacom API might be supported. + * + * Unsupported devices will keep the default settings. + */ +static void xinput2_vendor_peninfo(SDL_VideoDevice *_this, const XIDeviceInfo *dev, SDL_Pen *pen, pen_identity pident, int *valuator_5, Uint32 *axes) +{ + switch (pident.vendor) { + case SDL_PEN_VENDOR_WACOM: + { + if (SDL_PenModifyForWacomID(pen, pident.devicetype_id, axes)) { + if (*axes & SDL_PEN_AXIS_SLIDER_MASK) { + /* Air Brush Pen or eraser */ + *valuator_5 = SDL_PEN_AXIS_SLIDER; + } else if (*axes & SDL_PEN_AXIS_ROTATION_MASK) { + /* Art Pen or eraser, or 6D Art Pen */ + *valuator_5 = SDL_PEN_AXIS_ROTATION; + } + return; + } else { +#if DEBUG_PEN + printf("[pen] Could not identify wacom pen %d with device id %x, using default settings\n", + pident.deviceid, pident.devicetype_id); +#endif + break; + } + } + + default: +#if DEBUG_PEN + printf("[pen] Pen %d is not from a known vendor\n", pident.deviceid); +#endif + break; + } + + /* Fall back to default heuristics for identifying device type */ + + SDL_strlcpy(pen->name, dev->name, SDL_PEN_MAX_NAME); + + pen->type = pident.heuristic_type; +} + +/* Does this device have a valuator for pressure sensitivity? */ +static SDL_bool xinput2_device_is_pen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) +{ + int classct; + + pen_atoms_ensure_initialized(_this); + + for (classct = 0; classct < dev->num_classes; ++classct) { + const XIAnyClassInfo *classinfo = dev->classes[classct]; + + switch (classinfo->type) { + case XIValuatorClass: + { + XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo; + Atom vname = val_classinfo->label; + + if (vname == pen_atoms.abs_pressure) { + return SDL_TRUE; + } + } + } + } + return SDL_FALSE; +} + +void X11_InitPen(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = (SDL_VideoData *)_this->driverdata; + int i; + XIDeviceInfo *device_info; + int num_device_info; + + device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info); + if (!device_info) { + return; + } + + /* Reset the device id -> pen map */ + if (pen_map.entries) { + SDL_free(pen_map.entries); + pen_map.entries = NULL; + pen_map.num_entries = 0; + } + + SDL_PenGCMark(); + + for (i = 0; i < num_device_info; ++i) { + const XIDeviceInfo *dev = &device_info[i]; + int classct; + xinput2_pen pen_device; + Uint32 capabilities = 0; + Uint32 axis_mask = ~0; /* Permitted axes (default: all) */ + int valuator_5_axis = -1; /* For Wacom devices, the 6th valuator (offset 5) has a model-specific meaning */ + pen_identity pident; + SDL_PenID pen_id; + SDL_Pen *pen; + int old_num_pens_known = pen_map.num_pens_known; + int k; + + /* Only track physical devices that are enabled and look like pens */ + if (dev->use != XISlavePointer || dev->enabled == 0 || !xinput2_device_is_pen(_this, dev)) { + continue; + } + + pen_device.slider_bias = 0.0f; + pen_device.rotation_bias = 0.0f; + for (k = 0; k < SDL_PEN_NUM_AXES; ++k) { + pen_device.valuator_for_axis[k] = SDL_PEN_AXIS_VALUATOR_MISSING; + } + + pident = xinput2_identify_pen(_this, dev->deviceid, dev->name); + + pen_id = SDL_GetPenFromGUID(pident.guid); + if (pen_id == SDL_PEN_INVALID) { + /* We have never met this pen */ + pen_id = ++pen_map.num_pens_known; /* start at 1 */ + } + pen = SDL_PenModifyBegin(pen_id); + + /* Complement XF86 driver information with vendor-specific details */ + xinput2_vendor_peninfo(_this, dev, pen, pident, &valuator_5_axis, &axis_mask); + + for (classct = 0; classct < dev->num_classes; ++classct) { + const XIAnyClassInfo *classinfo = dev->classes[classct]; + + switch (classinfo->type) { + case XIValuatorClass: + { + XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo; + Sint8 valuator_nr = val_classinfo->number; + Atom vname = val_classinfo->label; + int axis = -1; + + float min = val_classinfo->min; + float max = val_classinfo->max; + + if (vname == pen_atoms.abs_pressure) { + axis = SDL_PEN_AXIS_PRESSURE; + } else if (vname == pen_atoms.abs_tilt_x) { + axis = SDL_PEN_AXIS_XTILT; + } else if (vname == pen_atoms.abs_tilt_y) { + axis = SDL_PEN_AXIS_YTILT; + } + + if (axis == -1 && valuator_nr == 5) { + /* Wacom model-specific axis support */ + /* The meaning of the various axes is highly underspecitied in Xinput2. + * As of 2023-08-26, Wacom seems to be the only vendor to support these axes, so the code below + * captures the de-facto standard. */ + axis = valuator_5_axis; + + switch (axis) { + case SDL_PEN_AXIS_SLIDER: + /* cf. xinput2_wacom_peninfo for how this axis is used. + In all current cases, our API wants this value in 0..1, but the xf86 driver + starts at a negative offset, so we normalise here. */ + pen_device.slider_bias = -min; + max -= min; + min = 0.0f; + break; + + case SDL_PEN_AXIS_ROTATION: + /* The "0" value points to the left, rather than up, so we must + rotate 90 degrees counter-clockwise to have 0 point to the top. */ + + pen_device.rotation_bias = -90.0f; + break; + + default: + break; + } + } + + if (axis >= 0) { + capabilities |= SDL_PEN_AXIS_CAPABILITY(axis); + + pen_device.valuator_for_axis[axis] = valuator_nr; + pen_device.axis_min[axis] = min; + pen_device.axis_max[axis] = max; + } + break; + } + default: + break; + } + } + + /* We have a pen if and only if the device measures pressure */ + if (capabilities & SDL_PEN_AXIS_PRESSURE_MASK) { + xinput2_pen *xinput2_deviceinfo; + Uint64 guid_a, guid_b; + + /* Done collecting data, write to pen */ + SDL_PenModifyAddCapabilities(pen, capabilities); + pen->guid = pident.guid; + + if (pen->deviceinfo) { + /* Updating a known pen */ + xinput2_deviceinfo = (xinput2_pen *)pen->deviceinfo; + xinput2_merge_deviceinfo(xinput2_deviceinfo, &pen_device); + } else { + /* Registering a new pen */ + xinput2_deviceinfo = SDL_malloc(sizeof(xinput2_pen)); + SDL_memcpy(xinput2_deviceinfo, &pen_device, sizeof(xinput2_pen)); + } + pen->deviceinfo = xinput2_deviceinfo; + +#if DEBUG_PEN + printf("[pen] pen %d [%04x] valuators pressure=%d, xtilt=%d, ytilt=%d [%s]\n", + pen->header.id, pen->header.flags, + pen_device.valuator_for_axis[SDL_PEN_AXIS_PRESSURE], + pen_device.valuator_for_axis[SDL_PEN_AXIS_XTILT], + pen_device.valuator_for_axis[SDL_PEN_AXIS_YTILT], + pen->name); +#endif + SDL_memcpy(&guid_a, &pen->guid.data[0], 8); + SDL_memcpy(&guid_b, &pen->guid.data[8], 8); + if (!(guid_a | guid_b)) { +#if DEBUG_PEN + printf("[pen] (pen eliminated due to zero GUID)\n"); +#endif + pen->type = SDL_PEN_TYPE_NONE; + } + + } else { + /* Not a pen, mark for deletion */ + pen->type = SDL_PEN_TYPE_NONE; + } + SDL_PenModifyEnd(pen, SDL_TRUE); + + if (pen->type != SDL_PEN_TYPE_NONE) { + const int map_pos = pen_map.num_entries; + + /* We found a pen: add mapping */ + if (pen_map.entries == NULL) { + pen_map.entries = SDL_calloc(sizeof(struct pen_device_id_mapping), 1); + pen_map.num_entries = 1; + } else { + pen_map.num_entries += 1; + pen_map.entries = SDL_realloc(pen_map.entries, + pen_map.num_entries * (sizeof(struct pen_device_id_mapping))); + } + pen_map.entries[map_pos].deviceid = dev->deviceid; + pen_map.entries[map_pos].pen_id = pen_id; + } else { + /* Revert pen number allocation */ + pen_map.num_pens_known = old_num_pens_known; + } + } + X11_XIFreeDeviceInfo(device_info); + + SDL_PenGCSweep(NULL, xinput2_pen_free_deviceinfo); +} + +static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo, + const xinput2_pen *xpen, + /* inout-mode paramters: */ + float *coords) +{ + int axis; + + /* Normalise axes */ + for (axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) { + int valuator = xpen->valuator_for_axis[axis]; + if (valuator != SDL_PEN_AXIS_VALUATOR_MISSING) { + float value = coords[axis]; + float min = xpen->axis_min[axis]; + float max = xpen->axis_max[axis]; + + if (axis == SDL_PEN_AXIS_SLIDER) { + value += xpen->slider_bias; + } + + /* min ... 0 ... max */ + if (min < 0.0) { + /* Normalise so that 0 remains 0.0 */ + if (value < 0) { + value = value / (-min); + } else { + if (max == 0.0) { + value = 0.0f; + } else { + value = value / max; + } + } + } else { + /* 0 ... min ... max */ + /* including 0.0 = min */ + if (max == 0.0) { + value = 0.0f; + } else { + value = (value - min) / max; + } + } + + switch (axis) { + case SDL_PEN_AXIS_XTILT: + case SDL_PEN_AXIS_YTILT: + if (peninfo->info.max_tilt > 0.0f) { + value *= peninfo->info.max_tilt; /* normalise to physical max */ + } + break; + + case SDL_PEN_AXIS_ROTATION: + /* normalised to -1..1, so let's convert to degrees */ + value *= 180.0; + value += xpen->rotation_bias; + + /* handle simple over/underflow */ + if (value >= 180.0f) { + value -= 360.0f; + } else if (value < -180.0f) { + value += 360.0f; + } + break; + + default: + break; + } + coords[axis] = value; + } + } +} + +void X11_PenAxesFromValuators(const SDL_Pen *peninfo, + const double *input_values, const unsigned char *mask, const int mask_len, + /* out-mode parameters: */ + float axis_values[SDL_PEN_NUM_AXES]) +{ + const xinput2_pen *pen = (xinput2_pen *)peninfo->deviceinfo; + int i; + + for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { + const int valuator = pen->valuator_for_axis[i]; + if (valuator == SDL_PEN_AXIS_VALUATOR_MISSING || valuator >= mask_len * 8 || !(XIMaskIsSet(mask, valuator))) { + axis_values[i] = 0.0f; + } else { + axis_values[i] = input_values[valuator]; + } + } + xinput2_normalize_pen_axes(peninfo, pen, axis_values); +} + +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/x11/SDL_x11pen.h b/src/video/x11/SDL_x11pen.h new file mode 100644 index 00000000..ebb7847d --- /dev/null +++ b/src/video/x11/SDL_x11pen.h @@ -0,0 +1,54 @@ +/* + Simple DirectMedia Layer + 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 + 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_x11pen_h_ +#define SDL_x11pen_h_ + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + +#include "SDL_x11video.h" +#include "../../events/SDL_pen_c.h" + +/* Pressure-sensitive pen */ + +/* Forward definition for SDL_x11video.h */ +struct SDL_VideoData; + +/* Function definitions */ + +/* Detect XINPUT2 devices that are pens / erasers, or update the list after hotplugging */ +extern void X11_InitPen(SDL_VideoDevice *_this); + +/* Converts XINPUT2 valuators into pen axis information, including normalisation */ +extern void X11_PenAxesFromValuators(const SDL_Pen *pen, + const double *input_values, const unsigned char *mask, const int mask_len, + /* out-mode parameters: */ + float axis_values[SDL_PEN_NUM_AXES]); + +/* Map X11 device ID to pen ID */ +extern int X11_PenIDFromDeviceID(int deviceid); + +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */ + +#endif /* SDL_x11pen_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/x11/SDL_x11shape.c b/src/video/x11/SDL_x11shape.c deleted file mode 100644 index 1acd33ce..00000000 --- a/src/video/x11/SDL_x11shape.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2023 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_x11video.h" -#include "SDL_x11shape.h" -#include "SDL_x11window.h" -#include "../SDL_shape_internals.h" - -SDL_WindowShaper *X11_CreateShaper(SDL_Window *window) -{ - SDL_WindowShaper *result = NULL; - -#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE - SDL_ShapeData *data = NULL; - - if (SDL_X11_HAVE_XSHAPE) { /* Make sure X server supports it. */ - result = SDL_malloc(sizeof(SDL_WindowShaper)); - if (result == NULL) { - SDL_OutOfMemory(); - return NULL; - } - result->window = window; - result->mode.mode = ShapeModeDefault; - result->mode.parameters.binarizationCutoff = 1; - data = SDL_malloc(sizeof(SDL_ShapeData)); - if (data == NULL) { - SDL_free(result); - SDL_OutOfMemory(); - return NULL; - } - result->driverdata = data; - data->bitmapsize = 0; - data->bitmap = NULL; - window->shaper = result; - } -#endif - - return result; -} - -int X11_SetWindowShape(SDL_WindowShaper *shaper, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode) -{ -#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE - SDL_ShapeData *data = NULL; - SDL_WindowData *windowdata = NULL; - Pixmap shapemask; -#endif - - if (shaper == NULL || shape == NULL || shaper->driverdata == NULL) { - return -1; - } - -#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE - if (shape->format->Amask == 0 && SDL_SHAPEMODEALPHA(shape_mode->mode)) { - return -2; - } - if (shape->w != shaper->window->w || shape->h != shaper->window->h) { - return -3; - } - data = shaper->driverdata; - - /* Assume that shaper->alphacutoff already has a value, because SDL_SetWindowShape() should have given it one. */ - SDL_CalculateShapeBitmap(shaper->mode, shape, data->bitmap, 8); - - windowdata = shaper->window->driverdata; - shapemask = X11_XCreateBitmapFromData(windowdata->videodata->display, windowdata->xwindow, data->bitmap, shaper->window->w, shaper->window->h); - - X11_XShapeCombineMask(windowdata->videodata->display, windowdata->xwindow, ShapeBounding, 0, 0, shapemask, ShapeSet); - X11_XSync(windowdata->videodata->display, False); - - X11_XFreePixmap(windowdata->videodata->display, shapemask); -#endif - - return 0; -} - -#endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 08447ebd..78436c1f 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -193,11 +193,19 @@ SDL_X11_SYM(void,XkbFreeKeyboard,(XkbDescPtr a,unsigned int b, Bool c),(a,b,c),) SDL_X11_SYM(Bool,XkbSetDetectableAutoRepeat,(Display* a, Bool b, Bool* c),(a,b,c),return) #endif +/* XKeycodeToKeysym is a deprecated function */ +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif #if NeedWidePrototypes SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,unsigned int b,int c),(a,b,c),return) #else SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,KeyCode b,int c),(a,b,c),return) #endif +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA +#pragma GCC diagnostic pop +#endif #ifdef X_HAVE_UTF8_STRING SDL_X11_MODULE(UTF8) @@ -281,6 +289,7 @@ SDL_X11_SYM(Status,XIQueryVersion,(Display *a,int *b,int *c),(a,b,c),return) SDL_X11_SYM(XIEventMask*,XIGetSelectedEvents,(Display *a,Window b,int *c),(a,b,c),return) SDL_X11_SYM(Bool,XIGetClientPointer,(Display *a,Window b,int *c),(a,b,c),return) SDL_X11_SYM(Bool,XIWarpPointer,(Display *a,int b,Window c,Window d,double e,double f,int g,int h,double i,double j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_X11_SYM(Status,XIGetProperty,(Display *a,int b,Atom c,long d,long e,Bool f, Atom g, Atom *h, int *i, unsigned long *j, unsigned long *k, unsigned char **l),(a,b,c,d,e,f,g,h,i,j,k,l),return); #endif /* XRandR support */ diff --git a/src/video/x11/SDL_x11touch.c b/src/video/x11/SDL_x11touch.c index 681e9d9c..9b2a86ce 100644 --- a/src/video/x11/SDL_x11touch.c +++ b/src/video/x11/SDL_x11touch.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11touch.h b/src/video/x11/SDL_x11touch.h index 93f66a87..2343faa5 100644 --- a/src/video/x11/SDL_x11touch.h +++ b/src/video/x11/SDL_x11touch.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 4192bf33..5a7ad268 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -24,16 +24,16 @@ #include /* For getpid() and readlink() */ -#include "../SDL_sysvideo.h" -#include "../SDL_pixels_c.h" #include "../../core/linux/SDL_system_theme.h" +#include "../SDL_pixels_c.h" +#include "../SDL_sysvideo.h" -#include "SDL_x11video.h" #include "SDL_x11framebuffer.h" -#include "SDL_x11shape.h" +#include "SDL_x11pen.h" #include "SDL_x11touch.h" -#include "SDL_x11xinput2.h" +#include "SDL_x11video.h" #include "SDL_x11xfixes.h" +#include "SDL_x11xinput2.h" #ifdef SDL_VIDEO_OPENGL_EGL #include "SDL_x11opengles.h" @@ -79,7 +79,7 @@ static int X11_SafetyNetErrHandler(Display *d, XErrorEvent *e) if (!safety_net_triggered) { safety_net_triggered = SDL_TRUE; device = SDL_GetVideoDevice(); - if (device != NULL) { + if (device) { int i; for (i = 0; i < device->num_displays; i++) { SDL_VideoDisplay *display = device->displays[i]; @@ -90,7 +90,7 @@ static int X11_SafetyNetErrHandler(Display *d, XErrorEvent *e) } } - if (orig_x11_errhandler != NULL) { + if (orig_x11_errhandler) { return orig_x11_errhandler(d, e); /* probably terminate. */ } @@ -115,21 +115,19 @@ static SDL_VideoDevice *X11_CreateDevice(void) /* Open the display first to be sure that X11 is available */ x11_display = X11_XOpenDisplay(display); - if (x11_display == NULL) { + if (!x11_display) { SDL_X11_UnloadSymbols(); return NULL; } /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); - if (device == NULL) { - SDL_OutOfMemory(); + if (!device) { return NULL; } data = (struct SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); - if (data == NULL) { + if (!data) { SDL_free(device); - SDL_OutOfMemory(); return NULL; } device->driverdata = data; @@ -142,7 +140,7 @@ static SDL_VideoDevice *X11_CreateDevice(void) data->display = x11_display; data->request_display = X11_XOpenDisplay(display); - if (data->request_display == NULL) { + if (!data->request_display) { X11_XCloseDisplay(data->display); SDL_free(device->driverdata); SDL_free(device); @@ -180,7 +178,6 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->SendWakeupEvent = X11_SendWakeupEvent; device->CreateSDLWindow = X11_CreateWindow; - device->CreateSDLWindowFrom = X11_CreateWindowFrom; device->SetWindowTitle = X11_SetWindowTitle; device->SetWindowIcon = X11_SetWindowIcon; device->SetWindowPosition = X11_SetWindowPosition; @@ -207,20 +204,17 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer; - device->GetWindowWMInfo = X11_GetWindowWMInfo; device->SetWindowHitTest = X11_SetWindowHitTest; device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->FlashWindow = X11_FlashWindow; device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; device->SetWindowFocusable = X11_SetWindowFocusable; + device->SyncWindow = X11_SyncWindow; #ifdef SDL_VIDEO_DRIVER_X11_XFIXES device->SetWindowMouseRect = X11_SetWindowMouseRect; #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */ - device->shape_driver.CreateShaper = X11_CreateShaper; - device->shape_driver.SetWindowShape = X11_SetWindowShape; - #ifdef SDL_VIDEO_OPENGL_GLX device->GL_LoadLibrary = X11_GL_LoadLibrary; device->GL_GetProcAddress = X11_GL_GetProcAddress; @@ -281,7 +275,8 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->system_theme = SDL_SystemTheme_Get(); #endif - device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; return device; } @@ -433,6 +428,10 @@ int X11_VideoInit(SDL_VideoDevice *_this) X11_InitTouch(_this); +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + X11_InitPen(_this); +#endif + return 0; } @@ -459,7 +458,7 @@ void X11_VideoQuit(SDL_VideoDevice *_this) SDL_bool X11_UseDirectColorVisuals(void) { - return SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") ? SDL_FALSE : SDL_TRUE; + return (SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") == NULL); } #endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 09efd645..e199dd51 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11vulkan.c b/src/video/x11/SDL_x11vulkan.c index b81b9804..e92b5928 100644 --- a/src/video/x11/SDL_x11vulkan.c +++ b/src/video/x11/SDL_x11vulkan.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -57,10 +57,10 @@ int X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) } /* Load the Vulkan loader library */ - if (path == NULL) { + if (!path) { path = SDL_getenv("SDL_VULKAN_LIBRARY"); } - if (path == NULL) { + if (!path) { path = DEFAULT_VULKAN; } _this->vulkan_config.loader_handle = SDL_LoadObject(path); @@ -84,7 +84,7 @@ int X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) (PFN_vkEnumerateInstanceExtensionProperties) _this->vulkan_config.vkEnumerateInstanceExtensionProperties, &extensionCount); - if (extensions == NULL) { + if (!extensions) { goto fail; } for (i = 0; i < extensionCount; i++) { @@ -108,7 +108,7 @@ int X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) goto fail; } else { const char *libX11XCBLibraryName = SDL_getenv("SDL_X11_XCB_LIBRARY"); - if (libX11XCBLibraryName == NULL) { + if (!libX11XCBLibraryName) { libX11XCBLibraryName = "libX11-xcb.so"; } videoData->vulkan_xlib_xcb_library = SDL_LoadObject(libX11XCBLibraryName); @@ -142,35 +142,35 @@ void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) } } -SDL_bool X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names) +char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) { SDL_VideoData *videoData = _this->driverdata; - if (!_this->vulkan_config.loader_handle) { - SDL_SetError("Vulkan is not loaded"); - return SDL_FALSE; - } if (videoData->vulkan_xlib_xcb_library) { static const char *const extensionsForXCB[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XCB_SURFACE_EXTENSION_NAME, }; - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForXCB), extensionsForXCB); + if(count) { + *count = SDL_arraysize(extensionsForXCB); + } + return extensionsForXCB; } else { static const char *const extensionsForXlib[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME, }; - return SDL_Vulkan_GetInstanceExtensions_Helper( - count, names, SDL_arraysize(extensionsForXlib), extensionsForXlib); + if(count) { + *count = SDL_arraysize(extensionsForXlib); + } + return extensionsForXlib; } } SDL_bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { SDL_VideoData *videoData = _this->driverdata; @@ -200,8 +200,7 @@ SDL_bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, return SDL_FALSE; } createInfo.window = (xcb_window_t)windowData->xwindow; - result = vkCreateXcbSurfaceKHR(instance, &createInfo, - NULL, surface); + result = vkCreateXcbSurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateXcbSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); return SDL_FALSE; @@ -222,8 +221,7 @@ SDL_bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; createInfo.dpy = videoData->display; createInfo.window = (xcb_window_t)windowData->xwindow; - result = vkCreateXlibSurfaceKHR(instance, &createInfo, - NULL, surface); + result = vkCreateXlibSurfaceKHR(instance, &createInfo, allocator, surface); if (result != VK_SUCCESS) { SDL_SetError("vkCreateXlibSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); return SDL_FALSE; diff --git a/src/video/x11/SDL_x11vulkan.h b/src/video/x11/SDL_x11vulkan.h index 30b047e0..2c81a33a 100644 --- a/src/video/x11/SDL_x11vulkan.h +++ b/src/video/x11/SDL_x11vulkan.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,12 +32,12 @@ typedef xcb_connection_t *(*PFN_XGetXCBConnection)(Display *dpy); int X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); -SDL_bool X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, - unsigned *count, - const char **names); +char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count); SDL_bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, + const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface); #endif diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index a1c5bb82..cda57897 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -31,7 +31,6 @@ #include "SDL_x11video.h" #include "SDL_x11mouse.h" -#include "SDL_x11shape.h" #include "SDL_x11xinput2.h" #include "SDL_x11xfixes.h" @@ -39,8 +38,6 @@ #include "SDL_x11opengles.h" #endif -#include - #define _NET_WM_STATE_REMOVE 0l #define _NET_WM_STATE_ADD 1l @@ -123,6 +120,8 @@ void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, Uint32 flags) Atom _NET_WM_STATE_MAXIMIZED_HORZ = videodata->_NET_WM_STATE_MAXIMIZED_HORZ; Atom _NET_WM_STATE_FULLSCREEN = videodata->_NET_WM_STATE_FULLSCREEN; Atom _NET_WM_STATE_ABOVE = videodata->_NET_WM_STATE_ABOVE; + Atom _NET_WM_STATE_SKIP_TASKBAR = videodata->_NET_WM_STATE_SKIP_TASKBAR; + Atom _NET_WM_STATE_SKIP_PAGER = videodata->_NET_WM_STATE_SKIP_PAGER; Atom atoms[16]; int count = 0; @@ -138,6 +137,10 @@ void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, Uint32 flags) if (flags & SDL_WINDOW_ALWAYS_ON_TOP) { atoms[count++] = _NET_WM_STATE_ABOVE; } + if (flags & SDL_WINDOW_UTILITY) { + atoms[count++] = _NET_WM_STATE_SKIP_TASKBAR; + atoms[count++] = _NET_WM_STATE_SKIP_PAGER; + } if (flags & SDL_WINDOW_INPUT_FOCUS) { atoms[count++] = _NET_WM_STATE_FOCUSED; } @@ -166,12 +169,12 @@ static void X11_ConstrainPopup(SDL_Window *window) SDL_Window *w; SDL_DisplayID displayID; SDL_Rect rect; - int abs_x = window->x; - int abs_y = window->y; + int abs_x = window->floating.x; + int abs_y = window->floating.y; int offset_x = 0, offset_y = 0; /* Calculate the total offset from the parents */ - for (w = window->parent; w->parent != NULL; w = w->parent) { + for (w = window->parent; w->parent; w = w->parent) { offset_x += w->x; offset_y += w->y; } @@ -193,8 +196,8 @@ static void X11_ConstrainPopup(SDL_Window *window) abs_x = SDL_max(abs_x, rect.x); abs_y = SDL_max(abs_y, rect.y); - window->x = window->windowed.x = abs_x - offset_x; - window->y = window->windowed.y = abs_y - offset_y; + window->floating.x = window->windowed.x = abs_x - offset_x; + window->floating.y = window->windowed.y = abs_y - offset_y; } } @@ -203,7 +206,7 @@ static void X11_SetKeyboardFocus(SDL_Window *window) SDL_Window *topmost = window; /* Find the topmost parent */ - while (topmost->parent != NULL) { + while (topmost->parent) { topmost = topmost->parent; } @@ -297,9 +300,10 @@ Uint32 X11_GetNetWMState(SDL_VideoDevice *_this, SDL_Window *window, Window xwin return flags; } -static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, BOOL created) +static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w) { SDL_VideoData *videodata = _this->driverdata; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); SDL_WindowData *data; int numwindows = videodata->numwindows; int windowlistlength = videodata->windowlistlength; @@ -307,11 +311,12 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, /* Allocate the window data */ data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); - if (data == NULL) { - return SDL_OutOfMemory(); + if (!data) { + return -1; } data->window = window; data->xwindow = w; + data->hit_test_result = SDL_HITTEST_NORMAL; #ifdef X_HAVE_UTF8_STRING if (SDL_X11_HAVE_UTF8 && videodata->im) { @@ -321,7 +326,6 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, NULL); } #endif - data->created = created; data->videodata = videodata; /* Associate the data with the window */ @@ -330,11 +334,12 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, windowlist[numwindows] = data; videodata->numwindows++; } else { - windowlist = (SDL_WindowData **)SDL_realloc(windowlist, (numwindows + 1) * sizeof(*windowlist)); - if (windowlist == NULL) { + SDL_WindowData ** new_windowlist = (SDL_WindowData **)SDL_realloc(windowlist, (numwindows + 1) * sizeof(*windowlist)); + if (!new_windowlist) { SDL_free(data); - return SDL_OutOfMemory(); + return -1; } + windowlist = new_windowlist; windowlist[numwindows] = data; videodata->numwindows++; videodata->windowlistlength++; @@ -347,11 +352,11 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, X11_XGetWindowAttributes(data->videodata->display, w, &attrib); if (!SDL_WINDOW_IS_POPUP(window)) { - window->x = attrib.x; - window->y = attrib.y; + window->x = data->expected.x = attrib.x; + window->y = data->expected.y = attrib.y - data->border_top; } - window->w = attrib.width; - window->h = attrib.height; + window->w = data->expected.w = attrib.width; + window->h = data->expected.h = attrib.height; if (attrib.map_state != IsUnmapped) { window->flags &= ~SDL_WINDOW_HIDDEN; } else { @@ -380,6 +385,17 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w, } } + if (window->flags & SDL_WINDOW_EXTERNAL) { + /* Query the title from the existing window */ + window->title = X11_GetWindowTitle(_this, w); + } + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + int screen = (displaydata ? displaydata->screen : 0); + SDL_SetProperty(props, SDL_PROPERTY_WINDOW_X11_DISPLAY_POINTER, data->videodata->display); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_SCREEN_NUMBER, screen); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_X11_WINDOW_NUMBER, data->xwindow); + /* All done! */ window->driverdata = data; return 0; @@ -415,8 +431,19 @@ static void SetWindowBordered(Display *display, int screen, Window window, SDL_b } } -int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) +int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { + Window w = (Window)SDL_GetNumberProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_X11_WINDOW_NUMBER, + (Window)SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL)); + if (w) { + window->flags |= SDL_WINDOW_EXTERNAL; + + if (SetupWindowData(_this, window, w) < 0) { + return -1; + } + return 0; + } + SDL_VideoData *data = _this->driverdata; SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); const SDL_bool force_override_redirect = SDL_GetHintBoolean(SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT, SDL_FALSE); @@ -426,7 +453,6 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) Visual *visual; int depth; XSetWindowAttributes xattr; - Window w; XSizeHints *sizehints; XWMHints *wmhints; XClassHint *classhints; @@ -445,7 +471,7 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? SDL_TRUE : SDL_FALSE; const char *forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID); - if (forced_visual_id != NULL && forced_visual_id[0] != '\0') { + if (forced_visual_id && forced_visual_id[0] != '\0') { XVisualInfo *vi, template; int nvis; @@ -479,7 +505,7 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) #endif } - if (vinfo == NULL) { + if (!vinfo) { return -1; } visual = vinfo->visual; @@ -516,8 +542,8 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* OK, we got a colormap, now fill it in as best as we can */ colorcells = SDL_malloc(visual->map_entries * sizeof(XColor)); - if (colorcells == NULL) { - return SDL_OutOfMemory(); + if (!colorcells) { + return -1; } ncolors = visual->map_entries; rmax = 0xffff; @@ -585,17 +611,17 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_ConstrainPopup(window); } SDL_RelativeToGlobalForWindow(window, - window->windowed.x, window->windowed.y, - &win_x, &win_y); + window->floating.x, window->floating.y, + &win_x, &win_y); - /* Always create this with the window->windowed.* fields; if we're - creating a windowed mode window, that's fine. If we're creating a - fullscreen window, the window manager will want to know these values - so it can use them if we go _back_ to windowed mode. SDL manages - migration to fullscreen after CreateSDLWindow returns, which will - put all the SDL_Window fields and system state as expected. */ + /* Always create this with the window->floating.* fields; if we're creating a windowed mode window, + * that's fine. If we're creating a maximized or fullscreen window, the window manager will want to + * know these values so it can use them if we go _back_ to the base floating windowed mode. SDL manages + * migration to fullscreen after CreateSDLWindow returns, which will put all the SDL_Window fields and + * system state as expected. + */ w = X11_XCreateWindow(display, RootWindow(display, screen), - win_x, win_y, window->windowed.w, window->windowed.h, + win_x, win_y, window->floating.w, window->floating.h, 0, depth, InputOutput, visual, (CWOverrideRedirect | CWBackPixmap | CWBorderPixel | CWBackingStore | CWColormap), @@ -663,7 +689,7 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) wintype_name = "_NET_WM_WINDOW_TYPE_TOOLTIP"; } else if (window->flags & SDL_WINDOW_POPUP_MENU) { wintype_name = "_NET_WM_WINDOW_TYPE_POPUP_MENU"; - } else if (hint != NULL && *hint) { + } else if (hint && *hint) { wintype_name = hint; } else { wintype_name = "_NET_WM_WINDOW_TYPE_NORMAL"; @@ -699,7 +725,7 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_XSetWMProtocols(display, w, protocols, proto_count); } - if (SetupWindowData(_this, window, w, SDL_TRUE) < 0) { + if (SetupWindowData(_this, window, w) < 0) { X11_XDestroyWindow(display, w); return -1; } @@ -747,12 +773,20 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_Xinput2SelectTouch(_this, window); - X11_XSelectInput(display, w, - (FocusChangeMask | EnterWindowMask | LeaveWindowMask | - ExposureMask | ButtonPressMask | ButtonReleaseMask | - PointerMotionMask | KeyPressMask | KeyReleaseMask | - PropertyChangeMask | StructureNotifyMask | - KeymapStateMask | fevent)); + { + unsigned int x11_pointer_events = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + if (X11_Xinput2SelectMouse(_this, window)) { + /* If XInput2 can handle pointer events, we don't track them here */ + x11_pointer_events = 0; + } + + X11_XSelectInput(display, w, + (FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask | + x11_pointer_events | + KeyPressMask | KeyReleaseMask | + PropertyChangeMask | StructureNotifyMask | + KeymapStateMask | fevent)); + } /* For _ICC_PROFILE. */ X11_XSelectInput(display, RootWindow(display, screen), PropertyChangeMask); @@ -762,18 +796,6 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) return 0; } -int X11_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data) -{ - Window w = (Window)data; - - window->title = X11_GetWindowTitle(_this, w); - - if (SetupWindowData(_this, window, w, SDL_FALSE) < 0) { - return -1; - } - return 0; -} - char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow) { SDL_VideoData *data = _this->driverdata; @@ -825,108 +847,76 @@ static int X11_CatchAnyError(Display *d, XErrorEvent *e) return 0; } -enum check_method { - COMPARE_POSITION = 1, - COMPARE_SIZE = 2, - COMPARE_DOUBLE_ATTEMPT = 3, - COMPARE_ORIG = 4, - COMPARE_NO_WAIT = 5 -}; /* Wait a brief time, or not, to see if the window manager decided to move/resize the window. * Send MOVED and RESIZED window events */ -static void X11_WaitAndSendWindowEvents(SDL_Window *window, int param_timeout, enum check_method method, - int orig_x, int orig_y, int dest_x, int dest_y, - int orig_w, int orig_h, int dest_w, int dest_h) +static int X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, int param_timeout) { SDL_WindowData *data = window->driverdata; Display *display = data->videodata->display; - Window xwindow = data->xwindow; int (*prev_handler)(Display *, XErrorEvent *); - int x, y; - XWindowAttributes attrs; Uint64 timeout = 0; - Window childReturn, root, parent; - Window *children; - unsigned int childCount; - SDL_bool window_size_changed = SDL_FALSE; - int window_position_changed = 0; + int ret = 0; + SDL_bool force_exit = SDL_FALSE; X11_XSync(display, False); prev_handler = X11_XSetErrorHandler(X11_CatchAnyError); - if (method != COMPARE_NO_WAIT) { + if (param_timeout) { timeout = SDL_GetTicks() + param_timeout; } - /* Get the parent */ - X11_XQueryTree(display, xwindow, &root, &parent, &children, &childCount); - while (SDL_TRUE) { - caught_x11_error = SDL_FALSE; X11_XSync(display, False); - X11_XGetWindowAttributes(display, xwindow, &attrs); - X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), - attrs.x, attrs.y, &x, &y, &childReturn); + X11_PumpEvents(_this); - if (method == COMPARE_NO_WAIT) { - break; + if ((data->pending_operation & X11_PENDING_OP_MOVE) && (window->x == data->expected.x && window->y == data->expected.y)) { + data->pending_operation &= ~X11_PENDING_OP_MOVE; + } + if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (window->w == data->expected.w && window->h == data->expected.h)) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; } - if (!caught_x11_error) { - if (method == COMPARE_POSITION) { - if (x != orig_x || y != orig_y) { - break; /* window moved, time to go. */ - } else if (x == dest_x && y == dest_y) { - break; /* we're at the place we wanted to be anyhow, drop out. */ - } - } else if (method == COMPARE_SIZE) { - if (attrs.width != orig_w || attrs.height != orig_h) { - break; /* window changed, time to go. */ - } else if (attrs.width == window->w && attrs.height == window->h) { - break; /* we've size we wanted anyhow, drop out. */ - } - } else if (method == COMPARE_ORIG) { - if (x != orig_x || y != orig_y || attrs.width != orig_w || attrs.height != orig_h) { - break; /* window moved or resized, time to go. */ - } - } else if (method == COMPARE_DOUBLE_ATTEMPT) { - if (x != orig_x || y != orig_y) { - orig_x = x; - orig_y = y; - window_position_changed += 1; - } - if (attrs.width != orig_w || attrs.height != orig_h) { - orig_w = attrs.width; - orig_h = attrs.height; - window_size_changed = SDL_TRUE; - } - /* Wait for at least 2 moves + 1 size changed to have valid values */ - if (window_position_changed >= 2 && window_size_changed) { - break; /* window changed, time to go. */ - } + if (data->pending_operation == X11_PENDING_OP_NONE) { + if (force_exit || + (window->x == data->expected.x && window->y == data->expected.y && + window->w == data->expected.w && window->h == data->expected.h)) { + /* The window is in the expected state and nothing is pending. Done. */ + break; } + + /* No operations are pending, but the window still isn't in the expected state. + * Try one more time before exiting. + */ + force_exit = SDL_TRUE; } if (SDL_GetTicks() >= timeout) { + /* Timed out without the expected values. Update the requested data so future sync calls won't block. */ + data->expected.x = window->x; + data->expected.y = window->y; + data->expected.w = window->w; + data->expected.h = window->h; + + ret = 1; break; } SDL_Delay(10); } - if (!caught_x11_error) { - if (SDL_WINDOW_IS_POPUP(window)) { - SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); - } + data->pending_operation = X11_PENDING_OP_NONE; - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, attrs.width, attrs.height); + if (!caught_x11_error) { + X11_PumpEvents(_this); + } else { + ret = -1; } X11_XSetErrorHandler(prev_handler); caught_x11_error = SDL_FALSE; -} + return ret; +} int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) { @@ -949,7 +939,7 @@ int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i propdata = SDL_malloc(propsize * sizeof(long)); if (!propdata) { - return SDL_OutOfMemory(); + return -1; } X11_XSync(display, False); @@ -967,8 +957,8 @@ int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i } X11_XChangeProperty(display, data->xwindow, _NET_WM_ICON, XA_CARDINAL, - 32, PropModeReplace, (unsigned char *)propdata, - propsize); + 32, PropModeReplace, (unsigned char *)propdata, + propsize); SDL_free(propdata); if (caught_x11_error) { @@ -978,7 +968,7 @@ int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i X11_XFlush(display); - if (prevHandler != NULL) { + if (prevHandler) { X11_XSetErrorHandler(prevHandler); caught_x11_error = SDL_FALSE; } @@ -986,45 +976,42 @@ int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i return rc; } -void X11_UpdateWindowPosition(SDL_Window *window) +void X11_UpdateWindowPosition(SDL_Window *window, SDL_bool use_current_position) { - SDL_Window *w; SDL_WindowData *data = window->driverdata; Display *display = data->videodata->display; - unsigned int childCount; - Window childReturn, root, parent; - Window *children; - XWindowAttributes attrs; - int dest_x, dest_y; - int orig_x, orig_y; - - X11_XSync(display, False); - X11_XQueryTree(display, data->xwindow, &root, &parent, &children, &childCount); - X11_XGetWindowAttributes(display, data->xwindow, &attrs); - X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), - attrs.x, attrs.y, &orig_x, &orig_y, &childReturn); + const int rel_x = use_current_position ? window->x : window->floating.x; + const int rel_y = use_current_position ? window->y : window->floating.y; SDL_RelativeToGlobalForWindow(window, - window->x - data->border_left, window->y - data->border_top, - &dest_x, &dest_y); + rel_x - data->border_left, rel_y - data->border_top, + &data->expected.x, &data->expected.y); /* Attempt to move the window */ - X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); - - /* Send MOVED/RESIZED event, if needed. Compare with initial/expected position. Timeout 100 */ - X11_WaitAndSendWindowEvents(window, 100, COMPARE_POSITION, orig_x, orig_y, dest_x, dest_y, 0, 0, 0, 0); - - for (w = window->first_child; w != NULL; w = w->next_sibling) { - X11_UpdateWindowPosition(w); - } + data->pending_operation |= X11_PENDING_OP_MOVE; + X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y); } int X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) { - if (SDL_WINDOW_IS_POPUP(window)) { - X11_ConstrainPopup(window); + /* Sync any pending fullscreen or maximize events. */ + if (window->driverdata->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE)) { + X11_SyncWindow(_this, window); + } + + /* Position will be set when window is de-maximized */ + if (window->flags & SDL_WINDOW_MAXIMIZED) { + return 0; + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (SDL_WINDOW_IS_POPUP(window)) { + X11_ConstrainPopup(window); + } + X11_UpdateWindowPosition(window, SDL_FALSE); + } else { + SDL_UpdateFullscreenMode(window, SDL_TRUE, SDL_TRUE); } - X11_UpdateWindowPosition(window); return 0; } @@ -1052,104 +1039,110 @@ static void X11_SetWMNormalHints(SDL_VideoDevice *_this, SDL_Window *window, XSi hide/show, because there are supposedly subtle problems with doing so and transitioning from windowed to fullscreen in Unity. */ - X11_XResizeWindow(display, data->xwindow, window->w, window->h); + X11_XResizeWindow(display, data->xwindow, window->floating.w, window->floating.h); SDL_RelativeToGlobalForWindow(window, - window->x - data->border_left, - window->y - data->border_top, + window->floating.x - data->border_left, + window->floating.y - data->border_top, &dest_x, &dest_y); X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); X11_XRaiseWindow(display, data->xwindow); } -void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) +void X11_SetWindowMinMax(SDL_Window *window, SDL_bool use_current) { SDL_WindowData *data = window->driverdata; Display *display = data->videodata->display; + XSizeHints *sizehints = X11_XAllocSizeHints(); + long hint_flags = 0; - if (window->flags & SDL_WINDOW_RESIZABLE) { - XSizeHints *sizehints = X11_XAllocSizeHints(); - long userhints; + X11_XGetWMNormalHints(display, data->xwindow, sizehints, &hint_flags); + sizehints->flags &= ~(PMinSize | PMaxSize); - X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); - - sizehints->min_width = window->min_w; - sizehints->min_height = window->min_h; - sizehints->flags |= PMinSize; - - X11_SetWMNormalHints(_this, window, sizehints); - - X11_XFree(sizehints); + if (data->window->flags & SDL_WINDOW_RESIZABLE) { + if (data->window->min_w || data->window->min_h) { + sizehints->flags |= PMinSize; + sizehints->min_width = data->window->min_w; + sizehints->min_height = data->window->min_h; + } + if (data->window->max_w || data->window->max_h) { + sizehints->flags |= PMaxSize; + sizehints->max_width = data->window->max_w; + sizehints->max_height = data->window->max_h; + } + } else { + /* Set the min/max to the same values to make the window non-resizable */ + sizehints->flags |= PMinSize | PMaxSize; + sizehints->min_width = sizehints->max_width = use_current ? data->window->floating.w : window->windowed.w; + sizehints->min_height = sizehints->max_height = use_current ? data->window->floating.h : window->windowed.h; } - X11_XFlush(display); + X11_XSetWMNormalHints(display, data->xwindow, sizehints); + X11_XFree(sizehints); +} + +void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->driverdata->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, SDL_TRUE); + } } void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_WindowData *data = window->driverdata; - Display *display = data->videodata->display; - - if (window->flags & SDL_WINDOW_RESIZABLE) { - XSizeHints *sizehints = X11_XAllocSizeHints(); - long userhints; - - X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); - - sizehints->max_width = window->max_w; - sizehints->max_height = window->max_h; - sizehints->flags |= PMaxSize; - - X11_SetWMNormalHints(_this, window, sizehints); - - X11_XFree(sizehints); + if (window->driverdata->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); } - X11_XFlush(display); + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, SDL_TRUE); + } } void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; Display *display = data->videodata->display; - XWindowAttributes attrs; - int orig_w, orig_h; - X11_XSync(display, False); - X11_XGetWindowAttributes(display, data->xwindow, &attrs); - orig_w = attrs.width; - orig_h = attrs.height; - - if (!(window->flags & SDL_WINDOW_RESIZABLE)) { - /* Apparently, if the X11 Window is set to a 'non-resizable' window, you cannot resize it using the X11_XResizeWindow, thus - we must set the size hints to adjust the window size. */ - XSizeHints *sizehints = X11_XAllocSizeHints(); - long userhints; - - X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); - - sizehints->min_width = sizehints->max_width = window->w; - sizehints->min_height = sizehints->max_height = window->h; - sizehints->flags |= PMinSize | PMaxSize; - - X11_SetWMNormalHints(_this, window, sizehints); - - X11_XFree(sizehints); - } else { - X11_XResizeWindow(display, data->xwindow, window->w, window->h); + /* Wait for pending maximize operations to complete, or the window can end up in a weird, + * partially-maximized state. + */ + if (data->pending_operation & (X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_FULLSCREEN)) { + X11_SyncWindow(_this, window); } - /* Timeout occurred and window size didn't change - * window manager likely denied the resize, - * or the new size is the same as the existing: - * - current width: is 'full width'. - * - try to set new width at 'full width + 1', which get truncated to 'full width'. - * - new width is/remains 'full width' - * So, even if we break here as a timeout, we can send an event, since the requested size isn't the same - * as the final size. (even if final size is same as original size). - */ + /* Don't try to resize a maximized or fullscreen window, it will be done on restore. */ + if (window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN)) { + return; + } - /* Send MOVED/RESIZED event, if needed. Compare with initial/expected size. Timeout 100 */ - X11_WaitAndSendWindowEvents(window, 100, COMPARE_SIZE, 0, 0, 0, 0, orig_w, orig_h, window->w, window->h); + if (!(window->flags & SDL_WINDOW_RESIZABLE)) { + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + /* Apparently, if the X11 Window is set to a 'non-resizable' window, you cannot resize it using the X11_XResizeWindow, thus + * we must set the size hints to adjust the window size. + */ + XSizeHints *sizehints = X11_XAllocSizeHints(); + long userhints; + + X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); + + sizehints->min_width = sizehints->max_width = window->floating.w; + sizehints->min_height = sizehints->max_height = window->floating.h; + sizehints->flags |= PMinSize | PMaxSize; + + X11_SetWMNormalHints(_this, window, sizehints); + + X11_XFree(sizehints); + } + } else { + data->expected.w = window->floating.w; + data->expected.h = window->floating.h; + data->pending_operation |= X11_PENDING_OP_RESIZE; + X11_XResizeWindow(display, data->xwindow, data->expected.w, data->expected.h); + } } int X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right) @@ -1213,61 +1206,56 @@ void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool Display *display = data->videodata->display; XEvent event; - SetWindowBordered(display, displaydata->screen, data->xwindow, bordered); - X11_XFlush(display); - - if (visible) { - XWindowAttributes attr; - do { - X11_XSync(display, False); - X11_XGetWindowAttributes(display, data->xwindow, &attr); - } while (attr.map_state != IsViewable); - - if (focused) { - X11_XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime); - } + if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); } - /* make sure these don't make it to the real event queue if they fired here. */ - X11_XSync(display, False); - X11_XCheckIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); - X11_XCheckIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); + /* If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode. */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + SetWindowBordered(display, displaydata->screen, data->xwindow, bordered); + X11_XFlush(display); - /* Make sure the window manager didn't resize our window for the difference. */ - X11_XResizeWindow(display, data->xwindow, window->w, window->h); - X11_XSync(display, False); + if (visible) { + XWindowAttributes attr; + do { + X11_XSync(display, False); + X11_XGetWindowAttributes(display, data->xwindow, &attr); + } while (attr.map_state != IsViewable); + + if (focused) { + X11_XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime); + } + } + + /* make sure these don't make it to the real event queue if they fired here. */ + X11_XSync(display, False); + X11_XCheckIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); + X11_XCheckIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); + + /* Turning the borders off doesn't send an extent event, so they must be cleared here. */ + X11_GetBorderValues(data); + + /* Make sure the window manager didn't resize our window for the difference. */ + X11_XResizeWindow(display, data->xwindow, window->floating.w, window->floating.h); + X11_XSync(display, False); + } else { + /* If fullscreen, set a flag to toggle the borders when returning to windowed mode. */ + data->toggle_borders = SDL_TRUE; + } } void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable) { SDL_WindowData *data = window->driverdata; - Display *display = data->videodata->display; - XSizeHints *sizehints = X11_XAllocSizeHints(); - long userhints; - - X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); - - if (resizable) { - /* FIXME: Is there a better way to get max window size from X? -flibit */ - const int maxsize = 0x7FFFFFFF; - sizehints->min_width = window->min_w; - sizehints->min_height = window->min_h; - sizehints->max_width = (window->max_w == 0) ? maxsize : window->max_w; - sizehints->max_height = (window->max_h == 0) ? maxsize : window->max_h; - } else { - sizehints->min_width = window->w; - sizehints->min_height = window->h; - sizehints->max_width = window->w; - sizehints->max_height = window->h; + if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); } - sizehints->flags |= PMinSize | PMaxSize; - X11_SetWMNormalHints(_this, window, sizehints); - - X11_XFree(sizehints); - - X11_XFlush(display); + /* If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode. */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, SDL_TRUE); + } } void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top) @@ -1308,7 +1296,7 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) if (window->parent) { /* Update our position in case our parent moved while we were hidden */ - X11_UpdateWindowPosition(window); + X11_UpdateWindowPosition(window, SDL_TRUE); } /* Whether XMapRaised focuses the window is based on the window type and it is @@ -1320,7 +1308,7 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Blocking wait for "MapNotify" event. * We use X11_XIfEvent because pXWindowEvent takes a mask rather than a type, * and XCheckTypedWindowEvent doesn't block */ - if (!(window->flags & SDL_WINDOW_FOREIGN)) { + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { X11_XIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); } X11_XFlush(display); @@ -1340,11 +1328,10 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - /* Get some valid border values, if we haven't them yet */ + /* Get some valid border values, if we haven't received them yet */ if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) { X11_GetBorderValues(data); } - } void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -1357,7 +1344,7 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) if (X11_IsWindowMapped(_this, window)) { X11_XWithdrawWindow(display, data->xwindow, displaydata->screen); /* Blocking wait for "UnmapNotify" event */ - if (!(window->flags & SDL_WINDOW_FOREIGN)) { + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); } X11_XFlush(display); @@ -1369,7 +1356,7 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_Window *new_focus = window->parent; /* Find the highest level window that isn't being hidden or destroyed. */ - while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + while (new_focus->parent && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } @@ -1377,8 +1364,8 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) } } - /* Send MOVED/RESIZED event, if needed. Immediate, no timeout */ - X11_WaitAndSendWindowEvents(window, 0, COMPARE_NO_WAIT, 0, 0, 0, 0, 0, 0, 0, 0); + X11_XSync(display, False); + X11_PumpEvents(_this); } static void X11_SetWindowActive(SDL_VideoDevice *_this, SDL_Window *window) @@ -1431,36 +1418,17 @@ static void X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, S Atom _NET_WM_STATE_MAXIMIZED_VERT = data->videodata->_NET_WM_STATE_MAXIMIZED_VERT; Atom _NET_WM_STATE_MAXIMIZED_HORZ = data->videodata->_NET_WM_STATE_MAXIMIZED_HORZ; - if (maximized) { - window->flags |= SDL_WINDOW_MAXIMIZED; - } else { - window->flags &= ~SDL_WINDOW_MAXIMIZED; - - if (window->flags & SDL_WINDOW_FULLSCREEN) { - /* Fullscreen windows are maximized on some window managers, - and this is functional behavior, so don't remove that state - now, we'll take care of it when we leave fullscreen mode. - */ - return; - } + if (!maximized && window->flags & SDL_WINDOW_FULLSCREEN) { + /* Fullscreen windows are maximized on some window managers, + and this is functional behavior, so don't remove that state + now, we'll take care of it when we leave fullscreen mode. + */ + return; } if (X11_IsWindowMapped(_this, window)) { - XWindowAttributes attrs; - Window childReturn, root, parent; - Window *children; - unsigned int childCount; - int orig_w, orig_h, orig_x, orig_y; XEvent e; - X11_XSync(display, False); - X11_XQueryTree(display, data->xwindow, &root, &parent, &children, &childCount); - X11_XGetWindowAttributes(display, data->xwindow, &attrs); - X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), - attrs.x, attrs.y, &orig_x, &orig_y, &childReturn); - orig_w = attrs.width; - orig_h = attrs.height; - SDL_zero(e); e.xany.type = ClientMessage; e.xclient.message_type = _NET_WM_STATE; @@ -1472,13 +1440,26 @@ static void X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, S e.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_HORZ; e.xclient.data.l[3] = 0l; + if (maximized) { + SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + SDL_Rect bounds; + + SDL_zero(bounds); + SDL_GetDisplayUsableBounds(displayID, &bounds); + + data->expected.x = bounds.x + data->border_left; + data->expected.y = bounds.y + data->border_top; + data->expected.w = bounds.w - (data->border_left + data->border_right); + data->expected.h = bounds.h - (data->border_top + data->border_bottom); + } else { + data->expected.x = window->floating.x; + data->expected.y = window->floating.y; + data->expected.w = window->floating.w; + data->expected.h = window->floating.h; + } + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, SubstructureNotifyMask | SubstructureRedirectMask, &e); - - /* Send MOVED/RESIZED event, if needed. Compare with initial position and size. Timeout 1000 */ - X11_WaitAndSendWindowEvents(window, 1000, COMPARE_ORIG, orig_x, orig_y, 0, 0, orig_w, orig_h, 0, 0); - - } else { X11_SetNetWMState(_this, data->xwindow, window->flags); } @@ -1487,7 +1468,14 @@ static void X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, S void X11_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) { - X11_SetWindowMaximized(_this, window, SDL_TRUE); + if (window->driverdata->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MINIMIZE)) { + SDL_SyncWindow(window); + } + + if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MINIMIZED))) { + window->driverdata->pending_operation |= X11_PENDING_OP_MAXIMIZE; + X11_SetWindowMaximized(_this, window, SDL_TRUE); + } } void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) @@ -1496,19 +1484,33 @@ void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); Display *display = data->videodata->display; + data->pending_operation |= X11_PENDING_OP_MINIMIZE; + data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED); X11_XIconifyWindow(display, data->xwindow, displaydata->screen); X11_XFlush(display); } void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) { - X11_SetWindowMaximized(_this, window, SDL_FALSE); + if (window->driverdata->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MINIMIZE)) { + SDL_SyncWindow(window); + } + + if (window->flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED) || + (window->driverdata->pending_operation & X11_PENDING_OP_MINIMIZE)) { + window->driverdata->pending_operation |= X11_PENDING_OP_RESTORE; + } + + /* If the window was minimized while maximized, restore as maximized. */ + const SDL_bool maximize = !!(window->flags & SDL_WINDOW_MINIMIZED) && window->driverdata->window_was_maximized; + window->driverdata->window_was_maximized = SDL_FALSE; + X11_SetWindowMaximized(_this, window, maximize); X11_ShowWindow(_this, window); X11_SetWindowActive(_this, window); } /* This asks the Window Manager to handle fullscreen for us. This is the modern way. */ -static void X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen) +static int X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen) { SDL_WindowData *data = window->driverdata; SDL_DisplayData *displaydata = _display->driverdata; @@ -1518,36 +1520,25 @@ static void X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *win if (X11_IsWindowMapped(_this, window)) { XEvent e; - unsigned int childCount; - Window childReturn, root, parent; - Window *children; - XWindowAttributes attrs; - int orig_w, orig_h, orig_x, orig_y; - X11_XSync(display, False); - X11_XQueryTree(display, data->xwindow, &root, &parent, &children, &childCount); - X11_XGetWindowAttributes(display, data->xwindow, &attrs); - X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), - attrs.x, attrs.y, &orig_x, &orig_y, &childReturn); - orig_w = attrs.width; - orig_h = attrs.height; + /* Flush any pending fullscreen events. */ + if (data->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE)) { + X11_SyncWindow(_this, window); + } - if (!(window->flags & SDL_WINDOW_RESIZABLE)) { + /* Nothing to do */ + if (!fullscreen && !(window->flags & SDL_WINDOW_FULLSCREEN)) { + return 0; + } + + if (fullscreen && !(window->flags & SDL_WINDOW_RESIZABLE)) { /* Compiz refuses fullscreen toggle if we're not resizable, so update the hints so we can be resized to the fullscreen resolution (or reset so we're not resizable again) */ XSizeHints *sizehints = X11_XAllocSizeHints(); long flags = 0; X11_XGetWMNormalHints(display, data->xwindow, sizehints, &flags); - /* set the resize flags on */ - if (fullscreen) { - /* we are going fullscreen so turn the flags off */ - sizehints->flags &= ~(PMinSize | PMaxSize); - } else { - /* Reset the min/max width height to make the window non-resizable again */ - sizehints->flags |= PMinSize | PMaxSize; - sizehints->min_width = sizehints->max_width = window->windowed.w; - sizehints->min_height = sizehints->max_height = window->windowed.h; - } + /* we are going fullscreen so turn the flags off */ + sizehints->flags &= ~(PMinSize | PMaxSize); X11_XSetWMNormalHints(display, data->xwindow, sizehints); X11_XFree(sizehints); } @@ -1565,20 +1556,36 @@ static void X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *win X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, SubstructureNotifyMask | SubstructureRedirectMask, &e); - /* Set the position so the window will be on the target display */ - if (fullscreen) { - X11_XMoveWindow(display, data->xwindow, displaydata->x, displaydata->y); + if (!!(window->flags & SDL_WINDOW_FULLSCREEN) != fullscreen) { + data->pending_operation |= X11_PENDING_OP_FULLSCREEN; } - /* Fullscreen windows sometimes end up being marked maximized by - window managers. Force it back to how we expect it to be. */ - if (!fullscreen) { + /* Set the position so the window will be on the target display */ + if (fullscreen) { + SDL_copyp(&data->requested_fullscreen_mode, &window->current_fullscreen_mode); + if (data->requested_fullscreen_mode.displayID == 0) { + data->requested_fullscreen_mode.displayID = _display->id; + } + if (fullscreen != !!(window->flags & SDL_WINDOW_FULLSCREEN)) { + data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED); + } + data->expected.x = displaydata->x; + data->expected.y = displaydata->y; + data->expected.w = _display->current_mode->w; + data->expected.h = _display->current_mode->h; + X11_XMoveWindow(display, data->xwindow, displaydata->x, displaydata->y); + } else { + SDL_zero(data->requested_fullscreen_mode); + + /* Fullscreen windows sometimes end up being marked maximized by + * window managers. Force it back to how we expect it to be. + */ SDL_zero(e); e.xany.type = ClientMessage; e.xclient.message_type = _NET_WM_STATE; e.xclient.format = 32; e.xclient.window = data->xwindow; - if (window->flags & SDL_WINDOW_MAXIMIZED) { + if (data->window_was_maximized) { e.xclient.data.l[0] = _NET_WM_STATE_ADD; } else { e.xclient.data.l[0] = _NET_WM_STATE_REMOVE; @@ -1589,21 +1596,6 @@ static void X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *win X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, SubstructureNotifyMask | SubstructureRedirectMask, &e); } - - if (!fullscreen) { - int dest_x = 0, dest_y = 0; - SDL_RelativeToGlobalForWindow(window, - window->windowed.x - data->border_left, window->windowed.y - data->border_top, - &dest_x, &dest_y); - - /* Attempt to move the window */ - X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); - } - - /* Send MOVED/RESIZED event, if needed. Compare with initial position and size. Timeout 100 */ - /* Wait for at least 2 moves + 1 size changed to have valid values */ - X11_WaitAndSendWindowEvents(window, 100, COMPARE_DOUBLE_ATTEMPT, orig_x, orig_y, 0, 0, orig_w, orig_h, 0, 0); - } else { Uint32 flags; @@ -1624,12 +1616,12 @@ static void X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *win } } - X11_XFlush(display); + return 1; } -void X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen) +int X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_bool fullscreen) { - X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen); + return X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen); } typedef struct @@ -1652,7 +1644,7 @@ static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop) int bytes_fetch = 0; do { - if (ret != NULL) { + if (ret) { X11_XFree(ret); } X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); @@ -1702,8 +1694,7 @@ void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t } ret_icc_profile_data = SDL_malloc(real_nitems); - if (ret_icc_profile_data == NULL) { - SDL_OutOfMemory(); + if (!ret_icc_profile_data) { return NULL; } @@ -1719,7 +1710,7 @@ void X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool SDL_WindowData *data = window->driverdata; Display *display; - if (data == NULL) { + if (!data) { return; } data->mouse_grabbed = SDL_FALSE; @@ -1774,7 +1765,7 @@ void X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_b SDL_WindowData *data = window->driverdata; Display *display; - if (data == NULL) { + if (!data) { return; } @@ -1800,16 +1791,6 @@ void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; - if (window->shaper) { - SDL_ShapeData *shapedata = (SDL_ShapeData *)window->shaper->driverdata; - if (shapedata) { - SDL_free(shapedata->bitmap); - SDL_free(shapedata); - } - SDL_free(window->shaper); - window->shaper = NULL; - } - if (data) { SDL_VideoData *videodata = data->videodata; Display *display = videodata->display; @@ -1832,7 +1813,7 @@ void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) X11_XDestroyIC(data->ic); } #endif - if (data->created) { + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { X11_XDestroyWindow(display, data->xwindow); X11_XFlush(display); } @@ -1848,23 +1829,6 @@ void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) window->driverdata = NULL; } -int X11_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, SDL_SysWMinfo *info) -{ - SDL_WindowData *data = window->driverdata; - SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); - - if (data == NULL) { - /* This sometimes happens in SDL_IBus_UpdateTextRect() while creating the window */ - return SDL_SetError("Window not initialized"); - } - - info->subsystem = SDL_SYSWM_X11; - info->info.x11.display = data->videodata->display; - info->info.x11.screen = displaydata->screen; - info->info.x11.window = data->xwindow; - return 0; -} - int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled) { return 0; /* just succeed, the real work is done elsewhere. */ @@ -1892,7 +1856,7 @@ int X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperati XWMHints *wmhints; wmhints = X11_XGetWMHints(display, data->xwindow); - if (wmhints == NULL) { + if (!wmhints) { return SDL_SetError("Couldn't get WM hints"); } @@ -1988,6 +1952,15 @@ void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y) X11_XFlush(display); } +int X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + /* 100ms is fine for most cases, but, for some reason, maximizing + * a window can take a very long time. + */ + const int timeout = window->driverdata->pending_operation & SDL_WINDOW_MAXIMIZED ? 1000 : 100; + return X11_SyncWindowTimeout(_this, window, timeout); +} + int X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable) { SDL_WindowData *data = window->driverdata; @@ -1995,7 +1968,7 @@ int X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool XWMHints *wmhints; wmhints = X11_XGetWMHints(display, data->xwindow); - if (wmhints == NULL) { + if (!wmhints) { return SDL_SetError("Couldn't get WM hints"); } diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index b9d6ab0d..84d92e59 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -79,13 +79,31 @@ struct SDL_WindowData PointerBarrier barrier[4]; SDL_Rect barrier_rect; #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */ + SDL_Rect expected; + SDL_DisplayMode requested_fullscreen_mode; + + enum + { + X11_PENDING_OP_NONE = 0x00, + X11_PENDING_OP_RESTORE = 0x01, + X11_PENDING_OP_MINIMIZE = 0x02, + X11_PENDING_OP_MAXIMIZE = 0x04, + X11_PENDING_OP_FULLSCREEN = 0x08, + X11_PENDING_OP_MOVE = 0x10, + X11_PENDING_OP_RESIZE = 0x20 + } pending_operation; + + SDL_bool window_was_maximized; + SDL_bool disable_size_position_events; + SDL_bool previous_borders_nonzero; + SDL_bool toggle_borders; + SDL_HitTestResult hit_test_result; }; extern void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, Uint32 flags); extern Uint32 X11_GetNetWMState(SDL_VideoDevice *_this, SDL_Window *window, Window xwindow); -extern int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int X11_CreateWindowFrom(SDL_VideoDevice *_this, SDL_Window *window, const void *data); +extern int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); extern char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow); extern void X11_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); @@ -106,19 +124,20 @@ extern void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool bordered); extern void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool resizable); extern void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool on_top); -extern void X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); +extern int X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_bool fullscreen); extern void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); extern void X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed); extern void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern int X11_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struct SDL_SysWMinfo *info); extern int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern void X11_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y); +extern int X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); int SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title); -void X11_UpdateWindowPosition(SDL_Window *window); +void X11_UpdateWindowPosition(SDL_Window *window, SDL_bool use_current_position); +void X11_SetWindowMinMax(SDL_Window *window, SDL_bool use_current); #endif /* SDL_x11window_h_ */ diff --git a/src/video/x11/SDL_x11xfixes.c b/src/video/x11/SDL_x11xfixes.c index 28ea7a6a..0f620995 100644 --- a/src/video/x11/SDL_x11xfixes.c +++ b/src/video/x11/SDL_x11xfixes.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11xfixes.h b/src/video/x11/SDL_x11xfixes.h index 2c927945..c1daf4d3 100644 --- a/src/video/x11/SDL_x11xfixes.h +++ b/src/video/x11/SDL_x11xfixes.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index 522d8d05..346130a3 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -22,9 +22,12 @@ #ifdef SDL_VIDEO_DRIVER_X11 +#include "SDL_x11pen.h" #include "SDL_x11video.h" #include "SDL_x11xinput2.h" +#include "../../events/SDL_events_c.h" #include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_pen_c.h" #include "../../events/SDL_touch_c.h" #define MAX_AXIS 16 @@ -74,19 +77,25 @@ static SDL_bool xinput2_version_atleast(const int version, const int wantmajor, return version >= ((wantmajor * 1000) + wantminor); } -#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH -static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) +static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window) { int i; for (i = 0; i < videodata->numwindows; i++) { SDL_WindowData *d = videodata->windowlist[i]; if (d->xwindow == window) { - return d->window; + return d; } } return NULL; } +static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) +{ + const SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, window); + return windowdata ? windowdata->window : NULL; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y) { if (window) { @@ -188,10 +197,10 @@ static void xinput2_remove_device_info(SDL_VideoData *videodata, const int devic SDL_XInput2DeviceInfo *prev = NULL; SDL_XInput2DeviceInfo *devinfo; - for (devinfo = videodata->mouse_device_info; devinfo != NULL; devinfo = devinfo->next) { + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { if (devinfo->device_id == device_id) { SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); - if (prev == NULL) { + if (!prev) { videodata->mouse_device_info = devinfo->next; } else { prev->next = devinfo->next; @@ -212,10 +221,10 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, int axis = 0; int i; - for (devinfo = videodata->mouse_device_info; devinfo != NULL; devinfo = devinfo->next) { + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { if (devinfo->device_id == device_id) { SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); - if (prev != NULL) { /* move this to the front of the list, assuming we'll get more from this one. */ + if (prev) { /* move this to the front of the list, assuming we'll get more from this one. */ prev->next = devinfo->next; devinfo->next = videodata->mouse_device_info; videodata->mouse_device_info = devinfo; @@ -227,13 +236,12 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, /* don't know about this device yet, query and cache it. */ devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo)); - if (devinfo == NULL) { - SDL_OutOfMemory(); + if (!devinfo) { return NULL; } xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i); - if (xidevinfo == NULL) { + if (!xidevinfo) { SDL_free(devinfo); return NULL; } @@ -246,7 +254,7 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, for (i = 0; i < xidevinfo->num_classes; i++) { const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; if (v->type == XIValuatorClass) { - devinfo->relative[axis] = (v->mode == XIModeRelative) ? SDL_TRUE : SDL_FALSE; + devinfo->relative[axis] = (v->mode == XIModeRelative); devinfo->minval[axis] = v->min; devinfo->maxval[axis] = v->max; if (++axis >= 2) { @@ -262,18 +270,51 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, return devinfo; } + +static void xinput2_pen_ensure_window(SDL_VideoDevice *_this, const SDL_Pen *pen, Window window) +{ + /* When "flipping" a Wacom eraser pen, we get an XI_DeviceChanged event + * with the newly-activated pen, but this event is global for the display. + * We won't get a window until the pen starts triggering motion or + * button events, so we instead hook the pen to its window at that point. */ + const SDL_WindowData *windowdata = X11_FindWindow(_this, window); + if (windowdata) { + SDL_SendPenWindowEvent(0, pen->header.id, windowdata->window); + } +} #endif -int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie) +int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) { #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + if (cookie->extension != xinput2_opcode) { return 0; } switch (cookie->evtype) { + case XI_PropertyEvent: + case XI_DeviceChanged: + { + X11_InitPen(_this); + } break; + + case XI_Enter: + case XI_Leave: + { + const XIEnterEvent *enterev = (const XIEnterEvent *)cookie->data; + const SDL_WindowData *windowdata = X11_FindWindow(_this, enterev->event); + const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(enterev->sourceid)); + SDL_Window *window = (windowdata && (cookie->evtype == XI_Enter)) ? windowdata->window : NULL; + if (pen) { + SDL_SendPenWindowEvent(0, pen->header.id, window); + } + } break; + case XI_RawMotion: { const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; + const SDL_bool is_pen = X11_PenIDFromDeviceID(rawev->sourceid) != SDL_PEN_INVALID; SDL_Mouse *mouse = SDL_GetMouse(); SDL_XInput2DeviceInfo *devinfo; double coords[2]; @@ -281,13 +322,18 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie int i; videodata->global_mouse_changed = SDL_TRUE; + if (is_pen) { + return 0; /* Pens check for XI_Motion instead */ + } + + /* Non-pen: */ if (!mouse->relative_mode || mouse->relative_mode_warp) { return 0; } devinfo = xinput2_get_device_info(videodata, rawev->deviceid); - if (devinfo == NULL) { + if (!devinfo) { return 0; /* oh well. */ } @@ -317,6 +363,7 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie xinput2_remove_device_info(videodata, hierev->info[i].deviceid); } } + X11_InitPen(_this); } break; case XI_RawButtonPress: @@ -326,29 +373,108 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie case XI_RawTouchUpdate: case XI_RawTouchEnd: #endif + { videodata->global_mouse_changed = SDL_TRUE; - break; + } break; -#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH - /* With multitouch, register to receive XI_Motion (which desctivates MotionNotify), - * so that we can distinguish real mouse motions from synthetic one. */ + case XI_ButtonPress: + case XI_ButtonRelease: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid)); + const int button = xev->detail; + const SDL_bool pressed = (cookie->evtype == XI_ButtonPress) ? SDL_TRUE : SDL_FALSE; + + if (pen) { + xinput2_pen_ensure_window(_this, pen, xev->event); + + /* Only report button event; if there was also pen movement / pressure changes, we expect + an XI_Motion event first anyway */ + if (button == 1) { + /* button 1 is the pen tip */ + if (pressed && SDL_PenPerformHitTest()) { + /* Check whether we should handle window resize / move events */ + SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event); + if (windowdata && X11_TriggerHitTestAction(_this, windowdata, pen->last.x, pen->last.y)) { + SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); + return 1; /* Don't pass on this event */ + } + } + SDL_SendPenTipEvent(0, pen->header.id, + pressed ? SDL_PRESSED : SDL_RELEASED); + } else { + SDL_SendPenButton(0, pen->header.id, + pressed ? SDL_PRESSED : SDL_RELEASED, + button - 1); + } + return 1; + } else { + /* Otherwise assume a regular mouse */ + SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event); + + if (xev->deviceid != xev->sourceid) { + /* Discard events from "Master" devices to avoid duplicates. */ + return 1; + } + + if (pressed) { + X11_HandleButtonPress(_this, windowdata, button, + xev->event_x, xev->event_y, xev->time); + } else { + X11_HandleButtonRelease(_this, windowdata, button); + } + } + } break; + + /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish + real mouse motions from synthetic ones, for multitouch and pen support. */ case XI_Motion: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid)); +#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH int pointer_emulated = (xev->flags & XIPointerEmulated); +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ + if (xev->deviceid != xev->sourceid) { + /* Discard events from "Master" devices to avoid duplicates. */ + return 1; + } + + if (pen) { + SDL_PenStatusInfo pen_status; + + pen_status.x = xev->event_x; + pen_status.y = xev->event_y; + + X11_PenAxesFromValuators(pen, + xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, + &pen_status.axes[0]); + + xinput2_pen_ensure_window(_this, pen, xev->event); + + SDL_SendPenMotion(0, pen->header.id, + SDL_TRUE, + &pen_status); + return 1; + } + +#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH if (!pointer_emulated) { SDL_Mouse *mouse = SDL_GetMouse(); if (!mouse->relative_mode || mouse->relative_mode_warp) { SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); if (window) { + X11_ProcessHitTest(_this, window->driverdata, (float)xev->event_x, (float)xev->event_y, SDL_FALSE); SDL_SendMouseMotion(0, window, 0, 0, (float)xev->event_x, (float)xev->event_y); } } } return 1; +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ } break; +#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH case XI_TouchBegin: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; @@ -376,10 +502,9 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); return 1; } break; - -#endif +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ } -#endif +#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */ return 0; } @@ -460,6 +585,38 @@ int X11_Xinput2IsInitialized(void) #endif } +SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata; + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + SDL_WindowData *window_data = (SDL_WindowData *)window->driverdata; + + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + eventmask.deviceid = XIAllDevices; + + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + XISetMask(mask, XI_Enter); + XISetMask(mask, XI_Leave); + /* Hotplugging: */ + XISetMask(mask, XI_DeviceChanged); + XISetMask(mask, XI_HierarchyChanged); + XISetMask(mask, XI_PropertyEvent); /* E.g., when swapping tablet pens */ + + if (X11_XISelectEvents(data->display, + window_data->xwindow, + &eventmask, 1) == Success) { + return SDL_TRUE; + } + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 mouse event handling\n"); +#endif + return SDL_FALSE; +} + int X11_Xinput2IsMultitouchSupported(void) { #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH diff --git a/src/video/x11/SDL_x11xinput2.h b/src/video/x11/SDL_x11xinput2.h index 2d4573a8..eba6cdf7 100644 --- a/src/video/x11/SDL_x11xinput2.h +++ b/src/video/x11/SDL_x11xinput2.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2023 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 @@ -32,11 +32,12 @@ typedef struct XGenericEventCookie XGenericEventCookie; extern void X11_InitXinput2(SDL_VideoDevice *_this); extern void X11_InitXinput2Multitouch(SDL_VideoDevice *_this); -extern int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie); +extern int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie); extern int X11_Xinput2IsInitialized(void); extern int X11_Xinput2IsMultitouchSupported(void); extern void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window); +extern SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window); #endif /* SDL_x11xinput2_h_ */ diff --git a/src/video/yuv2rgb/yuv_rgb.c b/src/video/yuv2rgb/yuv_rgb.c index bf400201..f24b27b4 100644 --- a/src/video/yuv2rgb/yuv_rgb.c +++ b/src/video/yuv2rgb/yuv_rgb.c @@ -31,6 +31,11 @@ typedef struct // |G| = 1/PRECISION_FACTOR * |y_factor u_g_factor v_g_factor| * | U-128 | // |B| |y_factor u_b_factor 0 | | V-128 | +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 26451) +#endif + #define V(value) (int16_t)((value*PRECISION_FACTOR)+0.5) // for ITU-T T.871, values can be found in section 7 @@ -56,6 +61,10 @@ static const RGB2YUVParam RGB2YUV[3] = { {/*.y_shift=*/ 16, /*.matrix=*/ {{V(0.1826), V(0.6142), V(0.062)}, {-V(0.1006), -V(0.3386), V(0.4392)}, {V(0.4392), -V(0.3989), -V(0.0403)}}} }; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + /* The various layouts of YUV data we support */ #define YUV_FORMAT_420 1 #define YUV_FORMAT_422 2 diff --git a/src/video/yuv2rgb/yuv_rgb_std_func.h b/src/video/yuv2rgb/yuv_rgb_std_func.h index 94872ec9..f359abae 100644 --- a/src/video/yuv2rgb/yuv_rgb_std_func.h +++ b/src/video/yuv2rgb/yuv_rgb_std_func.h @@ -69,10 +69,15 @@ #endif +#ifdef _MSC_VER /* Visual Studio analyzer can't tell that we're building this with different constants */ +#pragma warning(push) +#pragma warning(disable : 6239) +#endif + void STD_FUNCTION_NAME( - uint32_t width, uint32_t height, - const uint8_t *Y, const uint8_t *U, const uint8_t *V, uint32_t Y_stride, uint32_t UV_stride, - uint8_t *RGB, uint32_t RGB_stride, + uint32_t width, uint32_t height, + const uint8_t *Y, const uint8_t *U, const uint8_t *V, uint32_t Y_stride, uint32_t UV_stride, + uint8_t *RGB, uint32_t RGB_stride, YCbCrType yuv_type) { const YUV2RGBParam *const param = &(YUV2RGB[yuv_type]); @@ -113,26 +118,26 @@ void STD_FUNCTION_NAME( for(x=0; x<(width-(uv_x_sample_interval-1)); x+=uv_x_sample_interval) { // Compute U and V contributions, common to the four pixels - + int32_t u_tmp = ((*u_ptr)-128); int32_t v_tmp = ((*v_ptr)-128); - + int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); int32_t b_tmp = (u_tmp*param->u_b_factor); - + // Compute the Y contribution for each pixel - + int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - + y_tmp = ((y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - + #if uv_y_sample_interval > 1 y_tmp = ((y_ptr2[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); - + y_tmp = ((y_ptr2[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); #endif @@ -149,19 +154,19 @@ void STD_FUNCTION_NAME( if (uv_x_sample_interval == 2 && x == (width-1)) { // Compute U and V contributions, common to the four pixels - + int32_t u_tmp = ((*u_ptr)-128); int32_t v_tmp = ((*v_ptr)-128); - + int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); int32_t b_tmp = (u_tmp*param->u_b_factor); - + // Compute the Y contribution for each pixel - + int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - + #if uv_y_sample_interval > 1 y_tmp = ((y_ptr2[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); @@ -175,28 +180,28 @@ void STD_FUNCTION_NAME( const uint8_t *y_ptr1=Y+y*Y_stride, *u_ptr=U+(y/uv_y_sample_interval)*UV_stride, *v_ptr=V+(y/uv_y_sample_interval)*UV_stride; - + uint8_t *rgb_ptr1=RGB+y*RGB_stride; - + for(x=0; x<(width-(uv_x_sample_interval-1)); x+=uv_x_sample_interval) { // Compute U and V contributions, common to the four pixels - + int32_t u_tmp = ((*u_ptr)-128); int32_t v_tmp = ((*v_ptr)-128); - + int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); int32_t b_tmp = (u_tmp*param->u_b_factor); - + // Compute the Y contribution for each pixel - + int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - + y_tmp = ((y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - + y_ptr1+=2*y_pixel_stride; u_ptr+=2*uv_pixel_stride/uv_x_sample_interval; v_ptr+=2*uv_pixel_stride/uv_x_sample_interval; @@ -206,16 +211,16 @@ void STD_FUNCTION_NAME( if (uv_x_sample_interval == 2 && x == (width-1)) { // Compute U and V contributions, common to the four pixels - + int32_t u_tmp = ((*u_ptr)-128); int32_t v_tmp = ((*v_ptr)-128); - + int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); int32_t b_tmp = (u_tmp*param->u_b_factor); - + // Compute the Y contribution for each pixel - + int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); } @@ -227,6 +232,10 @@ void STD_FUNCTION_NAME( #undef uv_y_sample_interval } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #undef STD_FUNCTION_NAME #undef YUV_FORMAT #undef RGB_FORMAT diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4688be0e..014e29d1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,9 @@ # # CMake script for building the SDL tests # + +include(CheckStructHasMember) + if(SDL_TESTS_LINK_SHARED) set(sdl_name_component SDL3-shared) else() @@ -29,11 +32,35 @@ add_library(sdltests_utils OBJECT target_link_libraries(sdltests_utils PRIVATE SDL3::Headers) file(GLOB RESOURCE_FILES *.bmp *.wav *.hex moose.dat utf8.txt) + +if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(test_bin_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + if(NOT IS_ABSOLUTE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + set(test_bin_dir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + endif() +else() + set(test_bin_dir "${CMAKE_CURRENT_BINARY_DIR}") +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.20) + get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + set(test_bin_dir "${test_bin_dir}$<$:/$>") +endif() + set(RESOURCE_FILE_NAMES) -foreach(RESOURCE_FILE IN LISTS RESOURCE_FILES) - get_filename_component(res_file_name ${RESOURCE_FILE} NAME) +set(RESOURCE_FILES_BINDIR) +foreach(resource_file IN LISTS RESOURCE_FILES) + get_filename_component(res_file_name ${resource_file} NAME) list(APPEND RESOURCE_FILE_NAMES "${res_file_name}") + set(resource_file_bindir "${test_bin_dir}/${res_file_name}") + add_custom_command(OUTPUT "${resource_file_bindir}" + COMMAND "${CMAKE_COMMAND}" -E copy "${resource_file}" "${resource_file_bindir}" + DEPENDS "${resource_file}" + ) + list(APPEND RESOURCE_FILES_BINDIR "${resource_file_bindir}") endforeach() +add_custom_target(copy-sdl-test-resources + DEPENDS "${RESOURCE_FILES_BINDIR}" +) define_property(TARGET PROPERTY SDL_NONINTERACTIVE BRIEF_DOCS "If true, target is a non-interactive test executable." FULL_DOCS "If true, target is a noninteractive test executable.") define_property(TARGET PROPERTY SDL_NONINTERACTIVE_ARGUMENTS BRIEF_DOCS "Argument(s) to run executable in non-interactive mode." FULL_DOCS "Argument(s) to run executable in non-interactive mode.") @@ -44,11 +71,16 @@ if(WINDOWS_STORE) target_link_libraries(sdl_test_main_uwp PRIVATE SDL3::Headers) target_compile_options(sdl_test_main_uwp PRIVATE "/ZW") + add_library(sdl_test_main_callbacks_uwp OBJECT main.cpp) + target_link_libraries(sdl_test_main_callbacks_uwp PRIVATE SDL3::Headers) + target_compile_options(sdl_test_main_callbacks_uwp PRIVATE "/ZW") + target_compile_definitions(sdl_test_main_callbacks_uwp PRIVATE "SDL_MAIN_USE_CALLBACKS") + set_source_files_properties(${RESOURCE_FILES} PROPERTIES VS_DEPLOYENT_LOCATION "Assets") endif() macro(add_sdl_test_executable TARGET) - cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN}) + cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90;MAIN_CALLBACKS" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN}) if(AST_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}") endif() @@ -70,8 +102,14 @@ macro(add_sdl_test_executable TARGET) TARGET "${TARGET}" ) set_property(SOURCE "${uwp_bindir}/${TARGET}.appxmanifest" PROPERTY VS_DEPLOYMENT_CONTENT 1) + + if(AST_MAIN_CALLBACKS) + list(APPEND EXTRA_SOURCES "$") + else() + list(APPEND EXTRA_SOURCES "$") + endif() + list(APPEND EXTRA_SOURCES - "$" "${uwp_bindir}/${TARGET}.appxmanifest" "uwp/logo-50x50.png" "uwp/square-44x44.png" @@ -120,8 +158,7 @@ macro(add_sdl_test_executable TARGET) ) add_dependencies(${TARGET} zzz-resources-copy-${TARGET}) else() - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${RESOURCE_FILES} $) + add_dependencies(${TARGET} copy-sdl-test-resources) endif() if(APPLE) # Make sure resource files get installed into macOS/iOS .app bundles. @@ -182,19 +219,45 @@ if(HAVE_LIBUDEV_H) add_definitions(-DHAVE_LIBUDEV_H) endif() +set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) +include("${SDL3_SOURCE_DIR}/cmake/FindFFmpeg.cmake") +if(FFmpeg_FOUND) + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${FFmpeg_AVUTIL_INCLUDE_DIRS}") + check_struct_has_member("AVFrame" "ch_layout" "libavutil/frame.h" LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT) + cmake_pop_check_state() +endif() +if(FFmpeg_FOUND AND LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT) + add_sdl_test_executable(testffmpeg NO_C90 SOURCES testffmpeg.c ${icon_bmp_header}) + if(APPLE) + target_sources(testffmpeg PRIVATE testffmpeg_videotoolbox.m) + target_link_options(testffmpeg PRIVATE "-Wl,-framework,CoreFoundation" "-Wl,-framework,CoreVideo" "-Wl,-framework,Metal") + endif() + if(HAVE_OPENGLES_V2) + message(DEBUG "Enabling EGL support in testffmpeg") + target_compile_definitions(testffmpeg PRIVATE HAVE_EGL) + if(TARGET OpenGL::EGL) + target_link_libraries(testffmpeg PRIVATE OpenGL::EGL) + endif() + endif() + target_link_libraries(testffmpeg PRIVATE ${FFMPEG_LIBRARIES}) +else() + message(STATUS "Can't find ffmpeg 5.1.3 or newer, skipping testffmpeg") +endif() + add_sdl_test_executable(checkkeys SOURCES checkkeys.c) add_sdl_test_executable(checkkeysthreads SOURCES checkkeysthreads.c) -add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS SOURCES loopwave.c) +add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS MAIN_CALLBACKS SOURCES loopwave.c) add_sdl_test_executable(testsurround SOURCES testsurround.c) add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c) add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c) add_sdl_test_executable(testaudiostreamdynamicresample NEEDS_RESOURCES TESTUTILS SOURCES testaudiostreamdynamicresample.c) file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c) -add_sdl_test_executable(testautomation NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES}) +add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES}) add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c) add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c) -add_sdl_test_executable(testaudiocapture SOURCES testaudiocapture.c) +add_sdl_test_executable(testaudiocapture MAIN_CALLBACKS SOURCES testaudiocapture.c) add_sdl_test_executable(testatomic NONINTERACTIVE SOURCES testatomic.c) add_sdl_test_executable(testintersections SOURCES testintersections.c) add_sdl_test_executable(testrelative SOURCES testrelative.c) @@ -225,9 +288,20 @@ if(APPLE) endif() elseif(WINDOWS) add_sdl_test_executable(testnative BUILD_DEPENDENT NEEDS_RESOURCES TESTUTILS SOURCES testnative.c testnativew32.c) -elseif(HAVE_X11) - add_sdl_test_executable(testnative BUILD_DEPENDENT NEEDS_RESOURCES TESTUTILS SOURCES testnative.c testnativex11.c) - target_link_libraries(testnative PRIVATE X11) +elseif(HAVE_X11 OR HAVE_WAYLAND) + add_sdl_test_executable(testnative BUILD_DEPENDENT NO_C90 NEEDS_RESOURCES TESTUTILS SOURCES testnative.c) + if(HAVE_X11) + target_sources(testnative PRIVATE testnativex11.c) + target_link_libraries(testnative PRIVATE X11) + endif() + if(HAVE_WAYLAND) + set_property(SOURCE ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c PROPERTY GENERATED 1) + target_sources(testnative PRIVATE testnativewayland.c ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c) + + # Needed to silence the documentation warning in the generated header file + target_compile_options(testnative PRIVATE -Wno-documentation-unknown-command) + target_link_libraries(testnative PRIVATE wayland-client) + endif () endif() find_package(Python3) @@ -275,7 +349,7 @@ files2headers(gamepad_image_headers ) files2headers(icon_bmp_header icon.bmp) -add_sdl_test_executable(testaudio NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c) +add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c) add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c) add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers}) add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c) @@ -288,6 +362,7 @@ add_sdl_test_executable(testgles2 SOURCES testgles2.c) add_sdl_test_executable(testgles2_sdf NEEDS_RESOURCES TESTUTILS SOURCES testgles2_sdf.c) add_sdl_test_executable(testhaptic SOURCES testhaptic.c) add_sdl_test_executable(testhotplug SOURCES testhotplug.c) +add_sdl_test_executable(testpen SOURCES testpen.c) add_sdl_test_executable(testrumble SOURCES testrumble.c) add_sdl_test_executable(testthread NONINTERACTIVE NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c) add_sdl_test_executable(testiconv NEEDS_RESOURCES TESTUTILS SOURCES testiconv.c) @@ -304,7 +379,7 @@ add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c) add_sdl_test_executable(testpower NONINTERACTIVE SOURCES testpower.c) add_sdl_test_executable(testfilesystem NONINTERACTIVE SOURCES testfilesystem.c) if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4) - add_sdl_test_executable(testfilesystem_pre SOURCES testfilesystem_pre.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60) + add_sdl_test_executable(pretest SOURCES pretest.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60) endif() add_sdl_test_executable(testrendertarget NEEDS_RESOURCES TESTUTILS SOURCES testrendertarget.c) add_sdl_test_executable(testscale NEEDS_RESOURCES TESTUTILS SOURCES testscale.c) @@ -312,12 +387,14 @@ add_sdl_test_executable(testsem NONINTERACTIVE NONINTERACTIVE_ARGS 10 NONINTERAC add_sdl_test_executable(testsensor SOURCES testsensor.c) add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c) add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c) -add_sdl_test_executable(testsprite NEEDS_RESOURCES TESTUTILS SOURCES testsprite.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}) add_sdl_test_executable(teststreaming NEEDS_RESOURCES TESTUTILS SOURCES teststreaming.c) -add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c) +add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c) add_sdl_test_executable(testurl SOURCES testurl.c) add_sdl_test_executable(testver NONINTERACTIVE SOURCES testver.c) +add_sdl_test_executable(testvideocapture SOURCES testvideocapture.c) +add_sdl_test_executable(testvideocaptureminimal SOURCES testvideocaptureminimal.c) add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c) add_sdl_test_executable(testwm SOURCES testwm.c) add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c) @@ -332,6 +409,15 @@ add_sdl_test_executable(testvulkan NO_C90 SOURCES testvulkan.c) add_sdl_test_executable(testoffscreen SOURCES testoffscreen.c) add_sdl_test_executable(testpopup SOURCES testpopup.c) +if (HAVE_WAYLAND) + # Set the GENERATED property on the protocol file, since it is first created at build time + set_property(SOURCE ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c PROPERTY GENERATED 1) + add_sdl_test_executable(testwaylandcustom NO_C90 NEEDS_RESOURCES SOURCES testwaylandcustom.c ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c) + # Needed to silence the documentation warning in the generated header file + target_compile_options(testwaylandcustom PRIVATE -Wno-documentation-unknown-command) + target_link_libraries(testwaylandcustom PRIVATE wayland-client) +endif() + check_c_compiler_flag(-Wformat-overflow HAVE_WFORMAT_OVERFLOW) if(HAVE_WFORMAT_OVERFLOW) target_compile_definitions(testautomation PRIVATE HAVE_WFORMAT_OVERFLOW) @@ -511,13 +597,12 @@ foreach(TEST ${SDL_TEST_EXECUTABLES}) DESTINATION ${CMAKE_INSTALL_DATADIR}/installed-tests/SDL3 ) endif() + if(TARGET pretest AND NOT "${TEST}" MATCHES "pretest") + set_property(TEST ${TEST} APPEND PROPERTY DEPENDS pretest) + endif() endif() endforeach() -if(TARGET testfilesystem_pre) - set_property(TEST testfilesystem APPEND PROPERTY DEPENDS testfilesystem_pre) -endif() - if(SDL_INSTALL_TESTS) if(RISCOS) install( @@ -530,13 +615,18 @@ if(SDL_INSTALL_TESTS) DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3 ) endif() + if(MSVC) + foreach(test IN LISTS SDL_TEST_EXECUTABLES) + SDL_install_pdb(${test} "${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3") + endforeach() + endif() install( FILES ${RESOURCE_FILES} DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3 ) endif() -if(ANDROID AND TARGET SDL3-jar) +if(ANDROID AND TARGET SDL3::Jar) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake/android") find_package(SdlAndroid MODULE) if(SdlAndroid_FOUND) @@ -593,14 +683,14 @@ if(ANDROID AND TARGET SDL3-jar) COMMAND ${CMAKE_COMMAND} -E make_directory "${classes_path}" COMMAND ${Java_JAVAC_EXECUTABLE} -source 1.8 -target 1.8 - -bootclasspath "$" + -bootclasspath "$" "${JAVA_PACKAGE_DIR}/SDLEntryTestActivity.java" "${JAVA_PACKAGE_DIR}/SDLTestActivity.java" $ - -cp "$:${path_android_jar}" + -cp "$:${SDL_ANDROID_PLATFORM_ANDROID_JAR}" -d "${classes_path}" COMMAND ${Java_JAR_EXECUTABLE} cf "${OUT_JAR}" -C "${classes_path}" . - DEPENDS $ "$" "${JAVA_PACKAGE_DIR}/SDLTestActivity.java" "${JAVA_PACKAGE_DIR}/SDLEntryTestActivity.java" + DEPENDS $ "$" "${JAVA_PACKAGE_DIR}/SDLTestActivity.java" "${JAVA_PACKAGE_DIR}/SDLEntryTestActivity.java" ) add_custom_target(${TEST}-jar DEPENDS "${OUT_JAR}") set_property(TARGET ${TEST}-jar PROPERTY OUTPUT "${OUT_JAR}") @@ -616,10 +706,10 @@ if(ANDROID AND TARGET SDL3-jar) OUTPUT "${classes_dex}" COMMAND SdlAndroid::d8 $ - $ - --lib "${path_android_jar}" + $ + --lib "${SDL_ANDROID_PLATFORM_ANDROID_JAR}" --output "${dexworkdir}" - DEPENDS $ $ + DEPENDS $ $ ) add_custom_target(${TEST}-dex DEPENDS "${classes_dex}") set_property(TARGET ${TEST}-dex PROPERTY OUTPUT "${classes_dex}") diff --git a/test/checkkeys.c b/test/checkkeys.c index 49f39231..a5a27ff3 100644 --- a/test/checkkeys.c +++ b/test/checkkeys.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -165,7 +165,7 @@ static void loop(void) switch (event.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: - PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED) ? SDL_TRUE : SDL_FALSE, (event.key.repeat) ? SDL_TRUE : SDL_FALSE); + PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED), (event.key.repeat > 0)); if (event.type == SDL_EVENT_KEY_DOWN) { switch (event.key.keysym.sym) { case SDLK_BACKSPACE: @@ -182,10 +182,6 @@ static void loop(void) case SDL_EVENT_TEXT_EDITING: PrintText("EDIT", event.edit.text); break; - case SDL_EVENT_TEXT_EDITING_EXT: - PrintText("EDIT_EXT", event.editExt.text); - SDL_free(event.editExt.text); - break; case SDL_EVENT_TEXT_INPUT: PrintText("INPUT", event.text.text); SDLTest_TextWindowAddText(textwin, "%s", event.text.text); @@ -245,7 +241,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } state->window_title = "CheckKeys Test"; @@ -261,9 +257,6 @@ int main(int argc, char *argv[]) /* Disable mouse emulation */ SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - /* Enable extended text editing events */ - SDL_SetHint(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1"); - /* Initialize SDL */ if (!SDLTest_CommonInit(state)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); diff --git a/test/checkkeysthreads.c b/test/checkkeysthreads.c index 811584c0..549244ce 100644 --- a/test/checkkeysthreads.c +++ b/test/checkkeysthreads.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -179,7 +179,7 @@ static void loop(void) switch (event.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: - PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED) ? SDL_TRUE : SDL_FALSE, (event.key.repeat) ? SDL_TRUE : SDL_FALSE); + PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED), (event.key.repeat > 0)); break; case SDL_EVENT_TEXT_EDITING: PrintText("EDIT", event.text.text); @@ -247,7 +247,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -267,7 +267,7 @@ int main(int argc, char *argv[]) /* Set 640x480 video mode */ window = SDL_CreateWindow("CheckKeys Test", 640, 480, 0); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create 640x480 window: %s\n", SDL_GetError()); quit(2); diff --git a/test/gamepadutils.c b/test/gamepadutils.c index c3570aee..656c9970 100644 --- a/test/gamepadutils.c +++ b/test/gamepadutils.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -38,10 +38,10 @@ static const struct int x; int y; } button_positions[] = { - { 413, 190 }, /* SDL_GAMEPAD_BUTTON_A */ - { 456, 156 }, /* SDL_GAMEPAD_BUTTON_B */ - { 372, 159 }, /* SDL_GAMEPAD_BUTTON_X */ - { 415, 127 }, /* SDL_GAMEPAD_BUTTON_Y */ + { 413, 190 }, /* SDL_GAMEPAD_BUTTON_SOUTH */ + { 456, 156 }, /* SDL_GAMEPAD_BUTTON_EAST */ + { 372, 159 }, /* SDL_GAMEPAD_BUTTON_WEST */ + { 415, 127 }, /* SDL_GAMEPAD_BUTTON_NORTH */ { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */ { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */ @@ -120,7 +120,7 @@ struct GamepadImage int y; SDL_bool showing_front; SDL_bool showing_touchpad; - GamepadImageFaceStyle face_style; + SDL_GamepadType type; ControllerDisplayMode display_mode; SDL_bool elements[SDL_GAMEPAD_ELEMENT_MAX]; @@ -146,29 +146,6 @@ static SDL_Texture *CreateTexture(SDL_Renderer *renderer, unsigned char *data, u return texture; } -static SDL_GamepadButton GetRemappedButton(GamepadImageFaceStyle face_style, SDL_GamepadButton button) -{ - if (face_style == GAMEPAD_IMAGE_FACE_BAYX) { - switch (button) { - case SDL_GAMEPAD_BUTTON_A: - button = SDL_GAMEPAD_BUTTON_B; - break; - case SDL_GAMEPAD_BUTTON_B: - button = SDL_GAMEPAD_BUTTON_A; - break; - case SDL_GAMEPAD_BUTTON_X: - button = SDL_GAMEPAD_BUTTON_Y; - break; - case SDL_GAMEPAD_BUTTON_Y: - button = SDL_GAMEPAD_BUTTON_X; - break; - default: - break; - } - } - return button; -} - GamepadImage *CreateGamepadImage(SDL_Renderer *renderer) { GamepadImage *ctx = SDL_calloc(1, sizeof(*ctx)); @@ -241,22 +218,22 @@ void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front) ctx->showing_front = showing_front; } -void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_style) +void SetGamepadImageFaceButtonType(GamepadImage *ctx, SDL_GamepadType type) { if (!ctx) { return; } - ctx->face_style = face_style; + ctx->type = type; } -GamepadImageFaceStyle GetGamepadImageFaceStyle(GamepadImage *ctx) +SDL_GamepadType GetGamepadImageType(GamepadImage *ctx) { if (!ctx) { - return GAMEPAD_IMAGE_FACE_BLANK; + return SDL_GAMEPAD_TYPE_UNKNOWN; } - return ctx->face_style; + return ctx->type; } void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) @@ -407,7 +384,7 @@ int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y) rect.w = (float)ctx->button_width; rect.h = (float)ctx->button_height; if (SDL_PointInRectFloat(&point, &rect)) { - return GetRemappedButton(ctx->face_style, (SDL_GamepadButton)i); + return (SDL_GamepadButton)i; } } } @@ -440,29 +417,15 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) return; } + ctx->type = SDL_GetGamepadType(gamepad); char *mapping = SDL_GetGamepadMapping(gamepad); - SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad); - switch (gamepad_type) { - case SDL_GAMEPAD_TYPE_PS3: - case SDL_GAMEPAD_TYPE_PS4: - case SDL_GAMEPAD_TYPE_PS5: - ctx->face_style = GAMEPAD_IMAGE_FACE_SONY; - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: - ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; - break; - default: - if (mapping && SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { - ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; - } else { - ctx->face_style = GAMEPAD_IMAGE_FACE_ABXY; + if (mapping) { + if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { + /* Just for display purposes */ + ctx->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; } - break; + SDL_free(mapping); } - SDL_free(mapping); for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) { const SDL_GamepadButton button = (SDL_GamepadButton)i; @@ -557,7 +520,7 @@ void RenderGamepadImage(GamepadImage *ctx) for (i = 0; i < SDL_arraysize(button_positions); ++i) { if (ctx->elements[i]) { - SDL_GamepadButton button_position = GetRemappedButton(ctx->face_style, (SDL_GamepadButton)i); + SDL_GamepadButton button_position = (SDL_GamepadButton)i; SDL_bool on_front = SDL_TRUE; if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) { @@ -579,14 +542,14 @@ void RenderGamepadImage(GamepadImage *ctx) dst.w = (float)ctx->face_width; dst.h = (float)ctx->face_height; - switch (ctx->face_style) { - case GAMEPAD_IMAGE_FACE_ABXY: + switch (SDL_GetGamepadButtonLabelForType(ctx->type, SDL_GAMEPAD_BUTTON_SOUTH)) { + case SDL_GAMEPAD_BUTTON_LABEL_A: SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst); break; - case GAMEPAD_IMAGE_FACE_BAYX: + case SDL_GAMEPAD_BUTTON_LABEL_B: SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst); break; - case GAMEPAD_IMAGE_FACE_SONY: + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: SDL_RenderTexture(ctx->renderer, ctx->face_sony_texture, NULL, &dst); break; default: @@ -665,10 +628,10 @@ void DestroyGamepadImage(GamepadImage *ctx) static const char *gamepad_button_names[] = { - "A", - "B", - "X", - "Y", + "South", + "East", + "West", + "North", "Back", "Guide", "Start", @@ -801,9 +764,49 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s static SDL_bool GetButtonBindingString(SDL_GamepadButton button, char *mapping, char *text, size_t size) { char label[32]; + SDL_bool baxy_mapping = SDL_FALSE; + + if (!mapping) { + return SDL_FALSE; + } SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button)); - return GetBindingString(label, mapping, text, size); + if (GetBindingString(label, mapping, text, size)) { + return SDL_TRUE; + } + + /* Try the legacy button names */ + if (SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = SDL_TRUE; + } + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + if (baxy_mapping) { + return GetBindingString(",b:", mapping, text, size); + } else { + return GetBindingString(",a:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_EAST: + if (baxy_mapping) { + return GetBindingString(",a:", mapping, text, size); + } else { + return GetBindingString(",b:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_WEST: + if (baxy_mapping) { + return GetBindingString(",y:", mapping, text, size); + } else { + return GetBindingString(",x:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_NORTH: + if (baxy_mapping) { + return GetBindingString(",x:", mapping, text, size); + } else { + return GetBindingString(",y:", mapping, text, size); + } + default: + return SDL_FALSE; + } } static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char *mapping, char *text, size_t size) @@ -1707,7 +1710,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) highlight.h = (float)ctx->button_height; RenderJoystickButtonHighlight(ctx, i, &highlight); - SDL_snprintf(text, sizeof(text), "%2.d:", i); + SDL_snprintf(text, sizeof(text), "%2d:", i); SDLTest_DrawString(ctx->renderer, x, y, text); if (SDL_GetJoystickButton(joystick, (Uint8)i)) { @@ -2224,9 +2227,11 @@ static int FindMappingKey(const MappingParts *parts, const char *key) { int i; - for (i = 0; i < parts->num_elements; ++i) { - if (SDL_strcmp(key, parts->keys[i]) == 0) { - return i; + if (key) { + for (i = 0; i < parts->num_elements; ++i) { + if (SDL_strcmp(key, parts->keys[i]) == 0) { + return i; + } } } return -1; @@ -2243,6 +2248,55 @@ static void RemoveMappingValueAt(MappingParts *parts, int index) } } +static void ConvertBAXYMapping(MappingParts *parts) +{ + int i; + SDL_bool baxy_mapping = SDL_FALSE; + + for (i = 0; i < parts->num_elements; ++i) { + const char *key = parts->keys[i]; + const char *value = parts->values[i]; + + if (SDL_strcmp(key, "hint") == 0 && + SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { + baxy_mapping = SDL_TRUE; + } + } + + if (!baxy_mapping) { + return; + } + + /* Swap buttons, invert hint */ + for (i = 0; i < parts->num_elements; ++i) { + char *key = parts->keys[i]; + char *value = parts->values[i]; + + if (SDL_strcmp(key, "a") == 0) { + parts->keys[i] = SDL_strdup("b"); + SDL_free(key); + } else if (SDL_strcmp(key, "b") == 0) { + parts->keys[i] = SDL_strdup("a"); + SDL_free(key); + } else if (SDL_strcmp(key, "x") == 0) { + parts->keys[i] = SDL_strdup("y"); + SDL_free(key); + } else if (SDL_strcmp(key, "y") == 0) { + parts->keys[i] = SDL_strdup("x"); + SDL_free(key); + } else if (SDL_strcmp(key, "hint") == 0 && + SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { + parts->values[i] = SDL_strdup("!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1"); + SDL_free(value); + } + } +} + +static void UpdateLegacyElements(MappingParts *parts) +{ + ConvertBAXYMapping(parts); +} + static SDL_bool CombineMappingAxes(MappingParts *parts) { int i, matching, axis; @@ -2337,6 +2391,7 @@ static char *JoinMapping(MappingParts *parts) const char *name; MappingSortEntry *sort_order; + UpdateLegacyElements(parts); CombineMappingAxes(parts); guid = parts->guid; @@ -2420,6 +2475,43 @@ static char *RecreateMapping(MappingParts *parts, char *mapping) return mapping; } +static const char *GetLegacyKey(const char *key, SDL_bool baxy) +{ + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_SOUTH)) == 0) { + if (baxy) { + return "b"; + } else { + return "a"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_EAST)) == 0) { + if (baxy) { + return "a"; + } else { + return "b"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_WEST)) == 0) { + if (baxy) { + return "y"; + } else { + return "x"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_NORTH)) == 0) { + if (baxy) { + return "y"; + } else { + return "x"; + } + } + + return key; +} + static SDL_bool MappingHasKey(const char *mapping, const char *key) { int i; @@ -2428,6 +2520,14 @@ static SDL_bool MappingHasKey(const char *mapping, const char *key) SplitMapping(mapping, &parts); i = FindMappingKey(&parts, key); + if (i < 0) { + SDL_bool baxy_mapping = SDL_FALSE; + + if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = SDL_TRUE; + } + i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); + } if (i >= 0) { result = SDL_TRUE; } @@ -2444,6 +2544,14 @@ static char *GetMappingValue(const char *mapping, const char *key) SplitMapping(mapping, &parts); i = FindMappingKey(&parts, key); + if (i < 0) { + SDL_bool baxy_mapping = SDL_FALSE; + + if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = SDL_TRUE; + } + i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); + } if (i >= 0) { value = parts.values[i]; parts.values[i] = NULL; /* So we don't free it */ @@ -2658,7 +2766,7 @@ SDL_GamepadType GetMappingType(const char *mapping) char *SetMappingType(char *mapping, SDL_GamepadType type) { const char *type_string = SDL_GetGamepadStringForType(type); - if (type_string == NULL || type == SDL_GAMEPAD_TYPE_UNKNOWN) { + if (!type_string || type == SDL_GAMEPAD_TYPE_UNKNOWN) { return RemoveMappingValue(mapping, "type"); } else { return SetMappingValue(mapping, "type", type_string); diff --git a/test/gamepadutils.h b/test/gamepadutils.h index f0b62f9f..31813529 100644 --- a/test/gamepadutils.h +++ b/test/gamepadutils.h @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -20,14 +20,6 @@ typedef enum CONTROLLER_MODE_BINDING, } ControllerDisplayMode; -typedef enum -{ - GAMEPAD_IMAGE_FACE_BLANK, - GAMEPAD_IMAGE_FACE_ABXY, - GAMEPAD_IMAGE_FACE_BAYX, - GAMEPAD_IMAGE_FACE_SONY, -} GamepadImageFaceStyle; - enum { SDL_GAMEPAD_ELEMENT_INVALID = -1, @@ -63,8 +55,8 @@ extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer); extern void SetGamepadImagePosition(GamepadImage *ctx, int x, int y); extern void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area); extern void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front); -extern void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_style); -extern GamepadImageFaceStyle GetGamepadImageFaceStyle(GamepadImage *ctx); +extern void SetGamepadImageType(GamepadImage *ctx, SDL_GamepadType type); +extern SDL_GamepadType GetGamepadImageType(GamepadImage *ctx); extern void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode); extern int GetGamepadImageButtonWidth(GamepadImage *ctx); extern int GetGamepadImageButtonHeight(GamepadImage *ctx); diff --git a/test/loopwave.c b/test/loopwave.c index 1fa1b91e..1aa62706 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -17,10 +17,7 @@ */ #include -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -34,72 +31,28 @@ static struct } wave; static SDL_AudioStream *stream; +static SDLTest_CommonState *state; -static void fillerup(void) +static int fillerup(void) { const int minimum = (wave.soundlen / SDL_AUDIO_FRAMESIZE(wave.spec)) / 2; if (SDL_GetAudioStreamQueued(stream) < minimum) { SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); } + return 0; } -/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) -{ - SDL_Quit(); - /* Let 'main()' return normally */ - if (rc != 0) { - exit(rc); - } -} - -static void -close_audio(void) -{ - if (stream) { - SDL_DestroyAudioStream(stream); - stream = NULL; - } -} - -static void -open_audio(void) -{ - stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL); - if (!stream) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); - SDL_free(wave.sound); - quit(2); - } - SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); -} - - -static int done = 0; - - - -#ifdef __EMSCRIPTEN__ -static void loop(void) -{ - if (done) { - emscripten_cancel_main_loop(); - } else { - fillerup(); - } -} -#endif - -int main(int argc, char *argv[]) +int SDL_AppInit(int argc, char *argv[]) { int i; char *filename = NULL; - SDLTest_CommonState *state; + + /* this doesn't have to run very much, so give up tons of CPU time between iterations. */ + SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "5"); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -129,22 +82,25 @@ int main(int argc, char *argv[]) /* Load the SDL library */ if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); - return 1; + return -1; } filename = GetResourceFilename(filename, "sample.wav"); - if (filename == NULL) { + if (!filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); - quit(1); + return -1; } /* Load the wave file into memory */ if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); - quit(1); + SDL_free(filename); + return -1; } + SDL_free(filename); + /* Show the list of available drivers */ SDL_Log("Available audio drivers:"); for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) { @@ -153,30 +109,30 @@ int main(int argc, char *argv[]) SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver()); - open_audio(); - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(loop, 0, 1); -#else - while (!done) { - SDL_Event event; - - while (SDL_PollEvent(&event) > 0) { - if (event.type == SDL_EVENT_QUIT) { - done = 1; - } - } - - fillerup(); - SDL_Delay(100); + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); + return -1; } -#endif + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); - /* Clean up on signal */ - close_audio(); - SDL_free(wave.sound); - SDL_free(filename); - SDL_Quit(); - SDLTest_CommonDestroyState(state); return 0; } + +int SDL_AppEvent(const SDL_Event *event) +{ + return (event->type == SDL_EVENT_QUIT) ? 1 : 0; +} + +int SDL_AppIterate(void) +{ + return fillerup(); +} + +void SDL_AppQuit(void) +{ + SDL_DestroyAudioStream(stream); + SDL_free(wave.sound); + SDLTest_CommonDestroyState(state); +} + diff --git a/test/testfilesystem_pre.c b/test/pretest.c similarity index 76% rename from test/testfilesystem_pre.c rename to test/pretest.c index 1a7e0d61..66c2e367 100644 --- a/test/testfilesystem_pre.c +++ b/test/pretest.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -22,12 +22,15 @@ int main(int argc, char *argv[]) { Uint64 start; + Uint64 prequit; (void)argc; (void)argv; SDL_Init(0); start = SDL_GetTicks(); SDL_GetPrefPath("libsdl", "test_filesystem"); - SDL_Log("SDL_GetPrefPath took %" SDL_PRIu64 "ms", SDL_GetTicks() - start); + prequit = SDL_GetTicks(); + SDL_Log("SDL_GetPrefPath took %" SDL_PRIu64 "ms", prequit - start); SDL_Quit(); + SDL_Log("SDL_Quit took %" SDL_PRIu64 "ms", SDL_GetTicks() - prequit); return 0; } diff --git a/test/shapes/p01_shape24.bmp b/test/shapes/p01_shape24.bmp deleted file mode 100644 index 290e93d9c756f17c22eb86a3b1dda89287f6443a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 zcmeIzJ(46_QU=f-R3wm)kXZ*h8f$o~KtP@qAtJCPG|V3PCTx1rx~j4w}1#~=Uw`SIsJ9)JGx@0LG*`teVHeEj3btBES?YEyl|Ih#a%i~Lb`|tTXfBV!=1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0yhf$e%gOGhVp6x1PBly zP(f@)3z1PBm_6lk`M2A3PzenJr- zKwz4{;62tp`m|n^mjD3*0RruJj{r{s0Szev0RjZF1)gTM9|HxlyH`2_1PJsJ2wXEF ztq|zdrK%-BfIy7EiV5wJ5dtwSEeHVu1S$)R*eF775~$p_Y9l~^Kwp7PBiWb63H0r7 zl@lO9pqjwA6(aC%foeUh9s&diG!oc7gB?KN_eKq`R00GD+%8~k5I9!g_Lf!v0RjXn z3miLgokrlJK;`~d8vz0Yt{3rfB=D8 z1nwQD-cI0Mfm^PLml7aA;6QGzRZie8fejbUR|pUw@GNlG`0`!?-36ZSk)H_=Ah2Gb`}9*kfeHfaZ=5Fy5Fqdn zs4%$HKp;cFNFhLgz)FFPp(h1_Isz-Np2r9f7%Na`Ua5pYp1|0_A~*p8uLbhPokRqx z3B108eoKJBG=XX}N<9R!1*T0Hc?l5sR3LlMNk^cjz^9kdhXe@B5vVzuR7GHdz??ZF zDFFh{0u$z&j0EZnJl{$`6Ce;TP=6??kiZ;)_<c>*1$keUfh7RZ}I5)l|EFnNf{PN1*Az+oja zfh2*x!$;)=2qeuSSqO|0Fun*7=q@m7WC=?kL!kTEQ9l6!i$KOil7hel0aJ?rfjof; zvr9$-tp)PNjzk0qECQ`Zli~?v3m8)b2xJRnPcZ2SG!@7mJJJy#un06AP|7BdDqu7b zATUQDb&knJpqId$nIkCy0?z`yCY5Rlgb6%fMn4lEFijwApov4Ei@>x|BQF609|gM1 zD|Hfx5%_oweL{f1Oo5oOCJ2H00yBq<)C358B~X8AsgOW`z*iU0=L85$76=$_A`mDm zFnPAfPJqA~fwJREVFdaLthsugAwYmY-zleZ0tE$(6aoYYtQROa#FRv!qrm!0=Sczt z2rL2}XPue}loBvZ2oNB!O`y~$Qw)J#0^2T~ZxJ9sU=ipw@l;Eoh=2h?fB=F00!0Rz zG6*yh*njVwK!5;&Mng}j1S$v^8UzRsI7*7)L4W{(vjy&&Z{ACwtiaiq&iMog z5Lg7tjzNVHxL&|mAV7e?l>*mKId3FTP~gf-=UoH{5Lg5X4nrjoxKh9@9m zI`1M-O5m|c>=CE0RjXn3!FFooJ`}D@=d@f$joF%|qu9xJ;n?rBgov0t6O;%f_Y`5!f%_+7ci@AW2~VRCEG?YXp++ zoh$?h5V&S?dJBP_0009Dvz}fTD`2;o#xTFLK5XctT zJRW_Kz0~58fWRVf(jav# zfp-O5Ndg22OcQu_PI{ie;R4exoxB7H5Lg5bAEyo^@Rop^NPqx=nF4Q(N)Hn_R$%6( zlbQel0*k=06V+)1RtmU)1PBlyuySB}jKF~c?i~RF1PB~BR2@ZNjetu>fB*pki@=(h z=@|k?3AlFz2oNB!2plz6okQST0hf*d0RjY`1->1d9w4w^;JM}eOn?9Z0*k=@>FNXm zuLayS0t5&U_$csta{4WSodO?Q&L;#25FoG!>>RMZPT(s6SBwAw0t8+Qd^JCPPGFC~ z>#p-#0t5&U*fV2&m%yh2t`z|S1PH7V_;iH&kia&9H7(~E0t5&USOm6BTHhk@QNW!d zK!5;&^#UKKs80xN5Ln-Go+Lnk0D(nd!?5)g0>22jO#}!KAn>lhFN4%y3A`)tZp(R| z009C77J+x?t>+2+6mW?M5FkKckHF7y>IVXE3GC@Q-z7kR0D-qgu7?RM0xk{#0t5)` z7qC+Zyd|)|<(xo(009Dvz+250RjZB z6d1K)gnc1!W!HHZ0RjXFyco!SHA=u;AV7csfhz?@tr%gy61cMEyo&$<0t6O;uV%8( z=Lon91PBlyaJ|5sEhFit0@t^kHxeK~fWRW~=~(vR5COM<009C7?h+WXXvF*|a97KD zF98As2rL30C$mo`2KMtc(lr0r#1ov z2oM;)q*CK+Yyt!b5U4MZx`QU?s^4`gBtU=wfysL+HM_1RK!5;&`U0stXl$1((0t5&U=p~T1iDoA1 z)pe>RK!5;&ncFHXwKgU|fB=Dx0%6N&V4RL!r)B~K2oM;!u)-qiVFCmQ5a=inwv495 z>DY2=CP07yfkj~2&PvLwhY1iMK%lQc(mooMrEklroB#m=1Qvl&Ybz$K?j=Bg0DQJ;0RjZ_1iJ603H9f7okRo(5FjvNe|68OO9>Dl zKp;<``)-P_KdDlKp<71^?HggKDF!QB0zuuf%p~HI-vd} zK!5;&RDss(DZBX8mXnJB0RjXTf$S~TH=X_@K!5;&Y=OQTD!cORu9J=c0RjZFw^-kF zI+Fka0tB)J`fjM;%ClQeIsya;5Lg6)7g^J2I+Fka0t6-qG+k1`Wl!ij83_;|Kp=RL zb&RGV2@oJaV2(h?J(XJXoUW6U009C7Qg>O$T)L3}0RjZ(2z1<2sWs2(I!Or-AV45> zmvzjg8wn5~KwyqQ$32x=^PH}elmGz&1X6cdyIfk4009C7rU|rLRgncx>pFP}5FkJx za-Fpcr4I=ZAV6T6K)Y2HS@5*3la~Mi0t6!0S-VjBkN^P!1f~hJTUC(-PwP5)2@oJa zAab3x3#AVU5FkKcnn1f%l~?ezmXntN0RjXTfxL~@E0I1VK!5;&nF77GRbI6-yH08X z1PBnw+i1NK=|BPm2oRVl&}&=eRXelmq$WUs0D-)X)+>l~kv$ zBS3%v0Rl;TtxFbNM}PnU0tCA3tfV@19RUIa2oOlxYhAMFIsya;5FpTHXC>9C>j)4a zK!8BfUh9%Y*AXBN)}h2oNBU zwAZ?1(RBm}5FkLH%g#!wQ`Zq7K!5;&q`lT9i>@O;fB*pkU3OMdow|+y0RjXFB<-~> zS#%u%0t5&U=(4kt>eO`v2oNAZAZf34$)f8B5FkK+K$o4BRHv>ZK!5-N0!e$VOBP*6 zfB*pk1iI|3q&jsS0RjXF5J=iN)}h2oNB!2*j+m z7D03!0RjXF5Lg6StgVj)4aK!8A(ot0Flt|LHz009C?d#y_rT}OZb z0RjZN?5w0ZbsYf$1PBmF+G}01=sE%f2oNC9WoISTsp|+3AV7dX(q8M5Mb{A^K!5;& zE;}o!PF+WU009C7lJ;7cEV_;W0RjXFblF)+b?Q0-1PBlykhIsjWYKj52oNAZpv%rm zs#DhyAV7csfuz0GC5x^jK!5-N0$p}iQk}Yv009C72qf*bMp-l-0RjXFOcrRgu)<26 z+;y@OAV7dX*ivg0NAD3JK!Ctxfkq1}tklU}Cp!TG1PFvJwMKFD9svRb2uv1ew6MZT zo!oV@6Cgl;wo9m@Lq1 zTjf!uWc1s?d0~8od5v>GX)~oS-Vg(drxWt1PDwPXt$~&3!dD8vJ)UcV5UIiI%^kd zX75Q&fB=EX0_|2+WWkd=P<8?Y2+S0STxab<&Fnp?2@oJKS)kpjiY$0?2g*)>0D+kT zk?X8usG032H30$yW(suNQ>iu2>_Djr5Fjv9Aa$2@%r&zEr6xdtz)XRTdn&c&nH?xK z0RjYO3Z(9`j=5%bpwt8i5SS^@aZjbzJhKC(CP09|Oo7y0)->15CX|{00Rqzmnl7o} zvZr;RyaWgkm?;pv$eKo**@sdSAV6T6K+`3aUG}sll$QVj0@DPtw^-kF)A~?e0t5(5 z6X?64vMZm~hw>62Kwz3c_7>}#ZdxD8OMn1@X##yWRD9*rT2WpC1PII#h+ko?1J3C~ zNeK`jFioKKdWtW8S~to|fB=Cx0`V)Xb-+1&C@BE~1f~hJUQZK>pVpA_5+Fceg206R z)ji{cK9rFF0Rqzmy6>h5^-t?bc?l38FhO9#{tC!Ap%rB$K!Cs;fq=y{B*L7Yl#~Dg z0uuy=EU$o=6S`4G0t5)m5eQgJLn6%SNl6J1ATU8-$nwgFIiVqCBtU?`1c8jLG$+M` zo|KUQ0Rj^Q=4`Hvq!W5lMgjx~Oc2P}N~2OtXiFIh5Fn5(FlueZgw5_r=?D-YFhL+@ zC5;L)p)+M9K!8BDz^Jtq6E?dir6WLqzyyJql{78Lgyxiy009E20@HR@Qr^^_l#2iX z0uuz1_R+vB6IxV80t5);2@G6VVUhECQX&Eb2uu(NTSf!pOz2V>2@oKVCophf2oNBUCopqc<)zN+Oo<2(AdoGPw~59k%5GHY2oNBUBrtYW zMFvmmOj!sJAdoE(xrQc(%5GQb2oNBUAuxGQrDo6QOeqKuAdoGPx`QU?%I;X{2oNBU zAuxGQ1!vD_PALcwAdo5$ynu#BOYK;>2oNBUAuxPN1;@|mPbmlxAdo5$ynsYYZCbeq z5FpT9z=p~e=-!{|CqRHeszCPkk?tXo+PZQPAV8q6fb}#%pl^SwoB#m=sR9$WkBq6^ zD;EI*1o{ftO+y6wwy4Sp5Fn5zFl6>><*uQ!AS|S1j>I>LD1bzzC zZ&wu(AV45P;OF4=1A&J?MpsKgfB=E|0+tSeUj*tmtqKVcAkbalm%;0=1Ret2yITDO z2oR_*VBHY-RG@zAs*nHy0(}KO9lSmy@DS+R)hZ`IfIxi#i-y1pf%*-sLIMN`bQE|o zc>RjNL!e_C|0=>FgwFC$ds4rl-5LhWt zznN7?fB=Cm0xJiv#|S(Gx^%TV2@oJqU%*-+uwI~kKdX=c0RmkF*3Vu~5_kx7X>4^8 zAV8q9fQ3R}gFxk$RvQ5V1nLWH7`(nh;2}`Iw^c}h0D;N^RtbU40+kzEZ3GAqs4TE~ z@cJTwMWAwftBn8w0yPC}5CZ!JYWB9O2oNApSz!O{bpnBhK;HD!01Y2oNApN5IM;aJWF7_Erf20t9Lb96op*NZ=t*v)ffg zfB=Cy0+t1VlLYECxk?BSAW%)(0t6}u*bxNI7O2qYY9K&>KsAB0 zXRq@KJOrw>y?O``AaJjM)j;4ffqPrs+X)aLP)Fdh!Rti?9s+ebUnK+x5V%*sQXp`# zz`YIc?F0xAs336h;PpBJ4}l7uuLc4H2;3`R9T2!h;NG_Pb^-(l+$(U);Pp}hi@?3j z@9hK#5V%Xg79eo1z+Ij1y#xplxL4rb+3W2D9s>7X0dFTjfWY+vhChKK0@pXcHxeK~ z;4Xn8gI5^@7J<7ig7*?2K;TLNGoL^;fh+spy9f{s0-p80t5)0ArLuu zg(C0}IOD!JlK=q%M+q3z1cC*Qx+cycK!Cup0>Ohmy)5?BO|x;V}uK!Cs=0W+DvK!H71#&-!2Ah2Ix;NTURz#_2!`Z$3A z0Rr0u%wYn<1-4xr-y%SOz#f6&gI9b4i@=^s>?J_pr@*_{$MXaT5ZEU0 zbL{wm0D(nd+g0){0t5)G7cg%L5cpJJ{U!1w0RjZx75H@Q_>cgBMc~~F<#_@G2&@q> zV+j!WR$$Fl@(ck21l9|DJ9a!kfWRWK{#tpG009E81Q10leoKG=f!6{X#*VKLAg~C$zFdAwfB=E71WZu^ z1a=C1b-8>_fB=E71a^)cUnf9d5%}tg`J4a&0v`oTP67lD6!>_>d_sT#fsXw(Ed&UBDlmEY z$WCCgz^4PjhXe>*EHHTr$xdLJz{TUm>j)5dEii2a$xC3S!0S2Sw*&~>A~16tNljpm zz%7HtO9>EIBQR$WNlIXvz?uo*83F`u6qq)YZFp3K$1Yg(V-*)1ojIgjVW0OW0D&&U zO`QZP3j~Y;5eN{tMWFIzQyYQG0=HaPFC{=ANuctGQyYPr0!b4<76JtB6{tDoR7Ieg zz`gg>+X)az6{t4r)I*?-K%;W1PF{0xNG`(FM+!RMqO}W2@ohQaMvXCUIJGN6u*YbBS2uL zz?Eary9it_F!NqZO@Kfbf$OKDHxf8opv&!3CjkNk&Yp+PCvc5`yG(!pfsO*#Oh|7b zaE3s~8>eOh1PD9}oG}}nN#Hzz=kE100RjYC3!FD9olM|Zfz}sI@dOAE_)6f|QRy@S zM+tn@u|6k2fIyPKQ8Uvy1ojIg-7HxM5FoHpVE^!R0)cG;D_hiK1PBla7T7jFeT%?0 zf#9no8UX?XHVAB+rM^XAy}*XH^c4aG2+R>!KTbVKV2!|>yCW$90tEI8teLBxA@EvY ze;+!5009DH1zrzWza{WdVC-cPoB#m=hYNh1u|6U2QQ+{#b07f%1PD9?KF(X85Lg6U z5CQ}U5V%0V(jj2&UU1R8f&c*mUkO-4GX=it2cHulK!CvY0y8(!oYdEMls6I}K!Cto z0&`Z%90M z&g%#eAV45Vp#GYxQ(@A-yDS6<5Fk)Wpw6^oh#USnV+>uH zd2fXKnFx3P*MIx#+dsZN{`1#A{`((){P#cq_UC{9=Ix)~{_gR^AHV;@-@pC&-#Z$EP2E^YO#mzyH@i{qOt7zx?Gd|Nr-Y|Ifet@y5r0KELzvt-lBmAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t7k>{8;p#&gOC_K!5-N0t5)m6j(IgUEX%4iM0t3AV7cs0RmYCE)QAB7s&d*o45%O zAV7cs0RpcCl8v1!%~v~BK&VH1_A^K5FkLHpFopQD@~gH zPN0kl5FkK+0D+kTX@($y{vt5*Ojw%$0RjXF5U3;Y%K+g+Ie|JSRVD-o5FkK+z!`yZ z!;OSa37k1Q-XTDM009C7@(7$75WLhzAkRq_DFFfm2oNCfoj{x6MWRCmzB^NXBS3%v z0RjZ#2@D+sEY@Bi-U$~G0RjXF5FqdnXg{O?xJAHiBS3%v0RjZp3f$80Z9?FMz}nMq zcLD?m5FpS);6)quHGvQUJ#L{a2@oJafWUl#5RF|B1P%(!zZrHQK!5-N0<{DVwpx!9 z2rW?ScFKkT0RjXFj1>so#05v-qQKbOVl@H;2oNBUTi{|_^*Vu60=aLi=m`)YK!Ct` zfm97!ummOvoWDWdBtU=w0RnjhCbdz^5=bqO=LU;2oNB!USM&Hv^{|q z0_$(O2nY}$K!Cv8TZ^VENdmzH)E)u^2oNA}w?ME~C>#Q<1@6B8_9H-m0D*o2tsAZ6 z2?Q1BSBYdyfB*pkcL)S+d%_|>;FG`|wZI+(2oNApU*J<~@h1TSNd)RwCK(bSK!Csu zfh28Cm;?xXBQT>jSd#z&0tD&_eA7z&M1Vk2fqIonMg#~DATUZGX=@WU0RlS&M%4-H z5FkK+Kn;N%t-~V(2ow>hQMF`2fB*pkX9bG1GieYYa6;g0-S8d(0t5);6gbf;yh4CL zQGuLQOw7U)@ZWlewpfwcm6*LwRAAVA<*U~Lt&I{^X&`UpJNU!MsO zAh24XPX(4K0RjZ>7g$~6?N5LJfmZ_eS42A!AV8qLz^j_;djbRqL=mW8iDgKD0D*f1 zqSSS95FkKcufRRk(JllC5U44zx7K=&009Eg1Zq}gSrH&WV4gs@ zSp;%dYta)RKwykOmYOY20t5(*5*SlGtwMkRft&)PYN>Sy5Fn6EAZOJUH30$yt_ftT z)#4>UfWT;hYgN?S1PBnwB`~^%T8{t$0$BxeRd3M}AVA=_K-T&!ZUO`d%oaFaQN2Nc z0D<@dvumgI2@oKVT_AoX7a;)x1ojJLugT&kK!Ctq0{bhgCkYTB5Le)?nrR;b1PD|S zh+EM`Mt}f;ZGkGaSRMoj5V%`lyTW>c009EA1n#br_9H-mKvjWQ)m=0M2oU&MplS`4 z7XbnURtbDvZGB6C0D%|+t7@Zt2@oJqT_8r47X<+V1b!8$UVG(7fB=Ej0>4&Wp9l~j zuvTDoO|(A&0tC7UtgZHTCqRI}Pk}BqSDpk25Qrk6{}3QRV3k0WIw%eT1PF8$SXKY+ zOMpO6fv&Yy-UJ8`h$hgp3(1-Qfx88w)jshMAV8qIz}CD**xo2;>mx(UoLLfWT~l92HNL1PBly z@GdaBFIb-df%*dP^~Apf2oNBUL!f?Vk|6;CGX-)~I#CiJK!Curz|7uYZ2|=93Ov^m zp9v5kKp>Yu-3}!)0t99VC6)!N{F0t5&Us4bAEqluINfjt7XE0*jC5FkKcl)#>z z;u!)2atMs73)Ue(fB=Eo0y(;xC?rx$ZK;V@?j|wGA0t5&Um?iM4zxbX2ftUibYJqhL5FkLHmq5%eCn^F2z7ptF zon%XZ009EC1itDqz9T>&p1`acU|j+P2oUHg5U7Uk>Zvq4e5Fqd_u&xu@nLs~*_j~7G0t5&UAh1%PUk8>kfi(gv@43AR z5FkK+z_Y-bZfI8meFUEGoX-RZ5FkJxfsa3PiZyVjw_(009E81n%vMb|cV3 z;MINeJplp)2oQ)M(4!m6lE8fe5$?4Z2oNAZfWVf(eVx%x1ZoRx-8H`xAV7csfk*CcgQ*f2oNAZAfrIe?k#Ep z*90=&RWTDFK!5;&Q3BVxsJ98^6Bu=OtV4hR0RjXv3*_tIA|`N7AoD#HI{^X&2oM-8 zaITwri$E@c(f7uB1PBlyKp?X~t}ZTG0_OxW-%+s>AV7csfzblzx~aDa-3+1PBlqEpWV}dV@d?fzfxxdISg%AV8p!K#p!MN&?3O zD&0}J5FkK+0D;j0$GWPg3B(r|eMhWEfB*pk1S$%|@8}{VuwS6!9hDOS0t5&U7%i~B zvwD(1Y=O~t!+Hb=5FkLHvOw&vE;<7H1S;QAxe*{hfB=Ef0{gnFrwGIq7=0(KM}PnU z0t7k;#O>@NBd{&d;f~6Y009C72#glk?y#O95L00EU9cVj0t5&U=p+!cyNimz_X3^n zs9XsUAV7e?Xo2s$tX~Pl6c~LEtVe(V0RjX%3B>H~q9X8Gpws=7D**xo2oRVh@Ve9b zg+M%kS@*!Y1PBlyK%kRAybdoS0$&Ssx|eb#K!5-N0<#6a?zX-q5KCb8J+M9j0t5&U z=p+!U%ZrA`&%T;C9gBXHL}unz$O1PBo5BoL?5i-f?h0-f%m zTnP{$K!CvA0>5@$p9sVdxceU1j{pGz1PF8zh|%puLEuB6)BTex0RjXF5LhMf(Ruwv zV7_1PBo5D6qQ!+rO_s$NMH{0t5&UAP_~M?*t$-Dzc>gG zAV7dXM}d_yfW7+)bi8YFCP07y0RmA3`c5D+UnLOb?2Cf{0RjXFbQV}O2iUi#Ks10#&b>GY5FkK+KxctfbAWyO33R?^awkB5009Eg1o}-PGF~AN z?c9rp009C72y_-$F$>tUmq6$HC3gY@2oN9;RiM{2BHOzKqMmzk5gm|_nZpobh0RjXFL>1^YjmY*+fv9I*Tm%RZAV7e?j~{o=1orA9;BFBhK!5-N0&xZU zOe8YBQy}h%7a0Kp1PBlyaOX^5uO0$!6#)VS2oN9;U!ccSBFno3;-7dC5+Fc;009Db z%?0+UFW@#2AV7cs0Rnjh>Q5#zyh9+*i5DpW0t5&UAaKWQV2}C&ZV~|k1PBlykVl~Y zWFo^m1oE7CkrE(4fB*pkcgzO%s4d_Y5gu}J)OvIra->aE@A=%2oNAZ;9X$mj9~4$0`KR?zXS*nAV7dX zK7qOuip*vTeoK zBLwQ4XqgZoK!5-N0$TzjW(RBJ7uY%{ekVYH009C7>ImeYTx2jppw5Yw2>}8G2oNB! zB`{)kutt7?t#jgc0t5&UAV8pwK>o=^1|tOOoM@R4AV7cs0RmeBBW4F{YZpA5gnGqmBfB*pkTLM>R2=C?<*g6+}CqRGz z0RjZ-3gn$&M1Dn}?unKe0RjXF5FoH6aAk(@ZeD?{bK!Ra1PBlyK%lNb-U&wJR|M*w zXqgcpK!5-N0$T!CW(e=*71%l#ekVYH009C7>I&qYU_^dapzdjw836(W2oNB!SK#a% z;k}#!d(VaE2oNAZfB=EI0y(D`QJ)p4dy-{FfB*pk1PJUEI6FspFQ>rXbKyAx1PBly zK%lNb&M8LJX9en>WSJ2lK!5-N0(%9{&Jo_rDX{lkc#Z%80t5&Us4I|jiV^i$fx0JI zW&{WjAV7e?UV*c7g!gg^>^&ErBS3%v0RjZ-3gnz(M15AE?n#yz0RjXF5FoHu;OrdX zy_^Dj&xPj*5FkK+0D-y!Ij0y=pB1Qkl4VAK009C72<#O&J4bjgr@-EG;W+{X2oNAZ zpsqm9DMr+11?rw;nGqmBfB*pkdj-zU5#Gxwu=iYejsO7y1PBnQE0A-F5%pPtx+hs? z1PBlyK!CtrfwOaj_i_sCJr|xMK!5-N0tD&`>S~} zoC15#h35zmAV7csfw}@Yrx;P66{vfXWk!Gi0RjXF>=igWM|dx%z}|D=IRXR-5FkLH zu0YNyM$~5o>Yiko5gr&wkL z2oNAZfWT3KGqZ$u@(CP07oI0TfB*pk1nLUpn`A_MMxgEqmKgy81PBlya8%&TEa9De z0!Pn<=LrxXK!5;&x&rwo84;fmsC$BCMt}eT0t5&g6*x0XcqgC0(R1N>0t5&UAV8q5 zK)y*v#AgKRo?w{~AV7cs0Rl$_&dd_t$tQ61TzH-U0RjXF5U4AVZ;}!58G*VdSY`wW z5FkK+z)^uSvxIl@2^>8ao+m(n009C7>I&qWWJG*cpzbM_836(W2oNB!SK#a%;k}#! zd(VaE2oNAZfB=EI0y(D`QJ)p4dy-{FfB*pk1PJUEI6FspFQ>rXbKyAx1PBlyK%lNb z&M8LJX9en>WSJ2lK!5-N0(%9{&Jo_rDX{lkc#Z%80t5&Us4I|jiV^i$fx0JIW&{Wj zAV7e?UV*c7g!gg^>^&ErBS3%v0RjZ-3gnz(M15AE?n#yz0RjXF5FoHu;OrdXy_^Dj z&xPj*5FkK+0D-y!Ij0y=pB1Qkl4VAK009C72<#O&J4bjgr@-EG;W+{X2oNAZpsqm9 zDMr+11?rw;nGqmBfB*pkdj-zU5#Gxwu=iYejsO7y1PBnQE0A-F5%pPtx+hs?1PBly zK!CtrfwOaj_i_sCJr|xMK!5-N0tD&`>S~}oC15# zh35zmAV7csfw}@Yrx;P66{vfXWk!Gi0RjXF>=igWM|dx%z}|D=IRXR-5FkLHu0YNy zM$~5o>Yiko5g1PBnQE0A}B5&0E?x+hv@1PBlyK!CuOz?B)oyLkn+&V}Cz5FkK+0D-y!c_$c= zUlFK#qGd*a009C72y6*lnIXKJTVU&K_?-X&0t5&Us3(wndJ+9qfqExeMg#~DAV7e? zmcZ5d!TY%dw$6v&2@oJafB=Dd0=cIb(O(s)ccNuPfB*pk1PE*iT%8}hpIcz-eE6LJ z0RjXF5U3}RdwLQ5Re^dZT1Es25FkK+z?Q((`N8}71-8zJ-w6;PK!5;&Is*A87a5EY zsB@xaLVy4P0t5(b35=K>tdU<}>zw$V009C72oR_vkbiQK!3cpmCt4;12oNAZfWVf( zh}pp!`31JliQfqjAV7csfjR>DCl?uv5U6vaWkP@e0RjXFYzd5*9juXGVC$Utod5v> z1PBnQBanY`k-;c|I;UDD1PBlyK!Ct2fl+gVb!rH_Iw!s-K!5-N0tD&^)RsWkP@e0RjXFyb>5S zH&~~Jz^ilOdjbRq5FkLHjzEp6MHV9k>YQ$w5FkK+009Ee0wZSzYt<2WJ|{jCAV7cs z0RnXd>P##$87WZbgv*2g0RjXF5O@|CIWt(Rmca8_@tFVt0t5&U$S+W9T9M6Yf&3?2 z1_THYAV7e?v%u(i!Fsg>p3jTV1PBlyK!8Agfm+jwY(@*@KjAVUK!5-N0tB7~M$Ze@ zt0(Y$W_%_u} zJE6#Iwm`nqE@A=%2oNAZVD@}q{n`R<5CH-N2oNBUPoVa6BD>iF`A)ou2@oJafB=En z^MUp23%ErD2oNAZfIuFB`jd$a?-0my;zde;009C72;4Cn*rUFHn?!&B0RjXF&B3kr5z3fB=EM z0(Z{?_Uk9m_jbvg009C72*eZUH;Kr2g+RQMFCqd22oNC9S75~~V9%ZceQ%k}2@oJa zfIu99o>PddR|&*9`63}efB*pkeFav{0ru@F(D$avoB#m=1PH_t=sAVRdZj>|(=QSN z1PBly&`)6H3}ElR0{w29j0q4RK!89TfxZ)n%vTD;xd9>}K!5-N0{sM5&H(oAEzs|_ z$(R5E0t5)G7wA2G$bPlJ`Wqku0t5&UAka@>b^o_Nfqu76#smluAV6T9fXRbE1c7xo zz|I5+5FkLHpFo7(F9rhrZk~(@5FkK+z&Zhw2Z1O8>u!Oa2@oJafIuICD1BcX1Rer? zZk|jD5FkK+z&Zg_2Z2Ze>u!Rb2@oJafIuICNWEVy1Rer?Zl6pE5FkK+!2JTI4FVAb z?!O6kBtU=w0RnvlBKCeU5qJpnxrH(%K!5-N0{01+G6+N#xbG&|i2wlt1PJsIh}`?d zM&KdP=Qhfe009C72+S8ST@c71F#jglfdByl1PJsI$k6-6NZ=vR=T^#;009C72+R{O zRS?J|Fz+TT=XT1J009C72#gmnO%TW^F#aZ3kpKY#1PJsI z$k_YEOrXyVl_>!N1PBlqFJO`&kX2y(ZLlH%0t5&Us4tMU?~9wjL!kZ*l_3EF1PBlq zFJOuwkX>N>t*{~i0t5&Us4I}Y?~9+nL!j;rl^Fp71PBlqFJO8gP(@(;?XV&N0t5&U zs3%aR@5_V0L!jObl@S2~1PBlqFJNjQP)%U`EwLg20t5&Us3TCV@5_h4L!izLl?ed? z1PBlqFJM|AP*q_3ZLuN&0t5&U$S+W}@5_t8Lm>YRl>q?)1PBlqFJMX_P+egBt+65j z0t5&U$SY92@5_(CLm=-B6*&O{1PBlqFJL+#&_Q7Q&9Nc@0t5&U$ScsH_sfyMLm=-h z6*&O{1PBlqCtxZd&`n_6?XeO80t5&U$S2UP@5`6KLm=Nx6)^z<1PBlqCtw;N&{bgE zEwT~;0t5&U$Rp6T@5`IOyFi}XDpCRj2oNA}UEsa{`IkUvf$KNP3IqrcAV46GK zcLJXU^4waH5+Fc;0DI0t5&Uh%cbe6Cm(fApQ*&Aprse2oN|f@VfK)g#dwv!0}t< z4FUuR5Fij&KzAoVV4pzTTP!jH1PBlyuwP(b*Ygws0uO=xH_MX*2oNAZAg+L}PJqBM zfw;F>WCREhAV6SS;8@r5Gywwd0^7IC69fnlAV46Vz)Uk5fLCjfB=DQ zf#V&|8w3b^7TCUJo*+Pg009DV1U~mLe-j{ZP9V;W76}0Y1PBn=7C6`KyhVV(v%vOk z^8^6`1PBmVFYw&Id?rBPn!x&7Edl}r2oNCfTHsoj^ELqjUkSXvaeg5{fB*pk>jl2* zUA`kg;JU#2+bseD1PBly@VmhE4rc`d1ilma{nq)0009C72&@zMu4nm;0D*A=>u$N7 z2@oJafWU{qxXxxJ0tB`MK5m}B2oNAZfWSI|t$yWq0tChith?=YCP07y0RlgMjO}Vx zBS2uUfcis#009C7RtW6vQ=TJ0V2;3wdv8wy1PBo5D=?>ei5V%9&OmFfI0Rr;{?zsQ< zAV7csfqnw>JD42^5V#`HuPVuy009C7W(r*CN!}$u;68zwb->yL2oNC9N8rBBWhVjz zt_t+2Ofn@vfB=D+0$2Ny_X!ZVS72s6ur>h#1PIg@xVLNBjR1jB0`;qt3<(e*KwyTz zs6J#J0tD6w%%}_2BtU=wfw}@~x|LlC5Ev~`w@S&3009C7MhcAXKh`5aV4c9o+F&gL z1PBnQE3mFp*_i-=83J{ymCOhbAV6S*z>MBwO#%eg3yi1_)*wKD0D*b}>pPSP2oRVl zP_JsqhyVcs1g;3o>^asZKp>95l{(>F0t5&Us3Q=kGl_%%fjb21R4gnKp?)rp8DY#0t5&U$R`lL1Bs9Tft3RJ zs+x!i5FkL{JAsuw#oh!6#z9T?@0D<@d`8tk>2@r@R5WmWakN^P!1U?Hy>LFqwKp>~U=bGYg0t5&U zh%b<{+lZO~foKBptDOi55FkL{ArP&9h=%}y+yW{q0RjXF#1_ciWkgSaKvaR)l}~g8 z2oUHi5VdcJivWQd0)4Bi%n1-65L2K=caa4F0?`FxRzOh^AV8p>K=ghgJ^}=43G}P7 zGA2NPKrDe;T}3to2xJn7RSiW$fB=E|0-1V*SP2lQD^S1M%8&p70x<;Yb`qHpAdpcY zMr9NQ0RjZ-3S{gJVkSVKzChioD>DKF2&@&T-$7(ZfIwz}wH4Ct1PBnQCy==(h@Aj| zJ_7ZsuZ#!~Ah1TDPv?*+0RmM7)>KQo5+Fbzzd)5fAP)irdJ5#P!ZIL0fWW;1J-dah z2@t3zaBto&bTW0{2uTVgLIMN`j1hQM2YpX~0D-Oo zW2&oF2oN9;PoQgEmp1_d1ojBTtJ)$WK!CtCfjza)GXw|_=q_-r(t4W!0RnLZy4Q30 z6Cgm~s6d4&ep9l~j zFi+rEb@DU;0t5)m6qr}-txSLbfu901YnZhO5FkL{oPZuhfB=D60_Uodw+Ij*K;SNc zS@qw#1PJsKxT|j2hX4Tr1g;D8>ryf%Kwz}M^-5#~0t5&USRpXFA6Sn7f%*a~YLz_+ z5FkKcyg>bqB|`!Pt_qB=JXRz?fB=CA0$2Nk_X!ZFClH}FiGctC0tDs?)aza{B0%7* zz}#wMbpiwk5QrvlwqJOU0D=4h(dv5Fn6AV5^__odALO0-0)#SP2jy zKp>7l{0=8V0tB7~;#3fk5FkK+KxTpG{^BzM0`UYg*BG%AAV7dXT!DBUPecR=#H|=2 zBS3%vfhq#JEdc^?1gg{(c@Q8#fIuFBI9*UA1o{Z%sSqM1K!5;&$^v~luS^N76R2E6 zIAzuOn2oR_xa9_u?6M_5! zwQj#`2oNAZfWX^Z{_ZRT0^JIph009C72y_)V-%-6uAihA?do6DQ1PBlyuty+%Cl?`s{Q`T=yk`gyAV7cs zfe(TGUDlHX;s|`4V1E%HK!5-N0#^j$ba#;u_+8-2+4e300t5&UAh1v1_YUkE0{02* zJEfi?K!5-N0t99W+}HK()LCHG`L!+q0t5&UAaG5f^L!%r@dDRQqqhkVAV7csfx899 zPXku$AaM6NwI2Zj1PBlyFh`)n+#<*G0&`BCRS6IvK!5;&C<5oF1aDRnh;kmqL4W`O z0t5)G5vVlR$mMr|HK)q11PBlyK!89tf#0VH-((QTcFx30fB*pk1PH_y$T0tOiZS+S z5gh>n1PBlyP*tGQytOjds^>>u1PBlyK!89Eft7Pog}rN>0$C6sK!5-N0t7w;D$HnC za`=$&7XbnU2oNAZfWW)JmHBGryYKP)mjD3*1PBlyKwzxEsu^!=7FHubfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oT69 F@P7^ZPXPb` diff --git a/test/shapes/p01_shape8.bmp b/test/shapes/p01_shape8.bmp deleted file mode 100644 index 5adca298f715c601d0290c5210760c8614781cc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 diff --git a/test/shapes/p02_shape24.bmp b/test/shapes/p02_shape24.bmp deleted file mode 100644 index 61e411bb774ee38e755e08b7bb598d728cf1adc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 I1mX+)KN!N^pa1{> diff --git a/test/shapes/p02_shape32alpha.bmp b/test/shapes/p02_shape32alpha.bmp deleted file mode 100644 index 6497a7b0d82aa7004b8c3fc5fffa9c0da5a05c16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 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≪sB=ibc%=ljMh;uzaps_2okKh> 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 diff --git a/test/shapes/p04_shape24.bmp b/test/shapes/p04_shape24.bmp deleted file mode 100644 index 8cf61298637606882c94a3f38a71755a44d6ae12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 diff --git a/test/shapes/p04_shape32alpha.bmp b/test/shapes/p04_shape32alpha.bmp deleted file mode 100644 index 771ebc05337fb29d5d1b94a89d15767d3b54d428..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 zcmeI)JCY<(avjhHAyT49g$5DppiYfzm{mZaj1^%4*b+6G4W_19=<4eH#OHneo-qu& zGBe_Z`$_kkW;7W7=l}ZGAOHQw$AA9yZ~y(5zy0_B{Fk5q{f8g_^y42tKKR?`|Lwp2 z`1^nV{_^kt&tHH1`1wB{um1WEzdrc!zyIxj{J+nafBDN_{`2pD|G)qIw>y6Q=j%Pc z-t`v&0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+z-j`2^&bKR2>gG6)$&O_1PBly&|big9svRb zz6-QZ8YdDUK!CtX0^f7rYXSratdvn|AwYltfwlsE

ZU@Jyg>+Bl5>0RjY85qOsS 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 diff --git a/test/shapes/p06_shape24.bmp b/test/shapes/p06_shape24.bmp deleted file mode 100644 index 4cc257688f17aad0131b6cc65979184feaddcdef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 K1PBn=C-8rtukKL* diff --git a/test/shapes/p06_shape32alpha.bmp b/test/shapes/p06_shape32alpha.bmp deleted file mode 100644 index 04afd793cdda60d227071d8022e15d1fa5c590ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 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 z5Xd0V<9lTc0tCte8O|6r5+Fc;z&--yubEZ?IR*CF*(xDGfB*pk;{|fwCRANZV0?nj zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ P009C72oNAJx4{1aXoNQV diff --git a/test/shapes/p06_shape8.bmp b/test/shapes/p06_shape8.bmp deleted file mode 100644 index 017c1ed776f8251cb53531f72886485ca40b91f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 K1V|uy0{;hh#my@K diff --git a/test/shapes/p07_shape24.bmp b/test/shapes/p07_shape24.bmp deleted file mode 100644 index fa8012cae8c048e538f84f559f5da1e5bb05ca9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly 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 DW$&PM diff --git a/test/shapes/p08_shape32alpha.bmp b/test/shapes/p08_shape32alpha.bmp deleted file mode 100644 index 4d1d25e9ff436183f32abf468cc48667ee16887c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 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 diff --git a/test/shapes/p08_shape8.bmp b/test/shapes/p08_shape8.bmp deleted file mode 100644 index 920f9070ee08a5355fe5872e097f8b6d03107ecf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 J0wB;N@P87svs?fG diff --git a/test/shapes/p09_shape24.bmp b/test/shapes/p09_shape24.bmp deleted file mode 100644 index e5a7c0043d98f084f9d91d099bb33bddd47a01fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 diff --git a/test/shapes/p09_shape32alpha.bmp b/test/shapes/p09_shape32alpha.bmp deleted file mode 100644 index 250d267d1491cad72a1c13c8eb0d43e07ce2adb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 zcmeI*JCY<_wieL3AR-~6V2F$Z9gQ*cC=jlEN5lY_5*lVrXaDA8R%J$nyT^I#$7-XA ztO)mW_Fnt*hk!Nz?Z5xqumAMx<3IoQkN^J9fBg4f|J`5z{g+?=<=4Oe_~9SF|HuFE z>!1Jq=Rg1XfBpT}ufP7!$G`smFP}gB`XB%6KmXt3pMU$?-~RvK|NH;^*MFSx`Jb=n ze17XM0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlya9`l>{zHHO0RjXFv=+Gk7U9gS0<8nDc>)9o5FkL{p8{FmW7K_3K->us zAV7cs0Ro4>wYLaIXB9Z+?UMij0t5&UAdvNaM%~v0B%J^O0t5&UAaDp=dy8;%R)J&Q zJ_!&YK!5-N0$JZ@)O}4r(g_eCK!5-N0*Ao0w+KgP6*%VYlK=q%1PBlykoA2=-PZ&p zod5v>1PBlya0py`i*R&Sfn(l22@oJafB*pkS>I>WeN8~p2@oJafB*pkhrqSB2uEiX zIOgq>009C72oNBU^?gR&*90V;009C72oNA}2wZ!MaCBCIW8OXq5FkK+009D7-)Gc) zO+eBK5FkK+009Drz_qssM`sl{=IxUJ0RjXF5Fn8CeMa5a1SFjR0RjXF5Fl^}TziXf zbXI|5-aZKsAV7cs0RmayXViU7K+*{iAV7cs0Ro4>wYLaIXB9Z+?UMij0t5&UAdvNa zM%~v0B%J^O0t5&UAaDp=dy8;%R)J&QJ_!&YK!5-N0$JZ@)O}4r(g_eCK!5-N0*Ao0 zw+KgP6*%VYlK=q%1PBlykoA2=-PZ&pod5v>1PBlya0py`i*R&Sfn(l22@oJafB*pk zS>I>WeN8~p2@oJafB*pkhrqSB2uEiXIOgq>009C72oNBU^?gR&*90V;009C72oNA} z2wZ!MaCBCIW8OXq5FkK+009D7-)Gc)O+eBK5FkK+009Drz_qssM`sl{=IxUJ0RjXF z5Fn8CeMa5a1SFjR0RjXF5Fl^}TziXfbXI|5-aZKsAV7cs0RmayXViU7K+?|%TvLsG z^tp~5MSuVS0v!cnKfzQtN}yx5@AN#%JHk45`gaZi0t5*35Qy-cQp0?K9%+8B%lT7S zzr%YX&L%*B0D-Ck>z_z=+#ygkza#tFF_+nUj`Xn>0RjXF)Df8d{IPxvfjWtu(@Bge z&8cFJm{kc7AV46qz?>(JRig=H&ghETqs?n%JuCd|L4W`O0=Wc6K4+{IS0Gm^*H#;M zYU3(fD{40a1PBm_E->y1W2MLf(Q~+~zQ{8hUE3~W`w}2PfIvKf(a#v`#T1B_yciY5 zoYtJGVu-7P009C7A_&ZR(pWW`K!mJCt0CHqR@4)12I?U|fB=C#0xO<5_Si45Ctb03 z-9MG}6~vy1>P8E!PuRR2N84DBKzD(8SF4pH33Si-pZ!Og&FWhIwD9k`0;{t%bHBP% z(iwq00yD2yYsV4TldhP%#+k~#N@7k%RU-xVrEA{KBYmtzpu51lE7i)81iI(^YyXkv zva6P_9{#Q=uq#_L_pLc8T@lzLF!Nfqb{v5{>58{&oQdqOB;IsXG)iE9y5?0d%E&qd zx(m#^O0678pnJ}b{v*v~cP)pB&w2v8vo&-7dehPofjt5-*Jkm5`)zUp1mHLmBthv=xnwwq->=2lHg<3s| zz>aK1+B3==BGwUUPHK5iAY#5|)%2c}vk4HGb!}QVmO$sE$C^aUYC7kmO7Ag~HMgoN zv(pEG9RhQ&POC=|*paO$dq$Z-(aWh1iB_Y))Zn_ z(={U%`i`Bfxz$yepB@P85SV*qT0M%uj%@ATGm3qtI(E;@{?`RE@{bq)>NHwo@yqrLtxDn zX_wssJF>N7&)v?I_TMo-d!7@hl(W&joD*~u0Rp41J?q63=#uo9`Z=rWl9_CM=1kMt zsQkZOr+un@c0R0q?i1*fwsD>Ao0gr&O)<_&)utLpRwXk9 zs^#`;KQrB|{g=R3Z-1{Bki+`|>!)SM4}tp=i+g6Z$;Opd*+_wExjp(BX=JVE0*ATJ z^#ac)!)SMXYnD`Did#)F#kkzAy^?G*vQ{b3|&v^oWn)~Bd%VEdX<6;7!1!rd@k8LNahJcdPrl2Xy7!qqr$_zW z=NPH~Vp5C~FGVOWARUK-PTzRQC=)d;Tu)(_DNvM&Ng8|C%8%W>Qx95STHaY-?ss zLN;gh-V?~0&!6hPC*Z7+0zb{gckc^~oRPJj3%oxqD?AtO^=rm7^qNrLZZl@{SVG?`St6{TpW8%K)&_~T$_}mKLqwoEB?-z5)xlig(CzqW%5d`BW$d3 zPTs1JenqmUln*gEk|56?R9b_pajkg#7v;&^sabx{scmdtEszn?Up&j;Jr&Ofu9vVlLKrE|4Jy)oXk{ zA-`6i|IGd(r)Os0){Y|(IfEl>izA-Dl96+<)^maUY02Bc^U3)2Lm=;T_g5Y{LHl)7 zutFem21nPn!n67wqi15h*8z7ybsc^Cye=?nCf0o|aD7_NxGV5_ z8jiSY>%8v*5!3X&O5aozh?u>ZHC3G9s-9-f#oEsWR?XNx{{J(4p2WX?m6LG-XS`P* zGp;);MHX0{s|fo=o=4Q$BFwFO4Nl^LR>r zc~_uwuFm%UuCenU3!I(7isK$v>6c0ZE3y=Ik4kf#)l1abb*^jHtUIsUIZtPM@0^cS zy`P=M?BiDTYoEOWvvU-A{k`%zc8@&2uC?VzPOd7uX6Z`bxh9&k+AH&zd2CKSRbLgD znV}48U-jyAe1`dTt+7)MXZPAQLs$BqJp=213S61XtYbf`@mFPmS^3GbZspm&+gp|y zcCGW>`8wbK*2~#zM^@GeoSn(cbGn8fRo&Hhx&N92sbw||Us?*zwJo^gI%YaT!SJ}Y+3%$2_Tq#<6XSLQVO*mxQ$njtVcH<{O);hSyE z%=7D7d$x>4sJClYuJj#YPTgy`GOv-xc6aa6|H#Z_U#rW^pY@r2h8^pFHc!9(EYLA0 zS9|_h&bKQCuFh=K@hf|@*FJ$!d8x9_zDexgxylSX_OU-bc`E3bkE=cBnP&Y;ug-47 z@%2@8_#rSNE7jKMn2c&O?ATAo++OXuV=nIWeAU(Q^91h9@7;6ebz|j70`KOe;`x#0 zv#OSgbL`gBs;p$*r&}i8={NJ7tJMC^{N6dYil0873%rw&%I7{;tMzN;Id^RRZJ>Shl z#q+w%d}f~&a}l+tnR7h5c2pa6jTJbXht9{16_2r6=XrMNJx0>@R?%hFv-{jT#a_G5 zp5>YKdx`h$_DmMKA5$?M-DldN|BAW2+Ea%)uj=utz2mP7teWMkeXdvTjEn-Wa^UyB z1TxO{QO#!BN8nKgI(*+}%IiAqkegjSuAAcPopu@6cdfwdBsk(jVC_`q?q;IB1m>nG z=IXsBysq1r)2>m~x+%WisfMU7UJ1NTf+GkJ$Sm;c9#HZ3%=3y~d&Ru$?kU>LcYD}> zjX<>AsfPf8@d9h^CNXvyuV=-t0x_mstBS9_5&o{FtD8px5wfNR0tDs?Ji7iXem_^z z>K_6XbF-_bh>6-|Vc)d^5woWz0tChjti7}B*=@X<6(0%gnQ*OLAKC8yzLv6X9trHu znf(b6SSRr4K2YKNb&7WS5U7xu-91E2(ry>~uMvovKXnlxFkWEIU1iTMWW}=rdnTTH*R!^}j?1m9`m+MN@?~EF1ojD>y+33)Zl9u^KLj#lBwCHp zlM~HLJ#z%2=TLnF2#gn)b2r(u>UbS1UKQ9g`JB66wcT-iPFYo771)t2dlDc(fIuyQ ztIwW}$7h*%$Juo3IZGbZNq_(W0&xX8K0mMa9Cv!ZS9Y~8$LAFIed_*2fB*pk1nvps zd0z_Vvgx8Lq7?mM&FjJo!_FJX2jK!5;&iUM7q zr$6X?eY)*b{nO-StkLkx}4r0{lXN009C7?g?ak z9*(a0p4|SkMmtz9rojHBsek|h0t6}w#C+3Cwzi zMOycsOuVxqnW|-;K)mFshyVcs1S$&5dy?&6xzfD$YpP&{K&8y;g#ZBp1mX&;c&6>z zBd+@O%61vrcdfwnWH^HW0RjXF+!I*)gj=)QJ&Cwytx>bfZh^Q7R2cyR1PD|U*!|3# zvA-G3P_^c4foi$c4*>!M2*ehc{ajnQer)M`)vffi*FJ%J>2MYS0t5&UxF@jhi8p@d zdjhe~8n0x2pMNQ)cR%Xgx1PBlyK!8AOf$?t|R*WbRJB6wvK!5-N0t8kHM0{6K z(|CcEnX(rF0t5&UAP`$%{F{aqBMQV$q3Q?_AV7csft3Og-&NE!USMUW>_vb80RjXF z#1=dew z009C72oP8)5b<3_P2&YtX3Aa!2oNAZfIw`4@oySdj3^L0g{mV!fB*pk1Xc<}d{Ie`ZK!5;&l>!mpRn#tKbP<;fgA!?GVj>D0y)yCN&*D3 z3go>zUavgsjMPowtHAZ-J0qjO*O~j9009Cc1v1_f->rG1pS1|=5P0_*aDEPf9oe!c z0RlY)a@+;SR@q~+x~wcP_KL7tRDsGF)*Ar=`vjui^H$WgPu=LMP_37f3z^;7RmjHp90=h=yZ*C z?zMW3^+$lfI)Tpj(nr0o)3+0WF#?aW?E6{*W71+30tC7X)Vec{?WTJ+^q)&$Z2GMh zRUlU?RZD&>6;HyZF~c>-PTl_UC`Hw7yb7$-0y z>(+=bFfK7xB0!*Rygyb8NzM8Ek2;$1Z=njw%cnJOkgAd0|@ds6i^qs&en1Re=g zzXtX9m%yX>`=3ARrRnC8z>aL$Grqv1ykz`7 zzIMI}Gv**#&H1LT;w}Qwt`+sn5$KX~GwCG+>O#dB4cIS0{d&oz0~b`gknov3GyK$kr0^PIq(l*L%}oPeXA z3B;IWtty^*`;EX3fm&CXZXOBj$d*0h3Ove5h4164=cueg2BP+mBLS-HA`taDQP)_3 zE_v4HYk{%Jin-cr1xGv*h&j<3RXsBIJ%JqpHLfaMyb{=vEqlfjc$Je%zsJ*$Us0v` zN9`qk3RKudAnG-uuCW4L@~qG20%KDZd$s2he*H-x_C#w`_mjKt2<#B3aYgCkmB5Z{ z*)x{FtBh3qJ=O$bS5tBRQG1G=1l4sBh={SkRX!^J9%l-1E2%vHsJ+EagUY%HM7=iDHCCWYp7r@% zU~H0duJ&EUH-|vZiPot4n1N3MI|OQ6MY?z;up?Xcj3MwU8y$X+F^QN}beMnC9%Cj# zRb2$4UKi>bE6^p+`utsBY>INP_PdOKeF)^9XpQP6k-#p28ds1mUJ2~VmwopNyvjwV z-}g=;=I)*5AGOz*$xu}XfvDGny2c80$g&=P7Z{tM8ms*-)capGCR(G5-bvK`E`b_X zk1k#b?8=vY_XxboL+9W3OeEf}oih-v_ju`0Q3ru&*MWNG2z1D@9zO-q3=#XVS z{v|LsNi|lNl}~{h6V2Jh=M?-!V7)-jt47sV1=c6ajynXd=A!fQJLVI0&(1m6)qB(| zsH=~_uI$@)tw5hd_w96TqH65cH!Z(+USpcMy7+wp{zYKDK&~rBwRZ*9C(Dkj1@2~} z^LeXh6nVeSIau9$YRgBy+_Z0`uYg0%DjE{3iL^IzfSiitH$pAQuDau z8WWA##bb595Lhn|^D0r*Xo2;~vg0a&(OK!Z-l|z;*r((CSM{7>{%Y(auqyZV*(=Z| z(Y-p|o2pv7_e#$5Zfi|8MmNvJ{YqfHK#VIx6|)7_C(Dj21ZL-^)A}psm1U1kb6?eK zmf5SbkHD(z+h?yppG5cRbZ??+?%pRouRE^Mtcb#^-LH*0no*X=m}Q9Wmzx0?G1jLNxnVhi+1bp1|aC#^uQ1Z$+F`Nfz`R`yx)xZWnHs#4(|4zb=K`)z(hZweE6Ha!>WwgdI&_y}+I;!>+3Z)+fu3qXkxH#(uwkjh@;)sve#ZSQTMvaKFbyL-;;F~f%O6_t_OSU7g(PxJB}3C zpP;@KjGSZkwfd&ueCOF`to}X%=Tq&-%mRH9U9Z#3xvNrpy;R-rsLD)d^l@L+nFQ7g z%(w=u8Bbt+vg|lYAYPjKRWxdr71rrD{qr4Hn6DoC2%Jy0BQp#1Np!7FGiR<^?X{Bi zUN_ZdJEEWWM4e4wy}*dvTO+=}`efO0gh2dc^{H^gJS(lyXY#K*tu$A?^bvTSY)4cO z=#%JromR+S_8#h`>iv$g&vaz{?`v9tzK!9#6$0x8W?T=}j3=-@S#}&P z5HD5rD;hn+itE)+!Q&1q&Qni)1Rf{bFO>xPB)V>=mGYRSm%3>h+gX-*j;wR6pw$Sh z7Z`axSSzl;`efO0hCtke)vIjA{Hm^5Z~Cu0sya)3^$~cTaz|7U=#%K$omR+VmL6&+ zX?%BC<~pLz@p@JyuwG!qwP20-0_&4y$5{gL)0V%&S+lFWZvLsC@1XJ=_0~t=eBvFM zS)fm%dvuyPi_vTEk)SzUMxX77`sT=4mB4y|5!Zt?;tQ-#mK|pb#7|t_3TMu(!`gW# z|GM%H^V4G=f!B$5L8@V- zrDlG|yJk6K-}zd0Ah2Fw#8uK!89Ufw*^Om5mgrlUSWR6BwD9>T5l7^cw*J1gZ;EzsKwEFM;Yg z*59Xq%)b`+)bbYr0t9*ryuMR9qJlurbocDKLSAS0&@&mjCP09|r@-v{to36Ed`{e7 z9Ry;eE@l-S=C4Nr1PHtmhxKXU0`~+e-PiT@c8DMl zD|M<_DG(ulyK7kKZ7%`@2xJi0eP6c!8i5R1RO38>H7Sd^%RE&p6Cgk!uRzQ@wyH)8 z1+w1T)qPE%bH?@lDR3<{xsU$r+Ft|+5FqfUK<+!d z>dy-NId}i=D{wY5nUCu`U7ZskK;WD}=6k%_&kLMOgQL0#oKH@^BfHE{p9BaHct;@L z-Cf0Z1m4MrbL$D*$xxPa>P<^W1PBlqFOcP)uFm%a#wW*$*#+K9RLrxp&qVzM2oP8$ z5c4jss?h?g@?@Vp0;4k)VZA*1DkVUGz+Qm}_iQ!H7ucIJyGIq6pS*QDL^W0y0RjXf z3aq<3+i9mj#O$eQzrfB+j^2B}rV0oUAdp93^gY;mF$MA@Ql$h45FkK+z-)n-Z!4-A zEigMr)+a!K009C7@(7H6&#+!hfjo&+DFFfm2xJh5`Lw%NRR-_THQtkQRyKjrxv?Gr z0t5*37RdG-d{%GANj~%NTOEOp+14`w0t5)m5vcQY{On|oRNPfRJNH**fw&1&836(W z2xJqe_`J+EgLmt#I72;s7kD=(&L=>C009CY0^gr7b-w8|;W|^($uogYnbs=-0t5)m z6L|Kl$^P3swRkIMpOyO03B*gDiU<%OKp>mIxhGEUqq5E9ZoRpur24Z0ck|&q0t5&U zAn+k@_IXp`xDFGqFeg2{7U+;=JrW>5fWSO~*H4-{N6b@ExL}oW!Yw009C7 zvI#tUzEu4!+l;Q(TXk;w`d#2^E*wvQ009C74uRjFC_Vml^j`I|#}sv0HNW~IK!5;& zxdNS^pmSySuHJdBoqFG!GP@HXK!89-flklKN4;j8+nJgl_3Qgu0%x+|7y<+c5FqeX zpw^S{UN>JoD*b&=!&%t`DrHtL1PBlyFkc|sb8cq6^R?ISFw@K0aRk;U%Z>yH5Fn6Q zAkGtPekFX0t5&U zSR-)nDbwSuH4|8~OOJ`^a!s1-LVy4P0@(#TF&_(LpY7NBA9v}ON&;VJ?r#DF2oNCf zlR%~C-mG4Ja?AMLEFbH}63CcMH4`8}fWTUTSWm8))vTS!yxn5zs%o^ryaZX9009C7 zDhP~z?o?jSyem()R&N!uss{oD2oRVfQ0s}erkgpFShwmLCA;hvSeGa}5gUsDE6vsa6662oRVjQ0K`OuakLG8^3ZqJr&Im7@r&~5+Fc;KsJFH&lhX{ zE|4V?zgO*FRRyx-QJn+`5Fjv5pz1R&QeX3?H+JPnW@?!yFg7(-BS3%vfouZvo-kJa zQy@b&{;b)*YYJq@q8bShAV6TAK+UIH%&z85aoox=dDzxql8d+igrpAcsfAV7dXR)Kv_9XlTa zQL}T@=d-pz)cmQ7009C7<_gq)y2b8p?quIvJ+_?cMhU!^5@!=2K!8AIfl<#A>zo&e zo|p5TI5M+9^c<>>009C7<_Tneer2tF-ZaOooYhs`*969-#VP~{5Fn6M;M!Be(eDUk z$jCeWIX90$hAgU)009C7<_Y9^V&$)N-W12JoL^Z7X9UJ2#!3VT5Fn6M;LP*FG4Bav z$;NwKIy;L%mOQGH009C7<_ctaZe^@|2ODU)g?K!5;&xdOSLXfdmwJF)Sr$5d3+Xo2y`u_6Hi1PEjm82yZ~-mhQT zvS4OC1hVB)y#xplATU>;$J1|3mvbjIXZ1BQcG)d3Cp}grK!5;&%mTZgO%?XfI{ymx zdU!36HJ|DxK!5;&c>=GW9*%e=FmF0@SANx(-)jiWO_9|J5FkJxt3ZvX+@3D7&ite9 zJz{oUEs!~*Y9~N|0D*Y|tDi@e_M11A`72kNgI*pB%ukXX2oNAZAgjRRr-xr231pr1 zQTL<1d|yl8n72;?1PBly@TWknC*GQF{xr<@>c78fjQ~1Dggon2vicd|CH%-W|evOnON^mtK?N51PBlyFi)WOlk!~m^YquQ ze6CwZWfxeRD!UONK!8A2f$Yz_ne}I#=}&dfbh36FfuCmYyI2Bo((-;Kv1C<4fWQ@j z_wN98SGeLB{n)xw(%Dym=s8qht^X}f^CUnZx4_ruhrj<4$UV{T)thS%f$x*|O(cPe zDU9T+76JsW3sk%>-RreS~xG4K>Q^3t}s`c&Q;rcqPi!LU*O!8-s7nJ)BN5+ zkICus`{aESMWAvzqZq4$0D*e~mG4hyd%GtW|E#n9J1)0C{3Q0RFn6+^SKoK4Iww#= z;Q1Ba6>FM(OGcl({H*uCQwV@`4wLC*IE<&-c8M^>FQ5`@00h*^m16eJz3bN$gc&t#qC1rq_gZOQ4>>xhuWKQT3+xy`vhF(#0!*@00hz^MM}@UhcCNb~ zlh!4Hx&r5}^qfc4o!a-#a!yFqR|URL-Z!fSuFkCT@vH6aM}WXsfr|I1u|n}zt2jeF zeHVzI#QGI}pGx0v>Q7sT1ZoRZynEH2*!S)#&QMR^1-?(-H!B6cPsBGX{q047z<7a5 zcd_w0@mH)gKfOE_h@Zr|6+WJdU+PX>X9VgCRJw!JpVs#dE6q?{JG6Zh9jfh_sh zS?9`m*oy!G0tEgN*m>W#_c{T2UoWt3y7_lnKOH*~AV8qEK>jC(4$cVlPWk%o&&o`-- zSIM4<*_8kR0{sN?Jt0(lN1$J__jP<{KF;||VBgg2On?A^UILywJp_8CdSACa(locr zebciu0Rja22+X~cTRnL+=Bhc%KCql^91Q~?131ZoS!xZA5@wm|I^@9%E*{5r0` zf2t}VK!8A9fsW6Wjy=~+@czy^=4n;W`=_e{0t5)u6j*g1x6fXIn(2+v)!xb0-aW>I zRY8CNfqDY9pB}n91nMO>Mn{LQ&v^ndCanqr1PIg;n0MEno0>OiX23&cu*Y6uV@kXvB=UEGd41ahY|R`omPXV1R`VkJQ} z1PBnwE8w}(Lm+QL<5k`xF)_Q0mj)FPAV46eK+L0@EPtP+Twklxj;%D~EfdQVjMl?7H_NA}t$P&vaZd)qfbJ0Aio=WVZR0>_LheO?oA zG=V$b(6Ex zA+Tf4_B=0e%&Fq%c?Cxj$RkkkTGi8cfjo)KQR#PK-^>=sk&K>I&YpdQ^?OcM*Odh# zTvuwCFHkwdyLy{HH9H&vyJl?P*8<0kI()v?a0G!o0v)bdJ^uQYCy{w7m8*3Ec~a4@ z(se1=zf-^I>bSDN{wqoaD+DTMcxP`bX4id>o%6N#bAgW8d@kZw0(k^FUcViC&XdS| zm3GY2s-E+up;yJLau8{sUK7@BWr0Z7m0IQrRL=13-sVls%DZQ4|HlHI)A?A%F9h-k zbiRf=^`0k@IVY5n@s)?sCVOxK!P?+aAU zaHQVuPso{vK%{x9<-5Q!C!hXzwZ5mrH<<)n!#xBtWioTEJ<>F{%gpnyUi;k1=Ulz| z%=K4SAmZ<~E6CAIztFv+Zr$D@Es^}2-oRYu(6gZ50638U*XO8{5ra-1lX0Nqo zg4T4EefE{>Uz32GyHuXL-s%eEyxLTKRiJKyV|I3RK92YQmMUhF|EfwpUkgN@pV|og zB=9=bj;J8;)9igWPoP3(R`)P(8diQ+V09KU?)R>-^9igL$atNp`MSXBT-opMf4@GZ zzGuiwT!Fr+yxw`-DXNUXcY*5(cSc5m@00h<3W1DSSzYsrIoRW#!0Iei*zcaSvk0sg zsBpdM;kCf}WZCgU;Pqr2At`YMr1QE!+-a(e!0!Ur)9s9m0>4k)zg7xl%*(2pSI)s+ z*92B&pyEE)q#aFQk3hw1PfyGxB*|AnsIEM&MK6da|97 zQQ&jp{@NjsF*CDk-Z2Mzo)efo`wr`$lXet=eF7b>Lp}Q6R@--leeNuWp8}sM{)#B@ zIVpeL6^J-jH4*qKa5vk|%O&u2=KkIzkSj5BtKBmJyPgr4JNaI#pYe7Kfn5Tae;4SRz|YQq7xAxX0-uxe*SiAIW~&|oUj^RHxASude4V+!_X*@k%iJpW>D&30 zz}(6AT>X`~-wEsz=y^r<>v~td?E77yU-~{f{;uJhC<32T^4DmAC^J?Efv*Cib8fwu z0$*qD@BIQXQ!=-z{pu=sCNOvUy;pzc?l%Iv1bSbUeY)S3FZ=#1&?kAHo&GH0+Xw=m zQ}Wj=fe3R}1A(ssv$Ag8SOQ;X?(Y}^u@W-3ni%4$cqA})`d0rCcx3N;0=ooUnLPw{ z<;%X01$rd!uPz@e_+__1ryTC?b@#08PvEP-?(4z+YXrW|+~08o)=YozE^)L~@{_>c zboAQ&Cx71&*d@^G>g(I>u6)_|wLs-mzV6-;I|V9dFKTZ)=WTBSUj?FGBkCF}@O9?? zjwLX5(y>;HC9RsT0pV^>RMJr(-W8#u83G-%t!DxReioQI_L*!;1PELaczn0;%WQ!wb8;+! zSpu_jZv7Ynv+`qI0tE63#JD^4sv>V%R9;P>*Ok||+iJPh4*>%23-rAs?(6*hsab)* z7=e9Pft^2oUHYF#ir@haO4NWhMbvWe)B9%Z)bAn>Qa ztjt?CmcXBL_iq9O2>dA!>#kL!nm>*Gn?MwS8dsPuUI|3Wn>q*(h%NByUgG!j0H^38!{>Q{>Y31A z9f9+yc4TIOI*HW@0Rk%pGT--B*S>Nt_9F1E!0PM6etQJo&582~5a=tg=dP7&*S_h{ zc~*g3SD0$=3S`Zvx(N{2FL3w1)9JkZQ&IteYXY6F&(6JGONFBe5FqfoKE2z*Z5UjzscxG(U| zeW=>G_Z6Krp>H3+zvt3J4IWBJlHFrq{PsW~q<90==%-zTNgsxXuX>7%kBE zPWsvTXjAJESS|3GW`9){Se+~T5g<@opz{6lRd2N?tGg}&uX6798UkJNtWN?2)(O^4txtSDI-KY9(-1;QE!|jEn+j^WZoF1PI&}$arsjRr6g7=Mji5@GAR$uOSdU zhw39hAiqG3yXEg)dMYd(!=@s=&|F_bmYe1jY(fx`&RHu^NF`0+p^+y*w6( zl{(cBAW&7{@m(n2FI8uxF9II|`L0M6-w}{@0t5&UAV7e?A#mqi!a4Z_j(PheK!5-N z0t5);dy`S|9RW!vK!5-N0t5&g0(ageoRd%Bn72;?1PBlyK!8BLHyIV*5s-8O1PBly zK!Cs@aOYjZIr#*RdHW!6`y@bs009C72;_T{QSluCNhd&n z009C72pj@;-X)xqPvDrhPXYu85FkK+K)yE_72gq%bOHnj5FkK+z#(wwUBWr}1de(8 zBtU=w0RjXF z0t5&UAV7e?A#mqi!a4Z_j(PheK!5-N0t5);dy`S|9RW!vK!5-N0t5&g0(ageoRd%B zn72;?1PBlyK!8BLHyIV*5s-8O1PBlyK!Cs@aOYjZIr#*RdHW!6`y@bs009C72;_T{QSluCNhd&n009C72pj@;-X)xqPvDrhPXYu85FkK+K)yE_ z72gq%bOHnj5FkK+z#(wwUBWr}1de(8BtU=w0RjXF0t5&UAV7e?A#mqi!a4Z_j(PheK!5-N0t5); zdy`S|9RW!vK!5-N0t5&g0(ageoRd%Bn72;?1PBlyK!8BLHyIV*5s-8O1PBlyK!Cs@ zaOYjZIr#*RdHW!6`y@bs009C72;_T{QSluCNhd&n009C7 z2pj@;-X)xqPvDrhPXYu85FkK+K)yE_72gq%bOHnj5FkK+z#(wwUBWpz1&*2fBtU=w z0Rja23gmo?Q8fVq1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs U0RjXF5FkK+009C72t*P1f6jE^6aWAK diff --git a/test/shapes/p09_shape8.bmp b/test/shapes/p09_shape8.bmp deleted file mode 100644 index 4d1cd014ec313dcbbef5ce5655084c4456b8ab69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 diff --git a/test/shapes/p10_shape1.bmp b/test/shapes/p10_shape1.bmp deleted file mode 100644 index 42b5a7f347490c85a373aaaf4dd23275b6eccd9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51346 zcmeI$u}#B306V-0>nc9D~jm;Dd*Vv=Zo{o z+}v(nhV9tQ$NPD%&vtVh#$A~E<;8B!Y51JOFzsL4i?5a2a`$-kwmq2-tJUiJ{Q32I zzUgQDzGwgGG#xD2f82Wl2_%p}0tqCLKmrLQkU#@BF0A z=9`!Mtq=D<_G(=2_i)++zAyJX_JNk`m;3d6OCi3_kA1A>XF}ZbV;`&enGpB<*vD#q zCd55I_OY6u331PleXQnZLfrFXAFKJ95cmAp$7+5i#63Uuv6`O=anFx^tmbDz-1B1} ztNED__x#w$YJMifJwNubnx6@A&yRhq=4V3O^J5>Y`I!*+{Mg58ekQ~{KlZVjp9yi# zkA1A>XF}ZbV;`&enGpB<*vD#qCd55I_OY6u331PleXQnZLfrFXAFKJ95cmAp$7+5i z#63Uuv6`O=anFx^tmbDz-1B1}tNED__x#w$YJMifJwNubnx6@A&yRhq=4V3O^J5>Y z`I!*+{Mg58ekQ~{KlZVjp9yi#kA1A>XF}ZbV;`&enGpB<*vD#qCd55I_OY6u331Pl zeXQnZLfrFXAFKJ95cmAp$7+5i#63Uuv6`O=anFx^tmbDz-1B1}tNED__x#w$YJMif zJwNubnx6@A&yRhq=4V3O^J5>Y`I!*+{Mg58ekQ~{KlZVjp9yi#kA1A>XF}ZbV;`&e znGpB<*vD#qCd55I_OY6u331PleXQnZLfrFXAFKJ95cmAp$7+5i#63Uuv6`O=anFx^ ztmbDz-1B1}tNED__x#w$YJMifJwNubnx6@A&yRhq=4V3O^J5>Y`I!*+{Mg58ekR0y u>9^+PR>$Rj(~0As_p{$qPauH=5=bC{1QJLffdmprAb|uDNFaef1U>+I;GfL^ diff --git a/test/shapes/p10_shape24.bmp b/test/shapes/p10_shape24.bmp deleted file mode 100644 index bc1faf4e7b4605ee27a376685706859529eda898..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 zcmeI!F^(A44Fu42T31e$Dz(qy-d)b1Q=~}k6QxX%E2Rrye|cakmEgu0AoCX54{BEu z#YaM;AAbDlUw{7i{PENK{QLX-$Di`~&BtFpfBEC%egC^}KYsnp7e2o~|Muh8pM3M> z8y|oB=U@N(+fP4z`gHZb|MUI(Obpt1#!b^Yv z0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4 z@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7 z@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w z*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMg zfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw z0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSL zxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBly zup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l z-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dP zdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1# z!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&- z5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ4 z1zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E% zl3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDM zC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!| zo1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap z!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld< z1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@ zmiv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y z0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE> zbpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)> zQlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa= zDZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji z2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X; zD}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)> zQlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkB zFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB! zBk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`C zaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46y zGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!t zOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd z0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWf zXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80 zS~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y} zU*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$ zFM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-Qf zwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo z2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJ zAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pk zI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL z11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U z?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uI zC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh z5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP) zgO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv z0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4 z@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7 z@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w z*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMg zfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw z0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSL zxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBly zup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l z-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dP zdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1# z!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&- z5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ4 z1zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E% zl3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDM zC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!| zo1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap z!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld< z1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@ zmiv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y z0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE> zbpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)> zQlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa= zDZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji z2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X; zD}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)> zQlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkB zFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB! zBk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`C zaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46y zGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!t zOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd z0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWf zXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80 zS~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y} zU*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$ zFM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-Qf zwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo z2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJ zAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pk zI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL z11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U z?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uI zC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh z5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP) zgO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv z0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4 z@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7 z@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w z*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMg zfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw z0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSL zxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBly zup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l z-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dP zdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1# z!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&- z5FoH4@S-QfwlZkBFZt!|o1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ4 z1zI<7@+rIo2oNB!Bk-ap!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E% zl3(7w*$EIJAO%`CaPld<1PBlyup{uIC&RWfXt^)>QlNDM zC!fMgfB*pkI|46yGHfe@miv-l-oDuh5Fj80S~qa=DZB&-5FoH4@S-QfwlZkBFZt!| zo1Fjw0#cxL11F!tOMn0Y0y_dPdNOP)gO>Y}U*5ji2@oJ41zI<7@+rIo2oNB!Bk-ap z!?rSLxi9(U?VFtd0RmE>bpt1#!b^Yv0RlS$FM2X;D}$E%l3(7w*$EIJAO%`CaPld< z1PBlyup{uIC&RWfXt^)>QlNDMC!fMgfB*pkI|46yGHfe@ zmiv-l-oDuh5Fj80S~qb2&1?My^d&%m0D+c(2x!T_zK5?MK!5;&S%G2<&9Ju|MmIfC^x3|NhTkzfAo4YtH=oU)=~0AV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RrDA z@Yyc{1PBlyK;VZK_`b&As}zAB`iI@}1PBlyK!Ctkfs|fjkM9$hKla}@;VS|J2oNAZ zAVEL}NJ#N*ht9sH`|cz_fB*pk1o8#u|NGxsgsH0n{nLH*{oYN0009C72)qmE0q=H~ zCui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N z0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8H zeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@ z1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=- z?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBU zFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+o zko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pk zDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67 zcP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLm zjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%; z-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!Cuz zfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF z0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8n zddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK z(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5 zq$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQd zK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrq zU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}3 z0t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Msw zc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc; z009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA? zWxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC z0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&U zAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^ zUl)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs z0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd z=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBU zFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@ z0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+ zz`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5 zcP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~ z)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQc zUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5u zpa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG> z5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7# zddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25 znF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE z0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQd zK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1 zyxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99 zp8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7 zlDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk z2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+% z2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$ zfB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5= zd2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&U zAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH z+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*r zK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT z=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk z`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj z0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBly zkRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcL zeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~ z)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq z_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W z3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7= z5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dE zrMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k) z1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf z0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt z+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVo zAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|> zZg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk z2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^S zUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b z5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq z+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs z0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$ z^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN z5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3b zXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk z`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1Cd zFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C7 z2)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^ zeRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5* zQ2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH z`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci z0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5 zCjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b; zp|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X? zo0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e z9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf z0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7 z%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv z`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GP zB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2# zfB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WH zd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD z1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC` zmnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs z0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhC zynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO9 z0t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)h zX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBly zkS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c z7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K= z-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5* zP>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ- za^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e? zyMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs> zCjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^f zUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3w zPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U z2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO> zAV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL z%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ z2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7 z?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2# zfB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O z+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2 z009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8& zyFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF z5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{ zUtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b z009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc` za^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBly zkS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX z_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJa zfWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr z-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E| z0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8 zxnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBU zBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<` zcM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f z7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Q zy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf z=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO> zAV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZ zx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xn zy`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*x zN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y? z1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;P zwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH} zcX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF z5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ z#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAx zAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9 zW9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N z0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8L zbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&U zAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n- z?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E| z0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRba zAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXF zybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm z{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3 z^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0Rkxk zdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF z0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4 zx7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G* znIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR z@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y? z1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc z%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c z2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NR zx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc; z009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV z@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^} z2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~ zCui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N z0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8H zeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@ z1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=- z?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBU zFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+o zko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pk zDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67 zcP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLm zjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%; z-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!Cuz zfFAH}cX@K=-ghSf0t5&UAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF z0RjXF5Fn5*P>rFt+_eW^Ul)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8n zddtgQ#@qJ-a^IVoAV7cs0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK z(EAAxAV7e?yMP|>Zg+Wd=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5 zq$Ii9W9Hs>CjkNk2oNBUFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQd zK!5-N0{H^fUwX^SUB=t@0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrq zU3>8Lbpg3wPtf}b5FkK+z`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}3 z0t5&UAdn)U2c#sq+hgY5cP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Msw zc9$n-?tOO>AV7cs0Rs5~)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc; z009E|0@WCL%Uyf$^>qQcUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA? zWxRbaAosnQ2?7KN5Fn5upa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC z0RjXFybI_7?{=3bXYPG>5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7#ddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX@K=-ghSf0t5&U zAdoLm{iV0O+-1CdFCh25nF#^}2oNBUBA^GPB)QvT=H7QF0RjXF5Fn5*P>rFt+_eW^ zUl)-3^#r}2009C72)qmE0q=H~Cui<`cM>2#fB*pk`2y8nddtgQ#@qJ-a^IVoAV7cs z0RkxkdO%8&yFF&^eRmQdK!5-N0{H^f7<$WHd+_yj0l8mK(EAAxAV7e?yMP|>Zg+Wd z=H7QF0RjXF5Fn5*Q2nL1yxe8HeJ>#Qy_pFD1PBlykRqT5q$Ii9W9Hs>CjkNk2oNBU zFHnu4x7@V{UtbrH`}G99p8x>@1PHtf=mGC`mnUcLeRmQdK!5-N0{H^fUwX^SUB=t@ z0&?G*nIJ%b009Ci0(wA7lDj=-?tOO>AV7cs0Rs5~)fjrqU3>8Lbpg3wPtf}b5FkK+ zz`KAR@NRc`a^~K5CjkNk2oNBUFHrrZx4hhCynQbq_q~}30t5&UAdn)U2c#sq+hgY5 zcP9Y?1PBlykS|b;p|{+%2VY+oko)xny`KO90t5)W3+Mswc9$n-?tOO>AV7cs0Rs5~ z)n9tc%U#CX_X2X?o0%X$fB*pkDFS*xN|L)hX6}7=5+Fc;009E|0@WCL%Uyf$^>qQc zUr*5c2@oJafWW(e9`J5=d2;67cP9Y?1PBlykS|dErMJA?WxRbaAosnQ2?7KN5Fn5u zpa-NRx!Ysr-ghSf0t5&UAdoLmjiI;PwFh5c7m)k)1ihaC0RjXFybI_7?{=3bXYPG> z5+Fc;009E|0@Yu7%gbHH+xG%;-2#fB*pk`2y7# zddppV@bz^8xnEDv`w0*rK!CuzfFAH}cX_gNuk-RA-cEo30RjXF>;!axombM=GkbRe z1PBlyK;Xv|Na{9r`E7w8^QYX_1PBlyK!Cs$`0d8v3jzcP5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+z&C;a E0oy;U=l}o! diff --git a/test/shapes/p10_shape8.bmp b/test/shapes/p10_shape8.bmp deleted file mode 100644 index 64fb5c372067554fc0d3d7f15fce0d17397a2591..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 b1-8Hz*aBN%3v7Wcum!fj7T5y+&jtPmU}+S4 diff --git a/test/shapes/p11_shape32alpha.bmp b/test/shapes/p11_shape32alpha.bmp deleted file mode 100644 index 406d20a815ee196b67353bc40614484a8c6d76c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 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

Py 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�v7m{0-x`~xBu(?4cXq_;{sc=d&hRt+u7b}GTCn2JGPVF&h}1|$#&!3v7PjG zws)FLwj1}3?WDJ}z0+j!+~BR$0v51<1%9c(|NSOBzkmDtZNs-a&)$1n;OX=Bm+ho) zcbg#^?cOw}$z<|7XFF;4ra4U}lixYpNxL`AX)>Ao&e=}dy=hL9 z$>ev=cGB)mbDB&hzjL;ec5j-~WHR}kvn{oN1uS3z3)BMr`|+9UFXo9{V)KQ%A(vRS z=80Tl^M$%0msqvtiCkjyg}Nb^ShePfTw?Qux*?ZXwdRRjV)KQ%A(vRS=80Tl^M$%0 zmsqu&EMNf(Sik~VfW99emcL{&`JJCX>nUob9CDo8~l`On&EVC+*%er^#gU zJ7+s-_og{bCX?Se+ey1O&1o{3{La}<+P!H`Q(^%NSik}nI0dHf$A_O^?XT^|z1y#x z?d@#uW9+Z(#=YCGobByw?_=z*?Z&;^ubl1eZ0}?2ukFUY+pnDM?QHL3?62*{z1y#x z?d@#uW9+Z(#=YCGobByw?_>C~fCVgI0SnXu+xO$MtG}M7w>!^%zEC&M@8AAbYo6Zj zJp1`V-8{d4`&+Gfdb{)N=L>c7{Qm84wdU#V&a0v52qUxByp$7la-t8Z?3Z?0)SE-)9X8@7{HYo5|% zGGC}0wv$$Cp3-D8UvOgq3s}GczgmF57)ihFlO_}IaRIKClO(w_KUu-Acubgd~OpdX?*iO1%IomXu z9Akg6opigBzm{0Q0v51<1x|tO8*=ZCvA=F-d+%4ycDr%!j>$|CcksGlXh>K(_}(E|59oJ3s}Gc7N`a28$zsF^F%JO`9j^0ORQS+L@u%U zLfw!{tXlI#F0uJS-H=PHTJuCMvH3#XkV~vu^F%JO`9j^0ORQS+L@u%ULfv$?Zk;S( z0Sj2b0&{`>4VmWoLfuS$=hd30?oIQ2p>8I>^J>jg_ojKiP&bp`d9~)Ld(%8$sGG^} zyjt_ry=k5=)Xn5~UafiR-Zak_>Sppguhu->-ux>!7O;Q?EbuD@KHr6J|JVB)vc0{> z1-54Qj_stkv%S+~vfa3MY$v^)?VTo*?Z&-hJL&Cg?=+cgH|`zVNpEL+r^)2G!CR>X zEMNf({8EAc`%QR$|MvIWhHrPCz4y4l)939k+ezQPI^1rJ546rjeEy-(%aeIX)@Vv+&i|D-p=+;Q(^%NSik}n-~#=7!oNz|y=hL9 z$>ev=cGB)mbDB&hzjL;ec5j-~WHR}kvz@ei)0`%g$?u%)q}`k5G?`3(=WHkK-ZZDl zWb!*_TWSFdSik}ns0I4><1^P^%oDl9<_mR0F0pFO6S>6Z3w1*-v1-i|xy0rRbwe(( zYRwb5#O4ciLoTsu%@euA<_mR0F0pFO6S>6Z3w1*-v1&P4zycPqfCaJueLp@df5~L> zJ7+s-_og{bCX?Se+ey1O&1o{3{La}<+P!H`lgZ?F&UVu7O>>$|CcksGlXh>K(_}LF zowJ>^d()hz!~zzufCVgY3QXUR4?n-!U)zm)w_iEi+u7d7*k9X?d$(UX+uPaR$Jk%n zjeECWIosRW-pANq+l_m-Upd>`+1|(4U)zm)w_iEi+u7d7*k9X?d$(UX+uPaR$M9tV z3s}Gc7N`Zb@5g6Xe?3occb@%xp>Cevzx}P&JiXm{_Vb0hd4B))w_5Y`cIVm87wYEu z{oCJa&C}bRXFp%4o9Fj$f2%c5Z+D*ke4%ch-@pB>);zu4dG_;#x_N&8_P1J27O;Q? zEMS4Z0&m}s&;Hw1-`w)vT+@DBU@lfSY$vVOJf+EGzEC%8C#}{zrO9Nz;Kl+Luz&@A zwE%xHl78DKO(x#s0$eL8+ey=8k|qtvYj+dCTTKZJ1N^q(`1q+6SkAG zoit4*X)<9uDcecYWRj-D0v51<1%AB%e=(AN+bK;Z-s1vXD=FJa(`1q+6SkAGoit4* zX)<9uDcecYWRfNmwv)1*G|ePwNi1Li3s}Gc|Ni9{gUPq8*iPy_E|8^|u$`3cq-ioq zlL^~N*-n}!lQfyIos{jQX);NZ3EN58J~R!91uS3z3s~T2@W5EwO+FEMNf(oC4c7 zpS!P?XERf zbM|?%(P;Fotyjmowr7*s|G@nYI7}FR?RTgU_Kp8O=n#v4c7=VCfB5b9hhO{?cBw1u z+O;bj;t+=jhdksV!=Vm!sBq{*A37Z7Foy|;J?vq_KKtww4tKc2g~K2I@ZktYI6^q$ z5sw&-bfhDNBOm$5;V4HrN;v9Kj~b44w4;TiAN}az7{@q9IOZ{r8IEocYXW4re*bS;AS*de(5Zvz;xR{p@EC=QzhX!a2`*&amHp`-O9z>s;a7 z=RS8h&w0)h&U@bThVz~8eBu1(KYzHu1uhUSc)<&X3ti|!;ldZbaJa}tE)w?NfB$gN zi(WJwaKHiKVi&tuxcJ2{9xic-ON2{a@{-|Fm%3EA^rbHyE_0d7gv(y`vf;o34-A*P z+~vaMFMs)Pg)3YkT=9xm3|G3+mBN*;eC2SJt6U{q^{Q75SG(HP!qu;S^>B@ATq9ib zn%4~1y4JPAwXc2caGmR1CtUZs*A3UZ-u1%uuYdh;gB#o+-0+4s3^%&bjlzv@eB*GF zo7^Pa^rklrH@n%*!p(1f^Kgq>+#=lambVPIy49`1t#5tnaGTrQCfxS6w+*+u-R;8d zZ-4u6hdbOM-0_Zg40pQIox+{(eCKeNyWAz*^{#gfce~r&!rkwF_i&GU+#}rcp7#v* zy4StJz3+YRaG(3!C*1eG_YL>E-~Gbs}XL|N7U5 zH@x8u;f-&6V|de>-W1;a<~N78yyY$7t#5s6c-!0F7T*5$w}*GU;~n9h?|f%?*Sp>o z-u>=(hxfeaJ>k9YeQ$W*``#Dc|Ni%f4}9PQ;e#LiVEE97J`_Iu;SYz8eB>kHqaXcf z_}Irj7C!#*kB3iu;uGPMpZsL_)TcfbKKH{rLx{cZT&?|v74|NGyEKm6ej;g5g( zWBAjb{uKWF=Rb$P{N*p58i9E2$(ONCGP3Ka~R;q6FZ`E2crOjEbLx+(H0}$u;KXU<0 zLn}357W02DW=4ogZvLXdT+OYK0HZ7mpY9Y2-(t>;OB`m$CC<8d*qQ6E6q`w6cmNpt>r3vhCWfH#2RqF>Y2|^l@gTI~lh+$wEZ(_ByDHYcJN>aM6hx z8L{bMu9qr(53drK*e;oEI+rHCK)(Sm6^- z-L}QcIFDRV)!vrqiuZew1mG73EWIjIR1V{id0%VS8mKxO#>M@f?A=QNDi}pI%Q(!w zv&9qd)JKY%u z*2Y#PEV4u(yF4!(u@7 zZLHWV^1!R^iBd~pTx6v!W7K8m#V-|8ld;j>FzSF2qmQ8Gh=reV9$BHP7FJRg2G(uE zs+AtY8Mj!vqlGEkuFdC%qFY)EfM~>v*Te6aRUtNYGOk*Lv*x0cH!!k}FftDz6P-<0 zVaZCCVd;|=So$_Q4m0g;9X!7CQw3r&%D(K1j4QVx3M)!&%v3bFq5O36-3?ioHJI$h zY?LYEA{&2PkWpt#_2&>O7tPMM5}jp-;gSH;<01Rzb&sVdr_n-U9;u+KxNOA#|9&*V zIBoj2g8|x*q16-{Hq6r5)Ua~@2>6WKR4%DT zS{_@YDI!%H=Ek_;u`H`=&RqEYAE|m;%d$&og^L19%^4Saz#ijL{7LvTO)RZ>F(r08 zFWqi34->5|51I}b6x(ExG8$m>vsjUnxiu0X@6S?A+;WN86%RXe{k5+Vls@LCu44A| z4c$|I&4(3cYGthx5jOt32P|35oQoFIN_R5u4o*{fS<6qvl@9pol(o-4Zy=#q+Ffyj z_+h3t)_Pc~HcYT7HpaEZw&7e*RTFEmTBRK;t9k|J^@<5HF75YRa6wfqtklXdu%D|1 zdB&BwN#8Dgi~|h=Yf%ymEG4Spnh|6Qx>B*uHO_)Z}r#uZzv{<6~% zr6lSoi=#ypa`wB#Gm0r~wO zwJ1}8hTmF6Di)!;I8CUx!4vO<*i>z(NK=$^jzem8R)m@#u9$;(mi91=!!|@=&6%Gj zVQIKq)y2L>*H-0|j6r=%;{d?Y+EVyYg`h5ytbNk%yv89fJB#;XiTy}`+rY(6b@j+2 z`0;J}{4=OkVEC#yaPa=Dd@Rco*yp{cq2iW?ZgxAs*J#GYuIPq~metY$|{zChgCgbWF7;9opf#8>RYLA zm?*~H*`#-x9OLG&7*Ks1D>jQf@HX6crPj!NrRE#a|70>lS=x^-@#exYE`bm#bLWSu zT3BUA7=$I;2A}s`Su+5=#rXn)rM0LC8gX+~LzFsI##M`T)?9S*2FAD}XiLCsnqp(z z#CAni>wNfOrrj-m|3_S0Suubwo~6B=+c;$2+tSvCfz>4^KWn*Y9iz%_&dFghOdDw_; zY1Filrr0Knl+l2ipT&xt%&n0C9o~1vlkD`Vb=&fdiYp#==KAA3+fWewg?gx~sFoKW zvb3_+LB+;(x^oPLV_aLj8_GqCX{9?Ecj9tY-d+clapeMr*k;2;Cu(HGCkaMwKURb) z<7!quxS*;g)*!i{yYk{OsK7q#vj%~Xa+qQiBYpxto`W<}YS-<)vl0Q3l zR;XZaOEk3VlYuxVIf6vcdUd2fT0W*^`^dP62gTfUewb-ri{JlIFD|9OW^uWT(}a2( zJSk3n>?W-o6={l6TiXodFl9R{LgoFQXsvu?ZUGlN)fFSVTh*0_L|I6f`3@aFaRC%d zOBZ@oI4Kim#yD?}YSh-w+P3>H=9{#Z+KZj)>XD__RaXY;M69~jc1nrl&u%hPRL9to zBM9&JhJ}M-IOl>ytu5(S6YzCuF5BG}tQ9g>>+p1Tv2dG}e(I=1IdRrbZ zHnZ&Fy;uS<>h3r1tX9r%&v*CUveWTc%spRBZK%}(7Sf7- zqb{*AtIk%!lA2o{*ynwhp0Gp)`e@zaeEIz!Nex(WBXPvH=-Yb?K;F=57Ff1>e(t-b zJK!MlZOeC2bVJn@LpKGeP6QT3Qm2NMdmR*$ahuAe+;VA^jEts;RBf0P$e6hwca9_lKp<;90At*muWvGJeo z2t(l**B0-Fa?xU1=}yM2PELR&Z?A*OxN-r{ke?RN$cRr8jNE>#2vx?_tbA}mRZXlx za{T^}T3E7~KtaUgF%DT;SgGY6r^E-QUU! zYzywYyxiOE={)*tt{hR;HGJZ!JD_+O=aCDl+S?Lct-A4#b1F9AdUd2fT0W}fNzFem z;z2PtogZe}*W&ko)Qd~$uUTB~;xwV&MvoI#vzXcvkTK(ij{xNn(t!(xu(M*(bHJus-HrH!ouBN#9<#tlN1Ummz12Rmy| z-zV|UOSKf4>K0}bx!9?$9(e>SzD=Kh2DJ(dU+Ff4_jYmJp%BiwAW>_p+3n;epe%?6 zV#Y<0)Tv?R&+^Y}KUK`QXu0eS9xkRHx1tWzsJXaw70x4W_!z6`4Vbi*6`^L*n1Ohz zM-t=Q+ykQ0R#veDJgn*gBl8#-?4)ZCJ<#}9-Pv|B>76FWxS_P0z8H+=v7YQc!tUmP;(ZRyg)!0M_FCY!TbIln#s^g;DITP)_DFQzusY5@yr z6nK;3Fssg1!jhU>9{9hzPtp^NVAe+$|GWft6S?sFKT=h&;zmV^Z_&5+7=XN?)hw`V z_jtb-@t`!E|r-rqQ#JEl6Qf|4lN=8OgM5;DS zig8{JEByYCq-xc8*(LN?SO_1FGj+zrkiF_EAA9O#T(uo2xfImZZDVLHiz%_&dFghO zdDw_;Y1Filrr0Knl+l2ipT&xt%&n0Cd2g3$;+9Lyu6Wp)>yP(rLqYTx>Y=WpT3&p} z(#l!~6&wF~-*+*OYH?{Xt#l{jPF$|a+tsASDd*1WXSF3rRO}`wx;izi6h7n3IBQ{y zjHm~U+!TkZh;cP5SvB+Kg4vr`gXH-AAGNS##i1bL@fe3JEv(e?FtDGi1s~n1M?Fw| z18ecf#lVZ#!|$b!#q`VtmF{ol1-9aEbMPK8Rb?bVFI@U-t{hRY!Y7`(1B#b%9=V{Z zy)Dtz-2XnC>=2kotSCfO9GvvZPfK3J;V1f9{Qi%~yC{;9a*xG4l7>bLeH9lw)zu@9CLQ0V&p(4&1%|JR z1INmHBF0UKQl9AZx_>lO+|tm^ZU?9u&A2E)H(a!=_683j6P-=fh8i^&;mqQO$6RqH zLvmJDgqqhiW+0yW)GA)a&7BJ>Xk`^kz{9E@FfxyUf$R|=tT7ke>LaMPRj=TkOnRru zF>a{kxagb3rMKRe$BTTjYt=nbqR6<&N?XQASo7kSimAygtD@iQ0V75mL5*+G=bHhl zZ(${MVPM@htXk=VDCJ4bxOxH4hYN~sX)U@N8gaAR0jf@wan&N6H5Z+{fsu8Dk#}}- z8Xifa%JOGLmZgtzLZ#m7Yz`MxtGmVT|A^wqN&tNE)PfPOzc^&n+tQ_pfz?$VOg3k= za(;XM>4WNbwph$PUrcSN)dCjMDDWo5VOE{3ge5h%Jn+2kvGn95<<&jbO#M=-Yb?K;F=57Ff1>yx)s>P#igeMBjAh9Tv@|^IH@hiCJ1r0jd)-E1;@V z!`ek++@^9Vw_I8!BcmxIRU0P7I4_44e*Z^OwQ9WV5_&8wgpbFWI^$xgSP*+hcFFs^xWvzpXjsLvwyO>9{xU`s7x|4AyE?4F4YSQAAb7%Fl z+7cuxb`um`of=jOpK)fKwJ=6T)B{FtibGYzxSEx$nt5}<>`kmea{T^}T3E8;P!REW zj6;?dR%&?|*w591kM7i?9;m*7wRq%W;Kl3V_tM8=dgg*k_qXx_Tk*F!cn_GWG7_K{ zF8wuEjwo2+6Hna%#mhL4Tu{~Cmgs8kf1gcu2+Si^6e21PPI~31B`@Oe6MZdy|3~Cq z6iMl~isU&?6Yp*GIAJx5sntZrj2k`zlvdt>3x=?>V$pNPn1gt#R?fJoPB~kCsJNlU zuG6{e6#Fh+)0Iyu0#WRxI1FHE?WN=*MPwyNYhTAlO*I#eyQl~1V`q7}$6_8yL!*Vh zii@4<>XAp2j&IZFpFynx!&k+DW92;&<0eEYPxN`+KN>1-Y3OFR15}M>Toj-iE?QQ5 zgNKlb&ZcTZjhc&aW^uz~t~iq+IV&qd&FdO75Knz-6))rF&IJ{;vWg|(VO0+pna99D z_6QKxn2T=p5!BnNSMW|Iz0>3vH`H=m^v&YZTW`zbMLyZJ>YgZ3WL#vWEn_6CdGSld z)MS=b(eL$u5u=Tu#<%G6%>dQ6u#&nkux=Yxt@J^Z@}y>5y@2P#1x2^C7TpbvxY_Lh zRj10hY7x$wi%#Ca$U4HvJ3BcIk0eoL`LiO+(#JTVQg3xOhYPCJ-QxFuL~&##0KRx? z!HCyi95U){>C(i&>Z%SVo3mOuzdir-LG?RZEasjsrZ&`S0Sjpqc$4BVtIk%!lA2o{ zcwYBddUBHT>LZhRq=K&E!teh`bg<$^u;N?v?L7t{Z)i0OEZaTa??pT)jvPUvZ@Tji zi)PdLEsBoBEUl&h)rpxEP}Qkn?IJO5Q@NB|F0GQ0(G-!Y4U_T{C+$g_SaqK%UgRsD z`miG2`Ni@3Ka#3d<7Jo7V__kD(aWBSQ^gcx6yuJC1qLzY0Sr)z|(qWQ? zZ!u?|aaxZx`OY_PsHM(+1B?;qVS@S#6I53*d-~?d3RShT)`>`-TywwgYUh!t@4{8y z$|`t@)5qD$j61wp?SD`t9l6=ryu@jiYV5QwUg9)yD5E0^gGIIp<=tU2O{ z0KflZgoKW+1ZIDd&9wfPJh}qsU^RjG1kMjv_{Fod2lNydbJ3jy5~tOlfNlZh#2cre znyh>hi8*?SLq@c^6YwIU|92kUNprw4fqC8A=`&M$v2GFmi#*0K=rJKMDrTI;7Whn9 zbI=BX;=nQ8VFpopxd%+Es{w*88v@O42eIj-T*YZtG|UP7zc=r*KN;)J(VRUe5DCQ2 z=IMSfrh~FdW|gmeflyjq36w?BXDeo)nsgb5JZTvbD4T$9vB~OE(7TE?rA``s3E(mH zU)-NX@lKzXH~r7+%bBCK2@G8U??{i>79TRFWkVn-4nr3k_36$Q!{;BT=^=Sq{Ru2O z9*WWIfR670`xEDgC4sCrxVG#Ng2ZWA5ZLbT|3KfjV9zm&zhwXz%}20q`258oBU;@F zY@66}zHvv}9W@7R5l96|2aNn~d?pp5)F)KoTLgNEYeAu}AAymu;?UdX>qna-jR-Ww zhAWrp4mb$aN!$nu9sLM21*lHUqY#@qHLTcA%_Q#NV#=Bi4FdvA5vhtWA#ZWWjFu?@ z)mqUKqS*4o6;l&Wd3v0291^2tOF*?&3oti4%*|0;TwKVDmK}j~!0`5HEyXptXzZAC zC+z@1;*RhNVv;OW(}mC_)s^$jS|G#{V7fiu=9A63JxiI3wH~c8Nf%i*1*0 z2?$?yB_LW`^?x#m6VgMlpSZ4=Iar$jzyG7nh2CBSw&ui}fwuDP#hT+Z2*f9FKDOdb z@zkea@w$kMIrI37N*Y?a1>A-!m&m!5uOWU8tqrgepp*%G+{Ga^T4n_5dog_`iTv~t z>@d!ZIR~x@jKmh--U43@fv@Zc$k8HpyXg)%h*Cabn|oYZU3R=V@)Ch&w}aR;+Etup zMZ=tcyTirW!<6o9&DnDTkwENh+jbAgx|ZULM=w{ocCMycu;4EChz`A@)|>`BnKMb6Pe82K|)+l^#*dUM!lJu6} z>OTv$dXof0!952FCIGKeRf=MpGQBHcZZ295SP2NDP1(%{^uV6V`(i)f*DAO>c_Hd_?*R2gea(yl^Y)V!AO8U?*WZ)bJ!&U zA~(3jQ+Gh|`inzGwE7beUAes9ljML^5A(&TAMeuYO@QD3(VIBOs|jq)i8rU+%C{!M zS6vCjhg3e^Vp#Dk?EyW-#awv&Ma2y**(KFtb9&_xIk)mP#LuC%0T!F`R*~GrvGf~2 zz~}v*K3hi%smON&=yM7Kfl-mR!^h%p3W4IlG2J-^QOdS(?)Qah#cc|G&ZFG{AvPt{ z?38nj)2wKi6L5F9SbLZ%&->>1If2;OavuW=@sy=<6o*7<84+09CFI$P`86NGs?<5f zVXm}{2-Ka;v)E+WC}@8FhoZ1EVpa0s#l2YcO`n3j#Mv?D$RIFuh5FT^Pi%(|S<k z#UUeF1_b#19|q7lq!MU$HgRfH{CLjq)eN$v)t!Jyt=jK77F*6YuBPs&Ibe%GDoAQ> z`CTvfdppge1%#X19r#F0hb9h1*lF$>$b4i zJ84+ygLw6aH!zOh|Dn|}Hk#s4wPAAJ;*c3FQv#~BvWw}R3#R5Q4vEpSC7@cX1(+Ki z=H@7lS1>56sg*wb_4a73PwBED1?QA6UedBAFcRQS@6To&XU&}B7X(HEjPE)371`6Y zA+T=0Cr>Vy_kcFYIcheG9oZY4&O$g zTB}8w`yHm}A}*c#krOQo0;%O;U`O|RxtW)_W!JE{C_aF(v=$}745UQ$V_7zQ&f+LS z6x7Jd4G;ZbB)}Qp?^TPd&cg^dhg~8da)Vnubq5r$zc^$>t3LtJmCO4*Ne)XFNYsknL1d5INbO#(nDci!i-xsFUWyhN%FA->VJBUrAUBziuG|UOOJ6y=VHIi*En&>o# z&Iy1(tViy5g|FQKy~H6=T1Etl9?N#+J8llROrR-1bt1YDn>sbD*iX$Qj^F>GRWvr5B2pD$a^B*Q87)%+s-Iu%gwyZExU%rMezZQ zrL`ytW*{Z1AIq}ga~4MtqM$}rZg}ViBLVun2Qe>Q6v) zP>*(|IwQ`$Eyi!&51Xs-O9Hn!dG1h#D|p6`@Kj}GF#fq4*AA^ zc>L`uw5h>lmsA(Y>6ObMO8qrOg#NAsv;o$idgL-;D(>Qt8Z9#dKJWMR*{aP>7FUt4 z8FLO?6BrdKzP$y$8UkP05hyn5)16}wrECl5eqWeYmmP18yhNbc?I1Rdb`_^t(J&|A z?rAIV^TT#l2V*<@71oOI%5TwiSWlVT!Tak5=*ei$g}VYzQQ6VQ4vmtn7d> zeExCT;E_D7{sb1i4aJaNS9L`>gRJY6GmPs`oFkS5vfkj@vO@?Gr)5Ea-~VAjons0C zecp;=>eB*ww)%@hMzp#UQ2kYVJHKMf`Njmhqvn7u0;wQLZ~0v>_j^0dqXmV&egxVb zP`sFjc*@S<#fL0ubtljikvxG{eG(F9I{$mX?zlPNGJ&Q5)rsgrZ0gjoVm~#LIDY?! zR?*mKibz$2$$5)IX0%KRsMg9ZrZ>@;nzJ}0M$49fYONMvZg`lRqc~o{psc1=`taA= zqqRPz%V`RnQ@(gf%bLJQfIGcEn{k{qbB^tn4*igbnZt^ zv@8gumWP2I-S6dQUgnlv!{Va&0LIcI|P`v))kP)r^1VmRZ@Ao7*U~L_o@Gh<11o-_Qy@_+Yn!wha zcyrpVd}|_n)s;YeNcp_qixefZrM>KsZ~TYH->yQN8ccRcb&;H2xeTJzUsFWr?@B-$ zVEw5_E)%BWE)J>DG9ysmi|I2-n}mqcVVoIr4qOu$i7mdp1-=>rU)d2THtN$Ia1fR!Hpn?@L7>_vL2Me)?0{b4G)o#*1fsq$bW?14=i)C88PPH#Fh~yH zMxa`&MVb2@rsyILf9V^E(dtYfwLFaJ=zcFZ^D?*W8WtDD2QZe_q9mAsl&F3z%ZATc z97TwN8d`?drsOdW+0im1 zkaRW{Th<$<c&$btY8+ diff --git a/test/shapes/p12_shape24.bmp b/test/shapes/p12_shape24.bmp deleted file mode 100644 index 582cf994e2e21ad58919600c73b5546748e5300a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 diff --git a/test/shapes/p12_shape8.bmp b/test/shapes/p12_shape8.bmp deleted file mode 100644 index 59377200bbd9ef2b390ac08cabebacbb7e55958b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 diff --git a/test/shapes/p13_shape24.bmp b/test/shapes/p13_shape24.bmp deleted file mode 100644 index 70215db590c7c6c1bac1becf7333d51d5649ff3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228938 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 z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk v1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlykRtGZT`e)# diff --git a/test/shapes/p13_shape8.bmp b/test/shapes/p13_shape8.bmp deleted file mode 100644 index 822b896e1c7026fc5a62d33f604282bd9edbf337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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

+MX> ziB+Af=yj!^b|Kx;FYM>=JC~9dK~><9%{j&yuv2g1WJvf(Os3 z{GWU~V9VOS+Sd)5ZZxg-6y^*tI%)7|PW`rD3KC7HHS!L~-ETS1jI@#BdO2%dewR3r zbmsEcU89a^P;p(OfgMYY^GIhp8n!VsjVi9YaAWJzI&QmEW@A1D7x~E-`(=3!q+FCsX6aRz zm|P%I$#t1`rRz-b${~k$z?F_tQ1e25>d|3Y&~h4HCWq-pQ@fmM{wcV?Prmps>+@#R zMLCYLHmr7B4gjO6)gH4#s~g3ZM9tq0mw|X{&A8`AGwb_J^x%_6QSPhx^Khz3 zN<2C0qMS!1uCvtAImaGNFLkBOD?SP@=TSYsg~>VeXnOM;Qu0KFz3}w*p|jW;z4hrX zb<{pNkN69}Znt)b7imFv{}m_XG+a8pi*d!%)UkcR2CT~#3_f?H4U|t~zo7f`VV}$n zE@K60lWWg+W(6ZOpk+ShA2u_la74^sm4Uq?RybU*IRGedTIYo+ z`7BrD-c;xN=whczRpZ{5^BK{9?MM+VMt1$&^K{PT6_VZ^DOpf4^Yy9>{sJm5zxj8m zz+D+H)mz+3_Nb)imIlnvoeBd2gD14u7N}MUxxg_}rTTAIRykMWdZ*|XFIPpY_(@U~ zS2a{a`=-d2AXfpcgfUVjS20l|dk){5rPvlLSMzGso1x#j*m3&R238otYos%lI8>Ko z8QYYuxx{gnF$>l!YD0%4(`G4NckV?+EqGC#(!S6fQW-2@2yW4OxSokXmu`8lScARG zw2~Y_LyMI4*aMVfeWcDN(6wLb1}yX64J^xas@nrJn;=(m5m>_gZ?KiaW#)bfOA5#% zrK%;qjTka&(PODwH(q=O*R{k_;{j8zbfd>BmwG(DDvk>zFxa`$Uxf#Ky1%_N)ov3f zCr)&S?j@IMHWeQtDG)ZLCJ6}mvee`nvY%Pj8r3KJ+K7x15bI^C)LN(B>blp{7nZp; z^-8T0Sr!4|E?0G4(bn}|)rK!^NxSKjC6@=HpVqB1exFeTb!34_MzI5G!JyUSG z^h%`*#!R_c1cV$V0<&mwzHkBw^rhTal5Q0g0#G<)3eF}_K_{$uNwZmTr33;i-tEe@ zF99(nrx6%_$x%56taL#GQbRb?z;UiP0$t84-S3Ju@8H0C#o23qr9SD-|5wZeD_hRc zd{}V4aSFe>vK_BfGrF>ELVg_SpWkp<(1O~4)W$$tI>-VAZ(p#jt zveoJ_?-`_(H^BbTT~wGan_)_cs{S3S%oUb`l%g34=nh!s9#^<_mlJotjBY6{tilE< zb!nvmrLcoQ=lNgGzg=6Zck&nS&h5ZNXbA+84amQpb*nI>nIIUR;Bh4c68|*#!>~qM z31XVgBhc9#?N8#y=85i{^EhyARRo&nwdoVBiZ6W^5tzPT%{se?0oPYZpjlR%Hq8p* z(s~|&!3V)+zX|LE;4|+$Bdq$`w7W(#&aIj({pS%#?h4H?+vF6(;gdm!CwQzFfyuqA z|Cx#*W;t^S)ZcHT6U-f!i`64Ax_xy&svcifw1`06J*V|Anw0C+B%mFvv#KUrR+WW7 zoqg9|dzLAQsV;$fJFvQ3S=np?qyK1geI1zk1=G=yW{=L53KFQJ1IzsHkW_Gs8os2I z11tPXg6X9-d_!6{Udn+rkgeOE77Sbi+YOf3g3V~h&dS!V>E#Hu~jlb6U*L$mAyNl8hxG%TaSCM z>lCdDCu zLO=w3u2Zxspdb(@AV>LByd#+q5CNa-6s-y<2*e4}uCxlYllfPz4rfE?vh@s4CdKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv>tqLdz z#0khzJ{9jsCIm#l=Q>5J0ty0g0&fM>lCdDCuLO=w3u2Zxspdb(@ zAV>LByd#+q5CNa-6s-y<2*e4}uCxlYll zfPz4rfE?vh@s4CdKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv>tqLdz#0khzJ{9jsCIm#l z=Q>5J0ty0g0&fM>lCdDCuLO=w3u2Zxspdb(@AV>LByd#+q5CNa- z6s-y<2*e4}uCxlYllfPz4rfE?vh@s4Cd zKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv>tqLdz#0khzJ{9jsCIm#l=Q>5J0ty0g0&fM>lCdDCuLO=w3u2Zxspdb(@AV>LByd#+q5CNa-6s-y<2*e4}uCxlYllfPz4rfE?vh@s4CdKm>fQQ?x3eAP^@Y zNBLB|Bbg8o0iWv>tqLdz#0khzJ{9jsCIm#l=Q>5J0ty0g0& zfM>lCdDCuLO=w3u2Zxspdb(@AV>LByd#+q5CNa-6s-y<2*e4}uCxlYllfPz4rfE?vh@s4CdKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv> ztqLdz#0khzJ{9jsCIm#l=Q>5J0ty0g0&fM>lCdDCuLO=w3u2Zxs zpdb(@AV>LByd#+q5CNa-6s-y<2*e4}uC zxlYllfPz4rfE?vh@s4CdKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv>tqLdz#0khzJ{9js zCIm#l=Q>5J0ty0g0&fM>lCdDCuLO=w3u2Zxspdb(@AV>LByd#+q z5CNa-6s-y<2*e4}uCxlYllfPz4rfE?vh z@s4CdKm>fQQ?x3eAP^@YNBLB|Bbg8o0iWv>tqLdz#0khzJ{9jsCIm#l=Q>5J0ty0g z0&fM>lCdDCuLO=w3u2Zxspdb(@AV>LByd#+q5CNa-6s-y<2*e4< zQ9c##NG1eCz~?$es{#rFaRPFbPsKZu2>}uCxlYllfPz4rfE?vh@s4CdKm>fQQ?x3e zAP^@YNBLB|Bbg8o0iWv>tqLdz#0khzJ{9jsCIm#l=Q>5J0ty0g0&fVXh$T~zy0&^oxgqS69EDQ2oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5Fl_t;4gptbKym=5FkK+009Em1m1@4`+u)3;w=IM2oNAZ;JCp1*!}e3 z_zIpRK!5-N0t8M8e44r6{_)g;9wtD5009C7o(p_CZGZam`BHu*K!5-N0tAi-{4{4@ zet%?f&k!I$fB*pkUj<$c*_RJ|UDG!N2oNAZfWS`zF9++X2Y%Yc_XG$KAV7e?yTGa0 zdg|f#EqowAfB*pk1U?F!nx_{Y{Tpp&^UhGUQcLD?m5Fqd-aBYSTz6GBE0RjXFbQTyKqa&|(W|unw z0t5)O6&M+vQ{HVWFL43{2oPv1FlB&_FVfau;sgi~Aka`?d~Qx$p`pK&2@oJaprOFT z(K&0WhEFE7t5&5t$MoK!8Abffa*t(VFFJAwdEJ2oUHXuxLsyUA9AAN009C7N($_miJNyRSt&^oAV7dX8G+4Xap!hr zY9tW?1PBnQB(QT7-m+JvV#$R70RjYS2;4Fd_itLGSh65MfB=CS0{iFSjXTyTnJfqp zAV46yz>TBuuC23|O#B205Fk)M;I1Kf@4f{JCj|lo2oT6CaPJVjare9>6FC6_1PEjo zxN-bN*gAXp#7}?#0RkBXB22*BW5`%IF%uv_fIw=2+h<>p08$rG@B|1DAdpKS$m|Oe zMy}e4mH+_)1d<8_nR;=;NLoi>6CglR&$!?b#w@F-2oNAZAhhxqH&fn)M(`v4*>!M2t*LbIMH&&9HGi$AV7csflvau23gW*LseWb z1PBlyaJxX#ah5Uc+iNWV0t5&U2qKViisg+tNYRBsfB*pk_X^}4WJx2xx7>CoK!5;& zAOcCJSn9BYlwKGF2oNA}mq6+%mN)pj%57f)1PBm_Adq){6^J}S1BihD0RjZ}3ltb( zsZ-craXa=DcsnLNbC)%NfC8x}SB>BUc7>2?2>f(FYGfx10;vUlI-b7YERed31RqD> zVBf|GSR@2`3LHGD9@`+$vjb&4E^w_m$M>}Y0Rq7-2H zUV&MM*E(|r?(GV@M;4gdY458=PFZXO>Iu9bJs+Y8)N3{wjSDR8((&-DK!Cszfu#r8 z;^P8Gis+d>0^=QZWQ9JSG9_?}z>#CVlfN~bZgq?`3`o=+}E4e zLiz-*3Cul)R=ZK)S_611oxqJf_S4qs3X z!X9LST^1=g-Y1I|ECVhP z0*eF|9lfy2eq{_B>UdyZll4tHf$=uDYlU>w0w%Cc;I317%f8Q5ZwYC`rq8qXYczok zEwOovXu{$laJ#_fGkD{62dQt2aOu_u)A!gd0!vFDWbs>IZAu`lK#+sEI*e2PtAn{~ z{Zj}YUMFx@1KhsvI#MeW$R%+5VcZbF<%(>GZT}XRHN1FDV1J3@?|4npTLdZz#6PEl z5e^n>aQB$651M%WCxMvdjv3WYhQ24zK_KR-oEz169p}aqWVLaY<|_oLW8N^D_mA|O z3*e3Iycs< z%+{?MMzRDR0%Z>G*+gbl|Lnf;pPwbuXPx*pjUQp3ZjmX0H-Y%4a$STIdthCVx2!x- zro&RV)TrL39a=?>1l|Se9pO_My*rG5IJIy5hsQzstPsDNDI)CCFft|Z5J+(%uSc$6uGpG1j5G=4 zI+#nNo!bmcL*2jlT%3BV?XO7H9qaXmj0k)yQ1u93&g)yHYhPZDA?AzM5SqLdvt${g zYSJ*$B#`lVu8w(PSF8?o^ZFBIiY>Ld5QVoZ))>+u@RLB{Q#_K|Ph1zi9|RYo7TFPa2oyM{t5cZLC#!?JYyBxW1s1u>zwo{VxWD!3X9CtGdr0MbMuN=+vY*gx@n7qcZQ5LnL|oMC1gX3v@WY({p@I`u6MeO|vX`1Sjb;SxQhT&ZMm(YyvL@DxK4vxjcd0 z8Ai;#p5SEsDJF80s4_N;mrHij9)4`K?c`FO$lb2|HJ}h?c-vfig#R ze8j!U$Jt5H81z%jV~5puaUxyJz#4B zdjx76RawLo*we!kcZtc^Fsg|)wG@G{0u7J0l(z|lZToKn+s0y}=K|kW$Cm_(2|Paq zekCw2kmbmXXKIBh0$CbHoCF31rkodx5O^Xm*rI!0e-gi+wh8R1wOt4V5!iOJY}8pG zNVoqKM(5JR$^ED5_?|#XfjCD`B;^HSo}lvVSrUxd9-<Ab)9@|(DB&%|*8zflvai4!dOI3+y=^@f|uuxTn^3ArMxe!!eg*E`gRyT&oetC{W`p%c82l$eF5Y zQX{XC%6gYT0f8C^Sr)Yg4j#1H2*t8HSW}M?C@fIyP)nz68I_*?No|~0D%qyU+*H{5NIgS;jqh*0D(9H4X;@#6ZlCW&cPH3 z0RrU(e!7T!PoSYd`SUJ80tA8xG`wJ?OyIddkh3Wa0t9LcJimziN}!=Y?ISNc0t6xm zG`w1+OyGz>gySg&0tBiG9Jz%&L!hld)q^iD0t9XsXnU_poWK!*+fS$f2oNYKaOD2+ z41wkXCC|R32oShep!vNjeF8@W?meV-CqST{z>%BBGXw}c1nM1s84)0Gqkwyr0D5qAb0`^Z-7AwVFnz>1s4ngj?u1oB=BA}2s#gMfRI0D*M^ z8;-Co2oT69uhwiUbI36IgwmtxteJB7to; zkBtZrcnBoA9E3`Mz)}HMA^`$B1(qIXixVJ_MPTQ(V=n>(-UPDT58@<1V4Z;bkN|;O z1lApDD-$4)Lg1F0$EE}bJOol)5rQN@V1P@s}Ud& zSs>2MBN74x9s-fC3$YO(FiXI7M}R;;fmsLKIs^!W6$p6q2#ElJhd|g%Ltq35Oc8L= z5g-s#V9Eiv2mt~y1!7)1q9Q=xO(5pIAu0j{#s%DR1PBBd7(eD#AV46XK=7MKcmxPM z1Oi?iLLxw5M8M5PfIt?3k(2IS0tBK7WVw07Nr1pZAld~Y9s&d|3%Jt=5XdEP`LJ(z z@vG#9O>IFSsleCs;u``4f(wMYZ4O$6idirOZWlOsa6CqUKs13HZ;x?`8-uqsfgl3o z=g0~K2;3sD`3_l>veGk-%4hb&YCe0tB`Rd^+0O_T975dv<%aJHE?%U_KDIAaK0XJV}56 z0RmqIE?m4`A@C6R+DpD6K!5;&Qv#+5feQksdd$NF2oNCft-yur*DD0R?Im9lAV7e? zSpfruz$t;V4d!_Q1PBoLN#NA=>*3}CKXsAs2@oJa;DSK&nIiq;0vEc=D+CA-An;t^ z_$BPg&H~T7$gczl5Fl_tpz~Of`$2&VedQGb1PBm#DRA%-_E=kimo4N00t5&UxGd0i zxJdk^z~!d$A^`#f2pkc3c@KM_r@)cs@eBb11PF`>^qeoUekm~0RNf^(fB=Dm0xxf4 z4|Eea*f|~}K!5;&L4j`bMZV7k2HVN&1PBlya8Tg+ZS2=>0teg1V+05gATTb_ZN$j; zxxjcwS%Cln0tAi-Jim?o+D+g{&v=Fa0RjZZ1-i`_`92pI?uG`6Ahu0^=QJ1p)*J5I7?6@z)-1PBlq z7brhmB=}Nbyo;PX3XFG=6$lU@K;W#v z%gfmV zE)skxFy2L0AV7csfl~r6Z)Oj45IEH{9wtD50D*CV4#P!`F9pUM$qEDr5Fl_$;N{Ki zfer$vdd9;92oNAJF3@4P$nmAXcq3VX009C7P6@ocnLW@!;8f3em;eC+1jYqA3>P`R z6c}$LD-a++fWRq%mp8Kq$_t!o7!MO5K!Cs$f%3yeg3kq}bdf~}5FkL{l)&?w*{>Z0 zPBn~&2@oJaV2VJ8`69fx?4EYF`DW_m2e$5FkL{n!wj9+Ba1Nu62mF z2oNAZV1+={StGBn0xSB*ngj?CAaG6K>n-h@k^3bXCIJEj2#g4Px~ToJp1??Zc$WYH z0t6Nb)EhT4`XsQZb1X}M009Cc0-x?`e=H_2(i`3-K!5;&bppl4jdVT=tZN%96Cgl< zz=*)dyV~bs0way#T>=CM5LhZuY~o1gqrlS6u{Z$&1PF`>e7vrGt|Tzh72YL4fB=Ej z0+l9?T;2s%w~h4)5FkKcP~iQ>_MwcxU`u$N009C7HVBj%Iudyk*w8b!AV7csfk6Q? ztDeAMOL(0C0RjXz2-KTC%`@82GqxZ=fB=DUf#%bySo-7rU1PBngEYNmHRZaYICwP$n0RjZJ2~?dyZS&gJ zIW{6dfB=DO0&OQ$;l!_Xg0~0|AV6T7K;aqGHnnZdV`^WkO2oNA}LBNEnEpVYFyh4Bg0RpQ9YL6b-Jp@*_ zko5@=AVA=(fbmpb;A~5Ho&W&?1Xc@_A3YLW-9pwUK!5;&vjS#Qd4aPn;dufC2oP8; zP=54CaCHk=p8x>@1WpMUOdSMHwS1PBn=CQx+}b<1m8!`O%b0RjZ3 z2y~lM_41w41{NVefB=D=0`-PbpNw|)iMmBv%yT<&TQ`w}2PfWQiY!UL;DYAcFv zO#%c65V&2S#*iwR#qGTz00IOE5LhHoa%^Q!YEi8%OMn0Y0zm|_Lx2Wbpp5FkK+ zz*2#F^DA#gOABpr0t5&Uh$E19RF#Q5PBVyv009C7wh5G(V(AjuR$?0wAV7dXG=X$8 zD|^7v8bCY*2oNA}i$L~qmL>jMifdB>1PBm_DUfA&&5iR(Ew*j+BnCebAV7dXd4aj( zCirS2)jXcs$S&R`K!5;&4g$w#O6n(GonfaT@T&M&H5FpT8 z!0>I}0MaKwfB=F00*3E?b2}0sK!5-N^H&cLAV7csfz1Mj?`Ck@5g{mc-$yQ$N+TBn1PBl)EKq6w7A}+22oNAZpprnP;p;EI zl{!-{1PBl)EMWK+F0RxF5FkJxtbq9&wlo4GK!5;&sse^@)ym3?009C7!U_}{zK?Xm zQVWa#0RjXFya^b-I)MNI0t5(b7N|FT{pELa3)zkU0RjZh3X~ka=I_}y@H_zm1PJUD zFno7v+lv4J0t5)W37Ee+fdByl1PE*vFnl+I+l~MM0t5&czIuQF0RjXFY!)avd^a1~ zjsO7y1PGiGFnmvOd6)nJ0t5(*3mCrRhE^azfB*pkrvwb&Q(PVTMo>n73fB*pkF9os;-?unlrsDwu1PBlykU}8W{Cz~5LRF9i2oNAZ;JJYL``pK` z1PBlyKw!E+y5aj4@N@?Y5+Fc;0D+wXN$2k)?43&XB0zuu0RnRc%-^}5RwF=w009C= z1oF;be+52LU(XOAK!89@fz%UzXDHh z2@4V+K!Csofdcc_2;R^qwje-&0D-vz=I>ljs}UeTfB=Cb0tM#pku*I+fB*pk1VRa< zp1+|a1w()U0RjY$3nZPt#}o7<0RjXF5QrvV_(rRUcnA<6K%j#_y7}9oC~_n~fB=E= z0*U5t`C3Sj009C7`Uqs1zkMnqQvw7C5a=KffBtr;f*c7DAV8p-K;-$`trqenK!5;& zJ_2FqZ=Vv#lmGz&1R4qioWBjLAY}ps2oUHd5N-Z;^Or9H0t5*36bLncdzL`f1PBly z&``kqZCG?E6Cgl+noRb z0tDI$#2LYDOCWIq1PBmlC}93JEV`5l5FkJxf!5+Fc;Ktq90llNmV4QnA~0t5&U=p*oP zShrB*G2_k|O~E1PD|WcrxHp{8Y6r@*+Th z0DT<#fyPK)uq)hyVcs1ZoRpnXpIVtX(PD5g5Fn6U;KDdd{Yv(FiJt%g z0tCtkq@JfE!I!C^LiIFiE1ZQ0t5&UNG(uldd|%yby)>ZfB*pk@df6Nt$M4)ub>DC5FkJxsX)DEOK@uQ9fIvWj4MVEQ76FSZBmx8o5C|^NWJIn`GkBGSM}PnU0s#e9 z&!!IR2P~_Q2oNAZAca7ODY-7k6eSiU0RjXF#1vRJp31Krv!J3PK!5;&L;~e!<@5v- z6<4SP2oN9;Sz!8vs=Z+3Qi_cL0RjZl3Dh2%6SGTKSpgFuK!8Aefr)df>QeEGC_(}R z2oT69P<3w3$}3|{#Y}(z0RmYBW=*Sl>tv~*I0+CSKp?3=z0o-(qomaoHUR;jc$=twTv z>nMH#1PBmFConR$%DkJdY62!efB=CS0%a!YwM1$ZQ5FOU5Fn6I;M(M>@m9uqiJ1Ta z0tCtk)EK50vM5tQi4Y(_fIw1#3&Sh>D@iLQYyt!b5U3|GccgynGk1&Qt9=`cF9{GJ zK!89Hf#VbHrzg)Q^3yt(zdsv*=LrxXK!895fop?rV&ta+n7FT{2oNAZfWW;1$0y*P zIN##gvzJ{65FkK+z`X)brtrOi*_{9Z0t5)u7Wg`XYv(IF0t5&UAP_{r|MwfD5W*lp zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U OAV7cs0RjYK3j9CtHj-oj diff --git a/test/shapes/p14_shape8.bmp b/test/shapes/p14_shape8.bmp deleted file mode 100644 index d6f981f58add4872ed72ccc2181b10ec4eea6101..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 zcmeI51-SG^6^73(rIaGY-L<$Er%;Mh+}+*XDHL~icXxMpcX!v~et+VdWG6P6Nk-m2 z@NY75st_L20z1NRm=AbCA(ul)ZzvTxR&{&U2B`Tu37YLcCu9ocKI zz2rzoI+EQa_nOtTaI&_Qr*-Q=eK+bDGo0X-|7vIo;__ zC#OIC>E#S(ID?$=jAxVs4md#0bfz=Ona_M?Im=njB4<78S>}QvAoZ}pF z&U2nq&ULPH$+^#cZaL3+&LihN?|J2X=R2Pqc;JC@{_~$-E^vVh$OSKWLAlU{E+iMe z@P*|f7rBUB^r9D)i(Twua?n8s$;B^zak<1LE+GdWe6U>dl9!Z24mm_Fb*W3qr7wMH zxy)rQBbUAGW#w|0yPRD9@|Tw@T;U3G#VcM>4n6cxxzd%cBv-!jmE|f|xr$u%s#le( zUF~Xe^{Zc9u5pcP$ThEdO}W;!t|iyL_O<0Y*SU^d_qx}W>s{}9a{cRHUv6-N8^{fB zctg3-jcz12zVVIaCO5f>-1Me5m7Cq{W^(hJ-&}5Si(AMoZ+T0()vazNx4!kQsj*bXFpq>^PK0%bD#TMdEWD$C(nQW^W_CEc!9j|g)fvBz34^q z;upVIUhs}|XfBoy_4R3gZyzz~1lsCQUP4eb9zggb$mbb`T-}+X0+uPnIZ-4vSs|8hcfVWS^Pcy}d*AzBdEfiqC+~m%`{e^4_<(%ygCCR+edt5-;SYaUKJt-| z$VWf=QTf=%J|-Xk_{ZfFpZJ7)@{^yGPkriB^65{1T0Zlc&&X##`&s$i=RPN&|NQ6W z3t#wxeDRB4lrMehOY-F}e_6irm9NNGzxq}A+Sk4&U;p~os#{e zZ+~09^PTU=cfb2x`QG=wC*S}6_vHsa_<{WJhd-1b{pd&X;~)Q6e)5x_$WMRzQ~BA? zekMQv`OoDSzxajx@|VAqU;XM=^6Ov!T7L7J-^g!&`&;?l?|vu0|NZaf4}bWB{PB-} zlp~HfLjLrpKgpl}{Ac;gU;ZM0{p(-lZ-4un{Qd8Lmw)`@AM($C{!{+-uYbwE|NU?I z&wu_S|NZZO<$wSCpX~1L%AP%YHj4mT5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2>3~0&z>aiog((~)z4Ij+CKt& z(*K%aBf=&R_UsYJrr5L}0v@6zurnw>&I6+r z$1;g4mb_-uGGX8)P6AD>Dfi+8L)LZ)MkQ* zF#>^%fLddHt$>4oF#?6q8c)0Qi7)Ll<{U$@6G+rFNqV_7IRlJcH-_>r)HhjzhTLN& zZUS11A4lN0MPn@c{8*~@udyI76eoe5;$hRthu&gBoYctL41wZd)5+q?7t3s#nLw}- zDFMC1Zf#bi+(_F9fmBGEt6w?QNE{f6l0a=8)8CAe97!7>ps%^j%w+X65D6xtBcOfd zyU|{D>_&jmNyk9KrTXe((%#NKhvQ)^17!){L?V++DcWLFP2_{%@P%VGLzRkVw3!VK zYoQaEAvN{X1Q-Y4&{bnDze+?iZvIs^vJG>wVJUb5P3aifOPR3(2Cp0=2^tYx+ejW6 z5;RhwppCSZV`x8UW+Z2hKyuD!?6j}0U2_p)DPRJ1$r#*DB+bD$VA+^T&7kDkrc8y1 zoq!3nMPqV5U~HsjhJZ=AZQC*vBUXYXkV?kN#YbT_Sa2j}g248QWxB;gl-LNBfN8aD z+Y6N(c^M$EEf@1G2ExQdm;}s=O=U4mc4Rf5fJ!YE+U5s}g%Al?kebpY55k0~p4mAm zwPK@jcBB{xkHC%%rFGa0j~*G#CeWc38_lz0#XxWbZ0M}ZW^nLGXfA;+sn}_s8!ht# zBVb2nomK;*M>;bJbPB~%$INh<9vA^jGV3}5<8xlcsnL$7)68nw8{T9bga2d@I)%dCQmDb^5 ze29z;mq3R&7HAHaDVdEXuz=3GTqH4a6L55)0v*^T4vX#8mpcPRN?KzHq(3oUsllS+ zI!v?tjV)BD;}T)4s6YNJNT|9RnOC+httPu{;0Pa~;#w4@mnC&<2n$h^>dz;-F zEq*bpMVrH_PMy<@Y^OGa%}xT1Z(3FmR}f%lgbsJ~VsbccheGQ`xMZh8Oh$&%LnnPB zkF*l?umsbZYhJB_-kq=4V(-|DT3KU~@8Gsh^O%4g8Z!*ZyLZ&WbQ6M(tbJK67tUVS zU4D%|tiEJ~u8SzV%tjc5{L*9ek6B z>ft)PJx+c3D2LNnF|>q@W75m2wIY%6uCqwT(;II%nvwZC)4_i|sK8`u9^|xah?5B} zH(^;28s4U$D3(YKFF{Y4f^g}vj)PBej6d*}@ElKSsqpeA{Yog>uWkNm_^GjWyOx8c zSfr2HG%v?Sc-G1&nKZ(5G(&V5cFoJdb@fheU!x9NIxHG$?MWUp-dL z*6ITT{-c*wlR;1^se%tGm~wtd3VMK9d;4m8@$U8J-PHYRpiH{ITF zYI(gK}k%6_ihe14a8bL|-%Gr^9tVlH(5EHwz(J(q5{(%xAHv37VG z6PL-zO)nWPGqS75cV=3Rn6uGo-B3rd`Wa@@m$9M1eKgaDMZ2#Pgto={#7^rbI+nlk zUGRd+1R%3E@;eNs-|C$))@7u<-D%xihjwf_6U~L~oh2PyLvs=_cXJq;Jf^?fx~ZNq zQJ5QJDr)cSn7JLNC`302#o*~p-R5WYRJR1CM>J=~teAbCJO|pqTqTyYz60~dbt|nK zX)G>1#)V;Kl##f7G-3vy+KP&Yx!S6%&u(M&n~iXSf&3?6O<#9ZV(g5h8Zl?1)4G9< zA}(uW+Ii<8SK>F2fgdKUo%^MLyMNHhNL?)wZp*{=e&YdMi+MR_+CJ8fgpD}vux+5( zNO%*BbXHZ6eMGTElEHDF#t+q~v} zwFb_MMt8Pm)Sge%r8Q~ZhEZDMVrshnnOOrj=JmIdz%u$Ar<0Ar8!%Mi85pP8U74S=q3L`6G*-X)3D29`b7sn>W$u{x*DqTOkAxsF-!`#X7=w|H8{>Y~F! zO_MB=5@;_S79yHz(q9G5irmoE=TzFp$Itp3=y^2Dy%RUtS3YMI(c{S*6uxii7a`5O z62jsbGSB+K`#p1A^k!?FRAS-*I3I2{iYeAA(fci;8PPBD#;-8vwQ226DOm4VO$@0` zTOswvTXhzJPki%ert8+U_2E}ln7@&tom|BEZs%HXOA(7kGZ&p>^K*7nBYtXRpYZ-e*v%) z!6*V7*K558M}-;ipiKpW+ZwLgnknakAC58b5gCTH-u#y9KTtZ$-UrN=`Wre!7)ZkN z?iA8)pz`bZwr5$f0evjeUB(jWYyM*|i;!N&$)>LDWoDgtT3$Ed#k$o365MW5*M{=a z9e0w9Rdre3Zk588C>kX$oH%j5kp$6*Ey|sBKlQYwxu)H$u+64q&jk%eDY<&mTU80E zp<7|yJ&WdFuSA^Ni{f6znM!J^wl#u_Q*({1*TBA8hN{MA|8|Q%H_9FhIn^M95G42iCp8ATqAQQY7t=&jUvRnlhmFyaPc=l%!+H} zzBv219p-QLRt;j_W%*nF4H2{6wsKz{fAk)=Y7lQj@Xug5DE4^Orp(;I3WXh#vI-G5 zatgSp-Xm6;I`xD;_h6Qjpt=Y#w=TyXPds;Hfs22zvqgxLBf)KKe**MDL&u`viQ{ z#$qws#~;0i#cJuLqv+*f9YE_BF}+lYy`Zh*kKRF|zv44@dsXzjr{a4098AK@(`$;cUy#Uo5|f`0bg@_t zF$u$duIT}IvC{ePD18>oeo-Rx;J+-E%^)&)SWlN{?N83A4Gux{4LAto&Xd=OF>(Tr^^`b^4hTIpa<&~fmtfkR3&2-G+>H`8NmcARY- zY#W}M6Y{-kbTwZ!YBRPP-#yIno(2+jrVGcf1ECva+g47T>^ zpjwLk7CllbmbauwhGwz)(R)*&H$wZhvwvtawk3f)@3l4YVF@6om4z z9Y3#_bmsjEJN<;S3BE6a{VCErfg$CFg$#kf*0SPFLkRSv&LKa2@&t#fHHbPFUj)~RGwPa! zl6-j;(yFZwyP=eTJpBAh0Cfw9K)T?0b|)qv`lRCHm!hazPy~E9=WE26(B~t#U?k|@ z-w6u_eawl}R*=Tx;=xYLWeYNQ4>Z>ZF~}lBoK&N@?(gh!`WKd1-IrW5Lasl;(w}cP zVFII1I?d(9{XG`Iur%ww;+heLMW1Y%)pdW5iUmf%eaST<42(YMG^*>t4iyWFfCrLm zgcufmvS}38gB>as7y%D7R}V2T`lM4Yt_Rx`EHDBdXs#Y&VDw3+UR>w5c>p0i!8$Lx zGQjlPD&gUeKBKa{UcB=8Z*Y3`LU(15!Oc*wy1{??~O z1f~giqrctk>bLgu>843wZv_J0$S(pof6N`YS&f?1`Q(&j~mzzcMt>2@`%7f%LP~vd9je-deHrL`_#91X?9`WjDS57YJd}=qBKb+*EONM@>&4 z1XN{rr7aM|q|r@aBemMcU7N;Lx4NUICjbIYU-qWL4}dW#^buHfb?bIsti*kh)8{{d zZUH#a<3C_B=p*2S$do~3>tA2!^!ZF6``ANSc!wH&22AXO1orH4?{c@U{=ozo;4^`G zsh!;R87{F85pa9$OWqJe3DEB|fnvqmb@mvX!Yv=p@fkF+*9nvrb@G6_35p+?tQRf3 z?MmhqnfLSRmEDKu2v0f_)ef+XGgbv zM@;G7RFz0PiK`%akndkDBGxh$&u^O&C4>GGa{!}7~Q^Byn>^bkOO zJ#grk@EVei<9?_N^-so8Fz4pUuRv?F0db_2&d1g0M=!UHPSHbKtdF z{1uItm32yg<)hc>>xyh}y%2#NcmAYmDZS$Tun+*;tW3bE<*y26Whl5`ia=Fyhc=eV z9Y3oRa7cCKcaF>R(t=HEcXeU}V3L4S`b&aIazk(?3+%S7q~fj9{#6B8hlLF5Gy>gT zRc&qOfifnoP6BNOEV8d&a64n9BU}QD=xvOXa1oPPH-RzASYS+dfb;}NpgOT%Kw4cc zYRBLJ36s(&0$t)*vONj3dOwT`nn9ruC}dV!ik1{!;wu!8!y38x)J< zY13dnfRU`!^Ysjo3+VwIDzht=QTo39f%|Z0N|>0D*o< zjqe>73TxgtPSK1SFR;FmIj)_Z&Z8>2i$+nC673V!=)ShchS(ZA?UC$9PGGdgx(7y1 zko0v36suCMs_o0zZnbUo=y+!i1X2R2m}ZHub+-?y5ST(>YcZSNFvT5XA+RB^lP-TV zSHInjIkq4$mOy^?H+`|&o-x)V;~>x`u&t)aEp3zRLtrd{RBSU3zs+Y2#yVyk1oCU& z$?IKf0vsWrOQ0nstzBK;%!{6Ymfl*EqK60oT>>p7>Fw$|XI}IK^aR(O6+J`%=n`lO zNpnxvIrCyCpeeZKG#;RjT}ImZ7ebnM_fOk96JsUNcqD`sXja~d1Sd;P zIU$Lnkn^l~m>e4crJ5!;#zu{N4H1~EH1z~S(O@Pr0%|o)Z;cEa>6#%hU1;hVW}?AL zYy{M5n%){4H}W+?V7k!MGqPZ~{54XOh>WPkG^sVaXB`nTQZ+|llF+&bn+pg_F%jri z)RZ2+@)nb34CXpTWUaCF4~h*&gsF%Kv?Qgw8xb*5HAp~LZ>@O-Bf?Zf1X?oE-HnJC zsTw4ptGDJn9(XZWl9)_2Wi)lKy!wcV7`d7wurpO~>hV19XRahsSyIbsYHL);NY)5} z^b^|It6zHf-Q|~Fj1(k7BU9Ho<@gA>k+K;AGk@f2^Sl9%yO=3RWLA{x*~rG?%9ksT z$TVXx-H}S`8)h&XOhraO|LUW>>&KNxWRfwPZr{Q-rZ0_#Ge5I6iOAX*i7ChA%U?vK zNYw@brMAX3a?j63K_aucTb;&@6J2doWTZ&f0)bpkS{k$v4JKnFP+QKXZN>(Qe60|e zCc0|0l~^zu9f3+vlQyEmM8cK`Op;wXI5|_a6bfb|BrsKfiTwC_Z-hEAopSMk#Z>ua z<4wncy=V!bzi3OpZsM$e>+;8YNjY-&&+F_7er*CX^~bfx+6DC7|1AA^o;%^MP5}9> zUV^9t(9s`{|3xhsqsi;&FPmdD3XH`{Kv#dU@*;1u1oZUBPwkqmLcG>=^q0}%#!I}S zF_`%@{qe|^LG+kP2u#yoGPfh&(Kj5O#H7`5ot2I=}?9t2bbYSJ3Dsj@*E z1at|E5?yVOt{>(>KuMsctudQQ8#F>dm%td&)yC+$VIBn31g2d8t8LH<0bK&M#cjl< zt{3J(po72&)oq4!*q|8#x&$`j8nLD8gn1C?ATUC8n;{)GXoi3`fsML`x3rxw5ds|q zQq}E_ybSIzK{EvO31r$sfcgNLIg7yXVRtiPmLn!ZKt*69uYs*(kyOp64FYorB#)qu zJp7`+Ih>i>L119nYj$_opcw+X1ag55u7TA6T|dl&fQmqKfg9f2uib*3{V6;vz=FJyRf% z5Ev`8p>e>3fEIxvVU6zB^3J4q37ESIE;Sl2OET9dP^xF{4e7*=`Z?7?==u>dGfV>I z3Ro)8&}#{6XD4xx`#^#v6kZzyGGQ3fUENH>2|EzzB~Vw6vF%=ubV48}U~2`;bPQw& zj3Hob(c3mO#yw-=Ca{w{4sGD_w$?F z8kv-s+SX=yjgT`b_Yg3D9jsPhy|pJCx?&A03NwmpTG0xINpTWrN@q9ufrDm?darj- zoFK_skAOm8iMkpxOx4z_xQ2~93_b+bD@TO08tQ566fhcXJv$XU#8;tUV}L-S6}6<= z^Higf<8|7c4lOi?^xE9mR}!cTWm{UxDQROmw0103jjLN$_0tA{8i7VKh|qzj(@`OUq65U{`K9*51)@eK3;$R*S~!H_UrxN^WR^8|L3>= z`0m>ueEjRzAOH9Je}4J$<&WS0=f8h{{iSa|fBrk)p86dD0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5Fl`~z*j#62n-jvxiD@c zK;R@WJUq@PK;Tp0^n!a%fWUNdyR-1Ty6@gaColYwkG#0v8HM76AeofeQ=cBmx9Z0-5p{LV&>O zHTRqVfeQsBivWR)z=efz5&;4yflPS}Awb~tntM)wz=Z;mMSws?;KIT39$dtzr0t8O4x#t83Tqqz}1PEjVE-Z|b z2oN|4WXfX*0RpGj+;aj1E)r$dtzr0tE8CGLS$?K(YuB z$Ox3uWD9}3K&Cu~5Fn86m4O6G0+L05Kt`aHCR+&P1v2F^gaCnjuM8wm5|Atc1Tq4p zG}%HRFOVsZAp{8Idu1Sjl7M6pAdnF#rO6fod4WuM3?V=u-zx(NlmsM;0D+7^DNVKz z$O~l3V+a8P`Cb`Fpd=t!1PEjVN@=o%KwcnI9zzHa$oI-X0wn>-B0wM`P)d_61o8rz z@)$yZK)zQ75-15s76Aeofl``mA&?iyl*bSP1oFKykU&X5vIr2!2$a%f3xT{qraXoa zAdv5sfdondl0|?(Mxc}?TL|O@GUYLZ0D*k33?xtzkSqcOG6JPE*+O80Kqfwh5ZElR zAyzgLAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7dXOyH{@0tAK&#G+&lfeiw~tK)nE1U>~e#L7kj zF#+)+Kp-O!tCKkd+5(yK7(#$RJ66^ahzUp*0RkC;Se?uv&=$y)#}EPp+Oe{ZKukcg z2oT5!#Oh=Yfwn-VJcbY;(2kXL1Y!b`MSws?AXX=H2($$<j=aIB#Qunj6keT z<`8HLWXfX*0Rrt-5XcC`>SPXqwm_yl zh7cgoj+J!;VgiyyfIvndRwr`^v;{KdF@yktcC4%;5EGCr0t7Mwu{xPUpe>Lok0AsI zv}0u*ftY|~5g?Eeh}Fp)0&Rgzc?=;ypdBmg2*d;=ivWR)K&(#Y5NHcz%3}xt0_|8` zxXwq8009C72)qO?{DumjIJMJL?0(S~X76AeofjcAPDgp#f0-5+2LSVDN=>_+k009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNC96WIKJjoMD2A<*lRMFchoH2Px& zf#m`lVr3(NjKK05?G?HVBmB zV+(=h0vnQLBY}*-@?M!wpd^s#k|6{(2$bSu3xVYV85?G? zHVBmBV+(=h0vnQLBY}*-@?M!wpd^s#k|6{(2$bSu3xVYV85?G?HVBmBV+(=h0vnQLBY}*-@?M!wpd^s#k|6{(2$bSu3xVYV85?G?HVBmBV+(=h0vnQLBY}*-@?M!wpd^s#k|6{(2$bSu3xVYV8Xlp#KN1PBlyK;SJf<(sQk0AsI3@VRP z2@sGh0t7Mwf<}PAAc0JI3?V>ZPh#3=+tc#}EPp29?LD1PDkL0RkBT zK_ft5mOv&xh7i~+Fe^K*B|v}x0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RnGh#3=+tc#}EPp29?LD1PDkL0RkBTK_ft5kU*w9h7ceys60+3KtQqx5XcA! z8UX@>1Ty6@gaCm-<#8$j0+L05Kt@2&2oM+~kSUKL1PBZ&k5dT{kSqcOG6I4|fWRPu zOnD3;KwwaLoJxRzWDy{c5fC&21O^FY%3}xt0)xuqR00GfivWR)fS?f|Fi0R%9zzHa z7*rmo5+ER11PEjV1dRZJK?0fb7(#%+pz=7C00GG&Kp-O^Xaooh63CRt5CQ}Sm8Ws4 zj~oF41PBm#2{gXcrmXOhBS3%v0RnGu+zy^U#c?=;yU_-KOB#;r1ECK{F0+}uuLSTbHraXoaAh01>HWJ7P zNEQJC8G%ff3?Z;VAX6Sg2oTtiEE@@A1SE?9fs8<=ONJ2GAdo4KAp{6)NS2KRG6IrC zfIvnd(2xJ5@ zT{48g27yd@3?V>ZL$YinkP(n90t7MwnJyVZV1q!WJcbY;upwDC637Th76AeoflQYS zA+SLpQyxPI5ZI6`8wq3tB#Qunj6kMKh7edHkcp2W1U3sS>6MuT2oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk a1PBlyK!5-N0t5&UAV7cs0RjYW6Zk*>6Vn_3 diff --git a/test/shapes/p15_shape32alpha.bmp b/test/shapes/p15_shape32alpha.bmp deleted file mode 100644 index 6954a5d91c44dc333c5b4822f046d66858ffd919..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1638538 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 z=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^! zA==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)o zZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K= zd1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju z+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3? z?n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l? zXM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q) zjm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S z=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^! zA==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)o zZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K= zd1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju z+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3? z?n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l? zXM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q) zjm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S z=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^! zA==l?XM3K=d1`#S=WY3??n$)oZT{Q)jm~ju+|6^!A==l?XM3K=d1`#S=WY3??n$)o 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 I_*Q}c0cCyzH2?qr diff --git a/test/shapes/p16_shape8.bmp b/test/shapes/p16_shape8.bmp deleted file mode 100644 index 0b4d4221c5c47a45ff8418f747c4fe98218a7b4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410678 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 XOuz(8zywUd1WdpLOuz(ok-&ceOT{Tx diff --git a/test/shapes/trollface_24.bmp b/test/shapes/trollface_24.bmp deleted file mode 100644 index e18c2c2ad129e2dfa612d0c4382bbc26fc21edd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196662 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�o2DBOY1($ca-Y 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 diff --git a/test/shapes/trollface_32alpha.bmp b/test/shapes/trollface_32alpha.bmp deleted file mode 100644 index ee3ecf9be4c95a7e76f338bf2c985d1d97054364..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262198 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 UQ0hRb1Emg>I#B9BXY0WK4>RWDjQ{`u diff --git a/test/testatomic.c b/test/testatomic.c index b28ef9bc..84b41bf6 100644 --- a/test/testatomic.c +++ b/test/testatomic.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -50,28 +50,28 @@ static void RunBasicTest(void) SDL_Log("\natomic -----------------------------------------\n\n"); SDL_AtomicSet(&v, 0); - tfret = SDL_AtomicSet(&v, 10) == 0 ? SDL_TRUE : SDL_FALSE; + tfret = SDL_AtomicSet(&v, 10) == 0; SDL_Log("AtomicSet(10) tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); - tfret = SDL_AtomicAdd(&v, 10) == 10 ? SDL_TRUE : SDL_FALSE; + tfret = SDL_AtomicAdd(&v, 10) == 10; SDL_Log("AtomicAdd(10) tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); SDL_AtomicSet(&v, 0); SDL_AtomicIncRef(&v); - tfret = (SDL_AtomicGet(&v) == 1) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicGet(&v) == 1); SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); SDL_AtomicIncRef(&v); - tfret = (SDL_AtomicGet(&v) == 2) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicGet(&v) == 2); SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); - tfret = (SDL_AtomicDecRef(&v) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicDecRef(&v) == SDL_FALSE); SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); - tfret = (SDL_AtomicDecRef(&v) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicDecRef(&v) == SDL_TRUE); SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); SDL_AtomicSet(&v, 10); - tfret = (SDL_AtomicCAS(&v, 0, 20) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicCAS(&v, 0, 20) == SDL_FALSE); SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); value = SDL_AtomicGet(&v); - tfret = (SDL_AtomicCAS(&v, value, 20) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE; + tfret = (SDL_AtomicCAS(&v, value, 20) == SDL_TRUE); SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v)); } @@ -701,7 +701,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testaudio.c b/test/testaudio.c index 94de7847..0738b188 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -1,9 +1,4 @@ -#include - -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -75,7 +70,7 @@ struct Thing } poof; struct { SDL_AudioStream *stream; - int total_ticks; + int total_bytes; Uint64 next_level_update; Uint8 levels[5]; } stream; @@ -103,7 +98,6 @@ struct Thing static Uint64 app_ready_ticks = 0; -static int done = 0; static SDLTest_CommonState *state = NULL; static Thing *things = NULL; @@ -113,6 +107,9 @@ static Thing *mouseover_thing = NULL; static Thing *droppable_highlighted_thing = NULL; static Thing *dragging_thing = NULL; static int dragging_button = -1; +static int dragging_button_real = -1; +static SDL_bool ctrl_held = SDL_FALSE; +static SDL_bool alt_held = SDL_FALSE; static Texture *physdev_texture = NULL; static Texture *logdev_texture = NULL; @@ -121,51 +118,6 @@ static Texture *trashcan_texture = NULL; static Texture *soundboard_texture = NULL; static Texture *soundboard_levels_texture = NULL; -static void DestroyTexture(Texture *tex); -static void DestroyThing(Thing *thing); - - -/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void Quit(int rc) -{ - while (things != NULL) { - DestroyThing(things); /* make sure all the audio devices are closed, etc. */ - } - - DestroyTexture(physdev_texture); - DestroyTexture(logdev_texture); - DestroyTexture(audio_texture); - DestroyTexture(trashcan_texture); - DestroyTexture(soundboard_texture); - DestroyTexture(soundboard_levels_texture); - SDLTest_CommonQuit(state); - - /* Let 'main()' return normally */ - if (rc != 0) { - exit(rc); - } -} - -static char *xstrdup(const char *str) -{ - char *ptr = SDL_strdup(str); - if (!ptr) { - SDL_Log("Out of memory!"); - Quit(1); - } - return ptr; -} - -static void *xalloc(const size_t len) -{ - void *ptr = SDL_calloc(1, len); - if (!ptr) { - SDL_Log("Out of memory!"); - Quit(1); - } - return ptr; -} - static void SetTitleBar(const char *fmt, ...) { @@ -194,7 +146,7 @@ static Thing *FindThingAtPoint(const float x, const float y) const SDL_FPoint pt = { x, y }; Thing *retval = NULL; Thing *i; - for (i = things; i != NULL; i = i->next) { + for (i = things; i; i = i->next) { if ((i != dragging_thing) && SDL_PointInRectFloat(&pt, &i->rect)) { retval = i; /* keep going, though, because things drawn on top are later in the list. */ } @@ -229,7 +181,12 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl Thing *i; Thing *thing; - thing = (Thing *) xalloc(sizeof (Thing)); + thing = (Thing *) SDL_calloc(1, sizeof (Thing)); + if (!thing) { + SDL_Log("Out of memory!"); + return NULL; + } + if ((w < 0) || (h < 0)) { SDL_assert(texture != NULL); if (w < 0) { @@ -253,15 +210,15 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl thing->scale = 1.0f; thing->createticks = SDL_GetTicks(); thing->texture = texture; - thing->titlebar = titlebar ? xstrdup(titlebar) : NULL; + thing->titlebar = titlebar ? SDL_strdup(titlebar) : NULL; /* if allocation fails, oh well. */ /* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */ - if (things == NULL) { + if (!things) { things = thing; return thing; } - for (i = things; i != NULL; i = i->next) { + for (i = things; i; i = i->next) { if (z > i->z) { /* insert here. */ thing->next = i; thing->prev = i->prev; @@ -313,7 +270,9 @@ static void DestroyThing(Thing *thing) case THING_LOGDEV: case THING_LOGDEV_CAPTURE: SDL_CloseAudioDevice(thing->data.logdev.devid); - SDL_DestroyTexture(thing->data.logdev.visualizer); + if (state->renderers[0] != NULL) { + SDL_DestroyTexture(thing->data.logdev.visualizer); + } SDL_DestroyMutex(thing->data.logdev.postmix_lock); SDL_free(thing->data.logdev.postmix_buffer); break; @@ -354,9 +313,10 @@ static void DrawOneThing(SDL_Renderer *renderer, Thing *thing) if (thing->scale != 1.0f) { const float centerx = thing->rect.x + (thing->rect.w / 2); const float centery = thing->rect.y + (thing->rect.h / 2); - SDL_assert(thing->texture != NULL); - dst.w = thing->texture->w * thing->scale; - dst.h = thing->texture->h * thing->scale; + const int w = thing->texture ? (int) thing->texture->w : 128; + const int h = thing->texture ? (int) thing->texture->h : 128; + dst.w = w * thing->scale; + dst.h = h * thing->scale; dst.x = centerx - (dst.w / 2); dst.y = centery - (dst.h / 2); } @@ -413,11 +373,13 @@ static void DrawThings(SDL_Renderer *renderer) static void Draw(void) { SDL_Renderer *renderer = state->renderers[0]; - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 64, 0, 64, 255); - SDL_RenderClear(renderer); - DrawThings(renderer); - SDL_RenderPresent(renderer); + if (renderer) { /* might be NULL if we're shutting down. */ + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 64, 0, 64, 255); + SDL_RenderClear(renderer); + DrawThings(renderer); + SDL_RenderPresent(renderer); + } } static void RepositionRowOfThings(const ThingType what, const float y) @@ -427,7 +389,7 @@ static void RepositionRowOfThings(const ThingType what, const float y) float texh = 0.0f; Thing *i; - for (i = things; i != NULL; i = i->next) { + for (i = things; i; i = i->next) { if (i->what == what) { texw = i->rect.w; texh = i->rect.h; @@ -440,7 +402,7 @@ static void RepositionRowOfThings(const ThingType what, const float y) SDL_GetWindowSize(state->windows[0], &w, &h); const float spacing = w / ((float) total_things); float x = (spacing - texw) / 2.0f; - for (i = things; i != NULL; i = i->next) { + for (i = things; i; i = i->next) { if (i->what == what) { i->rect.x = x; i->rect.y = (y >= 0.0f) ? y : ((h + y) - texh); @@ -507,12 +469,14 @@ static Thing *CreatePoofThing(Thing *poofing_thing) const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2); const float z = poofing_thing->z; Thing *thing = CreateThing(THING_POOF, poofing_thing->rect.x, poofing_thing->rect.y, z, poofing_thing->rect.w, poofing_thing->rect.h, poofing_thing->texture, NULL); - thing->data.poof.startw = poofing_thing->rect.w; - thing->data.poof.starth = poofing_thing->rect.h; - thing->data.poof.centerx = centerx; - thing->data.poof.centery = centery; - thing->ontick = PoofThing_ontick; - thing->ondrag = PoofThing_ondrag; + if (thing) { + thing->data.poof.startw = poofing_thing->rect.w; + thing->data.poof.starth = poofing_thing->rect.h; + thing->data.poof.centerx = centerx; + thing->data.poof.centery = centery; + thing->ontick = PoofThing_ontick; + thing->ondrag = PoofThing_ondrag; + } return thing; } @@ -530,7 +494,7 @@ static void DestroyThingInPoof(Thing *thing) static void TrashThing(Thing *thing) { Thing *i, *next; - for (i = things; i != NULL; i = next) { + for (i = things; i; i = next) { next = i->next; if (i->line_connected_to == thing) { TrashThing(i); @@ -549,13 +513,11 @@ static void StreamThing_ontick(Thing *thing, Uint64 now) /* are we playing? See if we're done, or update state. */ if (thing->line_connected_to->what == THING_LOGDEV) { const int available = SDL_GetAudioStreamAvailable(thing->data.stream.stream); - SDL_AudioSpec spec; - if (!available || (SDL_GetAudioStreamFormat(thing->data.stream.stream, NULL, &spec) < 0)) { + if (!available) { DestroyThingInPoof(thing); + return; } else { - const int ticksleft = (int) ((((Uint64) (available / SDL_AUDIO_FRAMESIZE(spec))) * 1000) / spec.freq); - const float pct = thing->data.stream.total_ticks ? (((float) (ticksleft)) / ((float) thing->data.stream.total_ticks)) : 0.0f; - thing->progress = 1.0f - pct; + thing->progress = 1.0f - (thing->data.stream.total_bytes ? (((float) (available)) / ((float) thing->data.stream.total_bytes)) : 0.0f); } } @@ -575,6 +537,9 @@ static void StreamThing_ondrag(Thing *thing, int button, float x, float y) if (button == SDL_BUTTON_RIGHT) { /* this is kinda hacky, but use this to disconnect from a playing source. */ if (thing->line_connected_to) { SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */ + if (thing->line_connected_to->what == THING_LOGDEV_CAPTURE) { + SDL_FlushAudioStream(thing->data.stream.stream); + } thing->line_connected_to = NULL; } } @@ -589,16 +554,14 @@ static void StreamThing_ondrop(Thing *thing, int button, float x, float y) /* connect to a logical device! */ SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, (unsigned int) droppable_highlighted_thing->data.logdev.devid); if (thing->line_connected_to) { - const SDL_AudioSpec *spec = &droppable_highlighted_thing->data.logdev.spec; SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */ if (thing->line_connected_to->what == THING_LOGDEV_CAPTURE) { SDL_FlushAudioStream(thing->data.stream.stream); - thing->data.stream.total_ticks = (int) ((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / SDL_AUDIO_FRAMESIZE(*spec))) * 1000) / spec->freq); } } SDL_BindAudioStream(droppable_highlighted_thing->data.logdev.devid, thing->data.stream.stream); /* bind to new device! */ - + thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream); thing->progress = 0.0f; /* ontick will adjust this if we're on an output device.*/ thing->data.stream.next_level_update = SDL_GetTicks() + 100; thing->line_connected_to = droppable_highlighted_thing; @@ -631,18 +594,20 @@ static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, con { static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL }; Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname); - SDL_Log("Adding audio stream for %s", fname ? fname : "(null)"); - thing->data.stream.stream = SDL_CreateAudioStream(spec, spec); - if (buf && buflen) { - SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen); - SDL_FlushAudioStream(thing->data.stream.stream); - thing->data.stream.total_ticks = (int) ((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / SDL_AUDIO_FRAMESIZE(*spec))) * 1000) / spec->freq); + if (thing) { + SDL_Log("Adding audio stream for %s", fname ? fname : "(null)"); + thing->data.stream.stream = SDL_CreateAudioStream(spec, spec); + if (buf && buflen) { + SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen); + SDL_FlushAudioStream(thing->data.stream.stream); + thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream); + } + thing->ontick = StreamThing_ontick; + thing->ondrag = StreamThing_ondrag; + thing->ondrop = StreamThing_ondrop; + thing->ondraw = StreamThing_ondraw; + thing->can_be_dropped_onto = can_be_dropped_onto; } - thing->ontick = StreamThing_ontick; - thing->ondrag = StreamThing_ondrag; - thing->ondrop = StreamThing_ondrop; - thing->ondraw = StreamThing_ondraw; - thing->can_be_dropped_onto = can_be_dropped_onto; return thing; } @@ -696,13 +661,15 @@ static Thing *LoadWavThing(const char *fname, float x, float y) SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq); thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar); - SDL_free(titlebar); - SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec)); - thing->data.wav.buf = buf; - thing->data.wav.buflen = buflen; - thing->can_be_dropped_onto = can_be_dropped_onto; - thing->ondrag = WavThing_ondrag; - thing->ondrop = WavThing_ondrop; + if (thing) { + SDL_free(titlebar); + SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec)); + thing->data.wav.buf = buf; + thing->data.wav.buflen = buflen; + thing->can_be_dropped_onto = can_be_dropped_onto; + thing->ondrag = WavThing_ondrag; + thing->ondrop = WavThing_ondrop; + } } SDL_free(path); @@ -727,24 +694,30 @@ static void LoadStockWavThings(void) static void DestroyTexture(Texture *tex) { if (tex) { - SDL_DestroyTexture(tex->texture); + if (state->renderers[0] != NULL) { /* if the renderer went away, this pointer is already bogus. */ + SDL_DestroyTexture(tex->texture); + } SDL_free(tex); } } static Texture *CreateTexture(const char *fname) { - Texture *tex = (Texture *) xalloc(sizeof (Texture)); - int texw, texh; - tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh); - if (!tex->texture) { - SDL_Log("Failed to load '%s': %s", fname, SDL_GetError()); - SDL_free(tex); - Quit(1); + Texture *tex = (Texture *) SDL_calloc(1, sizeof (Texture)); + if (!tex) { + SDL_Log("Out of memory!"); + } else { + int texw, texh; + tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh); + if (!tex->texture) { + SDL_Log("Failed to load '%s': %s", fname, SDL_GetError()); + SDL_free(tex); + return NULL; + } + SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND); + tex->w = (float) texw; + tex->h = (float) texh; } - SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND); - tex->w = (float) texw; - tex->h = (float) texh; return tex; } @@ -754,9 +727,11 @@ static void DeviceThing_ondrag(Thing *thing, int button, float x, float y) { if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_CAPTURE)) { /* drag out a new stream. This is a UX mess. :/ */ dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y); - dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100; - SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */ - dragging_thing->line_connected_to = thing; + if (dragging_thing) { + dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100; + SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */ + dragging_thing->line_connected_to = thing; + } } else if (button == SDL_BUTTON_RIGHT) { /* drag out a new logical device. */ const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_CAPTURE)) ? thing->data.logdev.devid : thing->data.physdev.devid; const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL); @@ -858,7 +833,7 @@ static void UpdateVisualizer(SDL_Renderer *renderer, SDL_Texture *visualizer, co static void LogicalDeviceThing_ontick(Thing *thing, Uint64 now) { - const SDL_bool ismousedover = (thing == mouseover_thing) ? SDL_TRUE : SDL_FALSE; + const SDL_bool ismousedover = (thing == mouseover_thing); if (!thing->data.logdev.visualizer || !thing->data.logdev.postmix_lock) { /* need these to work, skip if they failed. */ return; @@ -920,22 +895,24 @@ static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID wh SDL_Log("Adding logical audio device %u", (unsigned int) which); thing = CreateThing(iscapture ? THING_LOGDEV_CAPTURE : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL); - thing->data.logdev.devid = which; - thing->data.logdev.iscapture = iscapture; - thing->data.logdev.physdev = physthing; - thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT); - thing->data.logdev.postmix_lock = SDL_CreateMutex(); - if (thing->data.logdev.visualizer) { - SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND); - } - thing->line_connected_to = physthing; - thing->ontick = LogicalDeviceThing_ontick; - thing->ondrag = DeviceThing_ondrag; - thing->ondrop = LogicalDeviceThing_ondrop; - thing->ondraw = LogicalDeviceThing_ondraw; - thing->can_be_dropped_onto = can_be_dropped_onto; + if (thing) { + thing->data.logdev.devid = which; + thing->data.logdev.iscapture = iscapture; + thing->data.logdev.physdev = physthing; + thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT); + thing->data.logdev.postmix_lock = SDL_CreateMutex(); + if (thing->data.logdev.visualizer) { + SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND); + } + thing->line_connected_to = physthing; + thing->ontick = LogicalDeviceThing_ontick; + thing->ondrag = DeviceThing_ondrag; + thing->ondrop = LogicalDeviceThing_ondrop; + thing->ondraw = LogicalDeviceThing_ondraw; + thing->can_be_dropped_onto = can_be_dropped_onto; - SetLogicalDeviceTitlebar(thing); + SetLogicalDeviceTitlebar(thing); + } return thing; } @@ -993,21 +970,23 @@ static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL SDL_Log("Adding physical audio device %u", (unsigned int) which); thing = CreateThing(iscapture ? THING_PHYSDEV_CAPTURE : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL); - thing->data.physdev.devid = which; - thing->data.physdev.iscapture = iscapture; - thing->data.physdev.name = SDL_GetAudioDeviceName(which); - thing->ondrag = DeviceThing_ondrag; - thing->ondrop = PhysicalDeviceThing_ondrop; - thing->ontick = PhysicalDeviceThing_ontick; - thing->can_be_dropped_onto = can_be_dropped_onto; + if (thing) { + thing->data.physdev.devid = which; + thing->data.physdev.iscapture = iscapture; + thing->data.physdev.name = SDL_GetAudioDeviceName(which); + thing->ondrag = DeviceThing_ondrag; + thing->ondrop = PhysicalDeviceThing_ondrop; + thing->ontick = PhysicalDeviceThing_ontick; + thing->can_be_dropped_onto = can_be_dropped_onto; - SetPhysicalDeviceTitlebar(thing); - if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */ - RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */ - RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */ - next_physdev_x = 0.0f; - } else { - next_physdev_x += physdev_texture->w * 1.5f; + SetPhysicalDeviceTitlebar(thing); + if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */ + RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */ + RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */ + next_physdev_x = 0.0f; + } else { + next_physdev_x += physdev_texture->w * 1.5f; + } } return thing; @@ -1030,7 +1009,7 @@ static void TickThings(void) Thing *i; Thing *next; const Uint64 now = SDL_GetTicks(); - for (i = things; i != NULL; i = next) { + for (i = things; i; i = next) { next = i->next; /* in case this deletes itself. */ if (i->ontick) { i->ontick(i, now); @@ -1045,7 +1024,7 @@ static void WindowResized(const int newwinw, const int newwinh) const float newh = (float) newwinh; const float oldw = (float) state->window_w; const float oldh = (float) state->window_h; - for (i = things; i != NULL; i = i->next) { + for (i = things; i; i = i->next) { const float halfw = i->rect.w / 2.0f; const float halfh = i->rect.h / 2.0f; const float x = (i->rect.x + halfw) / oldw; @@ -1057,140 +1036,13 @@ static void WindowResized(const int newwinw, const int newwinh) state->window_h = newwinh; } - -static void Loop(void) -{ - SDL_Event event; - SDL_bool saw_event = SDL_FALSE; - - if (app_ready_ticks == 0) { - app_ready_ticks = SDL_GetTicks(); - } - - while (SDL_PollEvent(&event)) { - Thing *thing = NULL; - - saw_event = SDL_TRUE; - - switch (event.type) { - case SDL_EVENT_MOUSE_MOTION: - thing = UpdateMouseOver(event.motion.x, event.motion.y); - if ((dragging_button == -1) && event.motion.state) { - if (event.motion.state & SDL_BUTTON_LMASK) { - dragging_button = SDL_BUTTON_LEFT; - } else if (event.motion.state & SDL_BUTTON_RMASK) { - dragging_button = SDL_BUTTON_RIGHT; - } else if (event.motion.state & SDL_BUTTON_MMASK) { - dragging_button = SDL_BUTTON_MIDDLE; - } - - - if (dragging_button != -1) { - dragging_thing = thing; - if (thing && thing->ondrag) { - thing->ondrag(thing, dragging_button, event.motion.x, event.motion.y); - } - } - } - - droppable_highlighted_thing = NULL; - if (dragging_thing) { - dragging_thing->rect.x = event.motion.x - (dragging_thing->rect.w / 2); - dragging_thing->rect.y = event.motion.y - (dragging_thing->rect.h / 2); - if (dragging_thing->can_be_dropped_onto) { - thing = FindThingAtPoint(event.motion.x, event.motion.y); - if (thing) { - int i; - for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) { - if (dragging_thing->can_be_dropped_onto[i] == thing->what) { - droppable_highlighted_thing = thing; - break; - } - } - } - } - } - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - thing = UpdateMouseOver(event.button.x, event.button.y); - break; - - case SDL_EVENT_MOUSE_BUTTON_UP: - if (dragging_button == event.button.button) { - Thing *dropped_thing = dragging_thing; - dragging_thing = NULL; - dragging_button = -1; - if (dropped_thing && dropped_thing->ondrop) { - dropped_thing->ondrop(dropped_thing, event.button.button, event.button.x, event.button.y); - } - droppable_highlighted_thing = NULL; - } - thing = UpdateMouseOver(event.button.x, event.button.y); - break; - - case SDL_EVENT_MOUSE_WHEEL: - UpdateMouseOver(event.wheel.mouseX, event.wheel.mouseY); - break; - - case SDL_EVENT_DROP_FILE: - SDL_Log("Drop file! '%s'", event.drop.file); - LoadWavThing(event.drop.file, event.drop.x, event.drop.y); - /* SDLTest_CommonEvent will free the string, below. */ - break; - - case SDL_EVENT_WINDOW_RESIZED: - WindowResized(event.window.data1, event.window.data2); - break; - - case SDL_EVENT_AUDIO_DEVICE_ADDED: - CreatePhysicalDeviceThing(event.adevice.which, event.adevice.iscapture); - break; - - case SDL_EVENT_AUDIO_DEVICE_REMOVED: { - const SDL_AudioDeviceID which = event.adevice.which; - Thing *i, *next; - SDL_Log("Removing audio device %u", (unsigned int) which); - for (i = things; i != NULL; i = next) { - next = i->next; - if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) { - TrashThing(i); - next = things; /* in case we mangled the list. */ - } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) { - TrashThing(i); - next = things; /* in case we mangled the list. */ - } - } - break; - } - - default: break; - } - - SDLTest_CommonEvent(state, &event, &done); - } - - TickThings(); - Draw(); - - if (!saw_event) { - SDL_Delay(10); - } - - #ifdef __EMSCRIPTEN__ - if (done) { - emscripten_cancel_main_loop(); - } - #endif -} - -int main(int argc, char *argv[]) +int SDL_AppInit(int argc, char *argv[]) { int i; state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); - if (state == NULL) { - Quit(1); + if (!state) { + return -1; } state->window_flags |= SDL_WINDOW_RESIZABLE; @@ -1208,13 +1060,13 @@ int main(int argc, char *argv[]) NULL }; SDLTest_CommonLogUsage(state, argv[0], options); - Quit(1); + return -1; } i += consumed; } if (!SDLTest_CommonInit(state)) { - Quit(2); + return -1; } if (state->audio_id) { @@ -1224,27 +1076,174 @@ int main(int argc, char *argv[]) SetDefaultTitleBar(); - physdev_texture = CreateTexture("physaudiodev.bmp"); - logdev_texture = CreateTexture("logaudiodev.bmp"); - audio_texture = CreateTexture("audiofile.bmp"); - trashcan_texture = CreateTexture("trashcan.bmp"); - soundboard_texture = CreateTexture("soundboard.bmp"); - soundboard_levels_texture = CreateTexture("soundboard_levels.bmp"); + if ((physdev_texture = CreateTexture("physaudiodev.bmp")) == NULL) { return -1; } + if ((logdev_texture = CreateTexture("logaudiodev.bmp")) == NULL) { return -1; } + if ((audio_texture = CreateTexture("audiofile.bmp")) == NULL) { return -1; } + if ((trashcan_texture = CreateTexture("trashcan.bmp")) == NULL) { return -1; } + if ((soundboard_texture = CreateTexture("soundboard.bmp")) == NULL) { return -1; } + if ((soundboard_levels_texture = CreateTexture("soundboard_levels.bmp")) == NULL) { return -1; } LoadStockWavThings(); CreateTrashcanThing(); CreateDefaultPhysicalDevice(SDL_FALSE); CreateDefaultPhysicalDevice(SDL_TRUE); -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(Loop, 0, 1); -#else - while (!done) { - Loop(); - } -#endif - - Quit(0); return 0; } + +static SDL_bool saw_event = SDL_FALSE; + +int SDL_AppEvent(const SDL_Event *event) +{ + Thing *thing = NULL; + + saw_event = SDL_TRUE; + + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: + thing = UpdateMouseOver(event->motion.x, event->motion.y); + if ((dragging_button == -1) && event->motion.state) { + if (event->motion.state & SDL_BUTTON_LMASK) { + /* for people that don't have all three buttons... */ + if (ctrl_held) { + dragging_button = SDL_BUTTON_RIGHT; + } else if (alt_held) { + dragging_button = SDL_BUTTON_MIDDLE; + } else { + dragging_button = SDL_BUTTON_LEFT; + } + dragging_button_real = SDL_BUTTON_LEFT; + } else if (event->motion.state & SDL_BUTTON_RMASK) { + dragging_button = SDL_BUTTON_RIGHT; + dragging_button_real = SDL_BUTTON_RIGHT; + } else if (event->motion.state & SDL_BUTTON_MMASK) { + dragging_button = SDL_BUTTON_MIDDLE; + dragging_button_real = SDL_BUTTON_MIDDLE; + } + + if (dragging_button != -1) { + dragging_thing = thing; + if (thing && thing->ondrag) { + thing->ondrag(thing, dragging_button, event->motion.x, event->motion.y); + } + } + } + + droppable_highlighted_thing = NULL; + if (dragging_thing) { + dragging_thing->rect.x = event->motion.x - (dragging_thing->rect.w / 2); + dragging_thing->rect.y = event->motion.y - (dragging_thing->rect.h / 2); + if (dragging_thing->can_be_dropped_onto) { + thing = FindThingAtPoint(event->motion.x, event->motion.y); + if (thing) { + int i; + for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) { + if (dragging_thing->can_be_dropped_onto[i] == thing->what) { + droppable_highlighted_thing = thing; + break; + } + } + } + } + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + thing = UpdateMouseOver(event->button.x, event->button.y); + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + if (dragging_button_real == event->button.button) { + Thing *dropped_thing = dragging_thing; + dragging_thing = NULL; + dragging_button = -1; + dragging_button_real = -1; + if (dropped_thing && dropped_thing->ondrop) { + dropped_thing->ondrop(dropped_thing, event->button.button, event->button.x, event->button.y); + } + droppable_highlighted_thing = NULL; + } + thing = UpdateMouseOver(event->button.x, event->button.y); + break; + + case SDL_EVENT_MOUSE_WHEEL: + UpdateMouseOver(event->wheel.mouseX, event->wheel.mouseY); + break; + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + ctrl_held = ((event->key.keysym.mod & SDL_KMOD_CTRL) != 0); + alt_held = ((event->key.keysym.mod & SDL_KMOD_ALT) != 0); + break; + + case SDL_EVENT_DROP_FILE: + SDL_Log("Drop file! '%s'", event->drop.data); + LoadWavThing(event->drop.data, event->drop.x, event->drop.y); + /* SDL frees event->drop.data for you when you use SDL_AppEvent(). */ + break; + + case SDL_EVENT_WINDOW_RESIZED: + WindowResized(event->window.data1, event->window.data2); + break; + + case SDL_EVENT_AUDIO_DEVICE_ADDED: + CreatePhysicalDeviceThing(event->adevice.which, event->adevice.iscapture); + break; + + case SDL_EVENT_AUDIO_DEVICE_REMOVED: { + const SDL_AudioDeviceID which = event->adevice.which; + Thing *i, *next; + SDL_Log("Removing audio device %u", (unsigned int) which); + for (i = things; i; i = next) { + next = i->next; + if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) { + TrashThing(i); + next = things; /* in case we mangled the list. */ + } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) { + TrashThing(i); + next = things; /* in case we mangled the list. */ + } + } + break; + } + + default: break; + } + + return SDLTest_CommonEventMainCallbacks(state, event); +} + +int SDL_AppIterate(void) +{ + if (app_ready_ticks == 0) { + app_ready_ticks = SDL_GetTicks(); + } + + TickThings(); + Draw(); + + if (saw_event) { + saw_event = SDL_FALSE; /* reset this so we know when SDL_AppEvent() runs again */ + } else { + SDL_Delay(10); + } + + return 0; /* keep going. */ +} + +void SDL_AppQuit(void) +{ + while (things) { + DestroyThing(things); /* make sure all the audio devices are closed, etc. */ + } + + DestroyTexture(physdev_texture); + DestroyTexture(logdev_texture); + DestroyTexture(audio_texture); + DestroyTexture(trashcan_texture); + DestroyTexture(soundboard_texture); + DestroyTexture(soundboard_levels_texture); + SDLTest_CommonQuit(state); +} + diff --git a/test/testaudiocapture.c b/test/testaudiocapture.c index efc4be10..04ab2f08 100644 --- a/test/testaudiocapture.c +++ b/test/testaudiocapture.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -10,94 +10,20 @@ freely. */ -#include - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif - static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; static SDL_AudioStream *stream_in = NULL; static SDL_AudioStream *stream_out = NULL; -static int done = 0; +static SDLTest_CommonState *state = NULL; -static void loop(void) -{ - const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in); - const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out); - SDL_bool please_quit = SDL_FALSE; - SDL_Event e; - - while (SDL_PollEvent(&e)) { - if (e.type == SDL_EVENT_QUIT) { - please_quit = SDL_TRUE; - } else if (e.type == SDL_EVENT_KEY_DOWN) { - if (e.key.keysym.sym == SDLK_ESCAPE) { - please_quit = SDL_TRUE; - } - } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { - if (e.button.button == 1) { - SDL_PauseAudioDevice(devid_out); - SDL_ResumeAudioDevice(devid_in); - } - } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) { - if (e.button.button == 1) { - SDL_PauseAudioDevice(devid_in); - SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */ - SDL_ResumeAudioDevice(devid_out); - } - } - } - - if (!SDL_AudioDevicePaused(devid_in)) { - SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); - } else { - SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); - } - SDL_RenderClear(renderer); - SDL_RenderPresent(renderer); - - /* Feed any new data we captured to the output stream. It'll play when we unpause the device. */ - while (!please_quit && (SDL_GetAudioStreamAvailable(stream_in) > 0)) { - Uint8 buf[1024]; - const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf)); - if (br < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError()); - please_quit = 1; - } else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError()); - please_quit = 1; - } - } - - if (please_quit) { - /* stop playing back, quit. */ - SDL_Log("Shutting down.\n"); - SDL_CloseAudioDevice(devid_in); - SDL_CloseAudioDevice(devid_out); - SDL_DestroyAudioStream(stream_in); - SDL_DestroyAudioStream(stream_out); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); -#ifdef __EMSCRIPTEN__ - emscripten_cancel_main_loop(); -#endif - /* Let 'main()' return normally */ - done = 1; - return; - } -} - -int main(int argc, char **argv) +int SDL_AppInit(int argc, char **argv) { SDL_AudioDeviceID *devices; - SDLTest_CommonState *state; SDL_AudioSpec outspec; SDL_AudioSpec inspec; SDL_AudioDeviceID device; @@ -105,9 +31,12 @@ int main(int argc, char **argv) const char *devname = NULL; int i; + /* this doesn't have to run very much, so give up tons of CPU time between iterations. */ + SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "15"); + /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -128,7 +57,7 @@ int main(int argc, char **argv) if (consumed <= 0) { static const char *options[] = { "[device_name]", NULL }; SDLTest_CommonLogUsage(state, argv[0], options); - exit(1); + return -1; } i += consumed; @@ -175,20 +104,17 @@ int main(int argc, char **argv) device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, NULL); if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for playback: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } SDL_PauseAudioDevice(device); SDL_GetAudioDeviceFormat(device, &outspec, NULL); stream_out = SDL_CreateAudioStream(&outspec, &outspec); if (!stream_out) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for playback: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } else if (SDL_BindAudioStream(device, stream_out) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for playback: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } SDL_Log("Opening capture device %s%s%s...\n", @@ -199,38 +125,89 @@ int main(int argc, char **argv) device = SDL_OpenAudioDevice(want_device, NULL); if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for capture: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } SDL_PauseAudioDevice(device); SDL_GetAudioDeviceFormat(device, &inspec, NULL); stream_in = SDL_CreateAudioStream(&inspec, &inspec); if (!stream_in) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for capture: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } else if (SDL_BindAudioStream(device, stream_in) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for capture: %s!\n", SDL_GetError()); - SDL_Quit(); - exit(1); + return -1; } SDL_SetAudioStreamFormat(stream_in, NULL, &outspec); /* make sure we output at the playback format. */ SDL_Log("Ready! Hold down mouse or finger to record!\n"); -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(loop, 0, 1); -#else - while (!done) { - loop(); - if (!done) { - SDL_Delay(16); - } - } -#endif - - SDLTest_CommonDestroyState(state); - return 0; } + +int SDL_AppEvent(const SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return 1; /* terminate as success. */ + } else if (event->type == SDL_EVENT_KEY_DOWN) { + if (event->key.keysym.sym == SDLK_ESCAPE) { + return 1; /* terminate as success. */ + } + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (event->button.button == 1) { + SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_out)); + SDL_FlushAudioStream(stream_out); /* so no samples are held back for resampling purposes. */ + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_in)); + } + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) { + if (event->button.button == 1) { + SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_in)); + SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */ + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_out)); + } + } + return 0; /* keep going. */ +} + +int SDL_AppIterate(void) +{ + if (!SDL_AudioDevicePaused(SDL_GetAudioStreamDevice(stream_in))) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + } else { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + } + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + + /* Feed any new data we captured to the output stream. It'll play when we unpause the device. */ + while (SDL_GetAudioStreamAvailable(stream_in) > 0) { + Uint8 buf[1024]; + const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf)); + if (br < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError()); + return -1; /* quit the app, report failure. */ + } else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError()); + return -1; /* quit the app, report failure. */ + } + } + + return 0; /* keep app going. */ +} + +void SDL_AppQuit(void) +{ + SDL_Log("Shutting down.\n"); + const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in); + const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out); + SDL_CloseAudioDevice(devid_in); /* !!! FIXME: use SDL_OpenAudioDeviceStream instead so we can dump this. */ + SDL_CloseAudioDevice(devid_out); + SDL_DestroyAudioStream(stream_in); + SDL_DestroyAudioStream(stream_out); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDLTest_CommonDestroyState(state); + SDL_Quit(); +} + + diff --git a/test/testaudiohotplug.c b/test/testaudiohotplug.c index 6f319848..daac0f7f 100644 --- a/test/testaudiohotplug.c +++ b/test/testaudiohotplug.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -72,7 +72,7 @@ static void iteration(void) const SDL_AudioDeviceID which = (SDL_AudioDeviceID) e.adevice.which; const SDL_bool iscapture = e.adevice.iscapture ? SDL_TRUE : SDL_FALSE; char *name = SDL_GetAudioDeviceName(which); - if (name != NULL) { + if (name) { SDL_Log("New %s audio device at id %u: %s", devtypestr(iscapture), (unsigned int)which, name); } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Got new %s device, id %u, but failed to get the name: %s", @@ -119,7 +119,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -154,7 +154,7 @@ int main(int argc, char *argv[]) /* Some targets (Mac CoreAudio) need an event queue for audio hotplug, so make and immediately hide a window. */ window = SDL_CreateWindow("testaudiohotplug", 640, 480, 0); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed: %s\n", SDL_GetError()); quit(1); } @@ -162,7 +162,7 @@ int main(int argc, char *argv[]) filename = GetResourceFilename(filename, "sample.wav"); - if (filename == NULL) { + if (!filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); quit(1); } diff --git a/test/testaudioinfo.c b/test/testaudioinfo.c index 8034f562..4ac371c0 100644 --- a/test/testaudioinfo.c +++ b/test/testaudioinfo.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ print_devices(SDL_bool iscapture) int frames; SDL_AudioDeviceID *devices = iscapture ? SDL_GetAudioCaptureDevices(&n) : SDL_GetAudioOutputDevices(&n); - if (devices == NULL) { + if (!devices) { SDL_Log(" Driver failed to report %s devices: %s\n\n", typestr, SDL_GetError()); } else if (n == 0) { SDL_Log(" No %s devices found.\n\n", typestr); @@ -31,7 +31,7 @@ print_devices(SDL_bool iscapture) SDL_Log("Found %d %s device%s:\n", n, typestr, n != 1 ? "s" : ""); for (i = 0; i < n; i++) { char *name = SDL_GetAudioDeviceName(devices[i]); - if (name != NULL) { + if (name) { SDL_Log(" %d: %s\n", i, name); SDL_free(name); } else { @@ -60,7 +60,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index 9dacc87d..b7b953ab 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -158,7 +158,7 @@ static void skip_audio(float amount) num_frames = (int)(new_spec.freq * ((speed * amount) / 100.0f)); buf = SDL_malloc(num_frames); - if (buf != NULL) { + if (buf) { retval = SDL_GetAudioStreamData(stream, buf, num_frames); SDL_free(buf); } @@ -233,9 +233,9 @@ static void loop(void) SDL_PauseAudioDevice(state->audio_id); } } else if (sym == SDLK_w) { - auto_loop = auto_loop ? SDL_FALSE : SDL_TRUE; + auto_loop = !auto_loop; } else if (sym == SDLK_e) { - auto_flush = auto_flush ? SDL_FALSE : SDL_TRUE; + auto_flush = !auto_flush; } else if (sym == SDLK_a) { SDL_ClearAudioStream(stream); SDL_Log("Cleared audio stream"); @@ -374,7 +374,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_AUDIO | SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testautomation.c b/test/testautomation.c index 2e483daf..7805bbc4 100644 --- a/test/testautomation.c +++ b/test/testautomation.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -33,17 +33,19 @@ static SDLTest_TestSuiteReference *testSuites[] = { &mainTestSuite, &mathTestSuite, &mouseTestSuite, + &penTestSuite, &pixelsTestSuite, &platformTestSuite, + &propertiesTestSuite, &rectTestSuite, &renderTestSuite, &rwopsTestSuite, &sdltestTestSuite, &stdlibTestSuite, &surfaceTestSuite, - &syswmTestSuite, &timerTestSuite, &videoTestSuite, + &subsystemsTestSuite, /* run last, not interfere with other test enviroment */ NULL }; @@ -71,7 +73,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); - if (state == NULL) { + if (!state) { return 1; } @@ -131,7 +133,7 @@ int main(int argc, char *argv[]) SDL_Log("Test suite: %s", testSuite->name); for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) { const SDLTest_TestCaseReference *testCase = testSuite->testCases[testCounter]; - SDL_Log(" test: %s", testCase->name); + SDL_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)"); } } return 0; diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index 829962fe..42d90fde 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -59,7 +59,7 @@ static SDL_AudioDeviceID g_audio_id = -1; /* Test case functions */ /** - * \brief Stop and restart audio subsystem + * Stop and restart audio subsystem * * \sa SDL_QuitSubSystem * \sa SDL_InitSubSystem @@ -77,7 +77,7 @@ static int audio_quitInitAudioSubSystem(void *arg) } /** - * \brief Start and stop audio directly + * Start and stop audio directly * * \sa SDL_InitAudio * \sa SDL_QuitAudio @@ -87,6 +87,7 @@ static int audio_initQuitAudio(void *arg) int result; int i, iMax; const char *audioDriver; + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); /* Stop SDL audio subsystem */ SDL_QuitSubSystem(SDL_INIT_AUDIO); @@ -102,6 +103,10 @@ static int audio_initQuitAudio(void *arg) SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL"); SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */ + if (hint && SDL_strcmp(audioDriver, hint) != 0) { + continue; + } + /* Call Init */ SDL_SetHint("SDL_AUDIO_DRIVER", audioDriver); result = SDL_InitSubSystem(SDL_INIT_AUDIO); @@ -133,7 +138,7 @@ static int audio_initQuitAudio(void *arg) } /** - * \brief Start, open, close and stop audio + * Start, open, close and stop audio * * \sa SDL_InitAudio * \sa SDL_OpenAudioDevice @@ -146,6 +151,7 @@ static int audio_initOpenCloseQuitAudio(void *arg) int i, iMax, j, k; const char *audioDriver; SDL_AudioSpec desired; + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); /* Stop SDL audio subsystem */ SDL_QuitSubSystem(SDL_INIT_AUDIO); @@ -161,6 +167,10 @@ static int audio_initOpenCloseQuitAudio(void *arg) SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL"); SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */ + if (hint && SDL_strcmp(audioDriver, hint) != 0) { + continue; + } + /* Change specs */ for (j = 0; j < 2; j++) { @@ -219,7 +229,7 @@ static int audio_initOpenCloseQuitAudio(void *arg) } /** - * \brief Pause and unpause audio + * Pause and unpause audio * * \sa SDL_PauseAudioDevice * \sa SDL_PlayAudioDevice @@ -231,6 +241,7 @@ static int audio_pauseUnpauseAudio(void *arg) int result; const char *audioDriver; SDL_AudioSpec desired; + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); /* Stop SDL audio subsystem */ SDL_QuitSubSystem(SDL_INIT_AUDIO); @@ -246,6 +257,10 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL"); SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */ + if (hint && SDL_strcmp(audioDriver, hint) != 0) { + continue; + } + /* Change specs */ for (j = 0; j < 2; j++) { @@ -340,7 +355,7 @@ static int audio_pauseUnpauseAudio(void *arg) } /** - * \brief Enumerate and name available audio devices (output and capture). + * Enumerate and name available audio devices (output and capture). * * \sa SDL_GetNumAudioDevices * \sa SDL_GetAudioDeviceName @@ -381,7 +396,7 @@ static int audio_enumerateAndNameAudioDevices(void *arg) } /** - * \brief Negative tests around enumeration and naming of audio devices. + * Negative tests around enumeration and naming of audio devices. * * \sa SDL_GetNumAudioDevices * \sa SDL_GetAudioDeviceName @@ -392,7 +407,7 @@ static int audio_enumerateAndNameAudioDevicesNegativeTests(void *arg) } /** - * \brief Checks available audio driver names. + * Checks available audio driver names. * * \sa SDL_GetNumAudioDrivers * \sa SDL_GetAudioDriver @@ -423,7 +438,7 @@ static int audio_printAudioDrivers(void *arg) } /** - * \brief Checks current audio driver name with initialized audio. + * Checks current audio driver name with initialized audio. * * \sa SDL_GetCurrentAudioDriver */ @@ -470,7 +485,7 @@ SDL_COMPILE_TIME_ASSERT(SDL_AUDIO_F32LE_FORMAT, SDL_AUDIO_F32LE == (SDL_AUDIO_BI SDL_COMPILE_TIME_ASSERT(SDL_AUDIO_F32BE_FORMAT, SDL_AUDIO_F32BE == (SDL_AUDIO_F32LE | SDL_AUDIO_MASK_BIG_ENDIAN)); /** - * \brief Builds various audio conversion structures + * Builds various audio conversion structures * * \sa SDL_CreateAudioStream */ @@ -481,6 +496,10 @@ static int audio_buildAudioStream(void *arg) SDL_AudioSpec spec2; int i, ii, j, jj, k, kk; + /* Call Quit */ + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); + /* No conversion needed */ spec1.format = SDL_AUDIO_S16LE; spec1.channels = 2; @@ -528,11 +547,14 @@ static int audio_buildAudioStream(void *arg) } } + /* Restart audio again */ + audioSetUp(NULL); + return TEST_COMPLETED; } /** - * \brief Checks calls with invalid input to SDL_CreateAudioStream + * Checks calls with invalid input to SDL_CreateAudioStream * * \sa SDL_CreateAudioStream */ @@ -612,7 +634,7 @@ static int audio_buildAudioStreamNegative(void *arg) } /** - * \brief Checks current audio status. + * Checks current audio status. * * \sa SDL_GetAudioDeviceStatus */ @@ -622,7 +644,7 @@ static int audio_getAudioStatus(void *arg) } /** - * \brief Opens, checks current audio status, and closes a device. + * Opens, checks current audio status, and closes a device. * * \sa SDL_GetAudioStatus */ @@ -632,7 +654,7 @@ static int audio_openCloseAndGetAudioStatus(void *arg) } /** - * \brief Locks and unlocks open audio device. + * Locks and unlocks open audio device. * * \sa SDL_LockAudioDevice * \sa SDL_UnlockAudioDevice @@ -643,7 +665,7 @@ static int audio_lockUnlockOpenAudioDevice(void *arg) } /** - * \brief Convert audio using various conversion structures + * Convert audio using various conversion structures * * \sa SDL_CreateAudioStream */ @@ -777,7 +799,7 @@ static int audio_convertAudio(void *arg) } /** - * \brief Opens, checks current connected status, and closes a device. + * Opens, checks current connected status, and closes a device. * * \sa SDL_AudioDeviceConnected */ @@ -799,7 +821,7 @@ static double sine_wave_sample(const Sint64 idx, const Sint64 rate, const Sint64 } /** - * \brief Check signal-to-noise ratio and maximum error of audio resampling. + * Check signal-to-noise ratio and maximum error of audio resampling. * * \sa https://wiki.libsdl.org/SDL_CreateAudioStream * \sa https://wiki.libsdl.org/SDL_DestroyAudioStream @@ -956,7 +978,7 @@ static int audio_resampleLoss(void *arg) } /** - * \brief Check accuracy converting between audio formats. + * Check accuracy converting between audio formats. * * \sa SDL_ConvertAudioSamples */ @@ -1091,7 +1113,7 @@ static int audio_convertAccuracy(void *arg) } /** - * \brief Check accuracy when switching between formats + * Check accuracy when switching between formats * * \sa SDL_SetAudioStreamFormat */ diff --git a/test/testautomation_clipboard.c b/test/testautomation_clipboard.c index c0be1968..bdc3b52d 100644 --- a/test/testautomation_clipboard.c +++ b/test/testautomation_clipboard.c @@ -74,7 +74,7 @@ static void ClipboardCleanupCallback(void *userdata) /* Test case functions */ /** - * \brief End-to-end test of SDL_xyzClipboardData functions + * End-to-end test of SDL_xyzClipboardData functions * \sa SDL_HasClipboardData * \sa SDL_GetClipboardData * \sa SDL_SetClipboardData @@ -373,7 +373,7 @@ static int clipboard_testClipboardDataFunctions(void *arg) } /** - * \brief End-to-end test of SDL_xyzClipboardText functions + * End-to-end test of SDL_xyzClipboardText functions * \sa SDL_HasClipboardText * \sa SDL_GetClipboardText * \sa SDL_SetClipboardText @@ -457,7 +457,7 @@ static int clipboard_testClipboardTextFunctions(void *arg) } /** - * \brief End-to-end test of SDL_xyzPrimarySelectionText functions + * End-to-end test of SDL_xyzPrimarySelectionText functions * \sa SDL_HasPrimarySelectionText * \sa SDL_GetPrimarySelectionText * \sa SDL_SetPrimarySelectionText diff --git a/test/testautomation_events.c b/test/testautomation_events.c index faa27b64..14418f76 100644 --- a/test/testautomation_events.c +++ b/test/testautomation_events.c @@ -38,7 +38,7 @@ static int SDLCALL events_sampleNullEventFilter(void *userdata, SDL_Event *event } /** - * \brief Test pumping and peeking events. + * Test pumping and peeking events. * * \sa SDL_PumpEvents * \sa SDL_PollEvent @@ -75,7 +75,7 @@ static int events_pushPumpAndPollUserevent(void *arg) } /** - * \brief Adds and deletes an event watch function with NULL userdata + * Adds and deletes an event watch function with NULL userdata * * \sa SDL_AddEventWatch * \sa SDL_DelEventWatch @@ -125,7 +125,7 @@ static int events_addDelEventWatch(void *arg) } /** - * \brief Adds and deletes an event watch function with userdata + * Adds and deletes an event watch function with userdata * * \sa SDL_AddEventWatch * \sa SDL_DelEventWatch diff --git a/test/testautomation_guid.c b/test/testautomation_guid.c index 1de17566..7bd1d90e 100644 --- a/test/testautomation_guid.c +++ b/test/testautomation_guid.c @@ -70,7 +70,7 @@ upper_lower_to_bytestring(Uint8 *out, Uint64 upper, Uint64 lower) /* Test case functions */ /** - * \brief Check String-to-GUID conversion + * Check String-to-GUID conversion * * \sa SDL_GUIDFromString */ @@ -95,7 +95,7 @@ TestGuidFromString(void *arg) } /** - * \brief Check GUID-to-String conversion + * Check GUID-to-String conversion * * \sa SDL_GUIDToString */ @@ -126,7 +126,7 @@ TestGuidToString(void *arg) SDL_GUIDToString(guid, guid_str, size); /* Check bytes before guid_str_buf */ - expected_prefix = fill_char | (fill_char << 8) | (fill_char << 16) | (fill_char << 24); + expected_prefix = fill_char | (fill_char << 8) | (fill_char << 16) | (((Uint32)fill_char) << 24); SDL_memcpy(&actual_prefix, guid_str_buf, 4); SDLTest_AssertCheck(expected_prefix == actual_prefix, "String buffer memory before output untouched, expected: %" SDL_PRIu32 ", got: %" SDL_PRIu32 ", at size=%d", expected_prefix, actual_prefix, size); diff --git a/test/testautomation_hints.c b/test/testautomation_hints.c index c1244b82..0e22b707 100644 --- a/test/testautomation_hints.c +++ b/test/testautomation_hints.c @@ -24,7 +24,6 @@ static const char *HintsEnum[] = { SDL_HINT_VIDEO_ALLOW_SCREENSAVER, SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT, SDL_HINT_VIDEO_WIN_D3DCOMPILER, SDL_HINT_VIDEO_X11_XRANDR, SDL_HINT_XINPUT_ENABLED, @@ -47,7 +46,6 @@ static const char *HintsVerbose[] = { "SDL_VIDEO_ALLOW_SCREENSAVER", "SDL_VIDEO_MAC_FULLSCREEN_SPACES", "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", - "SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT", "SDL_VIDEO_WIN_D3DCOMPILER", "SDL_VIDEO_X11_XRANDR", "SDL_XINPUT_ENABLED" @@ -60,7 +58,7 @@ static const int numHintsEnum = SDL_arraysize(HintsEnum); /* Test case functions */ /** - * \brief Call to SDL_GetHint + * Call to SDL_GetHint */ static int hints_getHint(void *arg) { @@ -89,7 +87,7 @@ static void SDLCALL hints_testHintChanged(void *userdata, const char *name, cons } /** - * \brief Call to SDL_SetHint + * Call to SDL_SetHint */ static int hints_setHint(void *arg) { diff --git a/test/testautomation_images.c b/test/testautomation_images.c index 389d7015..f3c7b1e6 100644 --- a/test/testautomation_images.c +++ b/test/testautomation_images.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -528,7 +528,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlit = { }; /** - * \brief Returns the Blit test image as SDL_Surface. + * Returns the Blit test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlit(void) { @@ -1000,7 +1000,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitColor = { }; /** - * \brief Returns the BlitColor test image as SDL_Surface. + * Returns the BlitColor test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitColor(void) { @@ -1635,7 +1635,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitAlpha = { }; /** - * \brief Returns the BlitAlpha test image as SDL_Surface. + * Returns the BlitAlpha test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitAlpha(void) { @@ -2203,7 +2203,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitBlendAdd = { }; /** - * \brief Returns the BlitBlendAdd test image as SDL_Surface. + * Returns the BlitBlendAdd test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitBlendAdd(void) { @@ -2792,7 +2792,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitBlend = { }; /** - * \brief Returns the BlitBlend test image as SDL_Surface. + * Returns the BlitBlend test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitBlend(void) { @@ -3211,7 +3211,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitBlendMod = { }; /** - * \brief Returns the BlitBlendMod test image as SDL_Surface. + * Returns the BlitBlendMod test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitBlendMod(void) { @@ -4013,7 +4013,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitBlendNone = { }; /** - * \brief Returns the BlitBlendNone test image as SDL_Surface. + * Returns the BlitBlendNone test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitBlendNone(void) { @@ -4547,7 +4547,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageBlitBlendAll = { }; /** - * \brief Returns the BlitBlendAll test image as SDL_Surface. + * Returns the BlitBlendAll test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageBlitBlendAll(void) { @@ -4758,7 +4758,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imageFace = { }; /** - * \brief Returns the Face test image as SDL_Surface. + * Returns the Face test image as SDL_Surface. */ SDL_Surface *SDLTest_ImageFace(void) { @@ -5251,7 +5251,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imagePrimitives = { }; /** - * \brief Returns the Primitives test image as SDL_Surface. + * Returns the Primitives test image as SDL_Surface. */ SDL_Surface *SDLTest_ImagePrimitives(void) { @@ -5916,7 +5916,7 @@ static const SDLTest_SurfaceImage_t SDLTest_imagePrimitivesBlend = { }; /** - * \brief Returns the PrimitivesBlend test image as SDL_Surface. + * Returns the PrimitivesBlend test image as SDL_Surface. */ SDL_Surface *SDLTest_ImagePrimitivesBlend(void) { diff --git a/test/testautomation_images.h b/test/testautomation_images.h index a0b0f108..92b381e5 100644 --- a/test/testautomation_images.h +++ b/test/testautomation_images.h @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/testautomation_intrinsics.c b/test/testautomation_intrinsics.c index da4617b6..566be51c 100644 --- a/test/testautomation_intrinsics.c +++ b/test/testautomation_intrinsics.c @@ -86,7 +86,7 @@ static void free_arrays(void *dest, void *a, void *b) { } /** - * \brief Verify element-wise addition of 2 int arrays. + * Verify element-wise addition of 2 int arrays. */ static void verify_ints_addition(const Sint32 *dest, const Sint32 *a, const Sint32 *b, size_t size, const char *desc) { size_t i; @@ -106,7 +106,7 @@ static void verify_ints_addition(const Sint32 *dest, const Sint32 *a, const Sint } /** - * \brief Verify element-wise multiplication of 2 int arrays. + * Verify element-wise multiplication of 2 int arrays. */ static void verify_ints_multiplication(const Sint32 *dest, const Sint32 *a, const Sint32 *b, size_t size, const char *desc) { size_t i; @@ -126,7 +126,7 @@ static void verify_ints_multiplication(const Sint32 *dest, const Sint32 *a, cons } /** - * \brief Verify element-wise addition of 2 float arrays. + * Verify element-wise addition of 2 float arrays. */ static void verify_floats_addition(const float *dest, const float *a, const float *b, size_t size, const char *desc) { size_t i; @@ -147,7 +147,7 @@ static void verify_floats_addition(const float *dest, const float *a, const floa } /** - * \brief Verify element-wise addition of 2 double arrays. + * Verify element-wise addition of 2 double arrays. */ static void verify_doubles_addition(const double *dest, const double *a, const double *b, size_t size, const char *desc) { size_t i; diff --git a/test/testautomation_joystick.c b/test/testautomation_joystick.c index 54b9b3e4..4f191047 100644 --- a/test/testautomation_joystick.c +++ b/test/testautomation_joystick.c @@ -12,7 +12,7 @@ /* Test case functions */ /** - * \brief Check virtual joystick creation + * Check virtual joystick creation * * \sa SDL_AttachVirtualJoystickEx */ @@ -20,6 +20,7 @@ static int TestVirtualJoystick(void *arg) { SDL_VirtualJoystickDesc desc; SDL_Joystick *joystick = NULL; + SDL_Gamepad *gamepad = NULL; SDL_JoystickID device_id; SDLTest_AssertCheck(SDL_InitSubSystem(SDL_INIT_GAMEPAD) == 0, "SDL_InitSubSystem(SDL_INIT_GAMEPAD)"); @@ -52,13 +53,65 @@ static int TestVirtualJoystick(void *arg) SDLTest_AssertCheck(SDL_GetNumJoystickHats(joystick) == desc.nhats, "SDL_GetNumJoystickHats()"); SDLTest_AssertCheck(SDL_GetNumJoystickButtons(joystick) == desc.nbuttons, "SDL_GetNumJoystickButtons()"); - SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_A, SDL_PRESSED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_A, SDL_PRESSED)"); + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED)"); SDL_UpdateJoysticks(); - SDLTest_AssertCheck(SDL_GetJoystickButton(joystick, SDL_GAMEPAD_BUTTON_A) == SDL_PRESSED, "SDL_GetJoystickButton(SDL_GAMEPAD_BUTTON_A) == SDL_PRESSED"); - SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_A, SDL_RELEASED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_A, SDL_RELEASED)"); + SDLTest_AssertCheck(SDL_GetJoystickButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED, "SDL_GetJoystickButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED"); + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED)"); SDL_UpdateJoysticks(); - SDLTest_AssertCheck(SDL_GetJoystickButton(joystick, SDL_GAMEPAD_BUTTON_A) == SDL_RELEASED, "SDL_GetJoystickButton(SDL_GAMEPAD_BUTTON_A) == SDL_RELEASED"); + SDLTest_AssertCheck(SDL_GetJoystickButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED, "SDL_GetJoystickButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED"); + + gamepad = SDL_OpenGamepad(SDL_GetJoystickInstanceID(joystick)); + SDLTest_AssertCheck(gamepad != NULL, "SDL_OpenGamepad() succeeded"); + if (gamepad) { + SDLTest_AssertCheck(SDL_strcmp(SDL_GetGamepadName(gamepad), desc.name) == 0, "SDL_GetGamepadName()"); + SDLTest_AssertCheck(SDL_GetGamepadVendor(gamepad) == desc.vendor_id, "SDL_GetGamepadVendor()"); + SDLTest_AssertCheck(SDL_GetGamepadProduct(gamepad) == desc.product_id, "SDL_GetGamepadProduct()"); + + /* Set an explicit mapping with a different name */ + SDL_SetGamepadMapping(SDL_GetJoystickInstanceID(joystick), "ff0013db5669727475616c2043007601,Virtual Gamepad,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + SDLTest_AssertCheck(SDL_strcmp(SDL_GetGamepadName(gamepad), "Virtual Gamepad") == 0, "SDL_GetGamepadName() == Virtual Gamepad"); + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_A, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_A"); + + /* Set the south button and verify that the gamepad responds */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED"); + + /* Set an explicit mapping with legacy Nintendo style buttons */ + SDL_SetGamepadMapping(SDL_GetJoystickInstanceID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); + SDLTest_AssertCheck(SDL_strcmp(SDL_GetGamepadName(gamepad), "Virtual Nintendo Gamepad") == 0, "SDL_GetGamepadName() == Virtual Nintendo Gamepad"); + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_B, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_B"); + + /* Set the south button and verify that the gamepad responds */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED"); + + /* Set an explicit mapping with PS4 style buttons */ + SDL_SetGamepadMapping(SDL_GetJoystickInstanceID(joystick), "ff0013db5669727475616c2043007601,Virtual PS4 Gamepad,type:ps4,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + SDLTest_AssertCheck(SDL_strcmp(SDL_GetGamepadName(gamepad), "Virtual PS4 Gamepad") == 0, "SDL_GetGamepadName() == Virtual PS4 Gamepad"); + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_CROSS, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_CROSS"); + + /* Set the south button and verify that the gamepad responds */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_PRESSED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_PRESSED"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED) == 0, "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_SOUTH, SDL_RELEASED)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == SDL_RELEASED"); + + SDL_CloseGamepad(gamepad); + } SDL_CloseJoystick(joystick); } diff --git a/test/testautomation_keyboard.c b/test/testautomation_keyboard.c index 5a3d0e90..b7a3f19d 100644 --- a/test/testautomation_keyboard.c +++ b/test/testautomation_keyboard.c @@ -11,7 +11,7 @@ /* Test case functions */ /** - * \brief Check call to SDL_GetKeyboardState with and without numkeys reference. + * Check call to SDL_GetKeyboardState with and without numkeys reference. * * \sa SDL_GetKeyboardState */ @@ -36,7 +36,7 @@ static int keyboard_getKeyboardState(void *arg) } /** - * \brief Check call to SDL_GetKeyboardFocus + * Check call to SDL_GetKeyboardFocus * * \sa SDL_GetKeyboardFocus */ @@ -50,7 +50,7 @@ static int keyboard_getKeyboardFocus(void *arg) } /** - * \brief Check call to SDL_GetKeyFromName for known, unknown and invalid name. + * Check call to SDL_GetKeyFromName for known, unknown and invalid name. * * \sa SDL_GetKeyFromName */ @@ -115,7 +115,7 @@ static void checkInvalidScancodeError(void) } /** - * \brief Check call to SDL_GetKeyFromScancode + * Check call to SDL_GetKeyFromScancode * * \sa SDL_GetKeyFromScancode */ @@ -153,7 +153,7 @@ static int keyboard_getKeyFromScancode(void *arg) } /** - * \brief Check call to SDL_GetKeyName + * Check call to SDL_GetKeyName * * \sa SDL_GetKeyName */ @@ -208,7 +208,7 @@ static int keyboard_getKeyName(void *arg) } /** - * \brief SDL_GetScancodeName negative cases + * SDL_GetScancodeName negative cases * * \sa SDL_GetScancodeName */ @@ -234,7 +234,7 @@ static int keyboard_getScancodeNameNegative(void *arg) } /** - * \brief SDL_GetKeyName negative cases + * SDL_GetKeyName negative cases * * \sa SDL_GetKeyName */ @@ -270,7 +270,7 @@ static int keyboard_getKeyNameNegative(void *arg) } /** - * \brief Check call to SDL_GetModState and SDL_SetModState + * Check call to SDL_GetModState and SDL_SetModState * * \sa SDL_GetModState * \sa SDL_SetModState @@ -329,7 +329,7 @@ static int keyboard_getSetModState(void *arg) } /** - * \brief Check call to SDL_StartTextInput and SDL_StopTextInput + * Check call to SDL_StartTextInput and SDL_StopTextInput * * \sa SDL_StartTextInput * \sa SDL_StopTextInput @@ -375,7 +375,7 @@ static void testSetTextInputRect(SDL_Rect refRect) } /** - * \brief Check call to SDL_SetTextInputRect + * Check call to SDL_SetTextInputRect * * \sa SDL_SetTextInputRect */ @@ -454,7 +454,7 @@ static int keyboard_setTextInputRect(void *arg) } /** - * \brief Check call to SDL_SetTextInputRect with invalid data + * Check call to SDL_SetTextInputRect with invalid data * * \sa SDL_SetTextInputRect */ @@ -491,7 +491,7 @@ static int keyboard_setTextInputRectNegative(void *arg) } /** - * \brief Check call to SDL_GetScancodeFromKey + * Check call to SDL_GetScancodeFromKey * * \sa SDL_GetScancodeFromKey * \sa SDL_Keycode @@ -514,7 +514,7 @@ static int keyboard_getScancodeFromKey(void *arg) } /** - * \brief Check call to SDL_GetScancodeFromName + * Check call to SDL_GetScancodeFromName * * \sa SDL_GetScancodeFromName * \sa SDL_Keycode @@ -585,7 +585,7 @@ static void checkInvalidNameError(void) } /** - * \brief Check call to SDL_GetScancodeFromName with invalid data + * Check call to SDL_GetScancodeFromName with invalid data * * \sa SDL_GetScancodeFromName * \sa SDL_Keycode diff --git a/test/testautomation_main.c b/test/testautomation_main.c index 7c350eda..01276ac3 100644 --- a/test/testautomation_main.c +++ b/test/testautomation_main.c @@ -11,7 +11,7 @@ #include "testautomation_suites.h" /** - * \brief Tests SDL_InitSubSystem() and SDL_QuitSubSystem() + * Tests SDL_InitSubSystem() and SDL_QuitSubSystem() * \sa SDL_Init * \sa SDL_Quit */ diff --git a/test/testautomation_math.c b/test/testautomation_math.c index 1f92a1d1..9d62809d 100644 --- a/test/testautomation_math.c +++ b/test/testautomation_math.c @@ -62,7 +62,7 @@ typedef double(SDLCALL *d_to_d_func)(double); typedef double(SDLCALL *dd_to_d_func)(double, double); /** - * \brief Runs all the cases on a given function with a signature double -> double. + * Runs all the cases on a given function with a signature double -> double. * The result is expected to be exact. * * \param func_name a printable name for the tested function. @@ -77,7 +77,7 @@ helper_dtod(const char *func_name, d_to_d_func func, Uint32 i; for (i = 0; i < cases_size; i++) { const double result = func(cases[i].input); - SDLTest_AssertCheck(result == cases[i].expected, + SDLTest_AssertCheck((result - cases[i].expected) < FLT_EPSILON, "%s(%f), expected %f, got %f", func_name, cases[i].input, @@ -88,7 +88,7 @@ helper_dtod(const char *func_name, d_to_d_func func, } /** - * \brief Runs all the cases on a given function with a signature double -> double. + * Runs all the cases on a given function with a signature double -> double. * Checks if the result between expected +/- EPSILON. * * \param func_name a printable name for the tested function. @@ -117,7 +117,7 @@ helper_dtod_inexact(const char *func_name, d_to_d_func func, } /** - * \brief Runs all the cases on a given function with a signature + * Runs all the cases on a given function with a signature * (double, double) -> double. The result is expected to be exact. * * \param func_name a printable name for the tested function. @@ -143,7 +143,7 @@ helper_ddtod(const char *func_name, dd_to_d_func func, } /** - * \brief Runs all the cases on a given function with a signature + * Runs all the cases on a given function with a signature * (double, double) -> double. Checks if the result between expected +/- EPSILON. * * \param func_name a printable name for the tested function. @@ -172,7 +172,7 @@ helper_ddtod_inexact(const char *func_name, dd_to_d_func func, } /** - * \brief Runs a range of values on a given function with a signature double -> double + * Runs a range of values on a given function with a signature double -> double * * This function is only meant to test functions that returns the input value if it is * integral: f(x) -> x for x in N. @@ -1139,7 +1139,7 @@ log_baseCases(void *args) 1.0, 0.0, result); result = SDL_log(EULER); - SDLTest_AssertCheck(1.0 == result, + SDLTest_AssertCheck((result - 1.) < FLT_EPSILON, "Log(%f), expected %f, got %f", EULER, 1.0, result); @@ -2275,7 +2275,7 @@ acos_limitCases(void *args) 1.0, 0.0, result); result = SDL_acos(-1.0); - SDLTest_AssertCheck(SDL_PI_D == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D - result) <= EPSILON, "Acos(%f), expected %f, got %f", -1.0, SDL_PI_D, result); @@ -2362,12 +2362,12 @@ asin_limitCases(void *args) double result; result = SDL_asin(1.0); - SDLTest_AssertCheck(SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D / 2.0 - result) <= EPSILON, "Asin(%f), expected %f, got %f", 1.0, SDL_PI_D / 2.0, result); result = SDL_asin(-1.0); - SDLTest_AssertCheck(-SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(-SDL_PI_D / 2.0 - result) <= EPSILON, "Asin(%f), expected %f, got %f", -1.0, -SDL_PI_D / 2.0, result); @@ -2554,7 +2554,7 @@ atan2_bothZeroCases(void *args) { 0.0, -0.0, SDL_PI_D }, { -0.0, -0.0, -SDL_PI_D }, }; - return helper_ddtod("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); + return helper_ddtod_inexact("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); } /** @@ -2573,7 +2573,7 @@ atan2_yZeroCases(void *args) { -0.0, 1.0, -0.0 }, { -0.0, -1.0, -SDL_PI_D } }; - return helper_ddtod("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); + return helper_ddtod_inexact("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); } /** @@ -2589,7 +2589,7 @@ atan2_xZeroCases(void *args) { 1.0, -0.0, SDL_PI_D / 2.0 }, { -1.0, -0.0, -SDL_PI_D / 2.0 } }; - return helper_ddtod("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); + return helper_ddtod_inexact("SDL_atan2", SDL_atan2, cases, SDL_arraysize(cases)); } /* Infinity cases */ @@ -2608,22 +2608,22 @@ atan2_bothInfCases(void *args) double result; result = SDL_atan2(INFINITY, INFINITY); - SDLTest_AssertCheck(SDL_PI_D / 4.0 == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D / 4.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", INFINITY, INFINITY, SDL_PI_D / 4.0, result); result = SDL_atan2(INFINITY, -INFINITY); - SDLTest_AssertCheck(3.0 * SDL_PI_D / 4.0 == result, + SDLTest_AssertCheck(SDL_fabs(3.0 * SDL_PI_D / 4.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", INFINITY, -INFINITY, 3.0 * SDL_PI_D / 4.0, result); result = SDL_atan2(-INFINITY, INFINITY); - SDLTest_AssertCheck(-SDL_PI_D / 4.0 == result, + SDLTest_AssertCheck(SDL_fabs(-SDL_PI_D / 4.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", -INFINITY, INFINITY, -SDL_PI_D / 4.0, result); result = SDL_atan2(-INFINITY, -INFINITY); - SDLTest_AssertCheck(-3.0 * SDL_PI_D / 4.0 == result, + SDLTest_AssertCheck(SDL_fabs(-3.0 * SDL_PI_D / 4.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", -INFINITY, -INFINITY, -3.0 * SDL_PI_D / 4.0, result); @@ -2640,22 +2640,22 @@ atan2_yInfCases(void *args) double result; result = SDL_atan2(INFINITY, 1.0); - SDLTest_AssertCheck(SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D / 2.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", INFINITY, 1.0, SDL_PI_D / 2.0, result); result = SDL_atan2(INFINITY, -1.0); - SDLTest_AssertCheck(SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D / 2.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", INFINITY, -1.0, SDL_PI_D / 2.0, result); result = SDL_atan2(-INFINITY, 1.0); - SDLTest_AssertCheck(-SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(-SDL_PI_D / 2.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", -INFINITY, 1.0, -SDL_PI_D / 2.0, result); result = SDL_atan2(-INFINITY, -1.0); - SDLTest_AssertCheck(-SDL_PI_D / 2.0 == result, + SDLTest_AssertCheck(SDL_fabs(-SDL_PI_D / 2.0 - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", -INFINITY, -1.0, -SDL_PI_D / 2.0, result); @@ -2684,12 +2684,12 @@ atan2_xInfCases(void *args) -1.0, INFINITY, -0.0, result); result = SDL_atan2(1.0, -INFINITY); - SDLTest_AssertCheck(SDL_PI_D == result, + SDLTest_AssertCheck(SDL_fabs(SDL_PI_D - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", 1.0, -INFINITY, SDL_PI_D, result); result = SDL_atan2(-1.0, -INFINITY); - SDLTest_AssertCheck(-SDL_PI_D == result, + SDLTest_AssertCheck(SDL_fabs(-SDL_PI_D - result) <= EPSILON, "Atan2(%f,%f), expected %f, got %f", -1.0, -INFINITY, -SDL_PI_D, result); diff --git a/test/testautomation_mouse.c b/test/testautomation_mouse.c index b0a94849..af037f02 100644 --- a/test/testautomation_mouse.c +++ b/test/testautomation_mouse.c @@ -25,7 +25,7 @@ static int mouseStateCheck(Uint32 state) } /** - * \brief Check call to SDL_GetMouseState + * Check call to SDL_GetMouseState * */ static int mouse_getMouseState(void *arg) @@ -70,7 +70,7 @@ static int mouse_getMouseState(void *arg) } /** - * \brief Check call to SDL_GetRelativeMouseState + * Check call to SDL_GetRelativeMouseState * */ static int mouse_getRelativeMouseState(void *arg) @@ -188,7 +188,7 @@ static SDL_Cursor *initArrowCursor(const char *image[]) } /** - * \brief Check call to SDL_CreateCursor and SDL_DestroyCursor + * Check call to SDL_CreateCursor and SDL_DestroyCursor * * \sa SDL_CreateCursor * \sa SDL_DestroyCursor @@ -213,7 +213,7 @@ static int mouse_createFreeCursor(void *arg) } /** - * \brief Check call to SDL_CreateColorCursor and SDL_DestroyCursor + * Check call to SDL_CreateColorCursor and SDL_DestroyCursor * * \sa SDL_CreateColorCursor * \sa SDL_DestroyCursor @@ -269,7 +269,7 @@ static void changeCursorVisibility(SDL_bool state) } /** - * \brief Check call to SDL_ShowCursor + * Check call to SDL_ShowCursor * * \sa SDL_ShowCursor */ @@ -294,7 +294,7 @@ static int mouse_showCursor(void *arg) } /** - * \brief Check call to SDL_SetCursor + * Check call to SDL_SetCursor * * \sa SDL_SetCursor */ @@ -326,7 +326,7 @@ static int mouse_setCursor(void *arg) } /** - * \brief Check call to SDL_GetCursor + * Check call to SDL_GetCursor * * \sa SDL_GetCursor */ @@ -343,7 +343,7 @@ static int mouse_getCursor(void *arg) } /** - * \brief Check call to SDL_GetRelativeMouseMode and SDL_SetRelativeMouseMode + * Check call to SDL_GetRelativeMouseMode and SDL_SetRelativeMouseMode * * \sa SDL_GetRelativeMouseMode * \sa SDL_SetRelativeMouseMode @@ -418,7 +418,7 @@ static SDL_Window *createMouseSuiteTestWindow(void) */ static void destroyMouseSuiteTestWindow(SDL_Window *window) { - if (window != NULL) { + if (window) { SDL_DestroyWindow(window); window = NULL; SDLTest_AssertPass("SDL_DestroyWindow()"); @@ -426,7 +426,7 @@ static void destroyMouseSuiteTestWindow(SDL_Window *window) } /** - * \brief Check call to SDL_WarpMouseInWindow + * Check call to SDL_WarpMouseInWindow * * \sa SDL_WarpMouseInWindow */ @@ -454,7 +454,7 @@ static int mouse_warpMouseInWindow(void *arg) yPositions[5] = (float)h + 1; /* Create test window */ window = createMouseSuiteTestWindow(); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -489,7 +489,7 @@ static int mouse_warpMouseInWindow(void *arg) } /** - * \brief Check call to SDL_GetMouseFocus + * Check call to SDL_GetMouseFocus * * \sa SDL_GetMouseFocus */ @@ -499,6 +499,7 @@ static int mouse_getMouseFocus(void *arg) float x, y; SDL_Window *window; SDL_Window *focusWindow; + const SDL_bool video_driver_is_wayland = !SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland"); /* Get focus - focus non-deterministic */ focusWindow = SDL_GetMouseFocus(); @@ -506,32 +507,37 @@ static int mouse_getMouseFocus(void *arg) /* Create test window */ window = createMouseSuiteTestWindow(); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } - /* Mouse to random position inside window */ - x = (float)SDLTest_RandomIntegerInRange(1, w - 1); - y = (float)SDLTest_RandomIntegerInRange(1, h - 1); - SDL_WarpMouseInWindow(window, x, y); - SDLTest_AssertPass("SDL_WarpMouseInWindow(...,%.f,%.f)", x, y); + /* Wayland explicitly disallows warping the mouse pointer, so this test must be skipped. */ + if (!video_driver_is_wayland) { + /* Mouse to random position inside window */ + x = (float)SDLTest_RandomIntegerInRange(1, w - 1); + y = (float)SDLTest_RandomIntegerInRange(1, h - 1); + SDL_WarpMouseInWindow(window, x, y); + SDLTest_AssertPass("SDL_WarpMouseInWindow(...,%.f,%.f)", x, y); - /* Pump events to update focus state */ - SDL_Delay(100); - SDL_PumpEvents(); - SDLTest_AssertPass("SDL_PumpEvents()"); + /* Pump events to update focus state */ + SDL_Delay(100); + SDL_PumpEvents(); + SDLTest_AssertPass("SDL_PumpEvents()"); - /* Get focus with explicit window setup - focus deterministic */ - focusWindow = SDL_GetMouseFocus(); - SDLTest_AssertPass("SDL_GetMouseFocus()"); - SDLTest_AssertCheck(focusWindow != NULL, "Check returned window value is not NULL"); - SDLTest_AssertCheck(focusWindow == window, "Check returned window value is test window"); + /* Get focus with explicit window setup - focus deterministic */ + focusWindow = SDL_GetMouseFocus(); + SDLTest_AssertPass("SDL_GetMouseFocus()"); + SDLTest_AssertCheck(focusWindow != NULL, "Check returned window value is not NULL"); + SDLTest_AssertCheck(focusWindow == window, "Check returned window value is test window"); - /* Mouse to random position outside window */ - x = (float)SDLTest_RandomIntegerInRange(-9, -1); - y = (float)SDLTest_RandomIntegerInRange(-9, -1); - SDL_WarpMouseInWindow(window, x, y); - SDLTest_AssertPass("SDL_WarpMouseInWindow(...,%.f,%.f)", x, y); + /* Mouse to random position outside window */ + x = (float)SDLTest_RandomIntegerInRange(-9, -1); + y = (float)SDLTest_RandomIntegerInRange(-9, -1); + SDL_WarpMouseInWindow(window, x, y); + SDLTest_AssertPass("SDL_WarpMouseInWindow(...,%.f,%.f)", x, y); + } else { + SDLTest_Log("Skipping mouse warp focus tests: Wayland does not support warping the mouse pointer"); + } /* Clean up test window */ destroyMouseSuiteTestWindow(window); @@ -549,7 +555,7 @@ static int mouse_getMouseFocus(void *arg) } /** - * \brief Check call to SDL_GetDefaultCursor + * Check call to SDL_GetDefaultCursor * * \sa SDL_GetDefaultCursor */ @@ -566,7 +572,7 @@ static int mouse_getDefaultCursor(void *arg) } /** - * \brief Check call to SDL_GetGlobalMouseState + * Check call to SDL_GetGlobalMouseState * * \sa SDL_GetGlobalMouseState */ diff --git a/test/testautomation_pen.c b/test/testautomation_pen.c new file mode 100644 index 00000000..d0c45084 --- /dev/null +++ b/test/testautomation_pen.c @@ -0,0 +1,1909 @@ +/* + Simple DirectMedia Layer + 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 + 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 + +/** + * Pen test suite + */ + +#define SDL_internal_h_ /* Inhibit dynamic symbol redefinitions that clash with ours */ + +/* ================= System Under Test (SUT) ================== */ +/* Renaming SUT operations to avoid link-time symbol clashes */ +#define SDL_GetPens SDL_SUT_GetPens +#define SDL_GetPenStatus SDL_SUT_GetPenStatus +#define SDL_GetPenFromGUID SDL_SUT_GetPenFromGUID +#define SDL_GetPenGUID SDL_SUT_GetPenGUID +#define SDL_PenConnected SDL_SUT_PenConnected +#define SDL_GetPenName SDL_SUT_GetPenName +#define SDL_GetPenCapabilities SDL_SUT_GetPenCapabilities +#define SDL_GetPenType SDL_SUT_GetPenType + +#define SDL_GetPenPtr SDL_SUT_GetPenPtr +#define SDL_PenModifyBegin SDL_SUT_PenModifyBegin +#define SDL_PenModifyAddCapabilities SDL_SUT_PenModifyAddCapabilities +#define SDL_PenModifyForWacomID SDL_SUT_PenModifyForWacomID +#define SDL_PenUpdateGUIDForWacom SDL_SUT_PenUpdateGUIDForWacom +#define SDL_PenUpdateGUIDForType SDL_SUT_PenUpdateGUIDForType +#define SDL_PenUpdateGUIDForGeneric SDL_SUT_PenUpdateGUIDForGeneric +#define SDL_PenModifyEnd SDL_SUT_PenModifyEnd +#define SDL_PenGCMark SDL_SUT_PenGCMark +#define SDL_PenGCSweep SDL_SUT_PenGCSweep +#define SDL_SendPenMotion SDL_SUT_SendPenMotion +#define SDL_SendPenButton SDL_SUT_SendPenButton +#define SDL_SendPenTipEvent SDL_SUT_SendPenTipEvent +#define SDL_SendPenWindowEvent SDL_SUT_SendPenWindowEvent +#define SDL_PenPerformHitTest SDL_SUT_PenPerformHitTest +#define SDL_PenInit SDL_SUT_PenInit +#define SDL_PenQuit SDL_SUT_PenQuit + +/* ================= Mock API ================== */ + +#include +#include +#include +/* For SDL_Window, SDL_Mouse, SDL_MouseID: */ +#include "../src/events/SDL_mouse_c.h" +/* Divert calls to mock mouse API: */ +#define SDL_SendMouseMotion SDL_Mock_SendMouseMotion +#define SDL_SendMouseButton SDL_Mock_SendMouseButton +#define SDL_GetMouse SDL_Mock_GetMouse +#define SDL_MousePositionInWindow SDL_Mock_MousePositionInWindow +#define SDL_SetMouseFocus SDL_Mock_SetMouseFocus + +/* Mock mouse API */ +static int SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, int relative, float x, float y); +static int SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 state, Uint8 button); +static SDL_Mouse *SDL_GetMouse(void); +static SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y); +static void SDL_SetMouseFocus(SDL_Window *window); + +/* Import SUT code with macro-renamed function names */ +#define SDL_waylanddyn_h_ /* hack: suppress spurious build problem with libdecor.h on Wayland */ +#include "../src/events/SDL_pen.c" +#include "../src/events/SDL_pen_c.h" + + +/* ================= Internal SDL API Compatibility ================== */ +/* Mock implementations of Pen -> Mouse calls */ +/* Not thread-safe! */ + +static SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y) +{ + return SDL_TRUE; +} + +static int _mouseemu_last_event = 0; +static float _mouseemu_last_x = 0.0f; +static float _mouseemu_last_y = 0.0f; +static int _mouseemu_last_mouseid = 0; +static int _mouseemu_last_button = 0; +static int _mouseemu_last_relative = 0; +static int _mouseemu_last_focus = -1; + +static int SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 state, Uint8 button) +{ + if (mouseID == SDL_PEN_MOUSEID) { + _mouseemu_last_event = (state == SDL_PRESSED) ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; + _mouseemu_last_button = button; + _mouseemu_last_mouseid = mouseID; + } + return 1; +} + +static int SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, int relative, float x, float y) +{ + if (mouseID == SDL_PEN_MOUSEID) { + _mouseemu_last_event = SDL_EVENT_MOUSE_MOTION; + _mouseemu_last_x = x; + _mouseemu_last_y = y; + _mouseemu_last_mouseid = mouseID; + _mouseemu_last_relative = relative; + } + return 1; +} + +static SDL_Mouse *SDL_GetMouse(void) +{ + static SDL_Mouse dummy_mouse; + + dummy_mouse.focus = NULL; + dummy_mouse.mouseID = 0; + + return &dummy_mouse; +} + +static void SDL_SetMouseFocus(SDL_Window *window) +{ + _mouseemu_last_focus = window ? 1 : 0; +} + +/* ================= Test Case Support ================== */ + +#define PEN_NUM_TEST_IDS 8 + +/* Helper functions */ + +/* Iterate over all pens to find index for pen ID, otherwise -1 */ +static int _pen_iterationFindsPenIDAt(SDL_PenID needle) +{ + int i; + int num_pens = -1; + + SDL_PenID *pens = SDL_GetPens(&num_pens); + /* Check for (a) consistency and (b) ability to handle NULL parameter */ + SDL_PenID *pens2 = SDL_GetPens(NULL); + + SDLTest_AssertCheck(num_pens >= 0, + "SDL_GetPens() yielded %d pens", num_pens); + SDLTest_AssertCheck(pens[num_pens] == 0, + "SDL_GetPens() not 0 terminated (num_pens = %d)", num_pens); + SDLTest_AssertCheck(pens2[num_pens] == 0, + "SDL_GetPens(NULL) not 0 terminated (num_pens = %d)", num_pens); + + for (i = 0; i < num_pens; ++i) { + SDLTest_AssertCheck(pens[i] == pens2[i], + "SDL_GetPens(&i) and SDL_GetPens(NULL) disagree at index %d/%d", i, num_pens); + SDLTest_AssertCheck(pens[i] != SDL_PEN_INVALID, + "Invalid pen ID %08lx at index %d/%d after SDL_GetPens()", (unsigned long) pens[i], i, num_pens); + } + SDL_free(pens2); + + for (i = 0; pens[i]; ++i) { + SDL_PenID pen_id = pens[i]; + + SDLTest_AssertCheck(pen_id != SDL_PEN_INVALID, + "Invalid pen ID %08lx at index %d/%d after SDL_GetPens()", (unsigned long) pen_id, i, num_pens); + if (pen_id == needle) { + SDL_free(pens); + return i; + } + } + SDL_free(pens); + return -1; +} + +/* Retrieve number of pens and sanity-check SDL_GetPens() */ +static int +_num_pens(void) +{ + int num_pens = -1; + SDL_PenID *pens = SDL_GetPens(&num_pens); + SDLTest_AssertCheck(pens != NULL, + "SDL_GetPens() => NULL"); + SDLTest_AssertCheck(num_pens >= 0, + "SDL_GetPens() reports %d pens", num_pens); + SDLTest_AssertCheck(pens[num_pens] == 0, + "SDL_GetPens()[%d] != 0", num_pens); + SDL_free(pens); + return num_pens; +} + +/* Assert number of pens is as expected */ +static void _AssertCheck_num_pens(int expected, char *location) +{ + int num_pens = _num_pens(); + SDLTest_AssertCheck(expected == num_pens, + "Expected SDL_GetPens() =>count = %d, actual = %d: %s", expected, num_pens, location); +} + +/* ---------------------------------------- */ +/* Test device deallocation */ + +typedef struct /* Collection of pen (de)allocation information */ +{ + unsigned int deallocated_id_flags; /* ith bits set to 1 if the ith test_id is deallocated */ + unsigned int deallocated_deviceinfo_flags; /* ith bits set to 1 if deviceinfo as *int with value i was deallocated */ + SDL_PenID ids[PEN_NUM_TEST_IDS]; + SDL_GUID guids[PEN_NUM_TEST_IDS]; + SDL_Window *window; + int num_ids; + int initial_pen_count; +} pen_testdata; + +/* SDL_PenGCSweep(): callback for tracking pen deallocation */ +static void _pen_testdata_callback(Uint32 deviceid, void *deviceinfo, void *tracker_ref) +{ + pen_testdata *tracker = (pen_testdata *)tracker_ref; + int offset = -1; + int i; + + for (i = 0; i < tracker->num_ids; ++i) { + if (deviceid == tracker->ids[i]) { + tracker->deallocated_id_flags |= (1 << i); + } + } + + SDLTest_AssertCheck(deviceinfo != NULL, + "Device %lu has deviceinfo", + (unsigned long) deviceid); + offset = *((int *)deviceinfo); + SDLTest_AssertCheck(offset >= 0 && offset <= 31, + "Device %lu has well-formed deviceinfo %d", + (unsigned long) deviceid, offset); + tracker->deallocated_deviceinfo_flags |= 1 << offset; + SDL_free(deviceinfo); +} + +/* GC Sweep tracking: update "tracker->deallocated_id_flags" and "tracker->deallocated_deviceinfo_flags" to record deallocations */ +static void _pen_trackGCSweep(pen_testdata *tracker) +{ + tracker->deallocated_id_flags = 0; + tracker->deallocated_deviceinfo_flags = 0; + SDL_PenGCSweep(tracker, _pen_testdata_callback); +} + +/* Finds a number of unused pen IDs (does not allocate them). Also initialises GUIDs. */ +static void _pen_unusedIDs(pen_testdata *tracker, int count) +{ + static int guidmod = 0; /* Ensure uniqueness as long as we use no more than 256 test pens */ + Uint32 synthetic_penid = 1000u; + int index = 0; + + tracker->num_ids = count; + SDLTest_AssertCheck(count < PEN_NUM_TEST_IDS, "Test setup: Valid number of test IDs requested: %d", (int)count); + + while (count--) { + int k; + + while (SDL_GetPenPtr(synthetic_penid)) { + ++synthetic_penid; + } + tracker->ids[index] = synthetic_penid; + for (k = 0; k < 15; ++k) { + tracker->guids[index].data[k] = (16 * k) + index; + } + tracker->guids[index].data[15] = ++guidmod; + + ++synthetic_penid; + ++index; + } +} + +#define DEVICEINFO_UNCHANGED -17 + +/* Allocate deviceinfo for pen */ +static void _pen_setDeviceinfo(SDL_Pen *pen, int deviceinfo) +{ + if (deviceinfo == DEVICEINFO_UNCHANGED) { + SDLTest_AssertCheck(pen->deviceinfo != NULL, + "pen->deviceinfo was already set for %p (%lu), as expected", + pen, (unsigned long) pen->header.id); + } else { + int *data = (int *)SDL_malloc(sizeof(int)); + *data = deviceinfo; + + SDLTest_AssertCheck(pen->deviceinfo == NULL, + "pen->deviceinfo was NULL for %p (%lu) when requesting deviceinfo %d", + pen, (unsigned long) pen->header.id, deviceinfo); + + pen->deviceinfo = data; + } + SDL_PenModifyEnd(pen, SDL_TRUE); +} + +/* ---------------------------------------- */ +/* Back up and restore device information */ + +typedef struct deviceinfo_backup +{ + Uint32 deviceid; + void *deviceinfo; + struct deviceinfo_backup *next; +} deviceinfo_backup; + +/* SDL_PenGCSweep(): Helper callback for collecting all deviceinfo records */ +static void _pen_accumulate_gc_sweep(Uint32 deviceid, void *deviceinfo, void *backup_ref) +{ + deviceinfo_backup **db_ref = (deviceinfo_backup **)backup_ref; + deviceinfo_backup *next = *db_ref; + + *db_ref = SDL_calloc(sizeof(deviceinfo_backup), 1); + (*db_ref)->deviceid = deviceid; + (*db_ref)->deviceinfo = deviceinfo; + (*db_ref)->next = next; +} + +/* SDL_PenGCSweep(): Helper callback that must never be called */ +static void _pen_assert_impossible(Uint32 deviceid, void *deviceinfo, void *backup_ref) +{ + SDLTest_AssertCheck(0, "Deallocation for deviceid %lu during enableAndRestore: not expected", + (unsigned long) deviceid); +} + +/* Disable all pens and store their status */ +static deviceinfo_backup *_pen_disableAndBackup(void) +{ + deviceinfo_backup *backup = NULL; + + SDL_PenGCMark(); + SDL_PenGCSweep(&backup, _pen_accumulate_gc_sweep); + return backup; +} + +/* Restore all pens to their previous status */ +static void _pen_enableAndRestore(deviceinfo_backup *backup, int test_marksweep) +{ + if (test_marksweep) { + SDL_PenGCMark(); + } + while (backup) { + SDL_Pen *disabledpen = SDL_GetPenPtr(backup->deviceid); + deviceinfo_backup *next = backup->next; + + SDL_PenModifyEnd(SDL_PenModifyBegin(disabledpen->header.id), + SDL_TRUE); + disabledpen->deviceinfo = backup->deviceinfo; + + SDL_free(backup); + backup = next; + } + if (test_marksweep) { + SDL_PenGCSweep(NULL, _pen_assert_impossible); + } +} + +static struct SDL_Window _test_window = { 0 }; + +/* ---------------------------------------- */ +/* Default set-up and tear down routines */ + +/* Back up existing pens, allocate fresh ones but don't assign them yet */ +static deviceinfo_backup *_setup_test(pen_testdata *ptest, int pens_for_testing) +{ + int i; + deviceinfo_backup *backup; + + /* Get number of pens */ + SDL_free(SDL_GetPens(&ptest->initial_pen_count)); + + /* Provide fake window for window enter/exit simulation */ + _test_window.id = 0x7e57da7a; + _test_window.w = 1600; + _test_window.h = 1200; + ptest->window = &_test_window; + + /* Grab unused pen IDs for testing */ + _pen_unusedIDs(ptest, pens_for_testing); + for (i = 0; i < pens_for_testing; ++i) { + int index = _pen_iterationFindsPenIDAt(ptest->ids[i]); + SDLTest_AssertCheck(-1 == index, + "Registered PenID(%lu) since index %d == -1", + (unsigned long) ptest->ids[i], index); + } + + /* Remove existing pens, but back up */ + backup = _pen_disableAndBackup(); + + _AssertCheck_num_pens(0, "after disabling and backing up all current pens"); + SDLTest_AssertPass("Removed existing pens"); + + return backup; +} + +static void _teardown_test_general(pen_testdata *ptest, deviceinfo_backup *backup, int with_gc_test) +{ + /* Restore previously existing pens */ + _pen_enableAndRestore(backup, with_gc_test); + + /* validate */ + SDLTest_AssertPass("Restored pens to pre-test state"); + _AssertCheck_num_pens(ptest->initial_pen_count, "after restoring all initial pens"); +} + +static void _teardown_test(pen_testdata *ptest, deviceinfo_backup *backup) +{ + _teardown_test_general(ptest, backup, 0); +} + +static void _teardown_test_with_gc(pen_testdata *ptest, deviceinfo_backup *backup) +{ + _teardown_test_general(ptest, backup, 1); +} + +/* ---------------------------------------- */ +/* Pen simulation */ + +#define SIMPEN_ACTION_DONE 0 +#define SIMPEN_ACTION_MOVE_X 1 +#define SIMPEN_ACTION_MOVE_Y 2 +#define SIMPEN_ACTION_AXIS 3 +#define SIMPEN_ACTION_MOTION_EVENT 4 /* epxlicit motion event */ +#define SIMPEN_ACTION_MOTION_EVENT_S 5 /* send motion event but expect it to be suppressed */ +#define SIMPEN_ACTION_PRESS 6 /* implicit update event */ +#define SIMPEN_ACTION_RELEASE 7 /* implicit update event */ +#define SIMPEN_ACTION_DOWN 8 /* implicit update event */ +#define SIMPEN_ACTION_UP 9 /* implicit update event */ +#define SIMPEN_ACTION_ERASER_MODE 10 + +/* Individual action in pen simulation script */ +typedef struct simulated_pen_action +{ + int type; + int pen_index; /* index into the list of simulated pens */ + int index; /* button or axis number, if needed */ + float update; /* x,y; for AXIS, update[0] is the updated axis */ +} simulated_pen_action; + +static simulated_pen_action _simpen_event(int type, int pen_index, int index, float v, int line_nr) +{ + simulated_pen_action action; + action.type = type; + action.pen_index = pen_index; + action.index = index; + action.update = v; + + /* Sanity check-- turned out to be necessary */ + if ((type == SIMPEN_ACTION_PRESS || type == SIMPEN_ACTION_RELEASE) && index == 0) { + SDL_Log("Error: SIMPEN_EVENT_BUTTON must have button > 0 (first button has number 1!), in line %d!", line_nr); + exit(1); + } + return action; +} + +/* STEP is passed in later (C macros use dynamic scoping) */ + +#define SIMPEN_DONE() \ + STEP _simpen_event(SIMPEN_ACTION_DONE, 0, 0, 0.0f, __LINE__) +#define SIMPEN_MOVE(pen_index, x, y) \ + STEP _simpen_event(SIMPEN_ACTION_MOVE_X, (pen_index), 0, (x), __LINE__); \ + STEP _simpen_event(SIMPEN_ACTION_MOVE_Y, (pen_index), 0, (y), __LINE__) + +#define SIMPEN_AXIS(pen_index, axis, y) \ + STEP _simpen_event(SIMPEN_ACTION_AXIS, (pen_index), (axis), (y), __LINE__) + +#define SIMPEN_EVENT_MOTION(pen_index) \ + STEP _simpen_event(SIMPEN_ACTION_MOTION_EVENT, (pen_index), 0, 0.0f, __LINE__) + +#define SIMPEN_EVENT_MOTION_SUPPRESSED(pen_index) \ + STEP _simpen_event(SIMPEN_ACTION_MOTION_EVENT_S, (pen_index), 0, 0.0f, __LINE__) + +#define SIMPEN_EVENT_BUTTON(pen_index, push, button) \ + STEP _simpen_event((push) ? SIMPEN_ACTION_PRESS : SIMPEN_ACTION_RELEASE, (pen_index), (button), 0.0f, __LINE__) + +#define SIMPEN_EVENT_TIP(pen_index, touch, tip) \ + STEP _simpen_event((touch) ? SIMPEN_ACTION_DOWN : SIMPEN_ACTION_UP, (pen_index), tip, 0.0f, __LINE__) + +#define SIMPEN_SET_ERASER(pen_index, eraser_mode) \ + STEP _simpen_event(SIMPEN_ACTION_ERASER_MODE, (pen_index), eraser_mode, 0.0f, __LINE__) + +static void +_pen_dump(const char *prefix, SDL_Pen *pen) +{ + int i; + char *axes_str; + + if (!pen) { + SDL_Log("(NULL pen)"); + return; + } + + axes_str = SDL_strdup(""); + for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { + char *old_axes_str = axes_str; + SDL_asprintf(&axes_str, "%s\t%f", old_axes_str, pen->last.axes[i]); + SDL_free(old_axes_str); + } + SDL_Log("%s: pen %lu (%s): status=%04lx, flags=%lx, x,y=(%f, %f) axes = %s", + prefix, + (unsigned long) pen->header.id, + pen->name, + (unsigned long) pen->last.buttons, + (unsigned long) pen->header.flags, + pen->last.x, pen->last.y, + axes_str); + SDL_free(axes_str); +} + +/* Runs until the next event has been issued or we are done and returns pointer to it. + Returns NULL once we hit SIMPEN_ACTION_DONE. + Updates simulated_pens accordingly. There must be as many simulated_pens as the highest pen_index used in + any of the "steps". + Also validates the internal state with expectations (via SDL_GetPenStatus()) and updates the, but does not poll SDL events. */ +static simulated_pen_action * +_pen_simulate(simulated_pen_action *steps, int *step_counter, SDL_Pen *simulated_pens, int num_pens) +{ + SDL_bool done = SDL_FALSE; + SDL_bool dump_pens = SDL_FALSE; + unsigned int mask; + int pen_nr; + + do { + simulated_pen_action step = steps[*step_counter]; + SDL_Pen *simpen = &simulated_pens[step.pen_index]; + + if (step.pen_index >= num_pens) { + SDLTest_AssertCheck(0, + "Unexpected pen index %d at step %d, action %d", step.pen_index, *step_counter, step.type); + return NULL; + } + + switch (step.type) { + case SIMPEN_ACTION_DONE: + SDLTest_AssertPass("SIMPEN_ACTION_DONE"); + return NULL; + + case SIMPEN_ACTION_MOVE_X: + SDLTest_AssertPass("SIMPEN_ACTION_MOVE_X [pen %d] : y <- %f", step.pen_index, step.update); + simpen->last.x = step.update; + break; + + case SIMPEN_ACTION_MOVE_Y: + SDLTest_AssertPass("SIMPEN_ACTION_MOVE_Y [pen %d] : x <- %f", step.pen_index, step.update); + simpen->last.y = step.update; + break; + + case SIMPEN_ACTION_AXIS: + SDLTest_AssertPass("SIMPEN_ACTION_AXIS [pen %d] : axis[%d] <- %f", step.pen_index, step.index, step.update); + simpen->last.axes[step.index] = step.update; + break; + + case SIMPEN_ACTION_MOTION_EVENT: + done = SDL_TRUE; + SDLTest_AssertCheck(SDL_SendPenMotion(0, simpen->header.id, SDL_TRUE, + &simpen->last), + "SIMPEN_ACTION_MOTION_EVENT [pen %d]", step.pen_index); + break; + + case SIMPEN_ACTION_MOTION_EVENT_S: + SDLTest_AssertCheck(!SDL_SendPenMotion(0, simpen->header.id, SDL_TRUE, + &simpen->last), + "SIMPEN_ACTION_MOTION_EVENT_SUPPRESSED [pen %d]", step.pen_index); + break; + + case SIMPEN_ACTION_PRESS: + mask = (1 << (step.index - 1)); + simpen->last.buttons |= mask; + SDLTest_AssertCheck(SDL_SendPenButton(0, simpen->header.id, SDL_PRESSED, step.index), + "SIMPEN_ACTION_PRESS [pen %d]: button %d (mask %x)", step.pen_index, step.index, mask); + done = SDL_TRUE; + break; + + case SIMPEN_ACTION_RELEASE: + mask = ~(1 << (step.index - 1)); + simpen->last.buttons &= mask; + SDLTest_AssertCheck(SDL_SendPenButton(0, simpen->header.id, SDL_RELEASED, step.index), + "SIMPEN_ACTION_RELEASE [pen %d]: button %d (mask %x)", step.pen_index, step.index, mask); + done = SDL_TRUE; + break; + + case SIMPEN_ACTION_DOWN: + simpen->last.buttons |= SDL_PEN_DOWN_MASK; + SDLTest_AssertCheck(SDL_SendPenTipEvent(0, simpen->header.id, SDL_PRESSED), + "SIMPEN_ACTION_DOWN [pen %d]: (mask %lx)", step.pen_index, SDL_PEN_DOWN_MASK); + done = SDL_TRUE; + break; + + case SIMPEN_ACTION_UP: + simpen->last.buttons &= ~SDL_PEN_DOWN_MASK; + SDLTest_AssertCheck(SDL_SendPenTipEvent(0, simpen->header.id, SDL_RELEASED), + "SIMPEN_ACTION_UP [pen %d]: (mask %lx)", step.pen_index, ~SDL_PEN_DOWN_MASK); + done = SDL_TRUE; + break; + + case SIMPEN_ACTION_ERASER_MODE: { + Uint32 pmask; + SDL_Pen *pen = SDL_PenModifyBegin(simpen->header.id); + + if (step.index) { + pmask = SDL_PEN_ERASER_MASK; + } else { + pmask = SDL_PEN_INK_MASK; + } + + SDL_PenModifyAddCapabilities(pen, pmask); + SDL_PenModifyEnd(pen, SDL_TRUE); + + simpen->header.flags &= ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK); + simpen->header.flags |= pmask; + break; + } + + default: + SDLTest_AssertCheck(0, + "Unexpected pen simulation action %d", step.type); + return NULL; + } + ++(*step_counter); + } while (!done); + + for (pen_nr = 0; pen_nr < num_pens; ++pen_nr) { + SDL_Pen *simpen = &simulated_pens[pen_nr]; + float x = -1.0f, y = -1.0f; + float axes[SDL_PEN_NUM_AXES]; + Uint32 actual_flags = SDL_GetPenStatus(simpen->header.id, &x, &y, axes, SDL_PEN_NUM_AXES); + int i; + + if (simpen->last.x != x || simpen->last.y != y) { + SDLTest_AssertCheck(0, "Coordinate mismatch in pen %d", pen_nr); + dump_pens = SDL_TRUE; + } + if ((actual_flags & ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK)) != (simpen->last.buttons & ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK))) { + SDLTest_AssertCheck(0, "Status mismatch in pen %d (reported: %08x)", pen_nr, (unsigned int)actual_flags); + dump_pens = SDL_TRUE; + } + if ((actual_flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK)) != (simpen->header.flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK))) { + SDLTest_AssertCheck(0, "Flags mismatch in pen %d (reported: %08x)", pen_nr, (unsigned int)actual_flags); + dump_pens = SDL_TRUE; + } + for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { + if (axes[i] != simpen->last.axes[i]) { + SDLTest_AssertCheck(0, "Axis %d mismatch in pen %d", pen_nr, i); + dump_pens = SDL_TRUE; + } + } + } + + if (dump_pens) { + int i; + for (i = 0; i < num_pens; ++i) { + SDL_Log("==== pen #%d", i); + _pen_dump("expect", simulated_pens + i); + _pen_dump("actual", SDL_GetPenPtr(simulated_pens[i].header.id)); + } + } + + return &steps[(*step_counter) - 1]; +} + +/* Init simulated_pens with suitable initial state */ +static void +_pen_simulate_init(pen_testdata *ptest, SDL_Pen *simulated_pens, int num_pens) +{ + int i; + for (i = 0; i < num_pens; ++i) { + simulated_pens[i] = *SDL_GetPenPtr(ptest->ids[i]); + } +} + +/* ---------------------------------------- */ +/* Other helper functions */ + +/* "standard" pen registration process */ +static SDL_Pen * +_pen_register(SDL_PenID penid, SDL_GUID guid, char *name, Uint32 flags) +{ + SDL_Pen *pen = SDL_PenModifyBegin(penid); + pen->guid = guid; + SDL_strlcpy(pen->name, name, SDL_PEN_MAX_NAME); + SDL_PenModifyAddCapabilities(pen, flags); + return pen; +} + +/* Test whether EXPECTED and ACTUAL of type TY agree. Their C format string must be FMT. + MESSAGE is a string with one format string, passed as ARG0. */ +#define SDLTest_AssertEq1(TY, FMT, EXPECTED, ACTUAL, MESSAGE, ARG0) \ + { \ + TY _t_expect = (EXPECTED); \ + TY _t_actual = (ACTUAL); \ + SDLTest_AssertCheck(_t_expect == _t_actual, "L%d: " MESSAGE ": expected " #EXPECTED " = " FMT ", actual = " FMT, __LINE__, (ARG0), _t_expect, _t_actual); \ + } + +/* ================= Test Case Implementation ================== */ + +/** + * @brief Check basic pen device introduction and iteration, as well as basic queries + * + * @sa SDL_GetPens, SDL_GetPenName, SDL_GetPenCapabilities + */ +static int +pen_iteration(void *arg) +{ + pen_testdata ptest; + int i; + char long_pen_name[SDL_PEN_MAX_NAME + 10]; + const char *name; + deviceinfo_backup *backup; + + /* Check initial pens */ + SDL_PumpEvents(); + SDLTest_AssertPass("SDL_GetPens() => count = %d", _num_pens()); + + /* Grab unused pen IDs for testing */ + backup = _setup_test(&ptest, 3); /* validates that we have zero pens */ + + /* Re-run GC, track deallocations */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _AssertCheck_num_pens(0, "after second GC pass"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocations"); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocations"); + SDLTest_AssertPass("Validated that GC on empty pen set is idempotent"); + + /* Add three pens, validate */ + SDL_PenGCMark(); + + SDL_memset(long_pen_name, 'x', sizeof(long_pen_name)); /* Include pen name that is too long */ + long_pen_name[sizeof(long_pen_name) - 1] = 0; + + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 16); + _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], long_pen_name, + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK), + 20); + _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", + SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT_MASK), + 24); + _pen_trackGCSweep(&ptest); + + _AssertCheck_num_pens(3, "after allocating three pens"); + + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocations"); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocations"); + + for (i = 0; i < 3; ++i) { + /* Check that all pens are accounted for */ + int index = _pen_iterationFindsPenIDAt(ptest.ids[i]); + SDLTest_AssertCheck(-1 != index, "Found PenID(%lu)", (unsigned long) ptest.ids[i]); + } + SDLTest_AssertPass("Validated that all three pens are indexable"); + + /* Check pen properties */ + SDLTest_AssertCheck(0 == SDL_strcmp("pen 0", SDL_GetPenName(ptest.ids[0])), + "Pen #0 name"); + SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK) == SDL_GetPenCapabilities(ptest.ids[0], NULL), + "Pen #0 capabilities"); + + SDLTest_AssertCheck(0 == SDL_strcmp("pen 1", SDL_GetPenName(ptest.ids[1])), + "Pen #1 name"); + SDLTest_AssertCheck((SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT_MASK) == SDL_GetPenCapabilities(ptest.ids[1], NULL), + "Pen #1 capabilities"); + + name = SDL_GetPenName(ptest.ids[2]); + SDLTest_AssertCheck(SDL_PEN_MAX_NAME - 1 == SDL_strlen(name), + "Pen #2 name length"); + SDLTest_AssertCheck(0 == SDL_memcmp(name, long_pen_name, SDL_PEN_MAX_NAME - 1), + "Pen #2 name contents"); + SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK) == SDL_GetPenCapabilities(ptest.ids[2], NULL), + "Pen #2 capabilities"); + SDLTest_AssertPass("Pen registration and basic queries"); + + /* Re-run GC, track deallocations */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _AssertCheck_num_pens(0, "after third GC pass"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x07, + "No unexpected device deallocation : %08x", ptest.deallocated_id_flags); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01110000, + "No unexpected deviceinfo deallocation : %08x ", ptest.deallocated_deviceinfo_flags); + SDLTest_AssertPass("Validated that GC on empty pen set is idempotent"); + + /* tear down and finish */ + _teardown_test(&ptest, backup); + return TEST_COMPLETED; +} + +static void +_expect_pen_attached(SDL_PenID penid) +{ + SDLTest_AssertCheck(-1 != _pen_iterationFindsPenIDAt(penid), + "Found PenID(%lu)", (unsigned long) penid); + SDLTest_AssertCheck(SDL_PenConnected(penid), + "Pen %lu was attached, as expected", (unsigned long) penid); +} + +static void +_expect_pen_detached(SDL_PenID penid) +{ + SDLTest_AssertCheck(-1 == _pen_iterationFindsPenIDAt(penid), + "Did not find PenID(%lu), as expected", (unsigned long) penid); + SDLTest_AssertCheck(!SDL_PenConnected(penid), + "Pen %lu was detached, as expected", (unsigned long) penid); +} + +#define ATTACHED(i) (1 << (i)) + +static void +_expect_pens_attached_or_detached(SDL_PenID *pen_ids, int ids, Uint32 mask) +{ + int i; + int attached_count = 0; + for (i = 0; i < ids; ++i) { + if (mask & (1 << i)) { + ++attached_count; + _expect_pen_attached(pen_ids[i]); + } else { + _expect_pen_detached(pen_ids[i]); + } + } + _AssertCheck_num_pens(attached_count, "While checking attached/detached status"); +} + +/** + * @brief Check pen device hotplugging + * + * @sa SDL_GetPens, SDL_GetPenName, SDL_GetPenCapabilities, SDL_PenConnected + */ +static int +pen_hotplugging(void *arg) +{ + pen_testdata ptest; + deviceinfo_backup *backup = _setup_test(&ptest, 3); + SDL_GUID checkguid; + + /* Add two pens */ + SDL_PenGCMark(); + + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 16); + _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 24); + _pen_trackGCSweep(&ptest); + + _AssertCheck_num_pens(2, "after allocating two pens (pass 1)"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocation (pass 1)"); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocation (pass 1)"); + + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); + SDLTest_AssertPass("Validated hotplugging (pass 1): attachmend of two pens"); + + /* Introduce pen #1, remove pen #2 */ + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + DEVICEINFO_UNCHANGED); + _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 20); + _pen_trackGCSweep(&ptest); + + _AssertCheck_num_pens(2, "after allocating two pens (pass 2)"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x04, "No unexpected device deallocation (pass 2): %x", ptest.deallocated_id_flags); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01000000, "No unexpected deviceinfo deallocation (pass 2): %x", ptest.deallocated_deviceinfo_flags); + + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(1)); + SDLTest_AssertPass("Validated hotplugging (pass 2): unplug one, attach another"); + + /* Return to previous state (#0 and #2 attached) */ + SDL_PenGCMark(); + + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT), + DEVICEINFO_UNCHANGED); + _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 24); + _pen_trackGCSweep(&ptest); + + _AssertCheck_num_pens(2, "after allocating two pens (pass 3)"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x02, "No unexpected device deallocation (pass 3)"); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x00100000, "No unexpected deviceinfo deallocation (pass 3)"); + + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); + SDLTest_AssertPass("Validated hotplugging (pass 3): return to state of pass 1"); + + /* Introduce pen #1, remove pen #0 */ + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 20); + _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + DEVICEINFO_UNCHANGED); + _pen_trackGCSweep(&ptest); + + _AssertCheck_num_pens(2, "after allocating two pens (pass 4)"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x01, "No unexpected device deallocation (pass 4): %x", ptest.deallocated_id_flags); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x00010000, "No unexpected deviceinfo deallocation (pass 4): %x", ptest.deallocated_deviceinfo_flags); + + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(1) | ATTACHED(2)); + SDLTest_AssertPass("Validated hotplugging (pass 5)"); + + /* Check detached pen */ + SDLTest_AssertCheck(0 == SDL_strcmp("pen 0", SDL_GetPenName(ptest.ids[0])), + "Pen #0 name"); + checkguid = SDL_GetPenGUID(ptest.ids[0]); + SDLTest_AssertCheck(0 == SDL_memcmp(ptest.guids[0].data, checkguid.data, sizeof(ptest.guids[0].data)), + "Pen #0 guid"); + SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT) == SDL_GetPenCapabilities(ptest.ids[0], NULL), + "Pen #0 capabilities"); + SDLTest_AssertPass("Validated that detached pens retained name, GUID, axis info after pass 5"); + + /* Individually detach #1 dn #2 */ + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(1) | ATTACHED(2)); + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[1]), SDL_FALSE); + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(2)); + + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_FALSE); + _expect_pens_attached_or_detached(ptest.ids, 3, 0); + + SDLTest_AssertPass("Validated individual hotplugging (pass 6)"); + + /* Individually attach all */ + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_TRUE); + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(2)); + + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[0]), SDL_TRUE); + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); + + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[1]), SDL_TRUE); + _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); + SDLTest_AssertPass("Validated individual hotplugging (pass 7)"); + + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _AssertCheck_num_pens(0, "after hotplugging test (cleanup)"); + SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x06, "No unexpected device deallocation (cleanup): %x", ptest.deallocated_id_flags); + SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01100000, "No unexpected deviceinfo deallocation (pass 4): %x", ptest.deallocated_deviceinfo_flags); + + _teardown_test_with_gc(&ptest, backup); + + return TEST_COMPLETED; +} + +/** + * @brief Check pen device GUID handling + * + * @sa SDL_GetPenGUID + */ +static int +pen_GUIDs(void *arg) +{ + int i; + char *names[4] = { "pen 0", "pen 1", "pen 2", "pen 3" }; + pen_testdata ptest; + deviceinfo_backup *backup; + + backup = _setup_test(&ptest, 4); + + /* Define four pens */ + SDL_PenGCMark(); + for (i = 0; i < 4; ++i) { + _pen_setDeviceinfo(_pen_register(ptest.ids[i], ptest.guids[i], names[i], SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + 20); + } + _pen_trackGCSweep(&ptest); + + /* Detach pens 0 and 2 */ + SDL_PenGCMark(); + for (i = 1; i < 4; i += 2) { + _pen_setDeviceinfo(_pen_register(ptest.ids[i], ptest.guids[i], names[i], SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), + DEVICEINFO_UNCHANGED); + } + _pen_trackGCSweep(&ptest); + + for (i = 0; i < 4; ++i) { + SDLTest_AssertCheck(ptest.ids[i] == SDL_GetPenFromGUID(ptest.guids[i]), + "GUID search succeeded for %d", i); + } + + /* detach all */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + + _teardown_test(&ptest, backup); + SDLTest_AssertPass("Pen ID lookup by GUID"); + + return TEST_COMPLETED; +} + +/** + * @brief Check pen device button reporting + * + */ +static int +pen_buttonReporting(void *arg) +{ + int i; + int button_nr, pen_nr; + pen_testdata ptest; + SDL_Event event; + SDL_PenStatusInfo update; + float axes[SDL_PEN_NUM_AXES + 1]; + const float expected_x[2] = { 10.0f, 20.0f }; + const float expected_y[2] = { 11.0f, 21.0f }; + const Uint32 all_axes = SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK | SDL_PEN_AXIS_SLIDER_MASK; + + /* Register pen */ + deviceinfo_backup *backup = _setup_test(&ptest, 2); + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "test pen", + SDL_PEN_INK_MASK | all_axes), + 20); + _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "test eraser", + SDL_PEN_ERASER_MASK | all_axes), + 24); + _pen_trackGCSweep(&ptest); + + /* Position mouse suitably before we start */ + for (i = 0; i <= SDL_PEN_NUM_AXES; ++i) { + axes[i] = 0.0625f * i; /* initialise with numbers that can be represented precisely in IEEE 754 and + are > 0.0f and <= 1.0f */ + } + + /* Let pens enter the test window */ + SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); + SDL_SendPenWindowEvent(0, ptest.ids[1], ptest.window); + + update.x = expected_x[0]; + update.y = expected_y[0]; + SDL_memcpy(update.axes, axes, sizeof(float) * SDL_PEN_NUM_AXES); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + update.x = expected_x[1]; + update.y = expected_y[1]; + SDL_memcpy(update.axes, axes + 1, sizeof(float) * SDL_PEN_NUM_AXES); + SDL_SendPenMotion(0, ptest.ids[1], SDL_TRUE, &update); + + while (SDL_PollEvent(&event)) + ; /* Flush event queue */ + + /* Trigger pen tip events for PEN_DOWN */ + SDLTest_AssertPass("Touch pens to surface"); + + for (pen_nr = 0; pen_nr < 2; ++pen_nr) { + float *expected_axes = axes + pen_nr; + SDL_bool found_event = SDL_FALSE; + Uint16 pen_state = 0x0000 | SDL_PEN_DOWN_MASK; + Uint8 tip = SDL_PEN_TIP_INK; + + if (pen_nr == 1) { + pen_state |= SDL_PEN_ERASER_MASK; + tip = SDL_PEN_TIP_ERASER; + } + + SDL_SendPenTipEvent(0, ptest.ids[pen_nr], SDL_PRESSED); + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_PEN_DOWN) { + SDLTest_AssertCheck(event.ptip.which == ptest.ids[pen_nr], + "Received SDL_EVENT_PEN_DOWN from correct pen"); + SDLTest_AssertCheck(event.ptip.tip == (pen_nr == 0)? SDL_PEN_TIP_INK : SDL_PEN_TIP_ERASER, + "Received SDL_EVENT_PEN_DOWN for correct tip"); + SDLTest_AssertCheck(event.ptip.state == SDL_PRESSED, + "Received SDL_EVENT_PEN_DOWN but and marked SDL_PRESSED"); + SDLTest_AssertCheck(event.ptip.tip == tip, + "Received tip %x but expected %x", event.ptip.tip, tip); + SDLTest_AssertCheck(event.ptip.pen_state == pen_state, + "Received SDL_EVENT_PEN_DOWN, and state %04x == %04x (expected)", + event.pbutton.pen_state, pen_state); + SDLTest_AssertCheck((event.ptip.x == expected_x[pen_nr]) && (event.ptip.y == expected_y[pen_nr]), + "Received SDL_EVENT_PEN_DOWN event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", + event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); + SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), + "Received SDL_EVENT_PEN_DOWN event with correct axis values"); + found_event = SDL_TRUE; + } + SDLTest_AssertCheck(found_event, + "Received the expected SDL_EVENT_PEN_DOWN event"); + } + } + + SDLTest_AssertPass("Pen and eraser set up for button testing"); + + /* Actual tests start: pen, then eraser */ + for (pen_nr = 0; pen_nr < 2; ++pen_nr) { + Uint16 pen_state = 0x0000 | SDL_PEN_DOWN_MASK; + float *expected_axes = axes + pen_nr; + + if (pen_nr == 1) { + pen_state |= SDL_PEN_ERASER_MASK; + } + for (button_nr = 1; button_nr <= 8; ++button_nr) { + SDL_bool found_event = SDL_FALSE; + pen_state |= (1 << (button_nr - 1)); + + SDL_SendPenButton(0, ptest.ids[pen_nr], SDL_PRESSED, button_nr); + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_PEN_BUTTON_DOWN) { + SDLTest_AssertCheck(event.pbutton.which == ptest.ids[pen_nr], + "Received SDL_EVENT_PEN_BUTTON_DOWN from correct pen"); + SDLTest_AssertCheck(event.pbutton.button == button_nr, + "Received SDL_EVENT_PEN_BUTTON_DOWN from correct button"); + SDLTest_AssertCheck(event.pbutton.state == SDL_PRESSED, + "Received SDL_EVENT_PEN_BUTTON_DOWN but and marked SDL_PRESSED"); + SDLTest_AssertCheck(event.pbutton.pen_state == pen_state, + "Received SDL_EVENT_PEN_BUTTON_DOWN, and state %04x == %04x (expected)", + event.pbutton.pen_state, pen_state); + SDLTest_AssertCheck((event.pbutton.x == expected_x[pen_nr]) && (event.pbutton.y == expected_y[pen_nr]), + "Received SDL_EVENT_PEN_BUTTON_DOWN event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", + event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); + SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), + "Received SDL_EVENT_PEN_BUTTON_DOWN event with correct axis values"); + if (0 != SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES)) { + int ax; + for (ax = 0; ax < SDL_PEN_NUM_AXES; ++ax) { + SDL_Log("\tax %d\t%.5f\t%.5f expected (equal=%d)", + ax, + event.pbutton.axes[ax], expected_axes[ax], + event.pbutton.axes[ax] == expected_axes[ax]); + } + } + found_event = SDL_TRUE; + } + } + SDLTest_AssertCheck(found_event, + "Received the expected SDL_EVENT_PEN_BUTTON_DOWN event"); + } + } + SDLTest_AssertPass("Pressed all buttons"); + + /* Release every other button */ + for (pen_nr = 0; pen_nr < 2; ++pen_nr) { + Uint16 pen_state = 0x00ff | SDL_PEN_DOWN_MASK; /* 8 buttons pressed */ + float *expected_axes = axes + pen_nr; + + if (pen_nr == 1) { + pen_state |= SDL_PEN_ERASER_MASK; + } + for (button_nr = pen_nr + 1; button_nr <= 8; button_nr += 2) { + SDL_bool found_event = SDL_FALSE; + pen_state &= ~(1 << (button_nr - 1)); + + SDL_SendPenButton(0, ptest.ids[pen_nr], SDL_RELEASED, button_nr); + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_PEN_BUTTON_UP) { + SDLTest_AssertCheck(event.pbutton.which == ptest.ids[pen_nr], + "Received SDL_EVENT_PEN_BUTTON_UP from correct pen"); + SDLTest_AssertCheck(event.pbutton.button == button_nr, + "Received SDL_EVENT_PEN_BUTTON_UP from correct button"); + SDLTest_AssertCheck(event.pbutton.state == SDL_RELEASED, + "Received SDL_EVENT_PEN_BUTTON_UP and is marked SDL_RELEASED"); + SDLTest_AssertCheck(event.pbutton.pen_state == pen_state, + "Received SDL_EVENT_PEN_BUTTON_UP, and state %04x == %04x (expected)", + event.pbutton.pen_state, pen_state); + SDLTest_AssertCheck((event.pbutton.x == expected_x[pen_nr]) && (event.pbutton.y == expected_y[pen_nr]), + "Received SDL_EVENT_PEN_BUTTON_UP event at correct coordinates"); + SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), + "Received SDL_EVENT_PEN_BUTTON_UP event with correct axis values"); + found_event = SDL_TRUE; + } + } + SDLTest_AssertCheck(found_event, + "Received the expected SDL_EVENT_PEN_BUTTON_UP event"); + } + } + SDLTest_AssertPass("Released every other button"); + + /* Trigger pen tip events for PEN_UP */ + SDLTest_AssertPass("Remove pens from surface"); + + for (pen_nr = 0; pen_nr < 2; ++pen_nr) { + float *expected_axes = axes + pen_nr; + SDL_bool found_event = SDL_FALSE; + Uint16 pen_state = 0x0000; + Uint8 tip = SDL_PEN_TIP_INK; + + if (pen_nr == 1) { + pen_state |= SDL_PEN_ERASER_MASK; + tip = SDL_PEN_TIP_ERASER; + } + + SDL_SendPenTipEvent(0, ptest.ids[pen_nr], SDL_RELEASED); + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_PEN_UP) { + SDLTest_AssertCheck(event.ptip.which == ptest.ids[pen_nr], + "Received SDL_EVENT_PEN_UP from correct pen"); + SDLTest_AssertCheck(event.ptip.tip == (pen_nr == 0)? SDL_PEN_TIP_INK : SDL_PEN_TIP_ERASER, + "Received SDL_EVENT_PEN_UP for correct tip"); + SDLTest_AssertCheck(event.ptip.state == SDL_RELEASED, + "Received SDL_EVENT_PEN_UP but and marked SDL_RELEASED"); + SDLTest_AssertCheck(event.ptip.tip == tip, + "Received tip %x but expected %x", event.ptip.tip, tip); + SDLTest_AssertCheck((event.ptip.pen_state & 0xff00) == (pen_state & 0xff00), + "Received SDL_EVENT_PEN_UP, and state %04x == %04x (expected)", + event.pbutton.pen_state, pen_state); + SDLTest_AssertCheck((event.ptip.x == expected_x[pen_nr]) && (event.ptip.y == expected_y[pen_nr]), + "Received SDL_EVENT_PEN_UP event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", + event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); + SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), + "Received SDL_EVENT_PEN_UP event with correct axis values"); + found_event = SDL_TRUE; + } + SDLTest_AssertCheck(found_event, + "Received the expected SDL_EVENT_PEN_UP event"); + } + } + + /* Cleanup */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _teardown_test(&ptest, backup); + + return TEST_COMPLETED; +} + +/** + * @brief Check pen device movement and axis update reporting + * + * Also tests SDL_GetPenStatus for agreement with the most recently reported events + * + * @sa SDL_GetPenStatus + */ +static int +pen_movementAndAxes(void *arg) +{ + pen_testdata ptest; + SDL_Event event; +#define MAX_STEPS 80 + /* Pen simulation */ + simulated_pen_action steps[MAX_STEPS]; + size_t num_steps = 0; + + SDL_Pen simulated_pens[2]; + int sim_pc = 0; + simulated_pen_action *last_action; + + /* Register pen */ + deviceinfo_backup *backup = _setup_test(&ptest, 2); + + /* Pen simulation program */ +#define STEP steps[num_steps++] = + + /* #1: Check basic reporting */ + /* Hover eraser, tilt axes */ + SIMPEN_MOVE(0, 30.0f, 31.0f); + SIMPEN_AXIS(0, SDL_PEN_AXIS_PRESSURE, 0.0f); + SIMPEN_AXIS(0, SDL_PEN_AXIS_XTILT, 22.5f); + SIMPEN_AXIS(0, SDL_PEN_AXIS_YTILT, 45.0f); + SIMPEN_EVENT_MOTION(0); + + /* #2: Check that motion events without motion aren't reported */ + SIMPEN_EVENT_MOTION_SUPPRESSED(0); + SIMPEN_EVENT_MOTION_SUPPRESSED(0); + + /* #3: Check multiple pens being reported */ + /* Move pen and touch surface, don't tilt */ + SIMPEN_MOVE(1, 40.0f, 41.0f); + SIMPEN_AXIS(1, SDL_PEN_AXIS_PRESSURE, 0.25f); + SIMPEN_EVENT_MOTION(1); + + /* $4: Multi-buttons */ + /* Press eraser buttons */ + SIMPEN_EVENT_TIP(0, "down", SDL_PEN_TIP_ERASER); + SIMPEN_EVENT_BUTTON(0, "push", 2); + SIMPEN_EVENT_BUTTON(0, "push", 1); + SIMPEN_EVENT_BUTTON(0, 0, 2); /* release again */ + SIMPEN_EVENT_BUTTON(0, "push", 3); + + /* #5: Check move + button actions connecting */ + /* Move and tilt pen, press some pen buttons */ + SIMPEN_MOVE(1, 3.0f, 8.0f); + SIMPEN_AXIS(1, SDL_PEN_AXIS_PRESSURE, 0.5f); + SIMPEN_AXIS(1, SDL_PEN_AXIS_XTILT, -21.0f); + SIMPEN_AXIS(1, SDL_PEN_AXIS_YTILT, -25.0f); + SIMPEN_EVENT_MOTION(1); + SIMPEN_EVENT_BUTTON(1, "push", 2); + SIMPEN_EVENT_TIP(1, "down", SDL_PEN_TIP_INK); + + /* #6: Check nonterference between pens */ + /* Eraser releases buttons */ + SIMPEN_EVENT_BUTTON(0, 0, 1); + SIMPEN_EVENT_TIP(0, 0, SDL_PEN_TIP_ERASER); + + /* #7: Press-move-release action */ + /* Eraser press-move-release */ + SIMPEN_EVENT_BUTTON(0, "push", 1); + SIMPEN_MOVE(0, 99.0f, 88.0f); + SIMPEN_AXIS(0, SDL_PEN_AXIS_PRESSURE, 0.625f); + SIMPEN_EVENT_MOTION(0); + SIMPEN_MOVE(0, 44.5f, 42.25f); + SIMPEN_EVENT_MOTION(0); + SIMPEN_EVENT_BUTTON(0, 0, 1); + + /* #8: Intertwining button release actions some more */ + /* Pen releases button */ + SIMPEN_EVENT_BUTTON(1, 0, 2); + SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_INK); + + /* Push one more pen button, then release all ereaser buttons */ + SIMPEN_EVENT_TIP(1, "down", SDL_PEN_TIP_INK); + SIMPEN_EVENT_BUTTON(0, 0, 2); + SIMPEN_EVENT_BUTTON(0, 0, 3); + + /* Lift up pen, flip it so it becomes an eraser, and touch it again */ + SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_INK); + SIMPEN_SET_ERASER(1, 1); + SIMPEN_EVENT_TIP(1, "push", SDL_PEN_TIP_ERASER); + + /* And back again */ + SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_ERASER); + SIMPEN_SET_ERASER(1, 0); + SIMPEN_EVENT_TIP(1, "push", SDL_PEN_TIP_INK); + + /* #9: Suppress move on unsupported axis */ + SIMPEN_AXIS(1, SDL_PEN_AXIS_DISTANCE, 0.25f); + SIMPEN_EVENT_MOTION_SUPPRESSED(0); + + SIMPEN_DONE(); +#undef STEP + /* End of pen simulation program */ + + SDLTest_AssertCheck(num_steps < MAX_STEPS, "Pen simulation program does not exceed buffer size"); +#undef MAX_STEPS + + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "test eraser", + SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK), + 20); + _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "test pen", + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK), + 24); + _pen_trackGCSweep(&ptest); + SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); + SDL_SendPenWindowEvent(0, ptest.ids[1], ptest.window); + while (SDL_PollEvent(&event)) + ; /* Flush event queue */ + SDLTest_AssertPass("Pen and eraser set up for testing"); + + _pen_simulate_init(&ptest, simulated_pens, 2); + /* Simulate pen movements */ + while ((last_action = _pen_simulate(steps, &sim_pc, &simulated_pens[0], 2))) { + int attempts = 0; + SDL_Pen *simpen = &simulated_pens[last_action->pen_index]; + SDL_PenID reported_which = -1; + float reported_x = -1.0f, reported_y = -1.0f; + float *reported_axes = NULL; + Uint32 reported_pen_state = 0; + Uint32 expected_pen_state = simpen->header.flags & SDL_PEN_ERASER_MASK; + SDL_bool dump_pens = SDL_FALSE; + + do { + SDL_PumpEvents(); + SDL_PollEvent(&event); + if (++attempts > 10000) { + SDLTest_AssertCheck(0, "Never got the anticipated event"); + return TEST_ABORTED; + } + } while (event.type != SDL_EVENT_PEN_DOWN + && event.type != SDL_EVENT_PEN_UP + && event.type != SDL_EVENT_PEN_MOTION + && event.type != SDL_EVENT_PEN_BUTTON_UP + && event.type != SDL_EVENT_PEN_BUTTON_DOWN); /* skip boring events */ + + expected_pen_state |= simpen->last.buttons; + + SDLTest_AssertCheck(0 != event.type, + "Received the anticipated event"); + + switch (last_action->type) { + case SIMPEN_ACTION_MOTION_EVENT: + SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_MOTION, "Expected pen motion event (but got 0x%lx)", (unsigned long) event.type); + reported_which = event.pmotion.which; + reported_x = event.pmotion.x; + reported_y = event.pmotion.y; + reported_pen_state = event.pmotion.pen_state; + reported_axes = &event.pmotion.axes[0]; + break; + + case SIMPEN_ACTION_PRESS: + SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_BUTTON_DOWN, "Expected PENBUTTONDOWN event (but got 0x%lx)", (unsigned long) event.type); + SDLTest_AssertCheck(event.pbutton.state == SDL_PRESSED, "Expected PRESSED button"); + /* Fall through */ + case SIMPEN_ACTION_RELEASE: + if (last_action->type == SIMPEN_ACTION_RELEASE) { + SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_BUTTON_UP, "Expected PENBUTTONUP event (but got 0x%lx)", (unsigned long) event.type); + SDLTest_AssertCheck(event.pbutton.state == SDL_RELEASED, "Expected RELEASED button"); + } + SDLTest_AssertCheck(event.pbutton.button == last_action->index, "Expected button %d, but got %d", + last_action->index, event.pbutton.button); + reported_which = event.pbutton.which; + reported_x = event.pbutton.x; + reported_y = event.pbutton.y; + reported_pen_state = event.pbutton.pen_state; + reported_axes = &event.pbutton.axes[0]; + break; + + case SIMPEN_ACTION_DOWN: + SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_DOWN, "Expected PENBUTTONDOWN event (but got 0x%lx)", (unsigned long) event.type); + SDLTest_AssertCheck(event.ptip.state == SDL_PRESSED, "Expected PRESSED button"); + /* Fall through */ + case SIMPEN_ACTION_UP: + if (last_action->type == SIMPEN_ACTION_UP) { + SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_UP, "Expected PENBUTTONUP event (but got 0x%lx)", (unsigned long) event.type); + SDLTest_AssertCheck(event.ptip.state == SDL_RELEASED, "Expected RELEASED button"); + } + SDLTest_AssertCheck(event.ptip.tip == last_action->index, "Expected tip %d, but got %d", + last_action->index, event.ptip.tip); + reported_which = event.ptip.which; + reported_x = event.ptip.x; + reported_y = event.ptip.y; + reported_pen_state = event.ptip.pen_state; + reported_axes = &event.ptip.axes[0]; + break; + + case SIMPEN_ACTION_ERASER_MODE: + break; + + default: + SDLTest_AssertCheck(0, "Error in pen simulator: unexpected action %d", last_action->type); + return TEST_ABORTED; + } + + if (reported_which != simpen->header.id) { + dump_pens = SDL_TRUE; + SDLTest_AssertCheck(0, "Expected report for pen %lu but got report for pen %lu", + (unsigned long) simpen->header.id, + (unsigned long) reported_which); + } + if (reported_x != simpen->last.x || reported_y != simpen->last.y) { + dump_pens = SDL_TRUE; + SDLTest_AssertCheck(0, "Mismatch in pen coordinates"); + } + if (reported_x != simpen->last.x || reported_y != simpen->last.y) { + dump_pens = SDL_TRUE; + SDLTest_AssertCheck(0, "Mismatch in pen coordinates"); + } + if (reported_pen_state != expected_pen_state) { + dump_pens = SDL_TRUE; + SDLTest_AssertCheck(0, "Mismatch in pen state: %lx vs %lx (expected)", + (unsigned long) reported_pen_state, + (unsigned long) expected_pen_state); + } + if (0 != SDL_memcmp(reported_axes, simpen->last.axes, sizeof(float) * SDL_PEN_NUM_AXES)) { + dump_pens = SDL_TRUE; + SDLTest_AssertCheck(0, "Mismatch in axes"); + } + + if (dump_pens) { + SDL_Log("----- Pen #%d:", last_action->pen_index); + _pen_dump("expect", simpen); + _pen_dump("actual", SDL_GetPenPtr(simpen->header.id)); + } + } + SDLTest_AssertPass("Pen and eraser move and report events correctly and independently"); + + /* Cleanup */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _teardown_test(&ptest, backup); + return TEST_COMPLETED; +} + +static void +_expect_pen_config(SDL_PenID penid, + SDL_GUID expected_guid, + SDL_bool expected_attached, + char *expected_name, + int expected_type, + int expected_num_buttons, + float expected_max_tilt, + int expected_axes) +{ + SDL_PenCapabilityInfo actual_info = { 0 }; + const char *actual_name = SDL_GetPenName(penid); + + if (penid == SDL_PEN_INVALID) { + SDLTest_Assert(0, "Invalid pen ID"); + return; + } + + SDLTest_AssertEq1(int, "%d", 0, SDL_GUIDCompare(expected_guid, SDL_GetPenGUID(penid)), + "Pen %lu guid equality", (unsigned long) penid); + + SDLTest_AssertCheck(0 == SDL_strcmp(expected_name, actual_name), + "Expected name='%s' vs actual='%s'", expected_name, actual_name); + + SDLTest_AssertEq1(int, "%d", expected_attached, SDL_PenConnected(penid), + "Pen %lu is attached", (unsigned long) penid); + SDLTest_AssertEq1(int, "%d", expected_type, SDL_GetPenType(penid), + "Pen %lu type", (unsigned long) penid); + SDLTest_AssertEq1(int, "%x", expected_axes, SDL_GetPenCapabilities(penid, &actual_info), + "Pen %lu axis flags", (unsigned long) penid); + SDLTest_AssertEq1(int, "%d", expected_num_buttons, actual_info.num_buttons, + "Pen %lu number of buttons", (unsigned long) penid); + SDLTest_AssertEq1(float, "%f", expected_max_tilt, actual_info.max_tilt, + "Pen %lu max tilt", (unsigned long) penid); +} + +/** + * @brief Check backend pen iniitalisation and pen meta-information + * + * @sa SDL_GetPenCapabilities, SDL_PenAxisInfo + */ +static int +pen_initAndInfo(void *arg) +{ + pen_testdata ptest; + SDL_Pen *pen; + Uint32 mask; + char strbuf[SDL_PEN_MAX_NAME]; + + /* Init */ + deviceinfo_backup *backup = _setup_test(&ptest, 7); + + /* Register default pen */ + _expect_pens_attached_or_detached(ptest.ids, 7, 0); + + /* Register completely default pen */ + pen = SDL_PenModifyBegin(ptest.ids[0]); + SDL_memcpy(pen->guid.data, ptest.guids[0].data, sizeof(ptest.guids[0].data)); + SDL_PenModifyEnd(pen, SDL_TRUE); + + SDL_snprintf(strbuf, sizeof(strbuf), + "Pen %lu", (unsigned long) ptest.ids[0]); + _expect_pen_config(ptest.ids[0], ptest.guids[0], SDL_TRUE, + strbuf, SDL_PEN_TYPE_PEN, SDL_PEN_INFO_UNKNOWN, 0.0f, + SDL_PEN_INK_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0)); + SDLTest_AssertPass("Pass #1: default pen"); + + /* Register mostly-default pen with buttons and custom name */ + pen = SDL_PenModifyBegin(ptest.ids[1]); + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); + SDL_memcpy(pen->guid.data, ptest.guids[1].data, sizeof(ptest.guids[1].data)); + SDL_strlcpy(strbuf, "My special test pen", SDL_PEN_MAX_NAME); + SDL_strlcpy(pen->name, strbuf, SDL_PEN_MAX_NAME); + pen->info.num_buttons = 7; + SDL_PenModifyEnd(pen, SDL_TRUE); + + _expect_pen_config(ptest.ids[1], ptest.guids[1], SDL_TRUE, + strbuf, SDL_PEN_TYPE_PEN, 7, 0.0f, + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1)); + SDLTest_AssertPass("Pass #2: default pen with button and name info"); + + /* Register eraser with default name, but keep initially detached */ + pen = SDL_PenModifyBegin(ptest.ids[2]); + SDL_memcpy(pen->guid.data, ptest.guids[2].data, sizeof(ptest.guids[2].data)); + pen->type = SDL_PEN_TYPE_ERASER; + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); + SDL_PenModifyEnd(pen, SDL_FALSE); + + SDL_snprintf(strbuf, sizeof(strbuf), + "Eraser %lu", (unsigned long) ptest.ids[2]); + _expect_pen_config(ptest.ids[2], ptest.guids[2], SDL_FALSE, + strbuf, SDL_PEN_TYPE_ERASER, SDL_PEN_INFO_UNKNOWN, SDL_PEN_INFO_UNKNOWN, + SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1)); + /* now make available */ + SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_TRUE); + _expect_pen_config(ptest.ids[2], ptest.guids[2], SDL_TRUE, + strbuf, SDL_PEN_TYPE_ERASER, SDL_PEN_INFO_UNKNOWN, SDL_PEN_INFO_UNKNOWN, + SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); + SDLTest_AssertPass("Pass #3: eraser-type pen initially detached, then attached"); + + /* Abort pen registration */ + pen = SDL_PenModifyBegin(ptest.ids[3]); + SDL_memcpy(pen->guid.data, ptest.guids[3].data, sizeof(ptest.guids[3].data)); + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); + pen->type = SDL_PEN_TYPE_NONE; + SDL_PenModifyEnd(pen, SDL_TRUE); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); + SDLTest_AssertCheck(NULL == SDL_GetPenName(ptest.ids[3]), "Pen with aborted registration remains unknown"); + SDLTest_AssertPass("Pass #4: aborted pen registration"); + + /* Brush with custom axes */ + pen = SDL_PenModifyBegin(ptest.ids[4]); + SDL_memcpy(pen->guid.data, ptest.guids[4].data, sizeof(ptest.guids[4].data)); + SDL_strlcpy(pen->name, "Testish Brush", SDL_PEN_MAX_NAME); + pen->type = SDL_PEN_TYPE_BRUSH; + pen->info.num_buttons = 1; + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_ROTATION_MASK); + pen->info.max_tilt = 72.5f; + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK); + SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); + SDL_PenModifyEnd(pen, SDL_TRUE); + _expect_pen_config(ptest.ids[4], ptest.guids[4], SDL_TRUE, + "Testish Brush", SDL_PEN_TYPE_BRUSH, 1, 72.5f, + SDL_PEN_INK_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_ROTATION_MASK | SDL_PEN_AXIS_PRESSURE_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2) | ATTACHED(4)); + SDLTest_AssertPass("Pass #5: brush-type pen with unusual axis layout"); + + /* Wacom airbrush pen */ + { + const Uint32 wacom_type_id = 0x0912; + const Uint32 wacom_serial_id = 0xa0b1c2d3; + SDL_GUID guid = { + { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } + }; + guid.data[0] = (wacom_serial_id >> 0) & 0xff; + guid.data[1] = (wacom_serial_id >> 8) & 0xff; + guid.data[2] = (wacom_serial_id >> 16) & 0xff; + guid.data[3] = (wacom_serial_id >> 24) & 0xff; + guid.data[4] = (wacom_type_id >> 0) & 0xff; + guid.data[5] = (wacom_type_id >> 8) & 0xff; + guid.data[6] = (wacom_type_id >> 16) & 0xff; + guid.data[7] = (wacom_type_id >> 24) & 0xff; + + pen = SDL_PenModifyBegin(ptest.ids[5]); + SDL_PenModifyForWacomID(pen, wacom_type_id, &mask); + SDL_PenUpdateGUIDForWacom(&pen->guid, wacom_type_id, wacom_serial_id); + SDL_PenModifyAddCapabilities(pen, mask); + SDL_PenModifyEnd(pen, SDL_TRUE); + _expect_pen_config(ptest.ids[5], guid, SDL_TRUE, + "Wacom Airbrush Pen", SDL_PEN_TYPE_AIRBRUSH, 1, 64.0f, /* Max tilt angle */ + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK); + _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2) | ATTACHED(4) | ATTACHED(5)); + } + SDLTest_AssertPass("Pass #6: wacom airbrush pen"); + + /* Cleanup */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _teardown_test(&ptest, backup); + return TEST_COMPLETED; +} + +#define SET_POS(update, xpos, ypos) \ + (update).x = (xpos); \ + (update).y = (ypos); + +static void +_penmouse_expect_button(int type, int button) +{ + SDL_bool press = type == SDL_PRESSED; + SDLTest_AssertCheck((press ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP) == _mouseemu_last_event, + "Mouse button %s: %x", + (press ? "press" : "release"), _mouseemu_last_event); + SDLTest_AssertCheck(button == _mouseemu_last_button, + "Observed the expected simulated button: %d", _mouseemu_last_button); + SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, + "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); + + _mouseemu_last_event = 0; +} + +/** + * @brief Check pen device mouse emulation and event suppression without SDL_HINT_PEN_DELAY_MOUSE_BUTTON + * + * Since we include SDL_pen.c, we link it against our own mock implementations of SDL_PSendMouseButton + * and SDL_SendMouseMotion; see tehere for details. + */ +static int +pen_mouseEmulation(void *arg) +{ + pen_testdata ptest; + SDL_Event event; + int i; + SDL_PenStatusInfo update; + deviceinfo_backup *backup; + + pen_delay_mouse_button_mode = 0; + pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* to trigger our own SDL_SendMouseButton */ + + /* Register pen */ + backup = _setup_test(&ptest, 1); + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "testpen", + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT | SDL_PEN_AXIS_YTILT), + 20); + _pen_trackGCSweep(&ptest); + + /* Move pen into window */ + SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); + + /* Initialise pen location */ + SDL_memset(update.axes, 0, sizeof(update.axes)); + SET_POS(update, 100.0f, 100.0f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + while (SDL_PollEvent(&event)) + ; /* Flush event queue */ + + /* Test motion forwarding */ + _mouseemu_last_event = 0; + SET_POS(update, 121.25f, 110.75f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + SDLTest_AssertCheck(SDL_EVENT_MOUSE_MOTION == _mouseemu_last_event, + "Mouse motion event: %d", _mouseemu_last_event); + SDLTest_AssertCheck(121.25f == _mouseemu_last_x && 110.75f == _mouseemu_last_y, + "Motion to correct position: %f,%f", _mouseemu_last_x, _mouseemu_last_y); + SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, + "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); + SDLTest_AssertCheck(0 == _mouseemu_last_relative, + "Absolute motion event"); + SDLTest_AssertPass("Motion emulation"); + + /* Test redundant motion report suppression */ + _mouseemu_last_event = 0; + + SET_POS(update, 121.25f, 110.75f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + + SET_POS(update, 121.25f, 110.75f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + + update.axes[0] = 1.0f; + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + + SET_POS(update, 121.25f, 110.75f); + update.axes[0] = 0.0f; + update.axes[1] = 0.75f; + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Redundant mouse motion suppressed: %d", _mouseemu_last_event); + SDLTest_AssertPass("Redundant motion suppression"); + + /* Test button press reporting */ + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); + _penmouse_expect_button(SDL_PRESSED, 1); + + for (i = 1; i <= 3; ++i) { + SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, i); + _penmouse_expect_button(SDL_PRESSED, i + 1); + } + SDLTest_AssertPass("Button press mouse emulation"); + + /* Test button release reporting */ + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); + _penmouse_expect_button(SDL_RELEASED, 1); + + for (i = 1; i <= 3; ++i) { + SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, i); + _penmouse_expect_button(SDL_RELEASED, i + 1); + } + SDLTest_AssertPass("Button release mouse emulation"); + + /* Cleanup */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _teardown_test(&ptest, backup); + return TEST_COMPLETED; +} + +/** + * @brief Check pen device mouse emulation when SDL_HINT_PEN_DELAY_MOUSE_BUTTON is enabled (default) + */ +static int +pen_mouseEmulationDelayed(void *arg) +{ + pen_testdata ptest; + SDL_Event event; + int i; + SDL_PenStatusInfo update; + deviceinfo_backup *backup; + + pen_delay_mouse_button_mode = 1; + pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* to trigger our own SDL_SendMouseButton */ + + /* Register pen */ + backup = _setup_test(&ptest, 1); + SDL_PenGCMark(); + _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "testpen", + SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT | SDL_PEN_AXIS_YTILT), + 20); + _pen_trackGCSweep(&ptest); + + /* Move pen into window */ + SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); + + /* Initialise pen location */ + SDL_memset(update.axes, 0, sizeof(update.axes)); + SET_POS(update, 100.0f, 100.0f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + while (SDL_PollEvent(&event)) + ; /* Flush event queue */ + + /* Test motion forwarding */ + _mouseemu_last_event = 0; + SET_POS(update, 121.25f, 110.75f); + SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); + SDLTest_AssertCheck(SDL_EVENT_MOUSE_MOTION == _mouseemu_last_event, + "Mouse motion event: %d", _mouseemu_last_event); + SDLTest_AssertCheck(121.25f == _mouseemu_last_x && 110.75f == _mouseemu_last_y, + "Motion to correct position: %f,%f", _mouseemu_last_x, _mouseemu_last_y); + SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, + "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); + SDLTest_AssertCheck(0 == _mouseemu_last_relative, + "Absolute motion event"); + SDLTest_AssertPass("Motion emulation"); + _mouseemu_last_event = 0; + + /* Test button press reporting */ + for (i = 1; i <= 2; ++i) { + SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button press suppressed: %d", _mouseemu_last_event); + SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button release suppressed: %d", _mouseemu_last_event); + } + + /* Touch surface */ + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); + _penmouse_expect_button(SDL_PRESSED, 1); + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); + _penmouse_expect_button(SDL_RELEASED, 1); + + /* Test button press reporting, releasing extra button AFTER lifting pen */ + for (i = 1; i <= 2; ++i) { + SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button press suppressed (A.1): %d", _mouseemu_last_event); + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); + _penmouse_expect_button(SDL_PRESSED, i + 1); + + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); + _penmouse_expect_button(SDL_RELEASED, i + 1); + + SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button press suppressed (A.2): %d", _mouseemu_last_event); + } + SDLTest_AssertPass("Delayed button press mouse emulation, touching without releasing button"); + + /* Test button press reporting, releasing extra button BEFORE lifting pen */ + for (i = 1; i <= 2; ++i) { + SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button press suppressed (B.1): %d", _mouseemu_last_event); + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); + _penmouse_expect_button(SDL_PRESSED, i + 1); + + SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, i); + SDLTest_AssertCheck(0 == _mouseemu_last_event, + "Non-touching button press suppressed (B.2): %d", _mouseemu_last_event); + SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); + _penmouse_expect_button(SDL_RELEASED, i + 1); + } + SDLTest_AssertPass("Delayed button press mouse emulation, touching and then releasing button"); + + /* Cleanup */ + SDL_PenGCMark(); + _pen_trackGCSweep(&ptest); + _teardown_test(&ptest, backup); + return TEST_COMPLETED; +} + +/** + * @brief Ensure that all SDL_Pen*Event structures have compatible memory layout, as expected by SDL_pen.c + */ +static int +pen_memoryLayout(void *arg) +{ +#define LAYOUT_COMPATIBLE(field) \ + SDLTest_AssertCheck(offsetof(SDL_PenTipEvent, field) == offsetof(SDL_PenMotionEvent, field), \ + "Memory layout SDL_PenTipEvent and SDL_PenMotionEvent compatibility: '" #field "'"); \ + SDLTest_AssertCheck(offsetof(SDL_PenTipEvent, field) == offsetof(SDL_PenButtonEvent, field), \ + "Memory layout SDL_PenTipEvent and SDL_PenBUttonEvent compatibility: '" #field "'"); + + LAYOUT_COMPATIBLE(which); + LAYOUT_COMPATIBLE(x); + LAYOUT_COMPATIBLE(y); + LAYOUT_COMPATIBLE(axes); + + return TEST_COMPLETED; +} + +/* ================= Test References ================== */ + +/* Mouse test cases */ +static const SDLTest_TestCaseReference penTest1 = { (SDLTest_TestCaseFp)pen_iteration, "pen_iteration", "Iterate over all pens with SDL_PenIDForIndex", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest2 = { (SDLTest_TestCaseFp)pen_hotplugging, "pen_hotplugging", "Hotplug pens and validate their status, including SDL_PenConnected", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest3 = { (SDLTest_TestCaseFp)pen_GUIDs, "pen_GUIDs", "Check Pen SDL_GUID operations", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest4 = { (SDLTest_TestCaseFp)pen_buttonReporting, "pen_buttonReporting", "Check pen button presses", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest5 = { (SDLTest_TestCaseFp)pen_movementAndAxes, "pen_movementAndAxes", "Check pen movement and axis update reporting", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest6 = { (SDLTest_TestCaseFp)pen_initAndInfo, "pen_info", "Check pen self-description and initialisation", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest7 = { (SDLTest_TestCaseFp)pen_mouseEmulation, "pen_mouseEmulation", "Check pen-as-mouse event forwarding (direct)", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest8 = { (SDLTest_TestCaseFp)pen_mouseEmulationDelayed, "pen_mouseEmulationDelayed", "Check pen-as-mouse event forwarding (delayed)", TEST_ENABLED }; + +static const SDLTest_TestCaseReference penTest9 = { (SDLTest_TestCaseFp)pen_memoryLayout, "pen_memoryLayout", "Check that all pen events have compatible layout (required by SDL_pen.c)", TEST_ENABLED }; + +/* Sequence of Mouse test cases */ +static const SDLTest_TestCaseReference *penTests[] = { + &penTest1, &penTest2, &penTest3, &penTest4, &penTest5, &penTest6, &penTest7, &penTest8, &penTest9, NULL +}; + +/* Mouse test suite (global) */ +SDLTest_TestSuiteReference penTestSuite = { + "Pen", + NULL, + penTests, + NULL +}; diff --git a/test/testautomation_pixels.c b/test/testautomation_pixels.c index 9980b561..790c9d86 100644 --- a/test/testautomation_pixels.c +++ b/test/testautomation_pixels.c @@ -11,6 +11,8 @@ static const Uint32 g_AllFormats[] = { SDL_PIXELFORMAT_INDEX1LSB, SDL_PIXELFORMAT_INDEX1MSB, + SDL_PIXELFORMAT_INDEX2LSB, + SDL_PIXELFORMAT_INDEX2MSB, SDL_PIXELFORMAT_INDEX4LSB, SDL_PIXELFORMAT_INDEX4MSB, SDL_PIXELFORMAT_INDEX8, @@ -39,7 +41,10 @@ static const Uint32 g_AllFormats[] = { SDL_PIXELFORMAT_RGBA8888, SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_BGRA8888, + SDL_PIXELFORMAT_XRGB2101010, + SDL_PIXELFORMAT_XBGR2101010, SDL_PIXELFORMAT_ARGB2101010, + SDL_PIXELFORMAT_ABGR2101010, SDL_PIXELFORMAT_YV12, SDL_PIXELFORMAT_IYUV, SDL_PIXELFORMAT_YUY2, @@ -53,6 +58,8 @@ static const int g_numAllFormats = SDL_arraysize(g_AllFormats); static const char *g_AllFormatsVerbose[] = { "SDL_PIXELFORMAT_INDEX1LSB", "SDL_PIXELFORMAT_INDEX1MSB", + "SDL_PIXELFORMAT_INDEX2LSB", + "SDL_PIXELFORMAT_INDEX2MSB", "SDL_PIXELFORMAT_INDEX4LSB", "SDL_PIXELFORMAT_INDEX4MSB", "SDL_PIXELFORMAT_INDEX8", @@ -81,7 +88,10 @@ static const char *g_AllFormatsVerbose[] = { "SDL_PIXELFORMAT_RGBA8888", "SDL_PIXELFORMAT_ABGR8888", "SDL_PIXELFORMAT_BGRA8888", + "SDL_PIXELFORMAT_XRGB2101010", + "SDL_PIXELFORMAT_XBGR2101010", "SDL_PIXELFORMAT_ARGB2101010", + "SDL_PIXELFORMAT_ABGR2101010", "SDL_PIXELFORMAT_YV12", "SDL_PIXELFORMAT_IYUV", "SDL_PIXELFORMAT_YUY2", @@ -105,7 +115,7 @@ static const char *g_invalidPixelFormatsVerbose[] = { /* Test case functions */ /** - * \brief Call to SDL_CreatePixelFormat and SDL_DestroyPixelFormat + * Call to SDL_CreatePixelFormat and SDL_DestroyPixelFormat * * \sa SDL_CreatePixelFormat * \sa SDL_DestroyPixelFormat @@ -198,7 +208,7 @@ static int pixels_allocFreeFormat(void *arg) } /** - * \brief Call to SDL_GetPixelFormatName + * Call to SDL_GetPixelFormatName * * \sa SDL_GetPixelFormatName */ @@ -265,7 +275,7 @@ static int pixels_getPixelFormatName(void *arg) } /** - * \brief Call to SDL_CreatePalette and SDL_DestroyPalette + * Call to SDL_CreatePalette and SDL_DestroyPalette * * \sa SDL_CreatePalette * \sa SDL_DestroyPalette diff --git a/test/testautomation_platform.c b/test/testautomation_platform.c index 516c186f..0e1ddd1c 100644 --- a/test/testautomation_platform.c +++ b/test/testautomation_platform.c @@ -11,7 +11,7 @@ /* Helper functions */ /** - * \brief Compare sizes of types. + * Compare sizes of types. * * @note Watcom C flags these as Warning 201: "Unreachable code" if you just * compare them directly, so we push it through a function to keep the @@ -25,7 +25,7 @@ static int compareSizeOfType(size_t sizeoftype, size_t hardcodetype) /* Test case functions */ /** - * \brief Tests type sizes. + * Tests type sizes. */ static int platform_testTypes(void *arg) { @@ -47,7 +47,7 @@ static int platform_testTypes(void *arg) } /** - * \brief Tests platform endianness and SDL_SwapXY functions. + * Tests platform endianness and SDL_SwapXY functions. */ static int platform_testEndianessAndSwap(void *arg) { @@ -118,7 +118,7 @@ static int platform_testEndianessAndSwap(void *arg) } /** - * \brief Tests SDL_GetXYZ() functions + * Tests SDL_GetXYZ() functions * \sa SDL_GetPlatform * \sa SDL_GetCPUCount * \sa SDL_GetRevision @@ -162,7 +162,7 @@ static int platform_testGetFunctions(void *arg) } /** - * \brief Tests SDL_HasXYZ() functions + * Tests SDL_HasXYZ() functions * \sa SDL_HasAltiVec * \sa SDL_HasMMX * \sa SDL_HasSSE @@ -204,7 +204,7 @@ static int platform_testHasFunctions(void *arg) } /** - * \brief Tests SDL_GetVersion + * Tests SDL_GetVersion * \sa SDL_GetVersion */ static int platform_testGetVersion(void *arg) @@ -227,7 +227,7 @@ static int platform_testGetVersion(void *arg) } /** - * \brief Tests SDL_VERSION macro + * Tests SDL_VERSION macro */ static int platform_testSDLVersion(void *arg) { @@ -249,7 +249,7 @@ static int platform_testSDLVersion(void *arg) } /** - * \brief Tests default SDL_Init + * Tests default SDL_Init */ static int platform_testDefaultInit(void *arg) { @@ -271,7 +271,7 @@ static int platform_testDefaultInit(void *arg) } /** - * \brief Tests SDL_Get/Set/ClearError + * Tests SDL_Get/Set/ClearError * \sa SDL_GetError * \sa SDL_SetError * \sa SDL_ClearError @@ -322,7 +322,7 @@ static int platform_testGetSetClearError(void *arg) } /** - * \brief Tests SDL_SetError with empty input + * Tests SDL_SetError with empty input * \sa SDL_SetError */ static int platform_testSetErrorEmptyInput(void *arg) @@ -363,13 +363,13 @@ static int platform_testSetErrorEmptyInput(void *arg) #endif /** - * \brief Tests SDL_SetError with invalid input + * Tests SDL_SetError with invalid input * \sa SDL_SetError */ static int platform_testSetErrorInvalidInput(void *arg) { int result; - const char *invalidError = NULL; + const char *invalidError = ""; const char *probeError = "Testing"; const char *lastError; size_t len; @@ -446,7 +446,7 @@ static int platform_testSetErrorInvalidInput(void *arg) #endif /** - * \brief Tests SDL_GetPowerInfo + * Tests SDL_GetPowerInfo * \sa SDL_GetPowerInfo */ static int platform_testGetPowerInfo(void *arg) diff --git a/test/testautomation_properties.c b/test/testautomation_properties.c new file mode 100644 index 00000000..7c05c240 --- /dev/null +++ b/test/testautomation_properties.c @@ -0,0 +1,350 @@ +/** + * Properties test suite + */ + +#include +#include +#include "testautomation_suites.h" + +/* Test case functions */ + +/** + * Test basic functionality + */ +static void SDLCALL count_properties(void *userdata, SDL_PropertiesID props, const char *name) +{ + int *count = (int *)userdata; + ++(*count); +} +static void SDLCALL count_foo_properties(void *userdata, SDL_PropertiesID props, const char *name) +{ + int *count = (int *)userdata; + if (SDL_strcmp(name, "foo") == 0) { + ++(*count); + } +} +static int properties_testBasic(void *arg) +{ + SDL_PropertiesID props; + char key[2], expected_value[2]; + SDL_PropertyType type; + void *value; + const char *value_string; + Sint64 value_number; + float value_float; + SDL_bool value_bool; + int i, result, count; + + props = SDL_CreateProperties(); + SDLTest_AssertPass("Call to SDL_CreateProperties()"); + SDLTest_AssertCheck(props != 0, + "Verify props were created, got: %" SDL_PRIu32 "", props); + + for (i = 0; i < 10; ++i) { + SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i); + SDL_snprintf(expected_value, SDL_arraysize(expected_value), "%c", 'a' + i); + result = SDL_SetProperty(props, key, expected_value); + SDLTest_AssertPass("Call to SDL_SetProperty()"); + SDLTest_AssertCheck(result == 0, + "Verify property value was set, got: %d", result); + value = SDL_GetProperty(props, key, NULL); + SDLTest_AssertPass("Call to SDL_GetProperty()"); + SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, expected_value) == 0, + "Verify property value was set, got %s, expected %s", value ? (const char *)value : "NULL", expected_value); + } + + count = 0; + SDL_EnumerateProperties(props, count_properties, &count); + SDLTest_AssertCheck(count == 10, + "Verify property count, expected 10, got: %d", count); + + for (i = 0; i < 10; ++i) { + SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i); + result = SDL_SetProperty(props, key, NULL); + SDLTest_AssertPass("Call to SDL_SetProperty(NULL)"); + SDLTest_AssertCheck(result == 0, + "Verify property value was set, got: %d", result); + value = SDL_GetProperty(props, key, NULL); + SDLTest_AssertPass("Call to SDL_GetProperty()"); + SDLTest_AssertCheck(value == NULL, + "Verify property value was set, got %s, expected NULL", (const char *)value); + } + + count = 0; + SDL_EnumerateProperties(props, count_properties, &count); + SDLTest_AssertCheck(count == 0, + "Verify property count, expected 0, got: %d", count); + + /* Check default values */ + value = SDL_GetProperty(props, "foo", (void *)0xabcd); + SDLTest_AssertCheck(value == (void *)0xabcd, + "Verify property, expected 0xabcd, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", "abcd"); + SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "abcd") == 0, + "Verify string property, expected abcd, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 1234); + SDLTest_AssertCheck(value_number == 1234, + "Verify number property, expected 1234, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 1234.0f); + SDLTest_AssertCheck(value_float == 1234.0f, + "Verify float property, expected 1234, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_TRUE); + SDLTest_AssertCheck(value_bool == SDL_TRUE, + "Verify boolean property, expected SDL_TRUE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Check data value */ + SDLTest_AssertPass("Call to SDL_SetProperty(\"foo\", 0x01)"); + SDL_SetProperty(props, "foo", (void *)0x01); + type = SDL_GetPropertyType(props, "foo"); + SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_POINTER, + "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_POINTER, type); + value = SDL_GetProperty(props, "foo", NULL); + SDLTest_AssertCheck(value == (void *)0x01, + "Verify property, expected 0x01, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", NULL); + SDLTest_AssertCheck(value_string == NULL, + "Verify string property, expected NULL, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 0); + SDLTest_AssertCheck(value_number == 0, + "Verify number property, expected 0, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 0.0f); + SDLTest_AssertCheck(value_float == 0.0f, + "Verify float property, expected 0, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_FALSE); + SDLTest_AssertCheck(value_bool == SDL_FALSE, + "Verify boolean property, expected SDL_FALSE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Check string value */ + SDLTest_AssertPass("Call to SDL_SetStringProperty(\"foo\", \"bar\")"); + SDL_SetStringProperty(props, "foo", "bar"); + type = SDL_GetPropertyType(props, "foo"); + SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_STRING, + "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_STRING, type); + value = SDL_GetProperty(props, "foo", NULL); + SDLTest_AssertCheck(value == NULL, + "Verify property, expected NULL, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", NULL); + SDLTest_AssertCheck(value_string != NULL && SDL_strcmp(value_string, "bar") == 0, + "Verify string property, expected bar, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 0); + SDLTest_AssertCheck(value_number == 0, + "Verify number property, expected 0, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 0.0f); + SDLTest_AssertCheck(value_float == 0.0f, + "Verify float property, expected 0, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_FALSE); + SDLTest_AssertCheck(value_bool == SDL_TRUE, + "Verify boolean property, expected SDL_TRUE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Check number value */ + SDLTest_AssertPass("Call to SDL_SetNumberProperty(\"foo\", 1)"); + SDL_SetNumberProperty(props, "foo", 1); + type = SDL_GetPropertyType(props, "foo"); + SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_NUMBER, + "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_NUMBER, type); + value = SDL_GetProperty(props, "foo", NULL); + SDLTest_AssertCheck(value == NULL, + "Verify property, expected NULL, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", NULL); + SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "1") == 0, + "Verify string property, expected 1, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 0); + SDLTest_AssertCheck(value_number == 1, + "Verify number property, expected 1, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 0.0f); + SDLTest_AssertCheck(value_float == 1.0f, + "Verify float property, expected 1, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_FALSE); + SDLTest_AssertCheck(value_bool == SDL_TRUE, + "Verify boolean property, expected SDL_TRUE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Check float value */ + SDLTest_AssertPass("Call to SDL_SetFloatProperty(\"foo\", 1)"); + SDL_SetFloatProperty(props, "foo", 1.75f); + type = SDL_GetPropertyType(props, "foo"); + SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_FLOAT, + "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_FLOAT, type); + value = SDL_GetProperty(props, "foo", NULL); + SDLTest_AssertCheck(value == NULL, + "Verify property, expected NULL, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", NULL); + SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "1.750000") == 0, + "Verify string property, expected 1.750000, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 0); + SDLTest_AssertCheck(value_number == 2, + "Verify number property, expected 2, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 0.0f); + SDLTest_AssertCheck(value_float == 1.75f, + "Verify float property, expected 1.75, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_FALSE); + SDLTest_AssertCheck(value_bool == SDL_TRUE, + "Verify boolean property, expected SDL_TRUE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Check boolean value */ + SDLTest_AssertPass("Call to SDL_SetBooleanProperty(\"foo\", SDL_TRUE)"); + SDL_SetBooleanProperty(props, "foo", 3); /* Note we're testing non-true/false value here */ + type = SDL_GetPropertyType(props, "foo"); + SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_BOOLEAN, + "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_BOOLEAN, type); + value = SDL_GetProperty(props, "foo", NULL); + SDLTest_AssertCheck(value == NULL, + "Verify property, expected NULL, got: %p", value); + value_string = SDL_GetStringProperty(props, "foo", NULL); + SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "true") == 0, + "Verify string property, expected true, got: %s", value_string); + value_number = SDL_GetNumberProperty(props, "foo", 0); + SDLTest_AssertCheck(value_number == 1, + "Verify number property, expected 1, got: %" SDL_PRIu64 "", value_number); + value_float = SDL_GetFloatProperty(props, "foo", 0.0f); + SDLTest_AssertCheck(value_float == 1.0f, + "Verify float property, expected 1, got: %f", value_float); + value_bool = SDL_GetBooleanProperty(props, "foo", SDL_FALSE); + SDLTest_AssertCheck(value_bool == SDL_TRUE, + "Verify boolean property, expected SDL_TRUE, got: %s", value_bool ? "SDL_TRUE" : "SDL_FALSE"); + + /* Make sure we have exactly one property named foo */ + count = 0; + SDL_EnumerateProperties(props, count_foo_properties, &count); + SDLTest_AssertCheck(count == 1, + "Verify foo property count, expected 1, got: %d", count); + + SDL_DestroyProperties(props); + + return TEST_COMPLETED; +} + +/** + * Test cleanup functionality + */ +static void SDLCALL cleanup(void *userdata, void *value) +{ + int *count = (int *)userdata; + ++(*count); +} +static int properties_testCleanup(void *arg) +{ + SDL_PropertiesID props; + char key[2], expected_value[2]; + int i, count; + + props = SDL_CreateProperties(); + + SDLTest_AssertPass("Call to SDL_SetProperty(cleanup)"); + count = 0; + SDL_SetPropertyWithCleanup(props, "a", "0", cleanup, &count); + SDL_SetPropertyWithCleanup(props, "a", NULL, cleanup, &count); + SDLTest_AssertCheck(count == 1, + "Verify cleanup for deleting property, got %d, expected 1", count); + + SDLTest_AssertPass("Call to SDL_DestroyProperties()"); + count = 0; + for (i = 0; i < 10; ++i) { + SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i); + SDL_snprintf(expected_value, SDL_arraysize(expected_value), "%c", 'a' + i); + SDL_SetPropertyWithCleanup(props, key, expected_value, cleanup, &count); + } + SDL_DestroyProperties(props); + SDLTest_AssertCheck(count == 10, + "Verify cleanup for destroying properties, got %d, expected 10", count); + + return TEST_COMPLETED; +} + +/** + * Test locking functionality + */ +struct properties_thread_data +{ + SDL_bool done; + SDL_PropertiesID props; +}; +static int properties_thread(void *arg) +{ + struct properties_thread_data *data = (struct properties_thread_data *)arg; + + while (!data->done) { + SDL_LockProperties(data->props); + SDL_SetProperty(data->props, "a", "thread_loop"); + SDL_UnlockProperties(data->props); + } + SDL_LockProperties(data->props); + SDL_SetProperty(data->props, "a", "thread_done"); + SDL_UnlockProperties(data->props); + return 0; +} +static int properties_testLocking(void *arg) +{ + struct properties_thread_data data; + SDL_Thread *thread; + void *value; + + SDLTest_AssertPass("Testing property locking"); + data.done = SDL_FALSE; + data.props = SDL_CreateProperties(); + SDLTest_AssertPass("Setting property to 'init'"); + SDL_SetProperty(data.props, "a", "init"); + thread = SDL_CreateThread(properties_thread, "properties_thread", &data); + if (thread) { + SDLTest_AssertPass("Waiting for property to change to 'thread_loop'"); + for ( ; ; ) + { + SDL_Delay(10); + SDL_LockProperties(data.props); + value = SDL_GetProperty(data.props, "a", NULL); + SDL_UnlockProperties(data.props); + + if (!value || SDL_strcmp((const char *)value, "thread_loop") == 0) { + break; + } + } + SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "thread_loop") == 0, + "After thread loop, property is %s, expected 'thread_loop'", value ? (const char *)value : "NULL"); + + SDLTest_AssertPass("Setting property to 'main'"); + SDL_LockProperties(data.props); + SDL_SetProperty(data.props, "a", "main"); + SDL_Delay(100); + value = SDL_GetProperty(data.props, "a", NULL); + SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "main") == 0, + "After 100ms sleep, property is %s, expected 'main'", value ? (const char *)value : "NULL"); + SDL_UnlockProperties(data.props); + + data.done = SDL_TRUE; + SDL_WaitThread(thread, NULL); + + value = SDL_GetProperty(data.props, "a", NULL); + SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "thread_done") == 0, + "After thread complete, property is %s, expected 'thread_done'", value ? (const char *)value : "NULL"); + } + SDL_DestroyProperties(data.props); + + return TEST_COMPLETED; +} + +/* ================= Test References ================== */ + +/* Properties test cases */ +static const SDLTest_TestCaseReference propertiesTest1 = { + (SDLTest_TestCaseFp)properties_testBasic, "properties_testBasic", "Test basic property functionality", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference propertiesTest2 = { + (SDLTest_TestCaseFp)properties_testCleanup, "properties_testCleanup", "Test property cleanup functionality", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference propertiesTest3 = { + (SDLTest_TestCaseFp)properties_testLocking, "properties_testLocking", "Test property locking functionality", TEST_ENABLED +}; + +/* Sequence of Properties test cases */ +static const SDLTest_TestCaseReference *propertiesTests[] = { + &propertiesTest1, &propertiesTest2, &propertiesTest3, NULL +}; + +/* Properties test suite (global) */ +SDLTest_TestSuiteReference propertiesTestSuite = { + "Properties", + NULL, + propertiesTests, + NULL +}; diff --git a/test/testautomation_rect.c b/test/testautomation_rect.c index ba6ac472..72823a06 100644 --- a/test/testautomation_rect.c +++ b/test/testautomation_rect.c @@ -11,7 +11,7 @@ /* Helper functions */ /** - * \brief Private helper to check SDL_GetRectAndLineIntersection results + * Private helper to check SDL_GetRectAndLineIntersection results */ static void validateIntersectRectAndLineResults( SDL_bool intersection, SDL_bool expectedIntersection, @@ -38,7 +38,7 @@ static void validateIntersectRectAndLineResults( /* Test case functions */ /** - * \brief Tests SDL_GetRectAndLineIntersection() clipping cases + * Tests SDL_GetRectAndLineIntersection() clipping cases * * \sa SDL_GetRectAndLineIntersection */ @@ -107,7 +107,7 @@ static int rect_testIntersectRectAndLine(void *arg) } /** - * \brief Tests SDL_GetRectAndLineIntersection() non-clipping case line inside + * Tests SDL_GetRectAndLineIntersection() non-clipping case line inside * * \sa SDL_GetRectAndLineIntersection */ @@ -172,7 +172,7 @@ static int rect_testIntersectRectAndLineInside(void *arg) } /** - * \brief Tests SDL_GetRectAndLineIntersection() non-clipping cases outside + * Tests SDL_GetRectAndLineIntersection() non-clipping cases outside * * \sa SDL_GetRectAndLineIntersection */ @@ -225,7 +225,7 @@ static int rect_testIntersectRectAndLineOutside(void *arg) } /** - * \brief Tests SDL_GetRectAndLineIntersection() with empty rectangle + * Tests SDL_GetRectAndLineIntersection() with empty rectangle * * \sa SDL_GetRectAndLineIntersection */ @@ -258,7 +258,7 @@ static int rect_testIntersectRectAndLineEmpty(void *arg) } /** - * \brief Negative tests against SDL_GetRectAndLineIntersection() with invalid parameters + * Negative tests against SDL_GetRectAndLineIntersection() with invalid parameters * * \sa SDL_GetRectAndLineIntersection */ @@ -291,7 +291,7 @@ static int rect_testIntersectRectAndLineParam(void *arg) } /** - * \brief Private helper to check SDL_HasRectIntersection results + * Private helper to check SDL_HasRectIntersection results */ static void validateHasIntersectionResults( SDL_bool intersection, SDL_bool expectedIntersection, @@ -314,7 +314,7 @@ static void validateHasIntersectionResults( } /** - * \brief Private helper to check SDL_GetRectIntersection results + * Private helper to check SDL_GetRectIntersection results */ static void validateIntersectRectResults( SDL_bool intersection, SDL_bool expectedIntersection, @@ -333,7 +333,7 @@ static void validateIntersectRectResults( } /** - * \brief Private helper to check SDL_GetRectUnion results + * Private helper to check SDL_GetRectUnion results */ static void validateUnionRectResults( SDL_Rect *rectA, SDL_Rect *rectB, SDL_Rect *refRectA, SDL_Rect *refRectB, @@ -356,7 +356,7 @@ static void validateUnionRectResults( } /** - * \brief Private helper to check SDL_RectEmpty results + * Private helper to check SDL_RectEmpty results */ static void validateRectEmptyResults( SDL_bool empty, SDL_bool expectedEmpty, @@ -374,7 +374,7 @@ static void validateRectEmptyResults( } /** - * \brief Private helper to check SDL_RectsEqual results + * Private helper to check SDL_RectsEqual results */ static void validateRectEqualsResults( SDL_bool equals, SDL_bool expectedEquals, @@ -397,7 +397,7 @@ static void validateRectEqualsResults( } /** - * \brief Private helper to check SDL_RectsEqualFloat results + * Private helper to check SDL_RectsEqualFloat results */ static void validateFRectEqualsResults( SDL_bool equals, SDL_bool expectedEquals, @@ -423,7 +423,7 @@ static void validateFRectEqualsResults( } /** - * \brief Tests SDL_GetRectIntersection() with B fully inside A + * Tests SDL_GetRectIntersection() with B fully inside A * * \sa SDL_GetRectIntersection */ @@ -450,7 +450,7 @@ static int rect_testIntersectRectInside(void *arg) } /** - * \brief Tests SDL_GetRectIntersection() with B fully outside A + * Tests SDL_GetRectIntersection() with B fully outside A * * \sa SDL_GetRectIntersection */ @@ -477,7 +477,7 @@ static int rect_testIntersectRectOutside(void *arg) } /** - * \brief Tests SDL_GetRectIntersection() with B partially intersecting A + * Tests SDL_GetRectIntersection() with B partially intersecting A * * \sa SDL_GetRectIntersection */ @@ -565,7 +565,7 @@ static int rect_testIntersectRectPartial(void *arg) } /** - * \brief Tests SDL_GetRectIntersection() with 1x1 pixel sized rectangles + * Tests SDL_GetRectIntersection() with 1x1 pixel sized rectangles * * \sa SDL_GetRectIntersection */ @@ -611,7 +611,7 @@ static int rect_testIntersectRectPoint(void *arg) } /** - * \brief Tests SDL_GetRectIntersection() with empty rectangles + * Tests SDL_GetRectIntersection() with empty rectangles * * \sa SDL_GetRectIntersection */ @@ -682,7 +682,7 @@ static int rect_testIntersectRectEmpty(void *arg) } /** - * \brief Negative tests against SDL_GetRectIntersection() with invalid parameters + * Negative tests against SDL_GetRectIntersection() with invalid parameters * * \sa SDL_GetRectIntersection */ @@ -711,7 +711,7 @@ static int rect_testIntersectRectParam(void *arg) } /** - * \brief Tests SDL_HasRectIntersection() with B fully inside A + * Tests SDL_HasRectIntersection() with B fully inside A * * \sa SDL_HasRectIntersection */ @@ -737,7 +737,7 @@ static int rect_testHasIntersectionInside(void *arg) } /** - * \brief Tests SDL_HasRectIntersection() with B fully outside A + * Tests SDL_HasRectIntersection() with B fully outside A * * \sa SDL_HasRectIntersection */ @@ -763,7 +763,7 @@ static int rect_testHasIntersectionOutside(void *arg) } /** - * \brief Tests SDL_HasRectIntersection() with B partially intersecting A + * Tests SDL_HasRectIntersection() with B partially intersecting A * * \sa SDL_HasRectIntersection */ @@ -829,7 +829,7 @@ static int rect_testHasIntersectionPartial(void *arg) } /** - * \brief Tests SDL_HasRectIntersection() with 1x1 pixel sized rectangles + * Tests SDL_HasRectIntersection() with 1x1 pixel sized rectangles * * \sa SDL_HasRectIntersection */ @@ -874,7 +874,7 @@ static int rect_testHasIntersectionPoint(void *arg) } /** - * \brief Tests SDL_HasRectIntersection() with empty rectangles + * Tests SDL_HasRectIntersection() with empty rectangles * * \sa SDL_HasRectIntersection */ @@ -931,7 +931,7 @@ static int rect_testHasIntersectionEmpty(void *arg) } /** - * \brief Negative tests against SDL_HasRectIntersection() with invalid parameters + * Negative tests against SDL_HasRectIntersection() with invalid parameters * * \sa SDL_HasRectIntersection */ @@ -953,7 +953,7 @@ static int rect_testHasIntersectionParam(void *arg) } /** - * \brief Test SDL_GetRectEnclosingPoints() without clipping + * Test SDL_GetRectEnclosingPoints() without clipping * * \sa SDL_GetRectEnclosingPoints */ @@ -1030,7 +1030,7 @@ static int rect_testEnclosePoints(void *arg) } /** - * \brief Test SDL_GetRectEnclosingPoints() with repeated input points + * Test SDL_GetRectEnclosingPoints() with repeated input points * * \sa SDL_GetRectEnclosingPoints */ @@ -1113,7 +1113,7 @@ static int rect_testEnclosePointsRepeatedInput(void *arg) } /** - * \brief Test SDL_GetRectEnclosingPoints() with clipping + * Test SDL_GetRectEnclosingPoints() with clipping * * \sa SDL_GetRectEnclosingPoints */ @@ -1219,7 +1219,7 @@ static int rect_testEnclosePointsWithClipping(void *arg) } /** - * \brief Negative tests against SDL_GetRectEnclosingPoints() with invalid parameters + * Negative tests against SDL_GetRectEnclosingPoints() with invalid parameters * * \sa SDL_GetRectEnclosingPoints */ @@ -1246,7 +1246,7 @@ static int rect_testEnclosePointsParam(void *arg) } /** - * \brief Tests SDL_GetRectUnion() where rect B is outside rect A + * Tests SDL_GetRectUnion() where rect B is outside rect A * * \sa SDL_GetRectUnion */ @@ -1324,7 +1324,7 @@ static int rect_testUnionRectOutside(void *arg) } /** - * \brief Tests SDL_GetRectUnion() where rect A or rect B are empty + * Tests SDL_GetRectUnion() where rect A or rect B are empty * * \sa SDL_GetRectUnion */ @@ -1388,7 +1388,7 @@ static int rect_testUnionRectEmpty(void *arg) } /** - * \brief Tests SDL_GetRectUnion() where rect B is inside rect A + * Tests SDL_GetRectUnion() where rect B is inside rect A * * \sa SDL_GetRectUnion */ @@ -1459,7 +1459,7 @@ static int rect_testUnionRectInside(void *arg) } /** - * \brief Negative tests against SDL_GetRectUnion() with invalid parameters + * Negative tests against SDL_GetRectUnion() with invalid parameters * * \sa SDL_GetRectUnion */ @@ -1486,7 +1486,7 @@ static int rect_testUnionRectParam(void *arg) } /** - * \brief Tests SDL_RectEmpty() with various inputs + * Tests SDL_RectEmpty() with various inputs * * \sa SDL_RectEmpty */ @@ -1528,7 +1528,7 @@ static int rect_testRectEmpty(void *arg) } /** - * \brief Negative tests against SDL_RectEmpty() with invalid parameters + * Negative tests against SDL_RectEmpty() with invalid parameters * * \sa SDL_RectEmpty */ @@ -1544,7 +1544,7 @@ static int rect_testRectEmptyParam(void *arg) } /** - * \brief Tests SDL_RectsEqual() with various inputs + * Tests SDL_RectsEqual() with various inputs * * \sa SDL_RectsEqual */ @@ -1573,7 +1573,7 @@ static int rect_testRectEquals(void *arg) } /** - * \brief Negative tests against SDL_RectsEqual() with invalid parameters + * Negative tests against SDL_RectsEqual() with invalid parameters * * \sa SDL_RectsEqual */ @@ -1605,7 +1605,7 @@ static int rect_testRectEqualsParam(void *arg) } /** - * \brief Tests SDL_RectsEqualFloat() with various inputs + * Tests SDL_RectsEqualFloat() with various inputs * * \sa SDL_RectsEqualFloat */ @@ -1634,7 +1634,7 @@ static int rect_testFRectEquals(void *arg) } /** - * \brief Negative tests against SDL_RectsEqualFloat() with invalid parameters + * Negative tests against SDL_RectsEqualFloat() with invalid parameters * * \sa SDL_RectsEqualFloat */ @@ -1804,7 +1804,7 @@ static const SDLTest_TestCaseReference rectTest31 = { }; /** - * \brief Sequence of Rect test cases; functions that handle simple rectangles including overlaps and merges. + * Sequence of Rect test cases; functions that handle simple rectangles including overlaps and merges. */ static const SDLTest_TestCaseReference *rectTests[] = { &rectTest1, &rectTest2, &rectTest3, &rectTest4, &rectTest5, &rectTest6, &rectTest7, &rectTest8, &rectTest9, &rectTest10, &rectTest11, &rectTest12, &rectTest13, &rectTest14, diff --git a/test/testautomation_render.c b/test/testautomation_render.c index 66288dcc..67b1f412 100644 --- a/test/testautomation_render.c +++ b/test/testautomation_render.c @@ -49,6 +49,7 @@ static int isSupported(int code); static void InitCreateRenderer(void *arg) { int width = 320, height = 240; + int renderer_flags = SDL_RENDERER_ACCELERATED; renderer = NULL; window = SDL_CreateWindow("render_testCreateRenderer", width, height, 0); SDLTest_AssertPass("SDL_CreateWindow()"); @@ -57,9 +58,13 @@ static void InitCreateRenderer(void *arg) return; } - renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED); + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "dummy") == 0) { + renderer_flags = 0; + } + + renderer = SDL_CreateRenderer(window, NULL, renderer_flags); SDLTest_AssertPass("SDL_CreateRenderer()"); - SDLTest_AssertCheck(renderer != NULL, "Check SDL_CreateRenderer result"); + SDLTest_AssertCheck(renderer != NULL, "Check SDL_CreateRenderer result: %s", renderer != NULL ? "success" : SDL_GetError()); if (renderer == NULL) { SDL_DestroyWindow(window); return; @@ -71,13 +76,13 @@ static void InitCreateRenderer(void *arg) */ static void CleanupDestroyRenderer(void *arg) { - if (renderer != NULL) { + if (renderer) { SDL_DestroyRenderer(renderer); renderer = NULL; SDLTest_AssertPass("SDL_DestroyRenderer()"); } - if (window != NULL) { + if (window) { SDL_DestroyWindow(window); window = NULL; SDLTest_AssertPass("SDL_DestroyWindow"); @@ -85,7 +90,7 @@ static void CleanupDestroyRenderer(void *arg) } /** - * \brief Tests call to SDL_GetNumRenderDrivers + * Tests call to SDL_GetNumRenderDrivers * * \sa SDL_GetNumRenderDrivers */ @@ -98,7 +103,7 @@ static int render_testGetNumRenderDrivers(void *arg) } /** - * \brief Tests the SDL primitives for rendering. + * Tests the SDL primitives for rendering. * * \sa SDL_SetRenderDrawColor * \sa SDL_RenderFillRect @@ -185,7 +190,7 @@ static int render_testPrimitives(void *arg) } /** - * \brief Tests the SDL primitives with alpha for rendering. + * Tests the SDL primitives with alpha for rendering. * * \sa SDL_SetRenderDrawColor * \sa SDL_SetRenderDrawBlendMode @@ -328,7 +333,7 @@ static int render_testPrimitivesBlend(void *arg) } /** - * \brief Tests some blitting routines. + * Tests some blitting routines. * * \sa SDL_RenderTexture * \sa SDL_DestroyTexture @@ -395,7 +400,7 @@ static int render_testBlit(void *arg) } /** - * \brief Blits doing color tests. + * Blits doing color tests. * * \sa SDL_SetTextureColorMod * \sa SDL_RenderTexture @@ -469,7 +474,7 @@ static int render_testBlitColor(void *arg) } /** - * \brief Tests blitting with alpha. + * Tests blitting with alpha. * * \sa SDL_SetTextureAlphaMod * \sa SDL_RenderTexture @@ -546,7 +551,7 @@ static int render_testBlitAlpha(void *arg) } /** - * \brief Tests a blend mode. + * Tests a blend mode. * * \sa SDL_SetTextureBlendMode * \sa SDL_RenderTexture @@ -597,7 +602,7 @@ testBlitBlendMode(SDL_Texture *tface, int mode) } /** - * \brief Tests some more blitting routines. + * Tests some more blitting routines. * * \sa SDL_SetTextureColorMod * \sa SDL_SetTextureAlphaMod @@ -754,7 +759,7 @@ static int render_testBlitBlend(void *arg) } /** - * \brief Test viewport + * Test viewport */ static int render_testViewport(void *arg) { @@ -811,7 +816,7 @@ static int render_testViewport(void *arg) } /** - * \brief Test logical size + * Test logical size */ static int render_testLogicalSize(void *arg) { @@ -921,7 +926,7 @@ static int render_testLogicalSize(void *arg) /* Helper functions */ /** - * \brief Checks to see if functionality is supported. Helper function. + * Checks to see if functionality is supported. Helper function. */ static int isSupported(int code) @@ -930,7 +935,7 @@ isSupported(int code) } /** - * \brief Test to see if we can vary the draw color. Helper function. + * Test to see if we can vary the draw color. Helper function. * * \sa SDL_SetRenderDrawColor * \sa SDL_GetRenderDrawColor @@ -971,7 +976,7 @@ hasDrawColor(void) } /** - * \brief Test to see if we can vary the blend mode. Helper function. + * Test to see if we can vary the blend mode. Helper function. * * \sa SDL_SetRenderDrawBlendMode * \sa SDL_GetRenderDrawBlendMode @@ -1038,7 +1043,7 @@ hasBlendModes(void) } /** - * \brief Loads the test image 'Face' as texture. Helper function. + * Loads the test image 'Face' as texture. Helper function. * * \sa SDL_CreateTextureFromSurface */ @@ -1049,12 +1054,12 @@ loadTestFace(void) SDL_Texture *tface; face = SDLTest_ImageFace(); - if (face == NULL) { + if (!face) { return NULL; } tface = SDL_CreateTextureFromSurface(renderer, face); - if (tface == NULL) { + if (!tface) { SDLTest_LogError("SDL_CreateTextureFromSurface() failed with error: %s", SDL_GetError()); } @@ -1064,7 +1069,7 @@ loadTestFace(void) } /** - * \brief Test to see if can set texture color mode. Helper function. + * Test to see if can set texture color mode. Helper function. * * \sa SDL_SetTextureColorMod * \sa SDL_GetTextureColorMod @@ -1080,7 +1085,7 @@ hasTexColor(void) /* Get test face. */ tface = loadTestFace(); - if (tface == NULL) { + if (!tface) { return 0; } @@ -1107,7 +1112,7 @@ hasTexColor(void) } /** - * \brief Test to see if we can vary the alpha of the texture. Helper function. + * Test to see if we can vary the alpha of the texture. Helper function. * * \sa SDL_SetTextureAlphaMod * \sa SDL_GetTextureAlphaMod @@ -1123,7 +1128,7 @@ hasTexAlpha(void) /* Get test face. */ tface = loadTestFace(); - if (tface == NULL) { + if (!tface) { return 0; } @@ -1150,7 +1155,7 @@ hasTexAlpha(void) } /** - * \brief Compares screen pixels with image pixels. Helper function. + * Compares screen pixels with image pixels. Helper function. * * \param referenceSurface Image to compare against. * \param allowable_error allowed difference from the reference image @@ -1195,7 +1200,7 @@ compare(SDL_Surface *referenceSurface, int allowable_error) } /** - * \brief Clears the screen. Helper function. + * Clears the screen. Helper function. * * \sa SDL_SetRenderDrawColor * \sa SDL_RenderClear diff --git a/test/testautomation_rwops.c b/test/testautomation_rwops.c index a8cdb903..f7199f09 100644 --- a/test/testautomation_rwops.c +++ b/test/testautomation_rwops.c @@ -90,7 +90,7 @@ static void RWopsTearDown(void *arg) } /** - * \brief Makes sure parameters work properly. Local helper function. + * Makes sure parameters work properly. Local helper function. * * \sa SDL_RWseek * \sa SDL_RWread @@ -110,11 +110,11 @@ static void testGenericRWopsValidations(SDL_RWops *rw, SDL_bool write) SDLTest_AssertPass("Call to SDL_RWseek succeeded"); SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i); - /* Test write. */ + /* Test write */ s = SDL_RWwrite(rw, RWopsHelloWorldTestString, sizeof(RWopsHelloWorldTestString) - 1); SDLTest_AssertPass("Call to SDL_RWwrite succeeded"); if (write) { - SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing one byte with SDL_RWwrite, expected 1, got %i", (int)s); + SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing with SDL_RWwrite, expected %i, got %i", (int)sizeof(RWopsHelloWorldTestString) - 1, (int)s); } else { SDLTest_AssertCheck(s == 0, "Verify result of writing with SDL_RWwrite, expected: 0, got %i", (int)s); } @@ -129,6 +129,37 @@ static void testGenericRWopsValidations(SDL_RWops *rw, SDL_bool write) SDLTest_AssertPass("Call to SDL_RWseek succeeded"); SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i); + /* Test read */ + s = SDL_RWread(rw, buf, sizeof(RWopsHelloWorldTestString) - 1); + SDLTest_AssertPass("Call to SDL_RWread succeeded"); + SDLTest_AssertCheck( + s == (sizeof(RWopsHelloWorldTestString) - 1), + "Verify result from SDL_RWread, expected %i, got %i", + (int)(sizeof(RWopsHelloWorldTestString) - 1), + (int)s); + SDLTest_AssertCheck( + SDL_memcmp(buf, RWopsHelloWorldTestString, sizeof(RWopsHelloWorldTestString) - 1) == 0, + "Verify read bytes match expected string, expected '%s', got '%s'", RWopsHelloWorldTestString, buf); + + /* Test seek back to start */ + i = SDL_RWseek(rw, 0, SDL_RW_SEEK_SET); + SDLTest_AssertPass("Call to SDL_RWseek succeeded"); + SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i); + + /* Test printf */ + s = SDL_RWprintf(rw, "%s", RWopsHelloWorldTestString); + SDLTest_AssertPass("Call to SDL_RWprintf succeeded"); + if (write) { + SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing with SDL_RWprintf, expected %i, got %i", (int)sizeof(RWopsHelloWorldTestString) - 1, (int)s); + } else { + SDLTest_AssertCheck(s == 0, "Verify result of writing with SDL_RWwrite, expected: 0, got %i", (int)s); + } + + /* Test seek back to start */ + i = SDL_RWseek(rw, 0, SDL_RW_SEEK_SET); + SDLTest_AssertPass("Call to SDL_RWseek succeeded"); + SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i); + /* Test read */ s = SDL_RWread(rw, buf, sizeof(RWopsHelloWorldTestString) - 1); SDLTest_AssertPass("Call to SDL_RWread succeeded"); @@ -214,7 +245,7 @@ static int rwops_testParamNegative(void *arg) } /** - * \brief Tests opening from memory. + * Tests opening from memory. * * \sa SDL_RWFromMem * \sa SDL_RWClose @@ -253,7 +284,7 @@ static int rwops_testMem(void *arg) } /** - * \brief Tests opening from memory. + * Tests opening from memory. * * \sa SDL_RWFromConstMem * \sa SDL_RWClose @@ -288,7 +319,7 @@ static int rwops_testConstMem(void *arg) } /** - * \brief Tests reading from file. + * Tests reading from file. * * \sa SDL_RWFromFile * \sa SDL_RWClose @@ -335,7 +366,7 @@ static int rwops_testFileRead(void *arg) } /** - * \brief Tests writing from file. + * Tests writing from file. * * \sa SDL_RWFromFile * \sa SDL_RWClose @@ -382,7 +413,7 @@ static int rwops_testFileWrite(void *arg) } /** - * \brief Tests alloc and free RW context. + * Tests alloc and free RW context. * * \sa SDL_CreateRW * \sa SDL_DestroyRW @@ -410,7 +441,7 @@ static int rwops_testAllocFree(void *arg) } /** - * \brief Compare memory and file reads + * Compare memory and file reads * * \sa SDL_RWFromMem * \sa SDL_RWFromFile @@ -473,7 +504,7 @@ static int rwops_testCompareRWFromMemWithRWFromFile(void *arg) } /** - * \brief Tests writing and reading from file using endian aware functions. + * Tests writing and reading from file using endian aware functions. * * \sa SDL_RWFromFile * \sa SDL_RWClose diff --git a/test/testautomation_sdltest.c b/test/testautomation_sdltest.c index e0464504..1f29c073 100644 --- a/test/testautomation_sdltest.c +++ b/test/testautomation_sdltest.c @@ -12,7 +12,7 @@ /* Test case functions */ /** - * \brief Calls to SDLTest_GenerateRunSeed() + * Calls to SDLTest_GenerateRunSeed() */ static int sdltest_generateRunSeed(void *arg) { @@ -42,7 +42,7 @@ static int sdltest_generateRunSeed(void *arg) } /** - * \brief Calls to SDLTest_GetFuzzerInvocationCount() + * Calls to SDLTest_GetFuzzerInvocationCount() */ static int sdltest_getFuzzerInvocationCount(void *arg) { @@ -64,7 +64,7 @@ static int sdltest_getFuzzerInvocationCount(void *arg) } /** - * \brief Calls to random number generators + * Calls to random number generators */ static int sdltest_randomNumber(void *arg) { @@ -131,7 +131,7 @@ static int sdltest_randomNumber(void *arg) } /** - * \brief Calls to random boundary number generators for Uint8 + * Calls to random boundary number generators for Uint8 */ static int sdltest_randomBoundaryNumberUint8(void *arg) { @@ -240,7 +240,7 @@ static int sdltest_randomBoundaryNumberUint8(void *arg) } /** - * \brief Calls to random boundary number generators for Uint16 + * Calls to random boundary number generators for Uint16 */ static int sdltest_randomBoundaryNumberUint16(void *arg) { @@ -349,7 +349,7 @@ static int sdltest_randomBoundaryNumberUint16(void *arg) } /** - * \brief Calls to random boundary number generators for Uint32 + * Calls to random boundary number generators for Uint32 */ static int sdltest_randomBoundaryNumberUint32(void *arg) { @@ -458,7 +458,7 @@ static int sdltest_randomBoundaryNumberUint32(void *arg) } /** - * \brief Calls to random boundary number generators for Uint64 + * Calls to random boundary number generators for Uint64 */ static int sdltest_randomBoundaryNumberUint64(void *arg) { @@ -567,7 +567,7 @@ static int sdltest_randomBoundaryNumberUint64(void *arg) } /** - * \brief Calls to random boundary number generators for Sint8 + * Calls to random boundary number generators for Sint8 */ static int sdltest_randomBoundaryNumberSint8(void *arg) { @@ -676,7 +676,7 @@ static int sdltest_randomBoundaryNumberSint8(void *arg) } /** - * \brief Calls to random boundary number generators for Sint16 + * Calls to random boundary number generators for Sint16 */ static int sdltest_randomBoundaryNumberSint16(void *arg) { @@ -785,7 +785,7 @@ static int sdltest_randomBoundaryNumberSint16(void *arg) } /** - * \brief Calls to random boundary number generators for Sint32 + * Calls to random boundary number generators for Sint32 */ static int sdltest_randomBoundaryNumberSint32(void *arg) { @@ -901,7 +901,7 @@ static int sdltest_randomBoundaryNumberSint32(void *arg) } /** - * \brief Calls to random boundary number generators for Sint64 + * Calls to random boundary number generators for Sint64 */ static int sdltest_randomBoundaryNumberSint64(void *arg) { @@ -1010,7 +1010,7 @@ static int sdltest_randomBoundaryNumberSint64(void *arg) } /** - * \brief Calls to SDLTest_RandomIntegerInRange + * Calls to SDLTest_RandomIntegerInRange */ static int sdltest_randomIntegerInRange(void *arg) { @@ -1084,7 +1084,7 @@ static int sdltest_randomIntegerInRange(void *arg) } /** - * \brief Calls to SDLTest_RandomAsciiString + * Calls to SDLTest_RandomAsciiString */ static int sdltest_randomAsciiString(void *arg) { @@ -1116,7 +1116,7 @@ static int sdltest_randomAsciiString(void *arg) } /** - * \brief Calls to SDLTest_RandomAsciiStringWithMaximumLength + * Calls to SDLTest_RandomAsciiStringWithMaximumLength */ static int sdltest_randomAsciiStringWithMaximumLength(void *arg) { @@ -1168,7 +1168,7 @@ static int sdltest_randomAsciiStringWithMaximumLength(void *arg) } /** - * \brief Calls to SDLTest_RandomAsciiStringOfSize + * Calls to SDLTest_RandomAsciiStringOfSize */ static int sdltest_randomAsciiStringOfSize(void *arg) { diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c index e9960f04..e447cf42 100644 --- a/test/testautomation_stdlib.c +++ b/test/testautomation_stdlib.c @@ -8,7 +8,7 @@ /* Test case functions */ /** - * \brief Call to SDL_strnlen + * Call to SDL_strnlen */ #undef SDL_strnlen static int stdlib_strnlen(void *arg) @@ -36,7 +36,7 @@ static int stdlib_strnlen(void *arg) } /** - * \brief Call to SDL_strlcpy + * Call to SDL_strlcpy */ #undef SDL_strlcpy static int stdlib_strlcpy(void *arg) @@ -60,6 +60,68 @@ static int stdlib_strlcpy(void *arg) return TEST_COMPLETED; } +/** + * Call to SDL_strstr + */ +static int stdlib_strstr(void *arg) +{ + char *result; + const char *text = "abcdef"; + const char *expected; + + result = SDL_strstr(text, ""); + expected = text; + SDLTest_AssertPass("Call to SDL_strstr(text, \"\")"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strstr(text, "abc"); + expected = text; + SDLTest_AssertPass("Call to SDL_strstr(text, \"abc\")"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strstr(text, "bcd"); + expected = text+1; + SDLTest_AssertPass("Call to SDL_strstr(text, \"bcd\")"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strstr(text, "xyz"); + expected = NULL; + SDLTest_AssertPass("Call to SDL_strstr(text, \"xyz\")"); + SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result); + + result = SDL_strnstr(text, "", SDL_strlen(text)); + expected = text; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"\", SDL_strlen(text))"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strnstr(text, "abc", SDL_strlen(text)); + expected = text; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"abc\", SDL_strlen(text))"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strnstr(text, "bcd", SDL_strlen(text)); + expected = text+1; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"bcd\", SDL_strlen(text))"); + SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result); + + result = SDL_strnstr(text, "bcd", 3); + expected = NULL; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"bcd\", 3)"); + SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result); + + result = SDL_strnstr(text, "xyz", 3); + expected = NULL; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"xyz\", 3)"); + SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result); + + result = SDL_strnstr(text, "xyz", SDL_strlen(text)*100000); + expected = NULL; + SDLTest_AssertPass("Call to SDL_strnstr(text, \"xyz\", SDL_strlen(text)*100000)"); + SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result); + + return TEST_COMPLETED; +} + #if defined(HAVE_WFORMAT) || defined(HAVE_WFORMAT_EXTRA_ARGS) #pragma GCC diagnostic push #ifdef HAVE_WFORMAT @@ -71,7 +133,7 @@ static int stdlib_strlcpy(void *arg) #endif /** - * \brief Call to SDL_snprintf + * Call to SDL_snprintf */ #undef SDL_snprintf static int stdlib_snprintf(void *arg) @@ -258,11 +320,18 @@ static int stdlib_snprintf(void *arg) SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); SDLTest_AssertCheck(result == SDL_strlen(expected), "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result); + if (sizeof(void *) >= 8) { + result = SDL_snprintf(text, sizeof(text), "%p", (void *)0x1ba07bddf60L); + expected = "0x1ba07bddf60"; + SDLTest_AssertPass("Call to SDL_snprintf(text, sizeof(text), \"%%p\", 0x1ba07bddf60)"); + SDLTest_AssertCheck(SDL_strcmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == SDL_strlen(expected), "Check result value, expected: %d, got: %d", (int)SDL_strlen(expected), result); + } return TEST_COMPLETED; } /** - * \brief Call to SDL_swprintf + * Call to SDL_swprintf */ #undef SDL_swprintf static int stdlib_swprintf(void *arg) @@ -429,7 +498,7 @@ static int stdlib_swprintf(void *arg) #endif /** - * \brief Call to SDL_getenv and SDL_setenv + * Call to SDL_getenv and SDL_setenv */ static int stdlib_getsetenv(void *arg) { @@ -453,10 +522,10 @@ static int stdlib_getsetenv(void *arg) text = SDL_getenv(name); SDLTest_AssertPass("Call to SDL_getenv('%s')", name); - if (text != NULL) { + if (text) { SDLTest_Log("Expected: NULL, Got: '%s' (%i)", text, (int)SDL_strlen(text)); } - } while (text != NULL); + } while (text); /* Create random values to set */ value1 = SDLTest_RandomAsciiStringOfSize(10); @@ -572,7 +641,7 @@ static int stdlib_getsetenv(void *arg) #endif /** - * \brief Call to SDL_sscanf + * Call to SDL_sscanf */ #undef SDL_sscanf static int stdlib_sscanf(void *arg) @@ -585,7 +654,7 @@ static int stdlib_sscanf(void *arg) long long_output, expected_long_output; long long long_long_output, expected_long_long_output; size_t size_output, expected_size_output; - char text[128]; + char text[128], text2[128]; expected_output = output = 123; expected_result = -1; @@ -649,6 +718,82 @@ static int stdlib_sscanf(void *arg) SDLTest_AssertCheck(expected_size_output == size_output, "Check output, expected: %zu, got: %zu", expected_size_output, size_output); SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc def", "%s", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc def\", \"%%s\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc,def", "%s", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%s\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc,def") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc,def", "%[cba]", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[cba]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc,def", "%[a-z]", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[z-a]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc,def", "%[^,]", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[^,]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 0; + text[0] = '\0'; + result = SDL_sscanf("abc,def", "%[A-Z]", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[A-Z]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "") == 0, "Check output, expected: \"\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 2; + text[0] = '\0'; + text2[0] = '\0'; + result = SDL_sscanf("abc,def", "%[abc],%[def]", text, text2); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[abc],%%[def]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(SDL_strcmp(text2, "def") == 0, "Check output, expected: \"def\", got: \"%s\"", text2); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 2; + text[0] = '\0'; + text2[0] = '\0'; + result = SDL_sscanf("abc,def", "%[abc]%*[,]%[def]", text, text2); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc,def\", \"%%[abc]%%*[,]%%[def]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(SDL_strcmp(text2, "def") == 0, "Check output, expected: \"def\", got: \"%s\"", text2); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 2; + text[0] = '\0'; + text2[0] = '\0'; + result = SDL_sscanf("abc def", "%[abc] %[def]", text, text2); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc def\", \"%%[abc] %%[def]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc") == 0, "Check output, expected: \"abc\", got: \"%s\"", text); + SDLTest_AssertCheck(SDL_strcmp(text2, "def") == 0, "Check output, expected: \"def\", got: \"%s\"", text2); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + + expected_result = 1; + text[0] = '\0'; + result = SDL_sscanf("abc123XYZ", "%[a-zA-Z0-9]", text); + SDLTest_AssertPass("Call to SDL_sscanf(\"abc123XYZ\", \"%%[a-zA-Z0-9]\", text)"); + SDLTest_AssertCheck(SDL_strcmp(text, "abc123XYZ") == 0, "Check output, expected: \"abc123XYZ\", got: \"%s\"", text); + SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result); + return TEST_COMPLETED; } @@ -665,7 +810,7 @@ static int stdlib_sscanf(void *arg) #endif /** - * \brief Call to SDL_aligned_alloc + * Call to SDL_aligned_alloc */ static int stdlib_aligned_alloc(void *arg) { @@ -854,22 +999,26 @@ static const SDLTest_TestCaseReference stdlibTest2 = { }; static const SDLTest_TestCaseReference stdlibTest3 = { - stdlib_snprintf, "stdlib_snprintf", "Call to SDL_snprintf", TEST_ENABLED + stdlib_strstr, "stdlib_strstr", "Call to SDL_strstr", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest4 = { - stdlib_swprintf, "stdlib_swprintf", "Call to SDL_swprintf", TEST_ENABLED + stdlib_snprintf, "stdlib_snprintf", "Call to SDL_snprintf", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest5 = { - stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED + stdlib_swprintf, "stdlib_swprintf", "Call to SDL_swprintf", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest6 = { - stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED + stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest7 = { + stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference stdlibTest8 = { stdlib_aligned_alloc, "stdlib_aligned_alloc", "Call to SDL_aligned_alloc", TEST_ENABLED }; @@ -886,6 +1035,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = { &stdlibTest5, &stdlibTest6, &stdlibTest7, + &stdlibTest8, &stdlibTestOverflow, NULL }; diff --git a/test/testautomation_subsystems.c b/test/testautomation_subsystems.c new file mode 100644 index 00000000..b60927a0 --- /dev/null +++ b/test/testautomation_subsystems.c @@ -0,0 +1,239 @@ +/** + * Events test suite + */ +#include "testautomation_suites.h" +#include +#include + +/* ================= Test Case Implementation ================== */ + +/* Fixture */ + +static void subsystemsSetUp(void *arg) +{ + /* Reset each one of the SDL subsystems */ + /* CHECKME: can we use SDL_Quit here, or this will break the flow of tests? */ + SDL_Quit(); + /* Alternate variant without SDL_Quit: + while (SDL_WasInit(SDL_INIT_EVERYTHING) != 0) { + SDL_QuitSubSystem(SDL_INIT_EVERYTHING); + } + */ + SDLTest_AssertPass("Reset all subsystems before subsystems test"); + SDLTest_AssertCheck(SDL_WasInit(SDL_INIT_EVERYTHING) == 0, "Check result from SDL_WasInit(SDL_INIT_EVERYTHING)"); +} + +static void subsystemsTearDown(void *arg) +{ + /* Reset each one of the SDL subsystems */ + SDL_Quit(); + + SDLTest_AssertPass("Cleanup of subsystems test completed"); +} + +/* Test case functions */ + +/** + * Inits and Quits particular subsystem, checking its Init status. + * + * \sa SDL_InitSubSystem + * \sa SDL_QuitSubSystem + * + */ +static int subsystems_referenceCount() +{ + const int system = SDL_INIT_VIDEO; + int result; + /* Ensure that we start with a non-initialized subsystem. */ + SDLTest_AssertCheck(SDL_WasInit(system) == 0, "Check result from SDL_WasInit(0x%x)", system); + + /* Init subsystem once, and quit once */ + SDL_InitSubSystem(system); + SDLTest_AssertPass("Call to SDL_InitSubSystem(0x%x)", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == system, "Check result from SDL_WasInit(0x%x), expected: 0x%x, got: 0x%x", system, system, result); + + SDL_QuitSubSystem(system); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(0x%x)", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == 0, "Check result from SDL_WasInit(0x%x), expected: 0, got: 0x%x", system, result); + + /* Init subsystem number of times, then decrement reference count until it's disposed of. */ + SDL_InitSubSystem(system); + SDL_InitSubSystem(system); + SDL_InitSubSystem(system); + SDLTest_AssertPass("Call to SDL_InitSubSystem(0x%x) x3 times", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == system, "Check result from SDL_WasInit(0x%x), expected: 0x%x, got: 0x%x", system, system, result); + + SDL_QuitSubSystem(system); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(0x%x) x1", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == system, "Check result from SDL_WasInit(0x%x), expected: 0x%x, got: 0x%x", system, system, result); + SDL_QuitSubSystem(system); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(0x%x) x2", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == system, "Check result from SDL_WasInit(0x%x), expected: 0x%x, got: 0x%x", system, system, result); + SDL_QuitSubSystem(system); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(0x%x) x3", system); + result = SDL_WasInit(system); + SDLTest_AssertCheck(result == 0, "Check result from SDL_WasInit(0x%x), expected: 0, got: 0x%x", system, result); + + return TEST_COMPLETED; +} + +/** + * Inits and Quits subsystems that have another as dependency; + * check that the dependency is not removed before the last of its dependents. + * + * \sa SDL_InitSubSystem + * \sa SDL_QuitSubSystem + * + */ +static int subsystems_dependRefCountInitAllQuitByOne() +{ + int result; + /* Ensure that we start with reset subsystems. */ + SDLTest_AssertCheck(SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) == 0, + "Check result from SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS)"); + + /* Following should init SDL_INIT_EVENTS and give it +3 ref counts. */ + SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + + /* Quit systems one by one. */ + SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_VIDEO)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_JOYSTICK)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == 0, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0, got: 0x%x", result); + + return TEST_COMPLETED; +} + +/** + * Inits and Quits subsystems that have another as dependency; + * check that the dependency is not removed before the last of its dependents. + * + * \sa SDL_InitSubSystem + * \sa SDL_QuitSubSystem + * + */ +static int subsystems_dependRefCountInitByOneQuitAll() +{ + int result; + /* Ensure that we start with reset subsystems. */ + SDLTest_AssertCheck(SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) == 0, + "Check result from SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS)"); + + /* Following should init SDL_INIT_EVENTS and give it +3 ref counts. */ + SDL_InitSubSystem(SDL_INIT_VIDEO); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_VIDEO)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + SDL_InitSubSystem(SDL_INIT_AUDIO); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_AUDIO)"); + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_JOYSTICK)"); + + /* Quit systems all at once. */ + SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == 0, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0, got: 0x%x", result); + + return TEST_COMPLETED; +} + +/** + * Inits and Quits subsystems that have another as dependency, + * but also inits that dependency explicitly, giving it extra ref count. + * Check that the dependency is not removed before the last reference is gone. + * + * \sa SDL_InitSubSystem + * \sa SDL_QuitSubSystem + * + */ +static int subsystems_dependRefCountWithExtraInit() +{ + int result; + /* Ensure that we start with reset subsystems. */ + SDLTest_AssertCheck(SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) == 0, + "Check result from SDL_WasInit(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS)"); + + /* Init EVENTS explicitly, +1 ref count. */ + SDL_InitSubSystem(SDL_INIT_EVENTS); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_EVENTS)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + /* Following should init SDL_INIT_EVENTS and give it +3 ref counts. */ + SDL_InitSubSystem(SDL_INIT_VIDEO); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_VIDEO)"); + SDL_InitSubSystem(SDL_INIT_AUDIO); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_AUDIO)"); + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_InitSubSystem(SDL_INIT_JOYSTICK)"); + + /* Quit EVENTS explicitly, -1 ref count. */ + SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_EVENTS)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + + /* Quit systems one by one. */ + SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_VIDEO)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == SDL_INIT_EVENTS, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0x4000, got: 0x%x", result); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_JOYSTICK)"); + result = SDL_WasInit(SDL_INIT_EVENTS); + SDLTest_AssertCheck(result == 0, "Check result from SDL_WasInit(SDL_INIT_EVENTS), expected: 0, got: 0x%x", result); + + return TEST_COMPLETED; +} + +/* ================= Test References ================== */ + +/* Subsystems test cases */ +static const SDLTest_TestCaseReference subsystemsTest1 = { + (SDLTest_TestCaseFp)subsystems_referenceCount, "subsystems_referenceCount", "Makes sure that subsystem stays until number of quits matches inits.", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference subsystemsTest2 = { + (SDLTest_TestCaseFp)subsystems_dependRefCountInitAllQuitByOne, "subsystems_dependRefCountInitAllQuitByOne", "Check reference count of subsystem dependencies.", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference subsystemsTest3 = { + (SDLTest_TestCaseFp)subsystems_dependRefCountInitByOneQuitAll, "subsystems_dependRefCountInitByOneQuitAll", "Check reference count of subsystem dependencies.", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference subsystemsTest4 = { + (SDLTest_TestCaseFp)subsystems_dependRefCountWithExtraInit, "subsystems_dependRefCountWithExtraInit", "Check reference count of subsystem dependencies.", TEST_ENABLED +}; + +/* Sequence of Events test cases */ +static const SDLTest_TestCaseReference *subsystemsTests[] = { + &subsystemsTest1, &subsystemsTest2, &subsystemsTest3, &subsystemsTest4, NULL +}; + +/* Events test suite (global) */ +SDLTest_TestSuiteReference subsystemsTestSuite = { + "Subsystems", + subsystemsSetUp, + subsystemsTests, + subsystemsTearDown +}; diff --git a/test/testautomation_suites.h b/test/testautomation_suites.h index 3fa83ea5..6d04b4f9 100644 --- a/test/testautomation_suites.h +++ b/test/testautomation_suites.h @@ -20,15 +20,17 @@ extern SDLTest_TestSuiteReference keyboardTestSuite; extern SDLTest_TestSuiteReference mainTestSuite; extern SDLTest_TestSuiteReference mathTestSuite; extern SDLTest_TestSuiteReference mouseTestSuite; +extern SDLTest_TestSuiteReference penTestSuite; extern SDLTest_TestSuiteReference pixelsTestSuite; extern SDLTest_TestSuiteReference platformTestSuite; +extern SDLTest_TestSuiteReference propertiesTestSuite; extern SDLTest_TestSuiteReference rectTestSuite; extern SDLTest_TestSuiteReference renderTestSuite; extern SDLTest_TestSuiteReference rwopsTestSuite; extern SDLTest_TestSuiteReference sdltestTestSuite; extern SDLTest_TestSuiteReference stdlibTestSuite; +extern SDLTest_TestSuiteReference subsystemsTestSuite; extern SDLTest_TestSuiteReference surfaceTestSuite; -extern SDLTest_TestSuiteReference syswmTestSuite; extern SDLTest_TestSuiteReference timerTestSuite; extern SDLTest_TestSuiteReference videoTestSuite; diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c index acde6396..93da3740 100644 --- a/test/testautomation_surface.c +++ b/test/testautomation_surface.c @@ -209,7 +209,7 @@ static void AssertFileExist(const char *filename) /* Test case functions */ /** - * \brief Tests sprite saving and loading + * Tests sprite saving and loading */ static int surface_testSaveLoadBitmap(void *arg) { @@ -327,7 +327,10 @@ static int surface_testCompleteSurfaceConversion(void *arg) SDL_PIXELFORMAT_RGBA8888, SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_BGRA8888, + SDL_PIXELFORMAT_XRGB2101010, + SDL_PIXELFORMAT_XBGR2101010, SDL_PIXELFORMAT_ARGB2101010, + SDL_PIXELFORMAT_ABGR2101010, }; SDL_Surface *face = NULL, *cvt1, *cvt2, *final; SDL_PixelFormat *fmt1, *fmt2; @@ -386,7 +389,7 @@ static int surface_testCompleteSurfaceConversion(void *arg) } /** - * \brief Tests sprite loading. A failure case. + * Tests sprite loading. A failure case. */ static int surface_testLoadFailure(void *arg) { @@ -397,7 +400,7 @@ static int surface_testLoadFailure(void *arg) } /** - * \brief Tests some blitting routines. + * Tests some blitting routines. */ static int surface_testBlit(void *arg) { @@ -419,7 +422,7 @@ static int surface_testBlit(void *arg) } /** - * \brief Tests some blitting routines with color mod + * Tests some blitting routines with color mod */ static int surface_testBlitColorMod(void *arg) { @@ -441,7 +444,7 @@ static int surface_testBlitColorMod(void *arg) } /** - * \brief Tests some blitting routines with alpha mod + * Tests some blitting routines with alpha mod */ static int surface_testBlitAlphaMod(void *arg) { @@ -463,7 +466,7 @@ static int surface_testBlitAlphaMod(void *arg) } /** - * \brief Tests some more blitting routines. + * Tests some more blitting routines. */ static int surface_testBlitBlendNone(void *arg) { @@ -485,7 +488,7 @@ static int surface_testBlitBlendNone(void *arg) } /** - * \brief Tests some more blitting routines. + * Tests some more blitting routines. */ static int surface_testBlitBlendBlend(void *arg) { @@ -507,7 +510,7 @@ static int surface_testBlitBlendBlend(void *arg) } /** - * \brief Tests some more blitting routines. + * Tests some more blitting routines. */ static int surface_testBlitBlendAdd(void *arg) { @@ -529,7 +532,7 @@ static int surface_testBlitBlendAdd(void *arg) } /** - * \brief Tests some more blitting routines. + * Tests some more blitting routines. */ static int surface_testBlitBlendMod(void *arg) { @@ -551,7 +554,7 @@ static int surface_testBlitBlendMod(void *arg) } /** - * \brief Tests some more blitting routines with loop + * Tests some more blitting routines with loop */ static int surface_testBlitBlendLoop(void *arg) { @@ -629,12 +632,12 @@ static int surface_testOverflow(void *arg) /* Less than 1 byte per pixel: the pitch can legitimately be less than * the width, but it must be enough to hold the appropriate number of - * bits per pixel. SDL_PIXELFORMAT_INDEX4LSB* needs 1 byte per 2 pixels. */ + * bits per pixel. SDL_PIXELFORMAT_INDEX4* needs 1 byte per 2 pixels. */ surface = SDL_CreateSurfaceFrom(buf, 6, 1, 3, SDL_PIXELFORMAT_INDEX4LSB); SDLTest_AssertCheck(surface != NULL, "6px * 4 bits per px fits in 3 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); - surface = SDL_CreateSurfaceFrom(buf, 6, 1, 3, SDL_PIXELFORMAT_INDEX4LSB); + surface = SDL_CreateSurfaceFrom(buf, 6, 1, 3, SDL_PIXELFORMAT_INDEX4MSB); SDLTest_AssertCheck(surface != NULL, "6px * 4 bits per px fits in 3 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); @@ -643,7 +646,7 @@ static int surface_testOverflow(void *arg) SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp"); SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); - surface = SDL_CreateSurfaceFrom(buf, 7, 1, 3, SDL_PIXELFORMAT_INDEX4LSB); + surface = SDL_CreateSurfaceFrom(buf, 7, 1, 3, SDL_PIXELFORMAT_INDEX4MSB); SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp"); SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); @@ -652,17 +655,45 @@ static int surface_testOverflow(void *arg) SDLTest_AssertCheck(surface != NULL, "7px * 4 bits per px fits in 4 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); - surface = SDL_CreateSurfaceFrom(buf, 7, 1, 4, SDL_PIXELFORMAT_INDEX4LSB); + surface = SDL_CreateSurfaceFrom(buf, 7, 1, 4, SDL_PIXELFORMAT_INDEX4MSB); SDLTest_AssertCheck(surface != NULL, "7px * 4 bits per px fits in 4 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); + /* SDL_PIXELFORMAT_INDEX2* needs 1 byte per 4 pixels. */ + surface = SDL_CreateSurfaceFrom(buf, 12, 1, 3, SDL_PIXELFORMAT_INDEX2LSB); + SDLTest_AssertCheck(surface != NULL, "12px * 2 bits per px fits in 3 bytes: %s", + surface != NULL ? "(success)" : SDL_GetError()); + SDL_DestroySurface(surface); + surface = SDL_CreateSurfaceFrom(buf, 12, 1, 3, SDL_PIXELFORMAT_INDEX2MSB); + SDLTest_AssertCheck(surface != NULL, "12px * 2 bits per px fits in 3 bytes: %s", + surface != NULL ? "(success)" : SDL_GetError()); + SDL_DestroySurface(surface); + + surface = SDL_CreateSurfaceFrom(buf, 13, 1, 3, SDL_PIXELFORMAT_INDEX2LSB); + SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp (%d)", surface ? surface->pitch : 0); + SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, + "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); + surface = SDL_CreateSurfaceFrom(buf, 13, 1, 3, SDL_PIXELFORMAT_INDEX2MSB); + SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp"); + SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, + "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); + + surface = SDL_CreateSurfaceFrom(buf, 13, 1, 4, SDL_PIXELFORMAT_INDEX2LSB); + SDLTest_AssertCheck(surface != NULL, "13px * 2 bits per px fits in 4 bytes: %s", + surface != NULL ? "(success)" : SDL_GetError()); + SDL_DestroySurface(surface); + surface = SDL_CreateSurfaceFrom(buf, 13, 1, 4, SDL_PIXELFORMAT_INDEX2MSB); + SDLTest_AssertCheck(surface != NULL, "13px * 2 bits per px fits in 4 bytes: %s", + surface != NULL ? "(success)" : SDL_GetError()); + SDL_DestroySurface(surface); + /* SDL_PIXELFORMAT_INDEX1* needs 1 byte per 8 pixels. */ surface = SDL_CreateSurfaceFrom(buf, 16, 1, 2, SDL_PIXELFORMAT_INDEX1LSB); SDLTest_AssertCheck(surface != NULL, "16px * 1 bit per px fits in 2 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); - surface = SDL_CreateSurfaceFrom(buf, 16, 1, 2, SDL_PIXELFORMAT_INDEX1LSB); + surface = SDL_CreateSurfaceFrom(buf, 16, 1, 2, SDL_PIXELFORMAT_INDEX1MSB); SDLTest_AssertCheck(surface != NULL, "16px * 1 bit per px fits in 2 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); @@ -671,7 +702,7 @@ static int surface_testOverflow(void *arg) SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp"); SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); - surface = SDL_CreateSurfaceFrom(buf, 17, 1, 2, SDL_PIXELFORMAT_INDEX1LSB); + surface = SDL_CreateSurfaceFrom(buf, 17, 1, 2, SDL_PIXELFORMAT_INDEX1MSB); SDLTest_AssertCheck(surface == NULL, "Should detect pitch < width * bpp"); SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0, "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError()); @@ -680,7 +711,7 @@ static int surface_testOverflow(void *arg) SDLTest_AssertCheck(surface != NULL, "17px * 1 bit per px fits in 3 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); - surface = SDL_CreateSurfaceFrom(buf, 17, 1, 3, SDL_PIXELFORMAT_INDEX1LSB); + surface = SDL_CreateSurfaceFrom(buf, 17, 1, 3, SDL_PIXELFORMAT_INDEX1MSB); SDLTest_AssertCheck(surface != NULL, "17px * 1 bit per px fits in 3 bytes: %s", surface != NULL ? "(success)" : SDL_GetError()); SDL_DestroySurface(surface); diff --git a/test/testautomation_syswm.c b/test/testautomation_syswm.c deleted file mode 100644 index 069c4b13..00000000 --- a/test/testautomation_syswm.c +++ /dev/null @@ -1,60 +0,0 @@ -/** - * SysWM test suite - */ - -/* Avoid inclusion of e.g. X11 headers when these are not installed */ -#include - -#include -#include -#include -#include "testautomation_suites.h" - -/* Test case functions */ - -/** - * \brief Call to SDL_GetWindowWMInfo - */ -static int syswm_getWindowWMInfo(void *arg) -{ - int result; - SDL_Window *window; - SDL_SysWMinfo info; - - window = SDL_CreateWindow("", 0, 0, SDL_WINDOW_HIDDEN); - SDLTest_AssertPass("Call to SDL_CreateWindow()"); - SDLTest_AssertCheck(window != NULL, "Check that value returned from SDL_CreateWindow is not NULL"); - if (window == NULL) { - return TEST_ABORTED; - } - - /* Make call */ - result = SDL_GetWindowWMInfo(window, &info, SDL_SYSWM_CURRENT_VERSION); - SDLTest_AssertPass("Call to SDL_GetWindowWMInfo()"); - SDLTest_Log((result == 0) ? "Got window information" : "Couldn't get window information"); - - SDL_DestroyWindow(window); - SDLTest_AssertPass("Call to SDL_DestroyWindow()"); - - return TEST_COMPLETED; -} - -/* ================= Test References ================== */ - -/* SysWM test cases */ -static const SDLTest_TestCaseReference syswmTest1 = { - (SDLTest_TestCaseFp)syswm_getWindowWMInfo, "syswm_getWindowWMInfo", "Call to SDL_GetWindowWMInfo", TEST_ENABLED -}; - -/* Sequence of SysWM test cases */ -static const SDLTest_TestCaseReference *syswmTests[] = { - &syswmTest1, NULL -}; - -/* SysWM test suite (global) */ -SDLTest_TestSuiteReference syswmTestSuite = { - "SysWM", - NULL, - syswmTests, - NULL -}; diff --git a/test/testautomation_timer.c b/test/testautomation_timer.c index 8f37e357..d7d6449e 100644 --- a/test/testautomation_timer.c +++ b/test/testautomation_timer.c @@ -30,7 +30,7 @@ static void timerSetUp(void *arg) /* Test case functions */ /** - * \brief Call to SDL_GetPerformanceCounter + * Call to SDL_GetPerformanceCounter */ static int timer_getPerformanceCounter(void *arg) { @@ -44,7 +44,7 @@ static int timer_getPerformanceCounter(void *arg) } /** - * \brief Call to SDL_GetPerformanceFrequency + * Call to SDL_GetPerformanceFrequency */ static int timer_getPerformanceFrequency(void *arg) { @@ -58,7 +58,7 @@ static int timer_getPerformanceFrequency(void *arg) } /** - * \brief Call to SDL_Delay and SDL_GetTicks + * Call to SDL_Delay and SDL_GetTicks */ static int timer_delayAndGetTicks(void *arg) { @@ -92,7 +92,10 @@ static int timer_delayAndGetTicks(void *arg) SDLTest_AssertCheck(result2 > 0, "Check result value, expected: >0, got: %" SDL_PRIu64, result2); difference = result2 - result; SDLTest_AssertCheck(difference > (testDelay - marginOfError), "Check difference, expected: >%d, got: %" SDL_PRIu64, testDelay - marginOfError, difference); +#if 0 + /* Disabled because this might fail on non-interactive systems. Moved to testtimer. */ SDLTest_AssertCheck(difference < (testDelay + marginOfError), "Check difference, expected: <%d, got: %" SDL_PRIu64, testDelay + marginOfError, difference); +#endif return TEST_COMPLETED; } @@ -113,7 +116,7 @@ static Uint32 SDLCALL timerTestCallback(Uint32 interval, void *param) } /** - * \brief Call to SDL_AddTimer and SDL_RemoveTimer + * Call to SDL_AddTimer and SDL_RemoveTimer */ static int timer_addRemoveTimer(void *arg) { diff --git a/test/testautomation_video.c b/test/testautomation_video.c index b57fb821..6c4001d7 100644 --- a/test/testautomation_video.c +++ b/test/testautomation_video.c @@ -13,9 +13,11 @@ static SDL_Window *createVideoSuiteTestWindow(const char *title) { SDL_Window *window; + SDL_Event event; int w, h; - SDL_WindowFlags flags; + Uint32 flags; SDL_bool needs_renderer = SDL_FALSE; + SDL_bool needs_events_pumped = SDL_FALSE; /* Standard window */ w = SDLTest_RandomIntegerInRange(320, 1024); @@ -23,7 +25,7 @@ static SDL_Window *createVideoSuiteTestWindow(const char *title) flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS; window = SDL_CreateWindow(title, w, h, flags); - SDLTest_AssertPass("Call to SDL_CreateWindow('Title',%d,%d,%d)", w, h, flags); + SDLTest_AssertPass("Call to SDL_CreateWindow('Title',%d,%d,%" SDL_PRIu32 ")", w, h, flags); SDLTest_AssertCheck(window != NULL, "Validate that returned window struct is not NULL"); /* Wayland and XWayland windows require that a frame be presented before they are fully mapped and visible onscreen. @@ -37,6 +39,9 @@ static SDL_Window *createVideoSuiteTestWindow(const char *title) if (session_type && SDL_strcasecmp(session_type, "wayland") == 0) { needs_renderer = SDL_TRUE; } + + /* X11 needs the initial events pumped, or it can erroneously deliver old configuration events at a later time. */ + needs_events_pumped = SDL_TRUE; } if (needs_renderer) { @@ -55,6 +60,12 @@ static SDL_Window *createVideoSuiteTestWindow(const char *title) } } + if (needs_events_pumped) { + /* Pump out the event queue */ + while (SDL_PollEvent(&event)) { + } + } + return window; } @@ -73,7 +84,7 @@ static void destroyVideoSuiteTestWindow(SDL_Window *window) /* Test case functions */ /** - * \brief Enable and disable screensaver while checking state + * Enable and disable screensaver while checking state */ static int video_enableDisableScreensaver(void *arg) { @@ -124,7 +135,7 @@ static int video_enableDisableScreensaver(void *arg) } /** - * \brief Tests the functionality of the SDL_CreateWindow function using different sizes + * Tests the functionality of the SDL_CreateWindow function using different sizes */ static int video_createWindowVariousSizes(void *arg) { @@ -178,7 +189,7 @@ static int video_createWindowVariousSizes(void *arg) } /** - * \brief Tests the functionality of the SDL_CreateWindow function using different flags + * Tests the functionality of the SDL_CreateWindow function using different flags */ static int video_createWindowVariousFlags(void *arg) { @@ -186,7 +197,7 @@ static int video_createWindowVariousFlags(void *arg) const char *title = "video_createWindowVariousFlags Test Window"; int w, h; int fVariation; - SDL_WindowFlags flags; + Uint32 flags; /* Standard window */ w = SDLTest_RandomIntegerInRange(320, 1024); @@ -233,7 +244,7 @@ static int video_createWindowVariousFlags(void *arg) flags = SDL_WINDOW_MOUSE_FOCUS; break; case 12: - flags = SDL_WINDOW_FOREIGN; + flags = SDL_WINDOW_EXTERNAL; break; case 13: flags = SDL_WINDOW_KEYBOARD_GRABBED; @@ -241,7 +252,7 @@ static int video_createWindowVariousFlags(void *arg) } window = SDL_CreateWindow(title, w, h, flags); - SDLTest_AssertPass("Call to SDL_CreateWindow('Title',%d,%d,%d)", w, h, flags); + SDLTest_AssertPass("Call to SDL_CreateWindow('Title',%d,%d,%" SDL_PRIu32 ")", w, h, flags); SDLTest_AssertCheck(window != NULL, "Validate that returned window struct is not NULL"); /* Clean up */ @@ -252,13 +263,13 @@ static int video_createWindowVariousFlags(void *arg) } /** - * \brief Tests the functionality of the SDL_GetWindowFlags function + * Tests the functionality of the SDL_GetWindowFlags function */ static int video_getWindowFlags(void *arg) { SDL_Window *window; const char *title = "video_getWindowFlags Test Window"; - SDL_WindowFlags flags; + Uint32 flags; Uint32 actualFlags; /* Reliable flag set always set in test window */ @@ -269,7 +280,7 @@ static int video_getWindowFlags(void *arg) if (window != NULL) { actualFlags = SDL_GetWindowFlags(window); SDLTest_AssertPass("Call to SDL_GetWindowFlags()"); - SDLTest_AssertCheck((flags & actualFlags) == flags, "Verify returned value has flags %d set, got: %" SDL_PRIu32, flags, actualFlags); + SDLTest_AssertCheck((flags & actualFlags) == flags, "Verify returned value has flags %" SDL_PRIu32 " set, got: %" SDL_PRIu32, flags, actualFlags); } /* Clean up */ @@ -279,7 +290,7 @@ static int video_getWindowFlags(void *arg) } /** - * \brief Tests the functionality of the SDL_GetFullscreenDisplayModes function + * Tests the functionality of the SDL_GetFullscreenDisplayModes function */ static int video_getFullscreenDisplayModes(void *arg) { @@ -308,7 +319,7 @@ static int video_getFullscreenDisplayModes(void *arg) } /** - * \brief Tests the functionality of the SDL_GetClosestFullscreenDisplayMode function against current resolution + * Tests the functionality of the SDL_GetClosestFullscreenDisplayMode function against current resolution */ static int video_getClosestDisplayModeCurrentResolution(void *arg) { @@ -358,7 +369,7 @@ static int video_getClosestDisplayModeCurrentResolution(void *arg) } /** - * \brief Tests the functionality of the SDL_GetClosestFullscreenDisplayMode function against random resolution + * Tests the functionality of the SDL_GetClosestFullscreenDisplayMode function against random resolution */ static int video_getClosestDisplayModeRandomResolution(void *arg) { @@ -396,7 +407,7 @@ static int video_getClosestDisplayModeRandomResolution(void *arg) } /** - * \brief Tests call to SDL_GetWindowFullscreenMode + * Tests call to SDL_GetWindowFullscreenMode * * \sa SDL_GetWindowFullscreenMode */ @@ -440,7 +451,7 @@ static void checkInvalidWindowError(void) } /** - * \brief Tests call to SDL_GetWindowFullscreenMode with invalid input + * Tests call to SDL_GetWindowFullscreenMode with invalid input * * \sa SDL_GetWindowFullscreenMode */ @@ -528,7 +539,7 @@ static void setAndCheckWindowKeyboardGrabState(SDL_Window *window, SDL_bool desi } /** - * \brief Tests keyboard and mouse grab support + * Tests keyboard and mouse grab support * * \sa SDL_GetWindowGrab * \sa SDL_SetWindowGrab @@ -542,7 +553,7 @@ static int video_getSetWindowGrab(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -551,14 +562,19 @@ static int video_getSetWindowGrab(void *arg) * so that it can be "grabbed" */ SDL_RaiseWindow(window); - { + if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS)) { + int count = 0; SDL_Event evt; SDL_zero(evt); - while (SDL_PollEvent(&evt)) { - if (evt.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { - hasFocusGained = SDL_TRUE; + while (!hasFocusGained && count++ < 3) { + while (SDL_PollEvent(&evt)) { + if (evt.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { + hasFocusGained = SDL_TRUE; + } } } + } else { + hasFocusGained = SDL_TRUE; } SDLTest_AssertCheck(hasFocusGained == SDL_TRUE, "Expectded window with focus"); @@ -689,7 +705,7 @@ static int video_getSetWindowGrab(void *arg) } /** - * \brief Tests call to SDL_GetWindowID and SDL_GetWindowFromID + * Tests call to SDL_GetWindowID and SDL_GetWindowFromID * * \sa SDL_GetWindowID * \sa SDL_SetWindowFromID @@ -703,7 +719,7 @@ static int video_getWindowId(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -746,7 +762,7 @@ static int video_getWindowId(void *arg) } /** - * \brief Tests call to SDL_GetWindowPixelFormat + * Tests call to SDL_GetWindowPixelFormat * * \sa SDL_GetWindowPixelFormat */ @@ -758,7 +774,7 @@ static int video_getWindowPixelFormat(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -812,7 +828,7 @@ static SDL_bool getSizeFromEvent(int *w, int *h) } /** - * \brief Tests call to SDL_GetWindowPosition and SDL_SetWindowPosition + * Tests call to SDL_GetWindowPosition and SDL_SetWindowPosition * * \sa SDL_GetWindowPosition * \sa SDL_SetWindowPosition @@ -821,28 +837,61 @@ static int video_getSetWindowPosition(void *arg) { const char *title = "video_getSetWindowPosition Test Window"; SDL_Window *window; + int result; + int maxxVariation, maxyVariation; int xVariation, yVariation; int referenceX, referenceY; int currentX, currentY; int desiredX, desiredY; + SDL_Rect display_bounds; /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } - for (xVariation = 0; xVariation < 4; xVariation++) { - for (yVariation = 0; yVariation < 4; yVariation++) { + /* Sanity check */ + SDL_GetWindowPosition(window, ¤tX, ¤tY); + if (SDL_SetWindowPosition(window, currentX, currentY) < 0) { + SDLTest_Log("Skipping window positioning tests: %s reports window positioning as unsupported", SDL_GetCurrentVideoDriver()); + goto null_tests; + } + + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + /* The X11 server allows arbitrary window placement, but compositing + * window managers such as GNOME and KDE force windows to be within + * desktop bounds. + */ + maxxVariation = 2; + maxyVariation = 2; + + SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &display_bounds); + } else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "cocoa") == 0) { + /* Platform doesn't allow windows with negative Y desktop bounds */ + maxxVariation = 4; + maxyVariation = 3; + + SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &display_bounds); + } else { + /* Platform allows windows to be placed out of bounds */ + maxxVariation = 4; + maxyVariation = 4; + + SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display_bounds); + } + + for (xVariation = 0; xVariation < maxxVariation; xVariation++) { + for (yVariation = 0; yVariation < maxyVariation; yVariation++) { switch (xVariation) { default: case 0: /* Zero X Position */ - desiredX = 0; + desiredX = display_bounds.x > 0 ? display_bounds.x : 0; break; case 1: /* Random X position inside screen */ - desiredX = SDLTest_RandomIntegerInRange(1, 100); + desiredX = SDLTest_RandomIntegerInRange(display_bounds.x + 1, display_bounds.x + 100); break; case 2: /* Random X position outside screen (positive) */ @@ -857,15 +906,15 @@ static int video_getSetWindowPosition(void *arg) switch (yVariation) { default: case 0: - /* Zero X Position */ - desiredY = 0; + /* Zero Y Position */ + desiredY = display_bounds.y > 0 ? display_bounds.y : 0; break; case 1: - /* Random X position inside screen */ - desiredY = SDLTest_RandomIntegerInRange(1, 100); + /* Random Y position inside screen */ + desiredY = SDLTest_RandomIntegerInRange(display_bounds.y + 1, display_bounds.y + 100); break; case 2: - /* Random X position outside screen (positive) */ + /* Random Y position outside screen (positive) */ desiredY = SDLTest_RandomIntegerInRange(10000, 11000); break; case 3: @@ -878,6 +927,10 @@ static int video_getSetWindowPosition(void *arg) SDL_SetWindowPosition(window, desiredX, desiredY); SDLTest_AssertPass("Call to SDL_SetWindowPosition(...,%d,%d)", desiredX, desiredY); + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + /* Get position */ currentX = desiredX + 1; currentY = desiredY + 1; @@ -891,9 +944,9 @@ static int video_getSetWindowPosition(void *arg) SDL_bool hasEvent; /* SDL_SetWindowPosition() and SDL_SetWindowSize() will make requests of the window manager and set the internal position and size, * and then we get events signaling what actually happened, and they get passed on to the application if they're not what we expect. */ - desiredX = currentX + 1; - desiredY = currentY + 1; - hasEvent = getPositionFromEvent(&desiredX, &desiredY); + currentX = desiredX + 1; + currentY = desiredY + 1; + hasEvent = getPositionFromEvent(¤tX, ¤tY); SDLTest_AssertCheck(hasEvent == SDL_TRUE, "Changing position was not honored by WM, checking present of SDL_EVENT_WINDOW_MOVED"); if (hasEvent) { SDLTest_AssertCheck(desiredX == currentX, "Verify returned X position is the position from SDL event; expected: %d, got: %d", desiredX, currentX); @@ -915,6 +968,8 @@ static int video_getSetWindowPosition(void *arg) } } +null_tests: + /* Dummy call with both pointers NULL */ SDL_GetWindowPosition(window, NULL, NULL); SDLTest_AssertPass("Call to SDL_GetWindowPosition(&x=NULL,&y=NULL)"); @@ -973,7 +1028,7 @@ static void checkInvalidParameterError(void) } /** - * \brief Tests call to SDL_GetWindowSize and SDL_SetWindowSize + * Tests call to SDL_GetWindowSize and SDL_SetWindowSize * * \sa SDL_GetWindowSize * \sa SDL_SetWindowSize @@ -989,9 +1044,13 @@ static int video_getSetWindowSize(void *arg) int referenceW, referenceH; int currentW, currentH; int desiredW, desiredH; + const SDL_bool restoreHint = SDL_GetHintBoolean("SDL_BORDERLESS_RESIZABLE_STYLE", SDL_TRUE); + + /* Win32 borderless windows are not resizable by default and need this undocumented hint */ + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); /* Get display bounds for size range */ - result = SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display); + result = SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &display); SDLTest_AssertPass("SDL_GetDisplayBounds()"); SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); if (result != 0) { @@ -1000,19 +1059,26 @@ static int video_getSetWindowSize(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } -#ifdef __WIN32__ - /* Platform clips window size to screen size */ - maxwVariation = 4; - maxhVariation = 4; -#else - /* Platform allows window size >= screen size */ - maxwVariation = 5; - maxhVariation = 5; -#endif + SDL_GetWindowSize(window, ¤tW, ¤tH); + if (SDL_SetWindowSize(window, currentW, currentH)) { + SDLTest_Log("Skipping window resize tests: %s reports window resizing as unsupported", SDL_GetCurrentVideoDriver()); + goto null_tests; + } + + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "windows") == 0 || + SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + /* Platform clips window size to screen size */ + maxwVariation = 4; + maxhVariation = 4; + } else { + /* Platform allows window size >= screen size */ + maxwVariation = 5; + maxhVariation = 5; + } for (wVariation = 0; wVariation < maxwVariation; wVariation++) { for (hVariation = 0; hVariation < maxhVariation; hVariation++) { @@ -1068,6 +1134,10 @@ static int video_getSetWindowSize(void *arg) SDL_SetWindowSize(window, desiredW, desiredH); SDLTest_AssertPass("Call to SDL_SetWindowSize(...,%d,%d)", desiredW, desiredH); + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + /* Get size */ currentW = desiredW + 1; currentH = desiredH + 1; @@ -1081,9 +1151,9 @@ static int video_getSetWindowSize(void *arg) SDL_bool hasEvent; /* SDL_SetWindowPosition() and SDL_SetWindowSize() will make requests of the window manager and set the internal position and size, * and then we get events signaling what actually happened, and they get passed on to the application if they're not what we expect. */ - desiredW = currentW + 1; - desiredH = currentH + 1; - hasEvent = getSizeFromEvent(&desiredW, &desiredH); + currentW = desiredW + 1; + currentH = desiredH + 1; + hasEvent = getSizeFromEvent(¤tW, ¤tH); SDLTest_AssertCheck(hasEvent == SDL_TRUE, "Changing size was not honored by WM, checking presence of SDL_EVENT_WINDOW_RESIZED"); if (hasEvent) { SDLTest_AssertCheck(desiredW == currentW, "Verify returned width is the one from SDL event; expected: %d, got: %d", desiredW, currentW); @@ -1106,6 +1176,8 @@ static int video_getSetWindowSize(void *arg) } } +null_tests: + /* Dummy call with both pointers NULL */ SDL_GetWindowSize(window, NULL, NULL); SDLTest_AssertPass("Call to SDL_GetWindowSize(&w=NULL,&h=NULL)"); @@ -1154,11 +1226,14 @@ static int video_getSetWindowSize(void *arg) SDLTest_AssertPass("Call to SDL_SetWindowSize(window=NULL)"); checkInvalidWindowError(); + /* Restore the hint to the previous value */ + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", restoreHint ? "1" : "0"); + return TEST_COMPLETED; } /** - * \brief Tests call to SDL_GetWindowMinimumSize and SDL_SetWindowMinimumSize + * Tests call to SDL_GetWindowMinimumSize and SDL_SetWindowMinimumSize * */ static int video_getSetWindowMinimumSize(void *arg) @@ -1183,7 +1258,7 @@ static int video_getSetWindowMinimumSize(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -1301,7 +1376,7 @@ static int video_getSetWindowMinimumSize(void *arg) } /** - * \brief Tests call to SDL_GetWindowMaximumSize and SDL_SetWindowMaximumSize + * Tests call to SDL_GetWindowMaximumSize and SDL_SetWindowMaximumSize * */ static int video_getSetWindowMaximumSize(void *arg) @@ -1325,7 +1400,7 @@ static int video_getSetWindowMaximumSize(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } @@ -1439,7 +1514,7 @@ static int video_getSetWindowMaximumSize(void *arg) } /** - * \brief Tests call to SDL_SetWindowData and SDL_GetWindowData + * Tests call to SDL_SetWindowData and SDL_GetWindowData * * \sa SDL_SetWindowData * \sa SDL_GetWindowData @@ -1463,50 +1538,49 @@ static int video_getSetWindowData(void *arg) /* Call against new test window */ window = createVideoSuiteTestWindow(title); - if (window == NULL) { + if (!window) { return TEST_ABORTED; } /* Create testdata */ datasize = SDLTest_RandomIntegerInRange(1, 32); referenceUserdata = SDLTest_RandomAsciiStringOfSize(datasize); - if (referenceUserdata == NULL) { + if (!referenceUserdata) { returnValue = TEST_ABORTED; goto cleanup; } userdata = SDL_strdup(referenceUserdata); - if (userdata == NULL) { + if (!userdata) { returnValue = TEST_ABORTED; goto cleanup; } datasize = SDLTest_RandomIntegerInRange(1, 32); referenceUserdata2 = SDLTest_RandomAsciiStringOfSize(datasize); - if (referenceUserdata2 == NULL) { + if (!referenceUserdata2) { returnValue = TEST_ABORTED; goto cleanup; } userdata2 = SDL_strdup(referenceUserdata2); - if (userdata2 == NULL) { + if (!userdata2) { returnValue = TEST_ABORTED; goto cleanup; } /* Get non-existent data */ - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s)", name); SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); /* Set data */ - result = (char *)SDL_SetWindowData(window, name, userdata); + SDL_SetProperty(SDL_GetWindowProperties(window), name, userdata); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,%s)", name, userdata); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); /* Get data (twice) */ for (iteration = 1; iteration <= 2; iteration++) { - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s) [iteration %d]", name, iteration); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); @@ -1514,130 +1588,104 @@ static int video_getSetWindowData(void *arg) /* Set data again twice */ for (iteration = 1; iteration <= 2; iteration++) { - result = (char *)SDL_SetWindowData(window, name, userdata); + SDL_SetProperty(SDL_GetWindowProperties(window), name, userdata); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,%s) [iteration %d]", name, userdata, iteration); - SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); } /* Get data again */ - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s) [again]", name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); /* Set data with new data */ - result = (char *)SDL_SetWindowData(window, name, userdata2); + SDL_SetProperty(SDL_GetWindowProperties(window), name, userdata2); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,%s) [new userdata]", name, userdata2); - SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, userdata2) == 0, "Validate that userdata2 was not changed, expected: %s, got: %s", referenceUserdata2, userdata2); /* Set data with new data again */ - result = (char *)SDL_SetWindowData(window, name, userdata2); + SDL_SetProperty(SDL_GetWindowProperties(window), name, userdata2); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,%s) [new userdata again]", name, userdata2); - SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata2, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, userdata2) == 0, "Validate that userdata2 was not changed, expected: %s, got: %s", referenceUserdata2, userdata2); /* Get new data */ - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s)", name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata2, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); /* Set data with NULL to clear */ - result = (char *)SDL_SetWindowData(window, name, NULL); + SDL_SetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,NULL)", name); - SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata2, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, userdata2) == 0, "Validate that userdata2 was not changed, expected: %s, got: %s", referenceUserdata2, userdata2); /* Set data with NULL to clear again */ - result = (char *)SDL_SetWindowData(window, name, NULL); + SDL_SetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,NULL) [again]", name); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata2, userdata2) == 0, "Validate that userdata2 was not changed, expected: %s, got: %s", referenceUserdata2, userdata2); /* Get non-existent data */ - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s)", name); SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); /* Get non-existent data new name */ - result = (char *)SDL_GetWindowData(window, name2); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name2, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s)", name2); SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName2, name2) == 0, "Validate that name2 was not changed, expected: %s, got: %s", referenceName2, name2); /* Set data (again) */ - result = (char *)SDL_SetWindowData(window, name, userdata); + SDL_SetProperty(SDL_GetWindowProperties(window), name, userdata); SDLTest_AssertPass("Call to SDL_SetWindowData(...%s,%s) [again, after clear]", name, userdata); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, userdata) == 0, "Validate that userdata was not changed, expected: %s, got: %s", referenceUserdata, userdata); /* Get data (again) */ - result = (char *)SDL_GetWindowData(window, name); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), name, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(..,%s) [again, after clear]", name); SDLTest_AssertCheck(SDL_strcmp(referenceUserdata, result) == 0, "Validate that correct result was returned; expected: %s, got: %s", referenceUserdata, result); SDLTest_AssertCheck(SDL_strcmp(referenceName, name) == 0, "Validate that name was not changed, expected: %s, got: %s", referenceName, name); - /* Negative test */ - SDL_ClearError(); - SDLTest_AssertPass("Call to SDL_ClearError()"); - - /* Set with invalid window */ - result = (char *)SDL_SetWindowData(NULL, name, userdata); - SDLTest_AssertPass("Call to SDL_SetWindowData(window=NULL)"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); - checkInvalidWindowError(); - /* Set data with NULL name, valid userdata */ - result = (char *)SDL_SetWindowData(window, NULL, userdata); + SDL_SetProperty(SDL_GetWindowProperties(window), NULL, userdata); SDLTest_AssertPass("Call to SDL_SetWindowData(name=NULL)"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); /* Set data with empty name, valid userdata */ - result = (char *)SDL_SetWindowData(window, "", userdata); + SDL_SetProperty(SDL_GetWindowProperties(window), "", userdata); SDLTest_AssertPass("Call to SDL_SetWindowData(name='')"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); /* Set data with NULL name, NULL userdata */ - result = (char *)SDL_SetWindowData(window, NULL, NULL); + SDL_SetProperty(SDL_GetWindowProperties(window), NULL, NULL); SDLTest_AssertPass("Call to SDL_SetWindowData(name=NULL,userdata=NULL)"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); /* Set data with empty name, NULL userdata */ - result = (char *)SDL_SetWindowData(window, "", NULL); + SDL_SetProperty(SDL_GetWindowProperties(window), "", NULL); SDLTest_AssertPass("Call to SDL_SetWindowData(name='',userdata=NULL)"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); - /* Get with invalid window */ - result = (char *)SDL_GetWindowData(NULL, name); - SDLTest_AssertPass("Call to SDL_GetWindowData(window=NULL)"); - SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); - checkInvalidWindowError(); - /* Get data with NULL name */ - result = (char *)SDL_GetWindowData(window, NULL); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), NULL, NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(name=NULL)"); SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); /* Get data with empty name */ - result = (char *)SDL_GetWindowData(window, ""); + result = (char *)SDL_GetProperty(SDL_GetWindowProperties(window), "", NULL); SDLTest_AssertPass("Call to SDL_GetWindowData(name='')"); SDLTest_AssertCheck(result == NULL, "Validate that result is NULL"); checkInvalidParameterError(); @@ -1655,7 +1703,7 @@ cleanup: } /** - * \brief Tests the functionality of the SDL_WINDOWPOS_CENTERED_DISPLAY along with SDL_WINDOW_FULLSCREEN. + * Tests the functionality of the SDL_WINDOWPOS_CENTERED_DISPLAY along with SDL_WINDOW_FULLSCREEN. * * Especially useful when run on a multi-monitor system with different DPI scales per monitor, * to test that the window size is maintained when moving between monitors. @@ -1703,6 +1751,7 @@ static int video_setWindowCenteredOnDisplay(void *arg) int currentDisplay; int expectedDisplay; SDL_Rect expectedDisplayRect; + SDL_PropertiesID props; /* xVariation is the display we start on */ expectedDisplay = displays[xVariation % displayNum]; @@ -1714,7 +1763,15 @@ static int video_setWindowCenteredOnDisplay(void *arg) expectedX = (expectedDisplayRect.x + ((expectedDisplayRect.w - w) / 2)); expectedY = (expectedDisplayRect.y + ((expectedDisplayRect.h - h) / 2)); - window = SDL_CreateWindowWithPosition(title, x, y, w, h, 0); + props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, title); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_X_NUMBER, x); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER, y); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN, SDL_TRUE); + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); SDLTest_AssertPass("Call to SDL_CreateWindow('Title',%d,%d,%d,%d,SHOWN)", x, y, w, h); SDLTest_AssertCheck(window != NULL, "Validate that returned window struct is not NULL"); @@ -1760,6 +1817,10 @@ static int video_setWindowCenteredOnDisplay(void *arg) result = SDL_SetWindowFullscreen(window, SDL_TRUE); SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + /* Check we are filling the full display */ currentDisplay = SDL_GetDisplayForWindow(window); SDL_GetWindowSize(window, ¤tW, ¤tH); @@ -1783,6 +1844,10 @@ static int video_setWindowCenteredOnDisplay(void *arg) result = SDL_SetWindowFullscreen(window, SDL_FALSE); SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + /* Check window was restored correctly */ currentDisplay = SDL_GetDisplayForWindow(window); SDL_GetWindowSize(window, ¤tW, ¤tH); @@ -1812,6 +1877,10 @@ static int video_setWindowCenteredOnDisplay(void *arg) expectedY = (expectedDisplayRect.y + ((expectedDisplayRect.h - h) / 2)); SDL_SetWindowPosition(window, x, y); + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + currentDisplay = SDL_GetDisplayForWindow(window); SDL_GetWindowSize(window, ¤tW, ¤tH); SDL_GetWindowPosition(window, ¤tX, ¤tY); @@ -1841,6 +1910,367 @@ static int video_setWindowCenteredOnDisplay(void *arg) return TEST_COMPLETED; } +/** + * Tests calls to SDL_MaximizeWindow(), SDL_RestoreWindow(), and SDL_SetWindowFullscreen(), + * interspersed with calls to set the window size and position, and verifies the flags, + * sizes, and positions of maximized, fullscreen, and restored windows. + * + * NOTE: This test is good on Mac, Win32, GNOME, and KDE (Wayland and X11). Other *nix + * desktops, particularly tiling desktops, may not support the expected behavior, + * so don't be surprised if this fails. + */ +static int video_getSetWindowState(void *arg) +{ + const char *title = "video_getSetWindowState Test Window"; + SDL_Window *window; + int result; + SDL_Rect display; + Uint32 flags; + int windowedX, windowedY; + int currentX, currentY; + int desiredX = 0, desiredY = 0; + int windowedW, windowedH; + int currentW, currentH; + int desiredW = 0, desiredH = 0; + Uint32 skipFlags = 0; + const SDL_bool restoreHint = SDL_GetHintBoolean("SDL_BORDERLESS_RESIZABLE_STYLE", SDL_TRUE); + const SDL_bool skipPos = SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0; + + /* This test is known to be good only on GNOME and KDE. At the time of writing, Weston seems to have maximize related bugs + * that prevent it from running correctly (no configure events are received when unsetting maximize), and tiling window + * managers such as Sway have fundamental behavioral differences that conflict with it. + * + * Other desktops can be enabled in the future as required. + */ + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0 || SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + const char *desktop = SDL_getenv("XDG_CURRENT_DESKTOP"); + if (SDL_strcmp(desktop, "GNOME") != 0 && SDL_strcmp(desktop, "KDE") != 0) { + SDLTest_Log("Skipping test video_getSetWindowState: desktop environment %s not supported", desktop); + return TEST_SKIPPED; + } + } + + /* Win32 borderless windows are not resizable by default and need this undocumented hint */ + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); + + /* Call against new test window */ + window = createVideoSuiteTestWindow(title); + if (!window) { + return TEST_ABORTED; + } + + SDL_GetWindowSize(window, &windowedW, &windowedH); + SDLTest_AssertPass("SDL_GetWindowSize()"); + + SDL_GetWindowPosition(window, &windowedX, &windowedY); + SDLTest_AssertPass("SDL_GetWindowPosition()"); + + if (skipPos) { + SDLTest_Log("Skipping positioning tests: %s reports window positioning as unsupported", SDL_GetCurrentVideoDriver()); + } + + /* Maximize and check the dimensions */ + result = SDL_MaximizeWindow(window); + SDLTest_AssertPass("SDL_MaximizeWindow()"); + if (result < 0) { + SDLTest_Log("Skipping state transition tests: %s reports window maximizing as unsupported", SDL_GetCurrentVideoDriver()); + skipFlags |= SDL_WINDOW_MAXIMIZED; + goto minimize_test; + } + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(flags & SDL_WINDOW_MAXIMIZED, "Verify the `SDL_WINDOW_MAXIMIZED` flag is set: %s", (flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + /* Check that the maximized window doesn't extend beyond the usable display bounds. + * FIXME: Maximizing Win32 borderless windows is broken, so this always fails. + * Skip it for now. + */ + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "windows") != 0) { + result = SDL_GetDisplayUsableBounds(SDL_GetDisplayForWindow(window), &display); + SDLTest_AssertPass("SDL_GetDisplayUsableBounds()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + desiredW = display.w; + desiredH = display.h; + currentW = windowedW + 1; + currentH = windowedH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(currentW <= desiredW, "Verify returned width; expected: <= %d, got: %d", desiredW, + currentW); + SDLTest_AssertCheck(currentH <= desiredH, "Verify returned height; expected: <= %d, got: %d", desiredH, + currentH); + } + + /* Restore and check the dimensions */ + result = SDL_RestoreWindow(window); + SDLTest_AssertPass("SDL_RestoreWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify that the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + if (!skipPos) { + currentX = windowedX + 1; + currentY = windowedY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(windowedX == currentX, "Verify returned X coordinate; expected: %d, got: %d", windowedX, currentX); + SDLTest_AssertCheck(windowedY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", windowedY, currentY); + } + + currentW = windowedW + 1; + currentH = windowedH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(windowedW == currentW, "Verify returned width; expected: %d, got: %d", windowedW, currentW); + SDLTest_AssertCheck(windowedH == currentH, "Verify returned height; expected: %d, got: %d", windowedH, currentH); + + /* Maximize, then immediately restore */ + result = SDL_MaximizeWindow(window); + SDLTest_AssertPass("SDL_MaximizeWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_RestoreWindow(window); + SDLTest_AssertPass("SDL_RestoreWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify that the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + /* Make sure the restored size and position matches the original windowed size and position. */ + if (!skipPos) { + currentX = windowedX + 1; + currentY = windowedY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(windowedX == currentX, "Verify returned X coordinate; expected: %d, got: %d", windowedX, currentX); + SDLTest_AssertCheck(windowedY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", windowedY, currentY); + } + + currentW = windowedW + 1; + currentH = windowedH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(windowedW == currentW, "Verify returned width; expected: %d, got: %d", windowedW, currentW); + SDLTest_AssertCheck(windowedH == currentH, "Verify returned height; expected: %d, got: %d", windowedH, currentH); + + /* Maximize, then enter fullscreen */ + result = SDL_MaximizeWindow(window); + SDLTest_AssertPass("SDL_MaximizeWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SetWindowFullscreen(window, SDL_TRUE); + SDLTest_AssertPass("SDL_SetWindowFullscreen(SDL_TRUE)"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(flags & SDL_WINDOW_FULLSCREEN, "Verify the `SDL_WINDOW_FULLSCREEN` flag is set: %s", (flags & SDL_WINDOW_FULLSCREEN) ? "true" : "false"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + /* Verify the fullscreen size and position */ + result = SDL_GetDisplayBounds(SDL_GetDisplayForWindow(window), &display); + SDLTest_AssertPass("SDL_GetDisplayBounds()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + if (!skipPos) { + desiredX = display.x; + desiredY = display.y; + currentX = windowedX + 1; + currentY = windowedY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X coordinate; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", desiredY, currentY); + } + + desiredW = display.w; + desiredH = display.h; + currentW = windowedW + 1; + currentH = windowedH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(currentW == desiredW, "Verify returned width; expected: %d, got: %d", desiredW, currentW); + SDLTest_AssertCheck(currentH == desiredH, "Verify returned height; expected: %d, got: %d", desiredH, currentH); + + /* Leave fullscreen and restore the window */ + result = SDL_SetWindowFullscreen(window, SDL_FALSE); + SDLTest_AssertPass("SDL_SetWindowFullscreen(SDL_FALSE)"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_RestoreWindow(window); + SDLTest_AssertPass("SDL_RestoreWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify that the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + /* Make sure the restored size and position matches the original windowed size and position. */ + if (!skipPos) { + currentX = windowedX + 1; + currentY = windowedY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(windowedX == currentX, "Verify returned X coordinate; expected: %d, got: %d", windowedX, currentX); + SDLTest_AssertCheck(windowedY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", windowedY, currentY); + } + + currentW = windowedW + 1; + currentH = windowedH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(windowedW == currentW, "Verify returned width; expected: %d, got: %d", windowedW, currentW); + SDLTest_AssertCheck(windowedH == currentH, "Verify returned height; expected: %d, got: %d", windowedH, currentH); + + /* Maximize, change size, and restore */ + result = SDL_MaximizeWindow(window); + SDLTest_AssertPass("SDL_MaximizeWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + desiredW = windowedW + 10; + desiredH = windowedH + 10; + result = SDL_SetWindowSize(window, desiredW, desiredH); + SDLTest_AssertPass("SDL_SetWindowSize()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + if (!skipPos) { + desiredX = windowedX + 10; + desiredY = windowedY + 10; + result = SDL_SetWindowPosition(window, desiredX, desiredY); + SDLTest_AssertPass("SDL_SetWindowPosition()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + } + + result = SDL_RestoreWindow(window); + SDLTest_AssertPass("SDL_RestoreWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify that the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + if (!skipPos) { + currentX = desiredX + 1; + currentY = desiredY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X coordinate; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", desiredY, currentY); + } + + currentW = desiredW + 1; + currentH = desiredH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(desiredW == currentW, "Verify returned width; expected: %d, got: %d", desiredW, currentW); + SDLTest_AssertCheck(desiredH == currentH, "Verify returned height; expected: %d, got: %d", desiredH, currentH); + + /* Change size and position, maximize and restore */ + desiredW = windowedW - 5; + desiredH = windowedH - 5; + result = SDL_SetWindowSize(window, desiredW, desiredH); + SDLTest_AssertPass("SDL_SetWindowSize()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + if (!skipPos) { + desiredX = windowedX + 5; + desiredY = windowedY + 5; + result = SDL_SetWindowPosition(window, desiredX, desiredY); + SDLTest_AssertPass("SDL_SetWindowPosition()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + } + + result = SDL_MaximizeWindow(window); + SDLTest_AssertPass("SDL_MaximizeWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_RestoreWindow(window); + SDLTest_AssertPass("SDL_RestoreWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_MAXIMIZED), "Verify that the `SDL_WINDOW_MAXIMIZED` flag is cleared: %s", !(flags & SDL_WINDOW_MAXIMIZED) ? "true" : "false"); + + if (!skipPos) { + currentX = desiredX + 1; + currentY = desiredY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X coordinate; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y coordinate; expected: %d, got: %d", desiredY, currentY); + } + + currentW = desiredW + 1; + currentH = desiredH + 1; + SDL_GetWindowSize(window, ¤tW, ¤tH); + SDLTest_AssertPass("Call to SDL_GetWindowSize()"); + SDLTest_AssertCheck(desiredW == currentW, "Verify returned width; expected: %d, got: %d", desiredW, currentW); + SDLTest_AssertCheck(desiredH == currentH, "Verify returned height; expected: %d, got: %d", desiredH, currentH); + +minimize_test: + + /* Minimize */ + result = SDL_MinimizeWindow(window); + if (result == 0) { + SDLTest_AssertPass("SDL_MinimizeWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == 0, "Verify return value; expected: 0, got: %d", result); + + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(flags & SDL_WINDOW_MINIMIZED, "Verify that the `SDL_WINDOW_MINIMIZED` flag is set: %s", (flags & SDL_WINDOW_MINIMIZED) ? "true" : "false"); + } else { + SDLTest_Log("Skipping minimize test: %s reports window minimizing as unsupported", SDL_GetCurrentVideoDriver()); + skipFlags |= SDL_WINDOW_MINIMIZED; + } + + /* Clean up */ + destroyVideoSuiteTestWindow(window); + + /* Restore the hint to the previous value */ + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", restoreHint ? "1" : "0"); + + return skipFlags != (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED) ? TEST_COMPLETED : TEST_SKIPPED; +} + /* ================= Test References ================== */ /* Video test cases */ @@ -1916,12 +2346,16 @@ static const SDLTest_TestCaseReference videoTest18 = { (SDLTest_TestCaseFp)video_setWindowCenteredOnDisplay, "video_setWindowCenteredOnDisplay", "Checks using SDL_WINDOWPOS_CENTERED_DISPLAY centers the window on a display", TEST_ENABLED }; +static const SDLTest_TestCaseReference videoTest19 = { + (SDLTest_TestCaseFp)video_getSetWindowState, "video_getSetWindowState", "Checks transitioning between windowed, minimized, maximized, and fullscreen states", TEST_ENABLED +}; + /* Sequence of Video test cases */ static const SDLTest_TestCaseReference *videoTests[] = { &videoTest1, &videoTest2, &videoTest3, &videoTest4, &videoTest5, &videoTest6, &videoTest7, &videoTest8, &videoTest9, &videoTest10, &videoTest11, &videoTest12, &videoTest13, &videoTest14, &videoTest15, &videoTest16, &videoTest17, - &videoTest18, NULL + &videoTest18, &videoTest19, NULL }; /* Video test suite (global) */ diff --git a/test/testbounds.c b/test/testbounds.c index b16a54f9..143f74c6 100644 --- a/test/testbounds.c +++ b/test/testbounds.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -22,7 +22,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testcontroller.c b/test/testcontroller.c index dd4b9819..d62030dc 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -86,6 +86,7 @@ static SDL_JoystickID mapping_controller = 0; static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID; static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; static SDL_bool binding_flow = SDL_FALSE; +static int binding_flow_direction = 0; static Uint64 binding_advance_time = 0; static SDL_FRect title_area; static SDL_bool title_highlighted; @@ -102,10 +103,10 @@ static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; static int s_arrBindingOrder[] = { /* Standard sequence */ - SDL_GAMEPAD_BUTTON_A, - SDL_GAMEPAD_BUTTON_B, - SDL_GAMEPAD_BUTTON_X, - SDL_GAMEPAD_BUTTON_Y, + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, SDL_GAMEPAD_BUTTON_DPAD_LEFT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, SDL_GAMEPAD_BUTTON_DPAD_UP, @@ -331,12 +332,13 @@ static void SetCurrentBindingElement(int element, SDL_bool flow) } if (element == SDL_GAMEPAD_ELEMENT_INVALID) { + binding_flow_direction = 0; last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; } else { last_binding_element = binding_element; } binding_element = element; - binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_A); + binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH); binding_advance_time = 0; for (i = 0; i < controller->num_axes; ++i) { @@ -356,6 +358,7 @@ static void SetNextBindingElement(void) for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) { if (binding_element == s_arrBindingOrder[i]) { + binding_flow_direction = 1; SetCurrentBindingElement(s_arrBindingOrder[i + 1], SDL_TRUE); return; } @@ -373,6 +376,7 @@ static void SetPrevBindingElement(void) for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) { if (binding_element == s_arrBindingOrder[i]) { + binding_flow_direction = -1; SetCurrentBindingElement(s_arrBindingOrder[i - 1], SDL_TRUE); return; } @@ -456,8 +460,8 @@ static void CommitBindingElement(const char *binding, SDL_bool force) } } if (native_axis) { - AxisInfo current_axis_info; - AxisInfo proposed_axis_info; + AxisInfo current_axis_info = { 0, 0 }; + AxisInfo proposed_axis_info = { 0, 0 }; SDL_bool current_axis = ParseAxisInfo(current, ¤t_axis_info); SDL_bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info); @@ -498,32 +502,50 @@ static void CommitBindingElement(const char *binding, SDL_bool force) if (!ignore_binding && binding_flow && !force) { int existing = GetElementForBinding(mapping, binding); if (existing != SDL_GAMEPAD_ELEMENT_INVALID) { - if (existing == SDL_GAMEPAD_BUTTON_A) { - if (binding_element == SDL_GAMEPAD_BUTTON_A) { - /* Just move on to the next one */ + SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; + SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; + SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; + if (binding_element == action_forward) { + /* Bind it! */ + } else if (binding_element == action_backward) { + if (existing == action_forward) { + SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward); + if (bound_backward) { + /* Just move on to the next one */ + ignore_binding = SDL_TRUE; + SetNextBindingElement(); + } else { + /* You can't skip the backward action, go back and start over */ + ignore_binding = SDL_TRUE; + SetPrevBindingElement(); + } + } else if (existing == action_backward && binding_flow_direction == -1) { + /* Keep going backwards */ ignore_binding = SDL_TRUE; - SetNextBindingElement(); + SetPrevBindingElement(); } else { - /* Clear the current binding and move to the next one */ - binding = NULL; - direction = 1; - force = SDL_TRUE; - } - } else if (existing == SDL_GAMEPAD_BUTTON_B) { - if (binding_element != SDL_GAMEPAD_BUTTON_A && - last_binding_element != SDL_GAMEPAD_BUTTON_A) { - /* Clear the current binding and move to the previous one */ - binding = NULL; - direction = -1; - force = SDL_TRUE; + /* Bind it! */ } + } else if (existing == action_forward) { + /* Just move on to the next one */ + ignore_binding = SDL_TRUE; + SetNextBindingElement(); + } else if (existing == action_backward) { + ignore_binding = SDL_TRUE; + SetPrevBindingElement(); } else if (existing == binding_element) { /* We're rebinding the same thing, just move to the next one */ ignore_binding = SDL_TRUE; SetNextBindingElement(); - } else if (binding_element != SDL_GAMEPAD_BUTTON_A && - binding_element != SDL_GAMEPAD_BUTTON_B) { - ignore_binding = SDL_TRUE; + } else if (existing == action_delete) { + /* Clear the current binding and move to the next one */ + binding = NULL; + direction = 1; + force = SDL_TRUE; + } else if (binding_element != action_forward && + binding_element != action_backward) { + /* Actually, we'll just clear the existing binding */ + /*ignore_binding = SDL_TRUE;*/ } } } @@ -572,7 +594,7 @@ static void SetDisplayMode(ControllerDisplayMode mode) if (MappingHasBindings(backup_mapping)) { SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE); } else { - SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_A, SDL_TRUE); + SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, SDL_TRUE); } } else { if (backup_mapping) { @@ -701,30 +723,31 @@ static const char *GetBindingInstruction(void) switch (binding_element) { case SDL_GAMEPAD_ELEMENT_INVALID: return "Select an element to bind from the list on the left"; - case SDL_GAMEPAD_BUTTON_A: - if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { - return "Press the Cross (X) button"; - } else { + case SDL_GAMEPAD_BUTTON_SOUTH: + case SDL_GAMEPAD_BUTTON_EAST: + case SDL_GAMEPAD_BUTTON_WEST: + case SDL_GAMEPAD_BUTTON_NORTH: + switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) { + case SDL_GAMEPAD_BUTTON_LABEL_A: return "Press the A button"; - } - case SDL_GAMEPAD_BUTTON_B: - if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { - return "Press the Circle button"; - } else { + case SDL_GAMEPAD_BUTTON_LABEL_B: return "Press the B button"; - } - case SDL_GAMEPAD_BUTTON_X: - if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { - return "Press the Square button"; - } else { + case SDL_GAMEPAD_BUTTON_LABEL_X: return "Press the X button"; - } - case SDL_GAMEPAD_BUTTON_Y: - if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { - return "Press the Triangle button"; - } else { + case SDL_GAMEPAD_BUTTON_LABEL_Y: return "Press the Y button"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "Press the Cross (X) button"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "Press the Circle button"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "Press the Square button"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "Press the Triangle button"; + default: + return ""; } + break; case SDL_GAMEPAD_BUTTON_BACK: return "Press the left center button (Back/View/Share)"; case SDL_GAMEPAD_BUTTON_GUIDE: @@ -829,7 +852,7 @@ static void AddController(SDL_JoystickID id, SDL_bool verbose) } new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers)); - if (new_controllers == NULL) { + if (!new_controllers) { return; } @@ -846,9 +869,12 @@ static void AddController(SDL_JoystickID id, SDL_bool verbose) joystick = new_controller->joystick; if (joystick) { if (verbose && !SDL_IsGamepad(id)) { - const char *name = SDL_GetJoystickName(new_controller->joystick); - const char *path = SDL_GetJoystickPath(new_controller->joystick); + const char *name = SDL_GetJoystickName(joystick); + const char *path = SDL_GetJoystickPath(joystick); + char guid[33]; SDL_Log("Opened joystick %s%s%s\n", name, path ? ", " : "", path ? path : ""); + SDL_GetJoystickGUIDString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid)); + SDL_Log("No gamepad mapping for %s\n", guid); } } else { SDL_Log("Couldn't open joystick: %s", SDL_GetError()); @@ -941,8 +967,6 @@ static void HandleGamepadAdded(SDL_JoystickID id, SDL_bool verbose) int i; i = FindController(id); - - SDL_assert(i >= 0); if (i < 0) { return; } @@ -969,6 +993,10 @@ static void HandleGamepadAdded(SDL_JoystickID id, SDL_bool verbose) if (SDL_GamepadHasRumbleTriggers(gamepad)) { SDL_Log("Trigger rumble supported"); } + + if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) { + SDL_Log("Player index: %d\n", SDL_GetGamepadPlayerIndex(gamepad)); + } } for (i = 0; i < SDL_arraysize(sensors); ++i) { @@ -981,6 +1009,14 @@ static void HandleGamepadAdded(SDL_JoystickID id, SDL_bool verbose) SDL_SetGamepadSensorEnabled(gamepad, sensor, SDL_TRUE); } } + + if (verbose) { + char *mapping = SDL_GetGamepadMapping(gamepad); + if (mapping) { + SDL_Log("Mapping: %s\n", mapping); + SDL_free(mapping); + } + } } else { SDL_Log("Couldn't open gamepad: %s", SDL_GetError()); } @@ -1084,7 +1120,7 @@ static void OpenVirtualGamepad(void) SDL_Log("Couldn't attach virtual device: %s\n", SDL_GetError()); } else { virtual_joystick = SDL_OpenJoystick(virtual_id); - if (virtual_joystick == NULL) { + if (!virtual_joystick) { SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError()); } } @@ -1254,6 +1290,13 @@ static void DrawGamepadInfo(SDL_Renderer *renderer) SDL_SetRenderDrawColor(renderer, r, g, b, a); } + if (controller->joystick) { + SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickInstanceID(controller->joystick)); + x = (float)SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f; + y = 8.0f; + SDLTest_DrawString(renderer, x, y, text); + } + if (controller_name && *controller_name) { x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2; y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2; @@ -1273,6 +1316,14 @@ static void DrawGamepadInfo(SDL_Renderer *renderer) SDLTest_DrawString(renderer, x, y, type); if (display_mode == CONTROLLER_MODE_TESTING) { + Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad); + if (steam_handle) { + SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64 "", steam_handle); + y = (float)SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT); + x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); + SDLTest_DrawString(renderer, x, y, text); + } + SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", SDL_GetJoystickVendor(controller->joystick), SDL_GetJoystickProduct(controller->joystick)); @@ -1290,6 +1341,30 @@ static void DrawGamepadInfo(SDL_Renderer *renderer) } } +static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button) +{ + switch (SDL_GetGamepadButtonLabelForType(type, button)) { + case SDL_GAMEPAD_BUTTON_LABEL_A: + return "A"; + case SDL_GAMEPAD_BUTTON_LABEL_B: + return "B"; + case SDL_GAMEPAD_BUTTON_LABEL_X: + return "X"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: + return "Y"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "Cross (X)"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "Circle"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "Square"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "Triangle"; + default: + return "UNKNOWN"; + } +} + static void DrawBindingTips(SDL_Renderer *renderer) { const char *text; @@ -1309,7 +1384,12 @@ static void DrawBindingTips(SDL_Renderer *renderer) } else { Uint8 r, g, b, a; SDL_FRect rect; - SDL_bool bound_A, bound_B; + SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; + SDL_bool bound_forward = MappingHasElement(controller->mapping, action_forward); + SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; + SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward); + SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; + SDL_bool bound_delete = MappingHasElement(controller->mapping, action_delete); y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; @@ -1328,15 +1408,22 @@ static void DrawBindingTips(SDL_Renderer *renderer) if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { text = "(press RETURN to complete)"; - } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { + } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE || + binding_element == action_forward || + binding_element == action_backward) { text = "(press ESC to cancel)"; } else { - bound_A = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_A); - bound_B = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_B); - if (binding_flow && bound_A && bound_B) { - text = "(press A to skip, B to go back, and ESC to cancel)"; + static char dynamic_text[128]; + SDL_GamepadType type = GetGamepadImageType(image); + if (binding_flow && bound_forward && bound_backward) { + if (binding_element != action_delete && bound_delete) { + SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, %s to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward), GetButtonLabel(type, action_delete)); + } else { + SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, SPACE to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward)); + } + text = dynamic_text; } else { - text = "(press SPACE to clear binding and ESC to cancel)"; + text = "(press SPACE to delete and ESC to cancel)"; } } SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text); @@ -1521,6 +1608,10 @@ static void loop(void *arg) HandleGamepadRemapped(event.gdevice.which); break; + case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: + RefreshControllerName(); + break; + #ifdef VERBOSE_TOUCHPAD case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: @@ -1801,6 +1892,7 @@ static void loop(void *arg) int main(int argc, char *argv[]) { + SDL_bool show_mappings = SDL_FALSE; int i; float content_scale; int screen_width, screen_height; @@ -1810,7 +1902,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -1832,16 +1924,7 @@ int main(int argc, char *argv[]) consumed = SDLTest_CommonArg(state, i); if (!consumed) { if (SDL_strcmp(argv[i], "--mappings") == 0) { - int map_i; - SDL_Log("Supported mappings:\n"); - for (map_i = 0; map_i < SDL_GetNumGamepadMappings(); ++map_i) { - char *mapping = SDL_GetGamepadMappingForIndex(map_i); - if (mapping) { - SDL_Log("\t%s\n", mapping); - SDL_free(mapping); - } - } - SDL_Log("\n"); + show_mappings = SDL_TRUE; consumed = 1; } else if (SDL_strcmp(argv[i], "--virtual") == 0) { OpenVirtualGamepad(); @@ -1874,6 +1957,18 @@ int main(int argc, char *argv[]) SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); + if (show_mappings) { + int count = 0; + char **mappings = SDL_GetGamepadMappings(&count); + int map_i; + SDL_Log("Supported mappings:\n"); + for (map_i = 0; map_i < count; ++map_i) { + SDL_Log("\t%s\n", mappings[map_i]); + } + SDL_Log("\n"); + SDL_free(mappings); + } + /* Create a window to display gamepad state */ content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); if (content_scale == 0.0f) { @@ -1882,13 +1977,13 @@ int main(int argc, char *argv[]) screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, 0); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError()); return 2; } screen = SDL_CreateRenderer(window, NULL, 0); - if (screen == NULL) { + if (!screen) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError()); SDL_DestroyWindow(window); return 2; @@ -1915,7 +2010,7 @@ int main(int argc, char *argv[]) type_area.y = (float)TITLE_HEIGHT / 2 - type_area.h / 2; image = CreateGamepadImage(screen); - if (image == NULL) { + if (!image) { SDL_DestroyRenderer(screen); SDL_DestroyWindow(window); return 2; diff --git a/test/testcustomcursor.c b/test/testcustomcursor.c index 12d54208..91af2bae 100644 --- a/test/testcustomcursor.c +++ b/test/testcustomcursor.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -65,6 +65,50 @@ static const char *arrow[] = { "0,0" }; +static const char *cross[] = { + /* width height num_colors chars_per_pixel */ + " 32 32 3 1", + /* colors */ + "o c #000000", + ". c #ffffff", + " c None", + /* pixels */ + /* pixels */ + " ", + " ", + " ", + " ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oooooooooooooooooooooooo ", + " oooooooooooooooooooooooo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " oo ", + " ", + " ", + " ", + " ", + "0,0" +}; + static SDL_Cursor * init_color_cursor(const char *file) { @@ -72,7 +116,12 @@ init_color_cursor(const char *file) SDL_Surface *surface = SDL_LoadBMP(file); if (surface) { if (surface->format->palette) { - SDL_SetSurfaceColorKey(surface, 1, *(Uint8 *)surface->pixels); + const Uint8 bpp = surface->format->BitsPerPixel; + const Uint8 mask = (1 << bpp) - 1; + if (SDL_PIXELORDER(surface->format->format) == SDL_BITMAPORDER_4321) + SDL_SetSurfaceColorKey(surface, 1, (*(Uint8 *)surface->pixels) & mask); + else + SDL_SetSurfaceColorKey(surface, 1, ((*(Uint8 *)surface->pixels) >> (8 - bpp)) & mask); } else { switch (surface->format->BitsPerPixel) { case 15: @@ -122,6 +171,9 @@ init_system_cursor(const char *image[]) case '.': mask[i] |= 0x01; break; + case 'o': + data[i] |= 0x01; + break; case ' ': break; } @@ -133,8 +185,8 @@ init_system_cursor(const char *image[]) static SDLTest_CommonState *state; static int done; -static SDL_Cursor *cursors[1 + SDL_NUM_SYSTEM_CURSORS]; -static SDL_SystemCursor cursor_types[1 + SDL_NUM_SYSTEM_CURSORS]; +static SDL_Cursor *cursors[3 + SDL_NUM_SYSTEM_CURSORS]; +static SDL_SystemCursor cursor_types[3 + SDL_NUM_SYSTEM_CURSORS]; static int num_cursors; static int current_cursor; static SDL_bool show_cursor; @@ -210,6 +262,30 @@ static void loop(void) case SDL_SYSTEM_CURSOR_HAND: SDL_Log("Hand"); break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT: + SDL_Log("Window resize top-left"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOP: + SDL_Log("Window resize top"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT: + SDL_Log("Window resize top-right"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_RIGHT: + SDL_Log("Window resize right"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT: + SDL_Log("Window resize bottom-right"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM: + SDL_Log("Window resize bottom"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT: + SDL_Log("Window resize bottom-left"); + break; + case SDL_SYSTEM_CURSOR_WINDOW_LEFT: + SDL_Log("Window resize left"); + break; default: SDL_Log("UNKNOWN CURSOR TYPE, FIX THIS PROGRAM."); break; @@ -228,7 +304,29 @@ static void loop(void) for (i = 0; i < state->num_windows; ++i) { SDL_Renderer *renderer = state->renderers[i]; - SDL_RenderClear(renderer); + SDL_FRect rect; + int x, y, row; + int window_w = 0, window_h = 0; + + SDL_GetWindowSize(state->windows[i], &window_w, &window_h); + rect.w = 128.0f; + rect.h = 128.0f; + for (y = 0, row = 0; y < window_h; y += (int)rect.h, ++row) { + SDL_bool black = ((row % 2) == 0) ? SDL_TRUE : SDL_FALSE; + for (x = 0; x < window_w; x += (int)rect.w) { + rect.x = (float)x; + rect.y = (float)y; + + if (black) { + SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); + } else { + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); + } + SDL_RenderFillRect(renderer, &rect); + + black = !black; + } + } SDL_RenderPresent(renderer); } #ifdef __EMSCRIPTEN__ @@ -242,13 +340,14 @@ int main(int argc, char *argv[]) { int i; const char *color_cursor = NULL; + SDL_Cursor *cursor; /* Enable standard application logging */ SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } for (i = 1; i < argc;) { @@ -270,23 +369,10 @@ int main(int argc, char *argv[]) quit(2); } - for (i = 0; i < state->num_windows; ++i) { - SDL_Renderer *renderer = state->renderers[i]; - SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); - SDL_RenderClear(renderer); - } - num_cursors = 0; if (color_cursor) { - SDL_Cursor *cursor = init_color_cursor(color_cursor); - if (cursor) { - cursors[num_cursors] = cursor; - cursor_types[num_cursors] = (SDL_SystemCursor)-1; - num_cursors++; - } - } else { - SDL_Cursor *cursor = init_system_cursor(arrow); + cursor = init_color_cursor(color_cursor); if (cursor) { cursors[num_cursors] = cursor; cursor_types[num_cursors] = (SDL_SystemCursor)-1; @@ -294,8 +380,22 @@ int main(int argc, char *argv[]) } } + cursor = init_system_cursor(arrow); + if (cursor) { + cursors[num_cursors] = cursor; + cursor_types[num_cursors] = (SDL_SystemCursor)-1; + num_cursors++; + } + + cursor = init_system_cursor(cross); + if (cursor) { + cursors[num_cursors] = cursor; + cursor_types[num_cursors] = (SDL_SystemCursor)-1; + num_cursors++; + } + for (i = 0; i < SDL_NUM_SYSTEM_CURSORS; ++i) { - SDL_Cursor *cursor = SDL_CreateSystemCursor((SDL_SystemCursor)i); + cursor = SDL_CreateSystemCursor((SDL_SystemCursor)i); if (cursor) { cursors[num_cursors] = cursor; cursor_types[num_cursors] = i; diff --git a/test/testdisplayinfo.c b/test/testdisplayinfo.c index 009e45c3..0e45b54a 100644 --- a/test/testdisplayinfo.c +++ b/test/testdisplayinfo.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -21,7 +21,7 @@ static void print_mode(const char *prefix, const SDL_DisplayMode *mode) { - if (mode == NULL) { + if (!mode) { return; } @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testdraw.c b/test/testdraw.c index d0c102c2..d0d70c33 100644 --- a/test/testdraw.c +++ b/test/testdraw.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -229,7 +229,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } for (i = 1; i < argc;) { diff --git a/test/testdrawchessboard.c b/test/testdrawchessboard.c index 75a0e7d7..1388b044 100644 --- a/test/testdrawchessboard.c +++ b/test/testdrawchessboard.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -57,6 +57,7 @@ static void DrawChessBoard(void) } } } + SDL_RenderPresent(renderer); } static void loop(void) @@ -106,7 +107,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -126,13 +127,13 @@ int main(int argc, char *argv[]) /* Create window and renderer for given surface */ window = SDL_CreateWindow("Chess Board", 640, 480, SDL_WINDOW_RESIZABLE); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Window creation fail : %s\n", SDL_GetError()); return 1; } surface = SDL_GetWindowSurface(window); renderer = SDL_CreateSoftwareRenderer(surface); - if (renderer == NULL) { + if (!renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Render creation for surface fail : %s\n", SDL_GetError()); return 1; } @@ -151,6 +152,9 @@ int main(int argc, char *argv[]) } #endif + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); SDLTest_CommonDestroyState(state); return 0; diff --git a/test/testdropfile.c b/test/testdropfile.c index 2e4efedb..0c0bfca7 100644 --- a/test/testdropfile.c +++ b/test/testdropfile.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -26,7 +26,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -56,9 +56,6 @@ int main(int argc, char *argv[]) goto quit; } - - SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, SDL_TRUE); - /* Main render loop */ done = 0; while (!done) { @@ -71,16 +68,13 @@ int main(int argc, char *argv[]) SDL_Log("Drop complete on window %u at (%f, %f)", (unsigned int)event.drop.windowID, event.drop.x, event.drop.y); } else if ((event.type == SDL_EVENT_DROP_FILE) || (event.type == SDL_EVENT_DROP_TEXT)) { const char *typestr = (event.type == SDL_EVENT_DROP_FILE) ? "File" : "Text"; - char *dropped_filedir = event.drop.file; - SDL_Log("%s dropped on window %u: %s at (%f, %f)", typestr, (unsigned int)event.drop.windowID, dropped_filedir, event.drop.x, event.drop.y); - /* Normally you'd have to do this, but this is freed in SDLTest_CommonEvent() */ - /*SDL_free(dropped_filedir);*/ + SDL_Log("%s dropped on window %u: %s at (%f, %f)", typestr, (unsigned int)event.drop.windowID, event.drop.data, event.drop.x, event.drop.y); } else if (event.type == SDL_EVENT_DROP_POSITION) { is_hover = SDL_TRUE; x = event.drop.x; y = event.drop.y; windowID = event.drop.windowID; - SDL_Log("Drop position on window %u at (%f, %f) file = %s", (unsigned int)event.drop.windowID, event.drop.x, event.drop.y, event.drop.file); + SDL_Log("Drop position on window %u at (%f, %f) data = %s", (unsigned int)event.drop.windowID, event.drop.x, event.drop.y, event.drop.data); } SDLTest_CommonEvent(state, &event, &done); diff --git a/test/testerror.c b/test/testerror.c index b52c206c..6792d7d4 100644 --- a/test/testerror.c +++ b/test/testerror.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -52,7 +52,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) alive = 1; thread = SDL_CreateThread(ThreadFunc, NULL, "#1"); - if (thread == NULL) { + if (!thread) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread: %s\n", SDL_GetError()); quit(1); } diff --git a/test/testevdev.c b/test/testevdev.c index 9758bbe2..a991d483 100644 --- a/test/testevdev.c +++ b/test/testevdev.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright (C) 2020-2022 Collabora Ltd. This software is provided 'as-is', without any express or implied @@ -1940,7 +1940,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testffmpeg.c b/test/testffmpeg.c new file mode 100644 index 00000000..d865a8d3 --- /dev/null +++ b/test/testffmpeg.c @@ -0,0 +1,1139 @@ +/* + 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 + 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. +*/ +/* Simple program: Display a video with a sprite bouncing around over it + * + * For a more complete video example, see ffplay.c in the ffmpeg sources. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_EGL +#include +#include +#include + +#include + +#ifndef fourcc_code +#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) +#endif +#ifndef DRM_FORMAT_R8 +#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') +#endif +#ifndef DRM_FORMAT_GR88 +#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') +#endif +#endif + +#ifdef __APPLE__ +#include "testffmpeg_videotoolbox.h" +#endif + +#ifdef __WIN32__ +#define COBJMACROS +#include +#endif /* __WIN32__ */ + +#include "icon.h" + +static SDL_Texture *sprite; +static SDL_FRect *positions; +static SDL_FRect *velocities; +static int sprite_w, sprite_h; +static int num_sprites = 0; + +static SDL_Window *window; +static SDL_Renderer *renderer; +static SDL_AudioStream *audio; +static SDL_Texture *video_texture; +static Uint64 video_start; +static SDL_bool software_only; +static SDL_bool has_eglCreateImage; +#ifdef HAVE_EGL +static SDL_bool has_EGL_EXT_image_dma_buf_import; +static PFNGLACTIVETEXTUREARBPROC glActiveTextureARBFunc; +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOESFunc; +#endif +#ifdef __APPLE__ +static SDL_bool has_videotoolbox_output; +#endif +#ifdef __WIN32__ +static ID3D11Device *d3d11_device; +static ID3D11DeviceContext *d3d11_context; +static const GUID SDL_IID_ID3D11Resource = { 0xdc8e63f3, 0xd12b, 0x4952, { 0xb4, 0x7b, 0x5e, 0x45, 0x02, 0x6a, 0x86, 0x2d } }; +#endif +struct SwsContextContainer +{ + struct SwsContext *context; +}; +static const char *SWS_CONTEXT_CONTAINER_PROPERTY = "SWS_CONTEXT_CONTAINER"; +static int done; + +static SDL_bool CreateWindowAndRenderer(Uint32 window_flags, const char *driver) +{ + SDL_RendererInfo info; + SDL_bool useEGL = (driver && SDL_strcmp(driver, "opengles2") == 0); + + SDL_SetHint(SDL_HINT_RENDER_DRIVER, driver); + if (useEGL) { + SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "1"); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + } else { + SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "0"); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + } + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + + /* The window will be resized to the video size when it's loaded, in OpenVideoStream() */ + if (SDL_CreateWindowAndRenderer(320, 200, window_flags, &window, &renderer) < 0) { + return SDL_FALSE; + } + + if (SDL_GetRendererInfo(renderer, &info) == 0) { + SDL_Log("Created renderer %s\n", info.name); + } + +#ifdef HAVE_EGL + if (useEGL) { + const char *extensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS); + if (SDL_strstr(extensions, "EGL_EXT_image_dma_buf_import") != NULL) { + has_EGL_EXT_image_dma_buf_import = SDL_TRUE; + } + + if (SDL_GL_ExtensionSupported("GL_OES_EGL_image")) { + glEGLImageTargetTexture2DOESFunc = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + } + + glActiveTextureARBFunc = (PFNGLACTIVETEXTUREARBPROC)SDL_GL_GetProcAddress("glActiveTextureARB"); + + if (has_EGL_EXT_image_dma_buf_import && + glEGLImageTargetTexture2DOESFunc && + glActiveTextureARBFunc) { + has_eglCreateImage = SDL_TRUE; + } + } +#endif /* HAVE_EGL */ + +#ifdef __APPLE__ + has_videotoolbox_output = SetupVideoToolboxOutput(renderer); +#endif + +#ifdef __WIN32__ + d3d11_device = (ID3D11Device *)SDL_GetProperty(SDL_GetRendererProperties(renderer), SDL_PROPERTY_RENDERER_D3D11_DEVICE_POINTER, NULL); + if (d3d11_device) { + ID3D11Device_AddRef(d3d11_device); + ID3D11Device_GetImmediateContext(d3d11_device, &d3d11_context); + } +#endif + + return SDL_TRUE; +} + +static SDL_Texture *CreateTexture(SDL_Renderer *r, unsigned char *data, unsigned int len, int *w, int *h) { + SDL_Texture *texture = NULL; + SDL_Surface *surface; + SDL_RWops *src = SDL_RWFromConstMem(data, len); + if (src) { + surface = SDL_LoadBMP_RW(src, SDL_TRUE); + if (surface) { + /* Treat white as transparent */ + SDL_SetSurfaceColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 255, 255, 255)); + + texture = SDL_CreateTextureFromSurface(r, surface); + *w = surface->w; + *h = surface->h; + SDL_DestroySurface(surface); + } + } + return texture; +} + +static void MoveSprite(void) +{ + SDL_Rect viewport; + SDL_FRect *position, *velocity; + int i; + + SDL_GetRenderViewport(renderer, &viewport); + + for (i = 0; i < num_sprites; ++i) { + position = &positions[i]; + velocity = &velocities[i]; + position->x += velocity->x; + if ((position->x < 0) || (position->x >= (viewport.w - sprite_w))) { + velocity->x = -velocity->x; + position->x += velocity->x; + } + position->y += velocity->y; + if ((position->y < 0) || (position->y >= (viewport.h - sprite_h))) { + velocity->y = -velocity->y; + position->y += velocity->y; + } + } + + /* Blit the sprite onto the screen */ + for (i = 0; i < num_sprites; ++i) { + position = &positions[i]; + + /* Blit the sprite onto the screen */ + SDL_RenderTexture(renderer, sprite, NULL, position); + } +} + +static Uint32 GetTextureFormat(enum AVPixelFormat format) +{ + switch (format) { + case AV_PIX_FMT_RGB8: + return SDL_PIXELFORMAT_RGB332; + case AV_PIX_FMT_RGB444: + return SDL_PIXELFORMAT_RGB444; + case AV_PIX_FMT_RGB555: + return SDL_PIXELFORMAT_RGB555; + case AV_PIX_FMT_BGR555: + return SDL_PIXELFORMAT_BGR555; + case AV_PIX_FMT_RGB565: + return SDL_PIXELFORMAT_RGB565; + case AV_PIX_FMT_BGR565: + return SDL_PIXELFORMAT_BGR565; + case AV_PIX_FMT_RGB24: + return SDL_PIXELFORMAT_RGB24; + case AV_PIX_FMT_BGR24: + return SDL_PIXELFORMAT_BGR24; + case AV_PIX_FMT_0RGB32: + return SDL_PIXELFORMAT_XRGB8888; + case AV_PIX_FMT_0BGR32: + return SDL_PIXELFORMAT_XBGR8888; + case AV_PIX_FMT_NE(RGB0, 0BGR): + return SDL_PIXELFORMAT_RGBX8888; + case AV_PIX_FMT_NE(BGR0, 0RGB): + return SDL_PIXELFORMAT_BGRX8888; + case AV_PIX_FMT_RGB32: + return SDL_PIXELFORMAT_ARGB8888; + case AV_PIX_FMT_RGB32_1: + return SDL_PIXELFORMAT_RGBA8888; + case AV_PIX_FMT_BGR32: + return SDL_PIXELFORMAT_ABGR8888; + case AV_PIX_FMT_BGR32_1: + return SDL_PIXELFORMAT_BGRA8888; + case AV_PIX_FMT_YUV420P: + return SDL_PIXELFORMAT_IYUV; + case AV_PIX_FMT_YUYV422: + return SDL_PIXELFORMAT_YUY2; + case AV_PIX_FMT_UYVY422: + return SDL_PIXELFORMAT_UYVY; + default: + return SDL_PIXELFORMAT_UNKNOWN; + } +} + +static SDL_bool SupportedPixelFormat(enum AVPixelFormat format) +{ + if (!software_only) { + if (has_eglCreateImage && + (format == AV_PIX_FMT_VAAPI || format == AV_PIX_FMT_DRM_PRIME)) { + return SDL_TRUE; + } +#ifdef __APPLE__ + if (has_videotoolbox_output && format == AV_PIX_FMT_VIDEOTOOLBOX) { + return SDL_TRUE; + } +#endif +#ifdef __WIN32__ + if (d3d11_device && format == AV_PIX_FMT_D3D11) { + return SDL_TRUE; + } +#endif + } + + if (GetTextureFormat(format) != SDL_PIXELFORMAT_UNKNOWN) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +static enum AVPixelFormat GetSupportedPixelFormat(AVCodecContext *s, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(*p); + + if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { + /* We support all memory formats using swscale */ + break; + } + + if (SupportedPixelFormat(*p)) { + /* We support this format */ + break; + } + } + + if (*p == AV_PIX_FMT_NONE) { + SDL_Log("Couldn't find a supported pixel format:\n"); + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + SDL_Log(" %s\n", av_get_pix_fmt_name(*p)); + } + } + + return *p; +} + +static AVCodecContext *OpenVideoStream(AVFormatContext *ic, int stream, const AVCodec *codec) +{ + AVStream *st = ic->streams[stream]; + AVCodecParameters *codecpar = st->codecpar; + AVCodecContext *context; + const AVCodecHWConfig *config; + enum AVHWDeviceType type; + int i; + int result; + + SDL_Log("Video stream: %s %dx%d\n", avcodec_get_name(codec->id), codecpar->width, codecpar->height); + + context = avcodec_alloc_context3(NULL); + if (!context) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_alloc_context3 failed"); + return NULL; + } + + result = avcodec_parameters_to_context(context, ic->streams[stream]->codecpar); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_parameters_to_context failed: %s\n", av_err2str(result)); + avcodec_free_context(&context); + return NULL; + } + context->pkt_timebase = ic->streams[stream]->time_base; + + /* Look for supported hardware accelerated configurations */ + i = 0; + while (!context->hw_device_ctx && + (config = avcodec_get_hw_config(codec, i++)) != NULL) { +#if 0 + SDL_Log("Found %s hardware acceleration with pixel format %s\n", av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt)); +#endif + + if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) || + !SupportedPixelFormat(config->pix_fmt)) { + continue; + } + + type = AV_HWDEVICE_TYPE_NONE; + while (!context->hw_device_ctx && + (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) { + if (type != config->device_type) { + continue; + } + +#ifdef __WIN32__ + if (d3d11_device && type == AV_HWDEVICE_TYPE_D3D11VA) { + AVD3D11VADeviceContext *device_context; + + context->hw_device_ctx = av_hwdevice_ctx_alloc(type); + + device_context = (AVD3D11VADeviceContext *)((AVHWDeviceContext *)context->hw_device_ctx->data)->hwctx; + device_context->device = d3d11_device; + ID3D11Device_AddRef(device_context->device); + device_context->device_context = d3d11_context; + ID3D11DeviceContext_AddRef(device_context->device_context); + + result = av_hwdevice_ctx_init(context->hw_device_ctx); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create hardware device context: %s", av_err2str(result)); + } else { + SDL_Log("Using %s hardware acceleration with pixel format %s\n", av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt)); + } + } else +#endif + { + result = av_hwdevice_ctx_create(&context->hw_device_ctx, type, NULL, NULL, 0); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create hardware device context: %s", av_err2str(result)); + } else { + SDL_Log("Using %s hardware acceleration with pixel format %s\n", av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt)); + } + } + } + } + + /* Allow supported hardware accelerated pixel formats */ + context->get_format = GetSupportedPixelFormat; + + result = avcodec_open2(context, codec, NULL); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open codec %s: %s", avcodec_get_name(context->codec_id), av_err2str(result)); + avcodec_free_context(&context); + return NULL; + } + + SDL_SetWindowSize(window, codecpar->width, codecpar->height); + + return context; +} + +static void SetYUVConversionMode(AVFrame *frame) +{ + SDL_YUV_CONVERSION_MODE mode = SDL_YUV_CONVERSION_AUTOMATIC; + if (frame && (frame->format == AV_PIX_FMT_YUV420P || frame->format == AV_PIX_FMT_YUYV422 || frame->format == AV_PIX_FMT_UYVY422)) { + if (frame->color_range == AVCOL_RANGE_JPEG) + mode = SDL_YUV_CONVERSION_JPEG; + else if (frame->colorspace == AVCOL_SPC_BT709) + mode = SDL_YUV_CONVERSION_BT709; + else if (frame->colorspace == AVCOL_SPC_BT470BG || frame->colorspace == AVCOL_SPC_SMPTE170M) + mode = SDL_YUV_CONVERSION_BT601; + } + SDL_SetYUVConversionMode(mode); /* FIXME: no support for linear transfer */ +} + +static void SDLCALL FreeSwsContextContainer(void *userdata, void *value) +{ + struct SwsContextContainer *sws_container = (struct SwsContextContainer *)value; + if (sws_container->context) { + sws_freeContext(sws_container->context); + } + SDL_free(sws_container); +} + +static SDL_bool GetTextureForMemoryFrame(AVFrame *frame, SDL_Texture **texture) +{ + int texture_width = 0, texture_height = 0; + Uint32 texture_format = SDL_PIXELFORMAT_UNKNOWN; + Uint32 frame_format = GetTextureFormat(frame->format); + + if (*texture) { + SDL_QueryTexture(*texture, &texture_format, NULL, &texture_width, &texture_height); + } + if (!*texture || texture_width != frame->width || texture_height != frame->height || + (frame_format != SDL_PIXELFORMAT_UNKNOWN && texture_format != frame_format) || + (frame_format == SDL_PIXELFORMAT_UNKNOWN && texture_format != SDL_PIXELFORMAT_ARGB8888)) { + if (*texture) { + SDL_DestroyTexture(*texture); + } + + if (frame_format == SDL_PIXELFORMAT_UNKNOWN) { + *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height); + } else { + *texture = SDL_CreateTexture(renderer, frame_format, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height); + } + if (!*texture) { + return SDL_FALSE; + } + + if (frame_format == SDL_PIXELFORMAT_UNKNOWN || SDL_ISPIXELFORMAT_ALPHA(frame_format)) { + SDL_SetTextureBlendMode(*texture, SDL_BLENDMODE_BLEND); + } else { + SDL_SetTextureBlendMode(*texture, SDL_BLENDMODE_NONE); + } + SDL_SetTextureScaleMode(*texture, SDL_SCALEMODE_LINEAR); + } + + switch (frame_format) { + case SDL_PIXELFORMAT_UNKNOWN: + { + SDL_PropertiesID props = SDL_GetTextureProperties(*texture); + struct SwsContextContainer *sws_container = (struct SwsContextContainer *)SDL_GetProperty(props, SWS_CONTEXT_CONTAINER_PROPERTY, NULL); + if (!sws_container) { + sws_container = (struct SwsContextContainer *)SDL_calloc(1, sizeof(*sws_container)); + if (!sws_container) { + return SDL_FALSE; + } + SDL_SetPropertyWithCleanup(props, SWS_CONTEXT_CONTAINER_PROPERTY, sws_container, FreeSwsContextContainer, NULL); + } + sws_container->context = sws_getCachedContext(sws_container->context, frame->width, frame->height, frame->format, frame->width, frame->height, AV_PIX_FMT_BGRA, SWS_POINT, NULL, NULL, NULL); + if (sws_container->context) { + uint8_t *pixels[4]; + int pitch[4]; + if (SDL_LockTexture(*texture, NULL, (void **)&pixels[0], &pitch[0]) == 0) { + sws_scale(sws_container->context, (const uint8_t * const *)frame->data, frame->linesize, 0, frame->height, pixels, pitch); + SDL_UnlockTexture(*texture); + } + } else { + SDL_SetError("Can't initialize the conversion context"); + return SDL_FALSE; + } + break; + } + case SDL_PIXELFORMAT_IYUV: + if (frame->linesize[0] > 0 && frame->linesize[1] > 0 && frame->linesize[2] > 0) { + SDL_UpdateYUVTexture(*texture, NULL, frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); + } else if (frame->linesize[0] < 0 && frame->linesize[1] < 0 && frame->linesize[2] < 0) { + SDL_UpdateYUVTexture(*texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0], + frame->data[1] + frame->linesize[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[1], + frame->data[2] + frame->linesize[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[2]); + } + SetYUVConversionMode(frame); + break; + default: + if (frame->linesize[0] < 0) { + SDL_UpdateTexture(*texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0]); + } else { + SDL_UpdateTexture(*texture, NULL, frame->data[0], frame->linesize[0]); + } + break; + } + return SDL_TRUE; +} + +static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture) +{ +#ifdef HAVE_EGL + const AVDRMFrameDescriptor *desc = (const AVDRMFrameDescriptor *)frame->data[0]; + int i, j, image_index, num_planes; + EGLDisplay display = eglGetCurrentDisplay(); + SDL_PropertiesID props; + GLuint textures[2]; + + /* FIXME: Assuming NV12 data format */ + num_planes = 0; + for (i = 0; i < desc->nb_layers; ++i) { + num_planes += desc->layers[i].nb_planes; + } + if (num_planes != 2) { + SDL_SetError("Expected NV12 frames with 2 planes, instead got %d planes", num_planes); + return SDL_FALSE; + } + + if (*texture) { + /* Free the previous texture now that we're about to render a new one */ + SDL_DestroyTexture(*texture); + } else { + /* First time set up for NV12 textures */ + SDL_SetHint("SDL_RENDER_OPENGL_NV12_RG_SHADER", "1"); + + SetYUVConversionMode(frame); + } + + *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STATIC, frame->width, frame->height); + if (!*texture) { + return SDL_FALSE; + } + SDL_SetTextureBlendMode(*texture, SDL_BLENDMODE_NONE); + SDL_SetTextureScaleMode(*texture, SDL_SCALEMODE_LINEAR); + + props = SDL_GetTextureProperties(*texture); + textures[0] = (GLuint)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_NUMBER, 0); + textures[1] = (GLuint)SDL_GetNumberProperty(props, SDL_PROPERTY_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER, 0); + if (!textures[0] || !textures[1]) { + SDL_SetError("Couldn't get NV12 OpenGL textures"); + return SDL_FALSE; + } + + /* import the frame into OpenGL */ + image_index = 0; + for (i = 0; i < desc->nb_layers; ++i) { + const AVDRMLayerDescriptor *layer = &desc->layers[i]; + for (j = 0; j < layer->nb_planes; ++j) { + static const uint32_t formats[ 2 ] = { DRM_FORMAT_R8, DRM_FORMAT_GR88 }; + const AVDRMPlaneDescriptor *plane = &layer->planes[j]; + const AVDRMObjectDescriptor *object = &desc->objects[plane->object_index]; + EGLAttrib img_attr[] = { + EGL_LINUX_DRM_FOURCC_EXT, formats[i], + EGL_WIDTH, frame->width / ( image_index + 1 ), /* half size for chroma */ + EGL_HEIGHT, frame->height / ( image_index + 1 ), + EGL_DMA_BUF_PLANE0_FD_EXT, object->fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane->offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, plane->pitch, + EGL_NONE + }; + EGLImage pImage = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr); + + glActiveTextureARBFunc(GL_TEXTURE0_ARB + image_index); + glBindTexture(GL_TEXTURE_2D, textures[image_index]); + glEGLImageTargetTexture2DOESFunc(GL_TEXTURE_2D, pImage); + ++image_index; + } + } + + return SDL_TRUE; +#else + return SDL_FALSE; +#endif +} + +static SDL_bool GetTextureForVAAPIFrame(AVFrame *frame, SDL_Texture **texture) +{ + AVFrame *drm_frame; + SDL_bool result = SDL_FALSE; + + drm_frame = av_frame_alloc(); + if (drm_frame) { + drm_frame->format = AV_PIX_FMT_DRM_PRIME; + if (av_hwframe_map(drm_frame, frame, 0) == 0) { + result = GetTextureForDRMFrame(drm_frame, texture); + } else { + SDL_SetError("Couldn't map hardware frame"); + } + av_frame_free(&drm_frame); + } + return result; +} + +static SDL_bool GetTextureForD3D11Frame(AVFrame *frame, SDL_Texture **texture) +{ +#ifdef __WIN32__ + int texture_width = 0, texture_height = 0; + ID3D11Texture2D *pTexture = (ID3D11Texture2D *)frame->data[0]; + UINT iSliceIndex = (UINT)(uintptr_t)frame->data[1]; + + D3D11_TEXTURE2D_DESC desc; + SDL_zero(desc); + ID3D11Texture2D_GetDesc(pTexture, &desc); + if (desc.Format != DXGI_FORMAT_NV12) { + SDL_SetError("Unsupported texture format, expected DXGI_FORMAT_NV12, got %d", desc.Format); + return SDL_FALSE; + } + + if (*texture) { + SDL_QueryTexture(*texture, NULL, NULL, &texture_width, &texture_height); + } + if (!*texture || (UINT)texture_width != desc.Width || (UINT)texture_height != desc.Height) { + if (*texture) { + SDL_DestroyTexture(*texture); + } else { + /* First time set up for NV12 textures */ + SetYUVConversionMode(frame); + } + + *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STATIC, desc.Width, desc.Height); + if (!*texture) { + return SDL_FALSE; + } + } + + ID3D11Resource *dx11_resource = SDL_GetProperty(SDL_GetTextureProperties(*texture), SDL_PROPERTY_TEXTURE_D3D11_TEXTURE_POINTER, NULL); + if (!dx11_resource) { + SDL_SetError("Couldn't get texture ID3D11Resource interface"); + return SDL_FALSE; + } + ID3D11DeviceContext_CopySubresourceRegion(d3d11_context, dx11_resource, 0, 0, 0, 0, (ID3D11Resource *)pTexture, iSliceIndex, NULL); + + return SDL_TRUE; +#else + return SDL_FALSE; +#endif +} + +static SDL_bool GetTextureForFrame(AVFrame *frame, SDL_Texture **texture) +{ + switch (frame->format) { + case AV_PIX_FMT_VAAPI: + return GetTextureForVAAPIFrame(frame, texture); + case AV_PIX_FMT_DRM_PRIME: + return GetTextureForDRMFrame(frame, texture); + case AV_PIX_FMT_D3D11: + return GetTextureForD3D11Frame(frame, texture); + default: + return GetTextureForMemoryFrame(frame, texture); + } +} + +static void DisplayVideoTexture(AVFrame *frame) +{ + /* Update the video texture */ + if (!GetTextureForFrame(frame, &video_texture)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't get texture for frame: %s\n", SDL_GetError()); + return; + } + + if (frame->linesize[0] < 0) { + SDL_RenderTextureRotated(renderer, video_texture, NULL, NULL, 0.0, NULL, SDL_FLIP_VERTICAL); + } else { + SDL_RenderTexture(renderer, video_texture, NULL, NULL); + } +} + +static void DisplayVideoToolbox(AVFrame *frame) +{ +#ifdef __APPLE__ + SDL_Rect viewport; + SDL_GetRenderViewport(renderer, &viewport); + DisplayVideoToolboxFrame(renderer, frame->data[3], 0, 0, frame->width, frame->height, viewport.x, viewport.y, viewport.w, viewport.h); +#endif +} + +static void DisplayVideoFrame(AVFrame *frame) +{ + switch (frame->format) { + case AV_PIX_FMT_VIDEOTOOLBOX: + DisplayVideoToolbox(frame); + break; + default: + DisplayVideoTexture(frame); + break; + } +} + +static void HandleVideoFrame(AVFrame *frame, double pts) +{ + /* Quick and dirty PTS handling */ + if (!video_start) { + video_start = SDL_GetTicks(); + } + double now = (double)(SDL_GetTicks() - video_start) / 1000.0; + while (now < pts - 0.001) { + SDL_Delay(1); + now = (double)(SDL_GetTicks() - video_start) / 1000.0; + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + DisplayVideoFrame(frame); + + /* Render any bouncing balls */ + MoveSprite(); + + SDL_RenderPresent(renderer); +} + +static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream, const AVCodec *codec) +{ + AVStream *st = ic->streams[stream]; + AVCodecParameters *codecpar = st->codecpar; + AVCodecContext *context; + int result; + + SDL_Log("Audio stream: %s %d channels, %d Hz\n", avcodec_get_name(codec->id), codecpar->ch_layout.nb_channels, codecpar->sample_rate); + + context = avcodec_alloc_context3(NULL); + if (!context) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_alloc_context3 failed\n"); + return NULL; + } + + result = avcodec_parameters_to_context(context, ic->streams[stream]->codecpar); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_parameters_to_context failed: %s\n", av_err2str(result)); + avcodec_free_context(&context); + return NULL; + } + context->pkt_timebase = ic->streams[stream]->time_base; + + result = avcodec_open2(context, codec, NULL); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open codec %s: %s", avcodec_get_name(context->codec_id), av_err2str(result)); + avcodec_free_context(&context); + return NULL; + } + + SDL_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate }; + audio = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL, NULL); + if (audio) { + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(audio)); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s", SDL_GetError()); + } + return context; +} + +static SDL_AudioFormat GetAudioFormat(enum AVSampleFormat format) +{ + switch (format) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + return SDL_AUDIO_U8; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + return SDL_AUDIO_S16; + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + return SDL_AUDIO_S32; + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + return SDL_AUDIO_F32; + default: + /* Unsupported */ + return 0; + } +} + +static SDL_bool IsPlanarAudioFormat(enum AVSampleFormat format) +{ + switch (format) { + case AV_SAMPLE_FMT_U8P: + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_DBLP: + case AV_SAMPLE_FMT_S64P: + return SDL_TRUE; + default: + return SDL_FALSE; + } +} + +static void InterleaveAudio(AVFrame *frame, const SDL_AudioSpec *spec) +{ + int c, n; + int samplesize = SDL_AUDIO_BYTESIZE(spec->format); + int framesize = SDL_AUDIO_FRAMESIZE(*spec); + Uint8 *data = (Uint8 *)SDL_malloc(frame->nb_samples * framesize); + if (!data) { + return; + } + + /* This could be optimized with SIMD and not allocating memory each time */ + for (c = 0; c < spec->channels; ++c) { + const Uint8 *src = frame->data[c]; + Uint8 *dst = data + c * samplesize; + for (n = frame->nb_samples; n--; ) { + SDL_memcpy(dst, src, samplesize); + src += samplesize; + dst += framesize; + } + } + SDL_PutAudioStreamData(audio, data, frame->nb_samples * framesize); + SDL_free(data); +} + +static void HandleAudioFrame(AVFrame *frame) +{ + if (audio) { + SDL_AudioSpec spec = { GetAudioFormat(frame->format), frame->ch_layout.nb_channels, frame->sample_rate }; + SDL_SetAudioStreamFormat(audio, &spec, NULL); + + if (frame->ch_layout.nb_channels > 1 && IsPlanarAudioFormat(frame->format)) { + InterleaveAudio(frame, &spec); + } else { + SDL_PutAudioStreamData(audio, frame->data[0], frame->nb_samples * SDL_AUDIO_FRAMESIZE(spec)); + } + } +} + +static void print_usage(SDLTest_CommonState *state, const char *argv0) { + static const char *options[] = { "[--sprites N]", "[--audio-codec codec]", "[--video-codec codec]", "[--software]", "video_file", NULL }; + SDLTest_CommonLogUsage(state, argv0, options); +} + +int main(int argc, char *argv[]) +{ + const char *file = NULL; + AVFormatContext *ic = NULL; + int audio_stream = -1; + int video_stream = -1; + const char *audio_codec_name = NULL; + const char *video_codec_name = NULL; + const AVCodec *audio_codec = NULL; + const AVCodec *video_codec = NULL; + AVCodecContext *audio_context = NULL; + AVCodecContext *video_context = NULL; + AVPacket *pkt = NULL; + AVFrame *frame = NULL; + double first_pts = -1.0; + int i; + int result; + int return_code = -1; + Uint32 window_flags; + SDL_bool flushing = SDL_FALSE; + SDL_bool decoded = SDL_FALSE; + SDLTest_CommonState *state; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (!consumed) { + if (SDL_strcmp(argv[i], "--sprites") == 0 && argv[i+1]) { + num_sprites = SDL_atoi(argv[i+1]); + consumed = 2; + } else if (SDL_strcmp(argv[i], "--audio-codec") == 0 && argv[i+1]) { + audio_codec_name = argv[i+1]; + consumed = 2; + } else if (SDL_strcmp(argv[i], "--video-codec") == 0 && argv[i+1]) { + video_codec_name = argv[i+1]; + consumed = 2; + } else if (SDL_strcmp(argv[i], "--software") == 0) { + software_only = SDL_TRUE; + consumed = 1; + } else if (!file) { + /* We'll try to open this as a media file */ + file = argv[i]; + consumed = 1; + } + } + if (consumed <= 0) { + print_usage(state, argv[0]); + return_code = 1; + goto quit; + } + + i += consumed; + } + + if (!file) { + print_usage(state, argv[0]); + return_code = 1; + goto quit; + } + + if (SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0) { + return_code = 2; + goto quit; + } + + window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; +#ifdef __APPLE__ + window_flags |= SDL_WINDOW_METAL; +#elif !defined(__WIN32__) + window_flags |= SDL_WINDOW_OPENGL; +#endif +#ifdef HAVE_EGL + /* Try to create an EGL compatible window for DRM hardware frame support */ + if (!window) { + CreateWindowAndRenderer(window_flags, "opengles2"); + } +#endif +#ifdef __APPLE__ + if (!window) { + CreateWindowAndRenderer(window_flags, "metal"); + } +#endif +#ifdef __WIN32__ + if (!window) { + CreateWindowAndRenderer(window_flags, "direct3d11"); + } +#endif + if (!window) { + if (!CreateWindowAndRenderer(window_flags, NULL)) { + return_code = 2; + goto quit; + } + } + + if (SDL_SetWindowTitle(window, file) < 0) { + SDL_Log("SDL_SetWindowTitle: %s", SDL_GetError()); + } + + /* Open the media file */ + result = avformat_open_input(&ic, file, NULL, NULL); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open %s: %d", argv[1], result); + return_code = 4; + goto quit; + } + video_stream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &video_codec, 0); + if (video_stream >= 0) { + if (video_codec_name) { + video_codec = avcodec_find_decoder_by_name(video_codec_name); + if (!video_codec) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find codec '%s'", video_codec_name); + return_code = 4; + goto quit; + } + } + video_context = OpenVideoStream(ic, video_stream, video_codec); + if (!video_context) { + return_code = 4; + goto quit; + } + } + audio_stream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, video_stream, &audio_codec, 0); + if (audio_stream >= 0) { + if (audio_codec_name) { + audio_codec = avcodec_find_decoder_by_name(audio_codec_name); + if (!audio_codec) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find codec '%s'", audio_codec_name); + return_code = 4; + goto quit; + } + } + audio_context = OpenAudioStream(ic, audio_stream, audio_codec); + if (!audio_context) { + return_code = 4; + goto quit; + } + } + pkt = av_packet_alloc(); + if (!pkt) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "av_packet_alloc failed"); + return_code = 4; + goto quit; + } + frame = av_frame_alloc(); + if (!frame) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "av_frame_alloc failed"); + return_code = 4; + goto quit; + } + + /* Create the sprite */ + sprite = CreateTexture(renderer, icon_bmp, icon_bmp_len, &sprite_w, &sprite_h); + + if (!sprite) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture (%s)", SDL_GetError()); + return_code = 3; + goto quit; + } + + /* Allocate memory for the sprite info */ + positions = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*positions)); + velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities)); + if (!positions || !velocities) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); + return_code = 3; + goto quit; + } + + /* Position sprites and set their velocities */ + SDL_Rect viewport; + SDL_GetRenderViewport(renderer, &viewport); + srand((unsigned int)time(NULL)); + for (i = 0; i < num_sprites; ++i) { + positions[i].x = (float)(rand() % (viewport.w - sprite_w)); + positions[i].y = (float)(rand() % (viewport.h - sprite_h)); + positions[i].w = (float)sprite_w; + positions[i].h = (float)sprite_h; + velocities[i].x = 0.0f; + velocities[i].y = 0.0f; + while (!velocities[i].x || !velocities[i].y) { + velocities[i].x = (float)((rand() % (2 + 1)) - 1); + velocities[i].y = (float)((rand() % (2 + 1)) - 1); + } + } + + /* We're ready to go! */ + SDL_ShowWindow(window); + + /* Main render loop */ + done = 0; + + while (!done) { + SDL_Event event; + + /* Check for events */ + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT || event.type == SDL_EVENT_KEY_DOWN) { + done = 1; + } + } + + if (!flushing) { + result = av_read_frame(ic, pkt); + if (result < 0) { + SDL_Log("End of stream, finishing decode\n"); + if (audio_context) { + avcodec_flush_buffers(audio_context); + } + if (video_context) { + avcodec_flush_buffers(video_context); + } + flushing = SDL_TRUE; + } else { + if (pkt->stream_index == audio_stream) { + result = avcodec_send_packet(audio_context, pkt); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_send_packet(audio_context) failed: %s", av_err2str(result)); + } + } else if (pkt->stream_index == video_stream) { + result = avcodec_send_packet(video_context, pkt); + if (result < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_send_packet(video_context) failed: %s", av_err2str(result)); + } + } + av_packet_unref(pkt); + } + } + + decoded = SDL_FALSE; + if (audio_context) { + while (avcodec_receive_frame(audio_context, frame) >= 0) { + HandleAudioFrame(frame); + decoded = SDL_TRUE; + } + if (flushing) { + /* Let SDL know we're done sending audio */ + SDL_FlushAudioStream(audio); + } + } + if (video_context) { + while (avcodec_receive_frame(video_context, frame) >= 0) { + double pts = ((double)frame->pts * video_context->pkt_timebase.num) / video_context->pkt_timebase.den; + if (first_pts < 0.0) { + first_pts = pts; + } + pts -= first_pts; + + HandleVideoFrame(frame, pts); + decoded = SDL_TRUE; + } + } else { + /* Update video rendering */ + SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); + SDL_RenderClear(renderer); + MoveSprite(); + SDL_RenderPresent(renderer); + } + + if (flushing && !decoded) { + if (SDL_GetAudioStreamQueued(audio) > 0) { + /* Wait a little bit for the audio to finish */ + SDL_Delay(10); + } else { + done = 1; + } + } + } + return_code = 0; +quit: +#ifdef __APPLE__ + CleanupVideoToolboxOutput(); +#endif +#ifdef __WIN32__ + if (d3d11_context) { + ID3D11DeviceContext_Release(d3d11_device); + d3d11_context = NULL; + } + if (d3d11_device) { + ID3D11Device_Release(d3d11_device); + d3d11_device = NULL; + } +#endif + SDL_free(positions); + SDL_free(velocities); + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&audio_context); + avcodec_free_context(&video_context); + avformat_close_input(&ic); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + SDLTest_CommonDestroyState(state); + return return_code; +} diff --git a/test/testffmpeg_videotoolbox.h b/test/testffmpeg_videotoolbox.h new file mode 100644 index 00000000..706ff77b --- /dev/null +++ b/test/testffmpeg_videotoolbox.h @@ -0,0 +1,15 @@ +/* + 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 + 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. +*/ + +extern SDL_bool SetupVideoToolboxOutput(SDL_Renderer *renderer); +extern SDL_bool DisplayVideoToolboxFrame(SDL_Renderer *renderer, void *buffer, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH ); +extern void CleanupVideoToolboxOutput(); diff --git a/test/testffmpeg_videotoolbox.m b/test/testffmpeg_videotoolbox.m new file mode 100644 index 00000000..4e7d98e6 --- /dev/null +++ b/test/testffmpeg_videotoolbox.m @@ -0,0 +1,147 @@ +/* + 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 + 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 "testffmpeg_videotoolbox.h" + +#include +#include +#include +#include + + +// Metal BT.601 to RGB conversion shader +static NSString *drawMetalShaderSource = +@" using namespace metal;\n" +"\n" +" struct Vertex\n" +" {\n" +" float4 position [[position]];\n" +" float2 texCoords;\n" +" };\n" +"\n" +" constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear);\n" +"\n" +" vertex Vertex draw_vs(constant Vertex *vertices [[ buffer(0) ]], uint vid [[ vertex_id ]])\n" +" {\n" +" return vertices[ vid ];\n" +" }\n" +"\n" +" fragment float4 draw_ps_bt601(Vertex in [[ stage_in ]],\n" +" texture2d textureY [[ texture(0) ]],\n" +" texture2d textureUV [[ texture(1) ]])\n" +" {\n" +" float3 yuv = float3(textureY.sample(s, in.texCoords).r, textureUV.sample(s, in.texCoords).rg);\n" +" float3 rgb;\n" +" yuv += float3(-0.0627451017, -0.501960814, -0.501960814);\n" +" rgb.r = dot(yuv, float3(1.1644, 0.000, 1.596));\n" +" rgb.g = dot(yuv, float3(1.1644, -0.3918, -0.813));\n" +" rgb.b = dot(yuv, float3(1.1644, 2.0172, 0.000));\n" +" return float4(rgb, 1.0);\n" +" }\n" +; + +// keep this structure aligned with the proceeding drawMetalShaderSource's struct Vertex +typedef struct Vertex +{ + vector_float4 position; + vector_float2 texCoord; +} Vertex; + +static void SetVertex(Vertex *vertex, float x, float y, float s, float t) +{ + vertex->position[ 0 ] = x; + vertex->position[ 1 ] = y; + vertex->position[ 2 ] = 0.0f; + vertex->position[ 3 ] = 1.0f; + vertex->texCoord[ 0 ] = s; + vertex->texCoord[ 1 ] = t; +} + +static CAMetalLayer *metal_layer; +static id library; +static id video_pipeline; + +SDL_bool SetupVideoToolboxOutput(SDL_Renderer *renderer) +{ @autoreleasepool { + NSError *error; + + // Create the metal view + metal_layer = (CAMetalLayer *)SDL_GetRenderMetalLayer(renderer); + if (!metal_layer) { + return SDL_FALSE; + } + + // FIXME: Handle other colorspaces besides BT.601 + library = [metal_layer.device newLibraryWithSource:drawMetalShaderSource options:nil error:&error]; + + MTLRenderPipelineDescriptor *videoPipelineDescriptor = [[MTLRenderPipelineDescriptor new] autorelease]; + videoPipelineDescriptor.vertexFunction = [library newFunctionWithName:@"draw_vs"]; + videoPipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"draw_ps_bt601"]; + videoPipelineDescriptor.colorAttachments[ 0 ].pixelFormat = metal_layer.pixelFormat; + + video_pipeline = [metal_layer.device newRenderPipelineStateWithDescriptor:videoPipelineDescriptor error:nil]; + if (!video_pipeline) { + SDL_SetError("Couldn't create video pipeline"); + return SDL_FALSE; + } + + return true; +}} + +SDL_bool DisplayVideoToolboxFrame(SDL_Renderer *renderer, void *buffer, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH ) +{ @autoreleasepool { + CVPixelBufferRef pPixelBuffer = (CVPixelBufferRef)buffer; + size_t nPixelBufferWidth = CVPixelBufferGetWidthOfPlane(pPixelBuffer, 0); + size_t nPixelBufferHeight = CVPixelBufferGetHeightOfPlane(pPixelBuffer, 0); + id videoFrameTextureY = nil; + id videoFrameTextureUV = nil; + + IOSurfaceRef pSurface = CVPixelBufferGetIOSurface(pPixelBuffer); + + MTLTextureDescriptor *textureDescriptorY = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm width:nPixelBufferWidth height:nPixelBufferHeight mipmapped:NO]; + MTLTextureDescriptor *textureDescriptorUV = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRG8Unorm width:CVPixelBufferGetWidthOfPlane(pPixelBuffer, 1) height:CVPixelBufferGetHeightOfPlane(pPixelBuffer, 1) mipmapped:NO]; + + videoFrameTextureY = [[metal_layer.device newTextureWithDescriptor:textureDescriptorY iosurface:pSurface plane:0] autorelease]; + videoFrameTextureUV = [[metal_layer.device newTextureWithDescriptor:textureDescriptorUV iosurface:pSurface plane:1] autorelease]; + + float flMinSrcX = ( srcX + 0.5f ) / nPixelBufferWidth; + float flMaxSrcX = ( srcX + srcW + 0.5f ) / nPixelBufferWidth; + float flMinSrcY = ( srcY + 0.5f ) / nPixelBufferHeight; + float flMaxSrcY = ( srcY + srcH + 0.5f ) / nPixelBufferHeight; + + int nOutputWidth, nOutputHeight; + nOutputWidth = metal_layer.drawableSize.width; + nOutputHeight = metal_layer.drawableSize.height; + float flMinDstX = 2.0f * ( ( dstX + 0.5f ) / nOutputWidth ) - 1.0f; + float flMaxDstX = 2.0f * ( ( dstX + dstW + 0.5f ) / nOutputWidth ) - 1.0f; + float flMinDstY = 2.0f * ( ( nOutputHeight - dstY - 0.5f ) / nOutputHeight ) - 1.0f; + float flMaxDstY = 2.0f * ( ( nOutputHeight - ( dstY + dstH ) - 0.5f ) / nOutputHeight ) - 1.0f; + + Vertex arrVerts[4]; + SetVertex(&arrVerts[0], flMinDstX, flMaxDstY, flMinSrcX, flMaxSrcY); + SetVertex(&arrVerts[1], flMinDstX, flMinDstY, flMinSrcX, flMinSrcY); + SetVertex(&arrVerts[2], flMaxDstX, flMaxDstY, flMaxSrcX, flMaxSrcY); + SetVertex(&arrVerts[3], flMaxDstX, flMinDstY, flMaxSrcX, flMinSrcY); + + id renderEncoder = (id)SDL_GetRenderMetalCommandEncoder(renderer); + [renderEncoder setRenderPipelineState:video_pipeline]; + [renderEncoder setFragmentTexture:videoFrameTextureY atIndex:0]; + [renderEncoder setFragmentTexture:videoFrameTextureUV atIndex:1]; + [renderEncoder setVertexBytes:arrVerts length:sizeof(arrVerts) atIndex:0]; + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:SDL_arraysize(arrVerts)]; + return SDL_TRUE; +}} + +void CleanupVideoToolboxOutput() +{ +} diff --git a/test/testfile.c b/test/testfile.c index 1561f176..be04064b 100644 --- a/test/testfile.c +++ b/test/testfile.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -123,25 +123,25 @@ int main(int argc, char *argv[]) RWOP_ERR_QUIT(rwops); } rwops = SDL_RWFromFile(FBASENAME2, "wb"); - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } SDL_RWclose(rwops); unlink(FBASENAME2); rwops = SDL_RWFromFile(FBASENAME2, "wb+"); - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } SDL_RWclose(rwops); unlink(FBASENAME2); rwops = SDL_RWFromFile(FBASENAME2, "ab"); - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } SDL_RWclose(rwops); unlink(FBASENAME2); rwops = SDL_RWFromFile(FBASENAME2, "ab+"); - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } SDL_RWclose(rwops); @@ -152,7 +152,7 @@ int main(int argc, char *argv[]) test : w mode, r mode, w+ mode */ rwops = SDL_RWFromFile(FBASENAME1, "wb"); /* write only */ - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } if (10 != SDL_RWwrite(rwops, "1234567890", 10)) { @@ -174,7 +174,7 @@ int main(int argc, char *argv[]) SDL_RWclose(rwops); rwops = SDL_RWFromFile(FBASENAME1, "rb"); /* read mode, file must exist */ - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } if (0 != SDL_RWseek(rwops, 0L, SDL_RW_SEEK_SET)) { @@ -212,7 +212,7 @@ int main(int argc, char *argv[]) /* test 3: same with w+ mode */ rwops = SDL_RWFromFile(FBASENAME1, "wb+"); /* write + read + truncation */ - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } if (10 != SDL_RWwrite(rwops, "1234567890", 10)) { @@ -263,7 +263,7 @@ int main(int argc, char *argv[]) /* test 4: same in r+ mode */ rwops = SDL_RWFromFile(FBASENAME1, "rb+"); /* write + read + file must exists, no truncation */ - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } if (10 != SDL_RWwrite(rwops, "1234567890", 10)) { @@ -314,7 +314,7 @@ int main(int argc, char *argv[]) /* test5 : append mode */ rwops = SDL_RWFromFile(FBASENAME1, "ab+"); /* write + read + append */ - if (rwops == NULL) { + if (!rwops) { RWOP_ERR_QUIT(rwops); } if (10 != SDL_RWwrite(rwops, "1234567890", 10)) { diff --git a/test/testfilesystem.c b/test/testfilesystem.c index bea7c0cc..e1986698 100644 --- a/test/testfilesystem.c +++ b/test/testfilesystem.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) } base_path = SDL_GetBasePath(); - if (base_path == NULL) { + if (!base_path) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find base path: %s\n", SDL_GetError()); } else { @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) } pref_path = SDL_GetPrefPath("libsdl", "test_filesystem"); - if (pref_path == NULL) { + if (!pref_path) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find pref path: %s\n", SDL_GetError()); } else { @@ -59,7 +59,7 @@ int main(int argc, char *argv[]) } pref_path = SDL_GetPrefPath(NULL, "test_filesystem"); - if (pref_path == NULL) { + if (!pref_path) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find pref path without organization: %s\n", SDL_GetError()); } else { diff --git a/test/testgeometry.c b/test/testgeometry.c index d494d956..9abefab4 100644 --- a/test/testgeometry.c +++ b/test/testgeometry.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -29,6 +29,8 @@ static SDL_Texture **sprites; static SDL_BlendMode blendMode = SDL_BLENDMODE_NONE; static float angle = 0.0f; static int sprite_w, sprite_h; +static int translate_cx = 0; +static int translate_cy = 0; static int done; @@ -92,6 +94,18 @@ static void loop(void) angle += yrel; } } + } else if (event.type == SDL_EVENT_KEY_DOWN) { + if (event.key.keysym.sym == SDLK_LEFT) { + translate_cx -= 1; + } else if (event.key.keysym.sym == SDLK_RIGHT) { + translate_cx += 1; + } else if (event.key.keysym.sym == SDLK_UP) { + translate_cy -= 1; + } else if (event.key.keysym.sym == SDLK_DOWN) { + translate_cy += 1; + } else { + SDLTest_CommonEvent(state, &event, &done); + } } else { SDLTest_CommonEvent(state, &event, &done); } @@ -119,6 +133,9 @@ static void loop(void) cy = viewport.y + viewport.h / 2; d = (viewport.w + viewport.h) / 5.f; + cx += translate_cx; + cy += translate_cy; + a = (angle * 3.1415f) / 180.0f; verts[0].position.x = cx + d * SDL_cosf(a); verts[0].position.y = cy + d * SDL_sinf(a); @@ -173,7 +190,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -224,7 +241,7 @@ int main(int argc, char *argv[]) /* Create the windows, initialize the renderers, and load the textures */ sprites = (SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites)); - if (sprites == NULL) { + if (!sprites) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); quit(2); } diff --git a/test/testgl.c b/test/testgl.c index 0f592812..d08a67e0 100644 --- a/test/testgl.c +++ b/test/testgl.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -199,6 +199,17 @@ static void Render(void) ctx.glRotatef(5.0, 1.0, 1.0, 1.0); } +static void LogSwapInterval(void) +{ + int interval = 0; + const int ret_interval = SDL_GL_GetSwapInterval(&interval); + if (ret_interval < 0) { + SDL_Log("Swap Interval : %d error: %s\n", interval, SDL_GetError()); + } else { + SDL_Log("Swap Interval : %d\n", interval); + } +} + int main(int argc, char *argv[]) { int fsaa, accel; @@ -211,8 +222,6 @@ int main(int argc, char *argv[]) int status; int dw, dh; int swap_interval = 0; - int interval = 0; - int ret_interval = 0; /* Initialize parameters */ fsaa = 0; @@ -220,7 +229,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -304,12 +313,7 @@ int main(int argc, char *argv[]) SDL_Log("Screen BPP : %" SDL_PRIu32 "\n", SDL_BITSPERPIXEL(mode->format)); } - ret_interval = SDL_GL_GetSwapInterval(&interval); - if (ret_interval < 0) { - SDL_Log("Swap Interval : %d error: %s\n", interval, SDL_GetError()); - } else { - SDL_Log("Swap Interval : %d\n", interval); - } + LogSwapInterval(); SDL_GetWindowSize(state->windows[0], &dw, &dh); SDL_Log("Window Size : %d,%d\n", dw, dh); @@ -421,6 +425,7 @@ int main(int argc, char *argv[]) SDL_GL_MakeCurrent(state->windows[i], context); if (update_swap_interval) { SDL_GL_SetSwapInterval(swap_interval); + LogSwapInterval(); } SDL_GetWindowSizeInPixels(state->windows[i], &w, &h); ctx.glViewport(0, 0, w, h); diff --git a/test/testgles.c b/test/testgles.c index 0afae542..36200e10 100644 --- a/test/testgles.c +++ b/test/testgles.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -32,7 +32,7 @@ quit(int rc) { int i; - if (context != NULL) { + if (context) { for (i = 0; i < state->num_windows; i++) { if (context[i]) { SDL_GL_DeleteContext(context[i]); @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -176,7 +176,7 @@ int main(int argc, char *argv[]) } context = (SDL_GLContext *)SDL_calloc(state->num_windows, sizeof(*context)); - if (context == NULL) { + if (!context) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); quit(2); } diff --git a/test/testgles2.c b/test/testgles2.c index 6975adba..af408eca 100644 --- a/test/testgles2.c +++ b/test/testgles2.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -99,7 +99,7 @@ quit(int rc) { int i; - if (context != NULL) { + if (context) { for (i = 0; i < state->num_windows; i++) { if (context[i]) { SDL_GL_DeleteContext(context[i]); @@ -681,7 +681,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } for (i = 1; i < argc;) { @@ -749,7 +749,7 @@ int main(int argc, char *argv[]) } context = (SDL_GLContext *)SDL_calloc(state->num_windows, sizeof(*context)); - if (context == NULL) { + if (!context) { SDL_Log("Out of memory!\n"); quit(2); } diff --git a/test/testgles2_sdf.c b/test/testgles2_sdf.c index aadb031f..7429aa06 100644 --- a/test/testgles2_sdf.c +++ b/test/testgles2_sdf.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -91,7 +91,7 @@ quit(int rc) { int i; - if (context != NULL) { + if (context) { for (i = 0; i < state->num_windows; i++) { if (context[i]) { SDL_GL_DeleteContext(context[i]); @@ -449,7 +449,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } for (i = 1; i < argc;) { @@ -511,7 +511,7 @@ int main(int argc, char *argv[]) } context = (SDL_GLContext *)SDL_calloc(state->num_windows, sizeof(*context)); - if (context == NULL) { + if (!context) { SDL_Log("Out of memory!\n"); quit(2); } @@ -552,17 +552,17 @@ int main(int argc, char *argv[]) #if 1 path = GetNearbyFilename(f); - if (path == NULL) { + if (!path) { path = SDL_strdup(f); } - if (path == NULL) { + if (!path) { SDL_Log("out of memory\n"); exit(-1); } tmp = SDL_LoadBMP(path); - if (tmp == NULL) { + if (!tmp) { SDL_Log("missing image file: %s", path); exit(-1); } else { diff --git a/test/testhaptic.c b/test/testhaptic.c index 6a6d6869..0ace2354 100644 --- a/test/testhaptic.c +++ b/test/testhaptic.c @@ -27,7 +27,7 @@ static void abort_execution(void); static void HapticPrintSupported(SDL_Haptic *); /** - * \brief The entry point of this force feedback demo. + * The entry point of this force feedback demo. * \param[in] argc Number of arguments. * \param[in] argv Array of argc arguments. */ @@ -43,7 +43,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -87,7 +87,7 @@ int main(int argc, char **argv) } if (SDL_NumHaptics() > 0) { /* We'll just use index or the first force feedback device found */ - if (name == NULL) { + if (!name) { i = (index != -1) ? index : 0; } /* Try to find matching device */ @@ -106,7 +106,7 @@ int main(int argc, char **argv) } haptic = SDL_HapticOpen(i); - if (haptic == NULL) { + if (!haptic) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create the haptic device: %s\n", SDL_GetError()); return 1; @@ -297,7 +297,7 @@ int main(int argc, char **argv) } /* Quit */ - if (haptic != NULL) { + if (haptic) { SDL_HapticClose(haptic); } SDL_Quit(); diff --git a/test/testhittesting.c b/test/testhittesting.c index 955bef0d..e65c2fa4 100644 --- a/test/testhittesting.c +++ b/test/testhittesting.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -80,7 +80,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -140,7 +140,7 @@ int main(int argc, char **argv) if (e.key.keysym.sym == SDLK_ESCAPE) { done = 1; } else if (e.key.keysym.sym == SDLK_x) { - if (areas == NULL) { + if (!areas) { areas = drag_areas; numareas = SDL_arraysize(drag_areas); } else { diff --git a/test/testhotplug.c b/test/testhotplug.c index 24ec6f48..434b18e8 100644 --- a/test/testhotplug.c +++ b/test/testhotplug.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) keepGoing = SDL_FALSE; break; case SDL_EVENT_JOYSTICK_ADDED: - if (joystick != NULL) { + if (joystick) { SDL_Log("Only one joystick supported by this test\n"); } else { joystick = SDL_OpenJoystick(event.jdevice.which); diff --git a/test/testiconv.c b/test/testiconv.c index daabe270..8bea7f58 100644 --- a/test/testiconv.c +++ b/test/testiconv.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -61,7 +61,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) fname = GetResourceFilename(fname, "utf8.txt"); file = fopen(fname, "rb"); - if (file == NULL) { + if (!file) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to open %s\n", fname); return 1; } diff --git a/test/testime.c b/test/testime.c index 84d3aecb..7553014e 100644 --- a/test/testime.c +++ b/test/testime.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -97,7 +97,7 @@ static Uint8 validate_hex(const char *cp, size_t len, Uint32 *np) } n = (n << 4) | c; } - if (np != NULL) { + if (np) { *np = n; } return 1; @@ -116,7 +116,7 @@ static int unifont_init(const char *fontname) /* Allocate memory for the glyph data so the file can be closed after initialization. */ unifontGlyph = (struct UnifontGlyph *)SDL_malloc(unifontGlyphSize); - if (unifontGlyph == NULL) { + if (!unifontGlyph) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for glyph data.\n", (int)(unifontGlyphSize + 1023) / 1024); return -1; } @@ -124,20 +124,20 @@ static int unifont_init(const char *fontname) /* Allocate memory for texture pointers for all renderers. */ unifontTexture = (SDL_Texture **)SDL_malloc(unifontTextureSize); - if (unifontTexture == NULL) { + if (!unifontTexture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for texture pointer data.\n", (int)(unifontTextureSize + 1023) / 1024); return -1; } SDL_memset(unifontTexture, 0, unifontTextureSize); filename = GetResourceFilename(NULL, fontname); - if (filename == NULL) { + if (!filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory\n"); return -1; } hexFile = SDL_RWFromFile(filename, "rb"); SDL_free(filename); - if (hexFile == NULL) { + if (!hexFile) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to open font file: %s\n", fontname); return -1; } @@ -279,7 +279,7 @@ static int unifont_load_texture(Uint32 textureID) } textureRGBA = (Uint8 *)SDL_malloc(UNIFONT_TEXTURE_SIZE); - if (textureRGBA == NULL) { + if (!textureRGBA) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d MiB for a texture.\n", UNIFONT_TEXTURE_SIZE / 1024 / 1024); return -1; } @@ -334,7 +334,7 @@ static Sint32 unifont_draw_glyph(Uint32 codepoint, int rendererID, SDL_FRect *ds } } texture = unifontTexture[UNIFONT_NUM_TEXTURES * rendererID + textureID]; - if (texture != NULL) { + if (texture) { const Uint32 cInTex = codepoint % UNIFONT_GLYPHS_IN_TEXTURE; srcrect.x = (float)(cInTex % UNIFONT_GLYPHS_IN_ROW * 16); srcrect.y = (float)(cInTex / UNIFONT_GLYPHS_IN_ROW * 16); @@ -348,12 +348,12 @@ static void unifont_cleanup(void) int i, j; for (i = 0; i < state->num_windows; ++i) { SDL_Renderer *renderer = state->renderers[i]; - if (state->windows[i] == NULL || renderer == NULL) { + if (state->windows[i] == NULL || !renderer) { continue; } for (j = 0; j < UNIFONT_NUM_TEXTURES; j++) { SDL_Texture *tex = unifontTexture[UNIFONT_NUM_TEXTURES * i + j]; - if (tex != NULL) { + if (tex) { SDL_DestroyTexture(tex); } } @@ -538,7 +538,7 @@ static void _Redraw(int rendererID) if (cursor) { char *p = utf8_advance(markedText, cursor); char c = 0; - if (p == NULL) { + if (!p) { p = &markedText[SDL_strlen(markedText)]; } @@ -639,7 +639,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -677,7 +677,7 @@ int main(int argc, char *argv[]) TTF_Init(); font = TTF_OpenFont(fontname, DEFAULT_PTSIZE); - if (font == NULL) { + if (!font) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to find font: %s\n", TTF_GetError()); return -1; } diff --git a/test/testintersections.c b/test/testintersections.c index 41b20ec1..126078fc 100644 --- a/test/testintersections.c +++ b/test/testintersections.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -292,7 +292,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testkeys.c b/test/testkeys.c index 0db5a4b3..88f75050 100644 --- a/test/testkeys.c +++ b/test/testkeys.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testloadso.c b/test/testloadso.c index c938d941..fbd3fcc2 100644 --- a/test/testloadso.c +++ b/test/testloadso.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -88,13 +88,13 @@ int main(int argc, char *argv[]) } lib = SDL_LoadObject(libname); - if (lib == NULL) { + if (!lib) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_LoadObject('%s') failed: %s\n", libname, SDL_GetError()); retval = 3; } else { fn = (fntype)SDL_LoadFunction(lib, symname); - if (fn == NULL) { + if (!fn) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_LoadFunction('%s') failed: %s\n", symname, SDL_GetError()); retval = 4; diff --git a/test/testlocale.c b/test/testlocale.c index d0c32397..b2e04334 100644 --- a/test/testlocale.c +++ b/test/testlocale.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -16,7 +16,7 @@ static void log_locales(void) { SDL_Locale *locales = SDL_GetPreferredLocales(); - if (locales == NULL) { + if (!locales) { SDL_Log("Couldn't determine locales: %s", SDL_GetError()); } else { SDL_Locale *l; @@ -40,7 +40,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testlock.c b/test/testlock.c index e0413d87..2437bd27 100644 --- a/test/testlock.c +++ b/test/testlock.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -80,17 +80,12 @@ Run(void *data) SDL_Log("Thread %lu: starting up", SDL_ThreadID()); while (!SDL_AtomicGet(&doterminate)) { SDL_Log("Thread %lu: ready to work\n", SDL_ThreadID()); - if (SDL_LockMutex(mutex) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't lock mutex: %s", SDL_GetError()); - exit(1); - } + SDL_LockMutex(mutex); SDL_Log("Thread %lu: start work!\n", SDL_ThreadID()); SDL_Delay(1 * worktime); SDL_Log("Thread %lu: work done!\n", SDL_ThreadID()); - if (SDL_UnlockMutex(mutex) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't unlock mutex: %s", SDL_GetError()); - exit(1); - } + SDL_UnlockMutex(mutex); + /* If this sleep isn't done, then threads may starve */ SDL_Delay(10); } @@ -119,7 +114,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -187,7 +182,7 @@ int main(int argc, char *argv[]) SDL_AtomicSet(&doterminate, 0); mutex = SDL_CreateMutex(); - if (mutex == NULL) { + if (!mutex) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create mutex: %s\n", SDL_GetError()); exit(1); } diff --git a/test/testmessage.c b/test/testmessage.c index c490787d..056a1d5e 100644 --- a/test/testmessage.c +++ b/test/testmessage.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testmouse.c b/test/testmouse.c index 5f5ef10d..c92e162e 100644 --- a/test/testmouse.c +++ b/test/testmouse.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -87,7 +87,7 @@ static void DrawObject(SDL_Renderer *renderer, Object *object) static void DrawObjects(SDL_Renderer *renderer) { Object *next = objects; - while (next != NULL) { + while (next) { DrawObject(renderer, next); next = next->next; } @@ -97,7 +97,7 @@ static void AppendObject(Object *object) { if (objects) { Object *next = objects; - while (next->next != NULL) { + while (next->next) { next = next->next; } next->next = object; @@ -125,15 +125,15 @@ static void loop(void *arg) /* "positive to the right and negative to the left" */ wheel_x += event.wheel.x * 10.0f; } - if (event.wheel.x != 0.0f) { + if (event.wheel.y != 0.0f) { wheel_y_active = SDL_TRUE; /* "positive away from the user and negative towards the user" */ - wheel_y -= event.wheel.x * 10.0f; + wheel_y -= event.wheel.y * 10.0f; } break; case SDL_EVENT_MOUSE_MOTION: - if (active == NULL) { + if (!active) { break; } @@ -142,7 +142,7 @@ static void loop(void *arg) break; case SDL_EVENT_MOUSE_BUTTON_DOWN: - if (active == NULL) { + if (!active) { active = SDL_calloc(1, sizeof(*active)); active->x1 = active->x2 = event.button.x; active->y1 = active->y2 = event.button.y; @@ -176,7 +176,7 @@ static void loop(void *arg) break; case SDL_EVENT_MOUSE_BUTTON_UP: - if (active == NULL) { + if (!active) { break; } @@ -259,7 +259,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testmultiaudio.c b/test/testmultiaudio.c index ecf0316c..404dc330 100644 --- a/test/testmultiaudio.c +++ b/test/testmultiaudio.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -146,7 +146,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -184,7 +184,7 @@ int main(int argc, char **argv) filename = GetResourceFilename(filename, "sample.wav"); devices = SDL_GetAudioOutputDevices(&devcount); - if (devices == NULL) { + if (!devices) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Don't see any specific audio devices!"); } else { /* Load the wave file into memory */ diff --git a/test/testnative.c b/test/testnative.c index 166e5d8c..4ac30de6 100644 --- a/test/testnative.c +++ b/test/testnative.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -30,6 +30,9 @@ static NativeWindowFactory *factories[] = { #ifdef TEST_NATIVE_WINDOWS &WindowsWindowFactory, #endif +#ifdef TEST_NATIVE_WAYLAND + &WaylandWindowFactory, +#endif #ifdef TEST_NATIVE_X11 &X11WindowFactory, #endif @@ -47,10 +50,10 @@ static SDLTest_CommonState *state; static void quit(int rc) { - SDL_Quit(); - if (native_window != NULL && factory != NULL) { + if (native_window && factory) { factory->DestroyNativeWindow(native_window); } + SDL_Quit(); SDLTest_CommonDestroyState(state); /* Let 'main()' return normally */ if (rc != 0) { @@ -100,6 +103,7 @@ int main(int argc, char *argv[]) { int i, done; const char *driver; + SDL_PropertiesID props; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *sprite; @@ -109,7 +113,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -135,19 +139,25 @@ int main(int argc, char *argv[]) break; } } - if (factory == NULL) { + if (!factory) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find native window code for %s driver\n", driver); quit(2); } SDL_Log("Creating native window for %s driver\n", driver); native_window = factory->CreateNativeWindow(WINDOW_W, WINDOW_H); - if (native_window == NULL) { + if (!native_window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create native window\n"); quit(3); } - window = SDL_CreateWindowFrom(native_window); - if (window == NULL) { + props = SDL_CreateProperties(); + SDL_SetProperty(props, "sdl2-compat.external_window", native_window); + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_TRUE); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, WINDOW_W); + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, WINDOW_H); + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create SDL window: %s\n", SDL_GetError()); quit(4); } @@ -155,7 +165,7 @@ int main(int argc, char *argv[]) /* Create the renderer */ renderer = SDL_CreateRenderer(window, NULL, 0); - if (renderer == NULL) { + if (!renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError()); quit(5); } @@ -165,7 +175,7 @@ int main(int argc, char *argv[]) SDL_RenderClear(renderer); sprite = LoadTexture(renderer, "icon.bmp", SDL_TRUE, NULL, NULL); - if (sprite == NULL) { + if (!sprite) { quit(6); } @@ -174,7 +184,7 @@ int main(int argc, char *argv[]) SDL_QueryTexture(sprite, NULL, NULL, &sprite_w, &sprite_h); positions = (SDL_FRect *)SDL_malloc(NUM_SPRITES * sizeof(*positions)); velocities = (SDL_FRect *)SDL_malloc(NUM_SPRITES * sizeof(*velocities)); - if (positions == NULL || velocities == NULL) { + if (!positions || !velocities) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); quit(2); } diff --git a/test/testnative.h b/test/testnative.h index 41ed6ef5..7a1c934d 100644 --- a/test/testnative.h +++ b/test/testnative.h @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -36,6 +36,11 @@ typedef struct extern NativeWindowFactory WindowsWindowFactory; #endif +#ifdef SDL_VIDEO_DRIVER_WAYLAND +#define TEST_NATIVE_WAYLAND +extern NativeWindowFactory WaylandWindowFactory; +#endif + #ifdef SDL_VIDEO_DRIVER_X11 #define TEST_NATIVE_X11 extern NativeWindowFactory X11WindowFactory; diff --git a/test/testnativew32.c b/test/testnativew32.c index 13603b66..94b04fcb 100644 --- a/test/testnativew32.c +++ b/test/testnativew32.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -68,7 +68,7 @@ CreateWindowNative(int w, int h) CreateWindow("SDL Test", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, w, h, NULL, NULL, GetModuleHandle(NULL), NULL); - if (hwnd == NULL) { + if (!hwnd) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; diff --git a/test/testnativewayland.c b/test/testnativewayland.c new file mode 100644 index 00000000..f5025ca4 --- /dev/null +++ b/test/testnativewayland.c @@ -0,0 +1,226 @@ +/* + 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 + 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 "testnative.h" + +#ifdef TEST_NATIVE_WAYLAND + +#include +#include +#include + +static void *native_userdata_ptr = (void *)0xBAADF00D; +static const char *native_surface_tag = "SDL_NativeSurfaceTag"; + +static void *CreateWindowWayland(int w, int h); +static void DestroyWindowWayland(void *window); + +NativeWindowFactory WaylandWindowFactory = { + "wayland", + CreateWindowWayland, + DestroyWindowWayland +}; + +/* Encapsulated in a struct to silence shadow variable warnings */ +static struct _state +{ + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_compositor *wl_compositor; + struct xdg_wm_base *xdg_wm_base; + struct wl_surface *wl_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; +} state; + +static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + xdg_surface_ack_configure(state.xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) +{ + /* NOP */ +} + +static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + SDL_Event event; + SDL_zero(event); + + event.type = SDL_EVENT_QUIT; + SDL_PushEvent(&event); +} + +static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + /* NOP */ +} + +static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) +{ + /* NOP */ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, + .configure_bounds = xdg_toplevel_configure_bounds, + .wm_capabilities = xdg_toplevel_wm_capabilities +}; + +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + xdg_wm_base_pong(state.xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +static void registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) +{ + if (SDL_strcmp(interface, wl_compositor_interface.name) == 0) { + state.wl_compositor = wl_registry_bind(state.wl_registry, name, &wl_compositor_interface, SDL_min(version, 4)); + } else if (SDL_strcmp(interface, xdg_wm_base_interface.name) == 0) { + state.xdg_wm_base = wl_registry_bind(state.wl_registry, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(state.xdg_wm_base, &xdg_wm_base_listener, NULL); + } +} + +static void registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) +{ + /* NOP */ +} + +static const struct wl_registry_listener wl_registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +static void *CreateWindowWayland(int w, int h) +{ + /* Export the display object from SDL and use it to create a registry object, + * which will enumerate the wl_compositor and xdg_wm_base protocols. + */ + state.wl_display = SDL_GetProperty(SDL_GetGlobalProperties(), SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL); + + if (!state.wl_display) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid 'wl_display' object!"); + goto error; + } + + state.wl_registry = wl_display_get_registry(state.wl_display); + wl_registry_add_listener(state.wl_registry, &wl_registry_listener, NULL); + + /* Roundtrip to enumerate registry objects. */ + wl_display_roundtrip(state.wl_display); + + /* Protocol sanity check */ + if (!state.wl_compositor) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "'wl_compositor' protocol not found!"); + goto error; + } + if (!state.xdg_wm_base) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "'xdg_wm_base' protocol not found!"); + goto error; + } + + /* Crate the backing wl_surface for the window. */ + state.wl_surface = wl_compositor_create_surface(state.wl_compositor); + + /* Set the native tag and userdata values, which should be the same at exit. */ + wl_proxy_set_tag((struct wl_proxy *)state.wl_surface, &native_surface_tag); + wl_surface_set_user_data(state.wl_surface, native_userdata_ptr); + + /* Create the xdg_surface from the wl_surface. */ + state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.wl_surface); + xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, NULL); + + /* Create the xdg_toplevel from the xdg_surface. */ + state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface); + xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title(state.xdg_toplevel, "Native Wayland Window"); + + /* Return the wl_surface to be wrapped in an SDL_Window. */ + return state.wl_surface; + +error: + if (state.xdg_toplevel) { + xdg_toplevel_destroy(state.xdg_toplevel); + state.xdg_toplevel = NULL; + } + if (state.xdg_surface) { + xdg_surface_destroy(state.xdg_surface); + state.xdg_surface = NULL; + } + if (state.wl_surface) { + wl_surface_destroy(state.wl_surface); + state.wl_surface = NULL; + } + if (state.xdg_wm_base) { + xdg_wm_base_destroy(state.xdg_wm_base); + state.xdg_wm_base = NULL; + } + if (state.wl_compositor) { + wl_compositor_destroy(state.wl_compositor); + state.wl_compositor = NULL; + } + if (state.wl_registry) { + wl_registry_destroy(state.wl_registry); + state.wl_registry = NULL; + } + + return NULL; +} + +static void DestroyWindowWayland(void *window) +{ + if (state.xdg_toplevel) { + xdg_toplevel_destroy(state.xdg_toplevel); + state.xdg_toplevel = NULL; + } + if (state.xdg_surface) { + xdg_surface_destroy(state.xdg_surface); + state.xdg_surface = NULL; + } + if (state.wl_surface) { + /* Surface sanity check; these should be unmodified. */ + if (wl_proxy_get_tag((struct wl_proxy *)state.wl_surface) != &native_surface_tag) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "The wl_surface tag was modified, this indicates a problem inside of SDL."); + } + if (wl_surface_get_user_data(state.wl_surface) != native_userdata_ptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "The wl_surface user data was modified, this indicates a problem inside of SDL."); + } + + wl_surface_destroy(state.wl_surface); + state.wl_surface = NULL; + } + if (state.xdg_wm_base) { + xdg_wm_base_destroy(state.xdg_wm_base); + state.xdg_wm_base = NULL; + } + if (state.wl_compositor) { + wl_compositor_destroy(state.wl_compositor); + state.wl_compositor = NULL; + } + if (state.wl_registry) { + wl_registry_destroy(state.wl_registry); + state.wl_registry = NULL; + } +} + +#endif diff --git a/test/testnativex11.c b/test/testnativex11.c index b33a60bf..e10a6fbd 100644 --- a/test/testnativex11.c +++ b/test/testnativex11.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/testoffscreen.c b/test/testoffscreen.c index 79852031..74df2839 100644 --- a/test/testoffscreen.c +++ b/test/testoffscreen.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -125,14 +125,14 @@ int main(int argc, char *argv[]) /* If OPENGL fails to init it will fallback to using a framebuffer for rendering */ window = SDL_CreateWindow("Offscreen Test", width, height, 0); - if (window == NULL) { + if (!window) { SDL_Log("Couldn't create window: %s\n", SDL_GetError()); return SDL_FALSE; } renderer = SDL_CreateRenderer(window, NULL, 0); - if (renderer == NULL) { + if (!renderer) { SDL_Log("Couldn't create renderer: %s\n", SDL_GetError()); return SDL_FALSE; diff --git a/test/testoverlay.c b/test/testoverlay.c index abd97c40..54f148c6 100644 --- a/test/testoverlay.c +++ b/test/testoverlay.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -147,6 +147,7 @@ static Uint64 next_fps_check; static Uint32 frames; static const Uint32 fps_check_delay = 5000; +static Uint32 yuv_format = SDL_PIXELFORMAT_YV12; static SDL_Surface *MooseYUVSurfaces[MOOSEFRAMES_COUNT]; static SDL_Texture *MooseTexture = NULL; static SDL_FRect displayrect; @@ -187,31 +188,58 @@ static void MoveSprites(SDL_Renderer *renderer) static int i = 0; if (streaming) { - if (!paused) { - i = (i + 1) % MOOSEFRAMES_COUNT; - SDL_UpdateTexture(MooseTexture, NULL, MooseYUVSurfaces[i]->pixels, MooseYUVSurfaces[i]->pitch); - } - SDL_RenderClear(renderer); - SDL_RenderTexture(renderer, MooseTexture, NULL, &displayrect); - SDL_RenderPresent(renderer); + if (!paused) { + i = (i + 1) % MOOSEFRAMES_COUNT; + /* Test both upload paths for NV12/NV21 formats */ + if ((yuv_format == SDL_PIXELFORMAT_NV12 || yuv_format == SDL_PIXELFORMAT_NV21) && + (i % 2) == 0) { +#ifdef TEST_RECT_UPDATE + SDL_Rect rect; + + if (i == 0) { + rect.x = 0; + rect.y = 0; + rect.w = MOOSEPIC_W; + rect.h = MOOSEPIC_H; + } else { + rect.x = MOOSEPIC_W / 4; + rect.y = MOOSEPIC_H / 4; + rect.w = MOOSEPIC_W / 2; + rect.h = MOOSEPIC_H / 2; + } + SDL_UpdateNVTexture(MooseTexture, &rect, + (Uint8 *)MooseYUVSurfaces[i]->pixels + rect.y * MooseYUVSurfaces[i]->pitch + rect.x, MooseYUVSurfaces[i]->pitch, + (Uint8 *)MooseYUVSurfaces[i]->pixels + MOOSEFRAME_SIZE + (rect.y + 1) / 2 * MooseYUVSurfaces[i]->pitch + (rect.x + 1) / 2, MooseYUVSurfaces[i]->pitch); +#else + SDL_UpdateNVTexture(MooseTexture, NULL, + MooseYUVSurfaces[i]->pixels, MooseYUVSurfaces[i]->pitch, + (Uint8 *)MooseYUVSurfaces[i]->pixels + MOOSEFRAME_SIZE, MooseYUVSurfaces[i]->pitch); +#endif + } else { + SDL_UpdateTexture(MooseTexture, NULL, MooseYUVSurfaces[i]->pixels, MooseYUVSurfaces[i]->pitch); + } + } + SDL_RenderClear(renderer); + SDL_RenderTexture(renderer, MooseTexture, NULL, &displayrect); + SDL_RenderPresent(renderer); } else { - SDL_Texture *tmp; + SDL_Texture *tmp; - /* Test SDL_CreateTextureFromSurface */ - if (!paused) { - i = (i + 1) % MOOSEFRAMES_COUNT; - } + /* Test SDL_CreateTextureFromSurface */ + if (!paused) { + i = (i + 1) % MOOSEFRAMES_COUNT; + } - tmp = SDL_CreateTextureFromSurface(renderer, MooseYUVSurfaces[i]); - if (tmp == NULL) { - SDL_Log("Error %s", SDL_GetError()); - quit(7); - } + tmp = SDL_CreateTextureFromSurface(renderer, MooseYUVSurfaces[i]); + if (!tmp) { + SDL_Log("Error %s", SDL_GetError()); + quit(7); + } - SDL_RenderClear(renderer); - SDL_RenderTexture(renderer, tmp, NULL, &displayrect); - SDL_RenderPresent(renderer); - SDL_DestroyTexture(tmp); + SDL_RenderClear(renderer); + SDL_RenderTexture(renderer, tmp, NULL, &displayrect); + SDL_RenderPresent(renderer); + SDL_DestroyTexture(tmp); } } @@ -295,11 +323,10 @@ int main(int argc, char **argv) int nodelay = 0; int scale = 5; char *filename = NULL; - int yuv_format = SDL_PIXELFORMAT_YV12; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -403,20 +430,20 @@ int main(int argc, char **argv) } RawMooseData = (Uint8 *)SDL_malloc(MOOSEFRAME_SIZE * MOOSEFRAMES_COUNT); - if (RawMooseData == NULL) { + if (!RawMooseData) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Can't allocate memory for movie !\n"); quit(1); } /* load the trojan moose images */ filename = GetResourceFilename(NULL, "moose.dat"); - if (filename == NULL) { + if (!filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory\n"); quit(2); } handle = SDL_RWFromFile(filename, "rb"); SDL_free(filename); - if (handle == NULL) { + if (!handle) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Can't find the file moose.dat !\n"); quit(2); } @@ -439,7 +466,7 @@ int main(int argc, char **argv) if (streaming) { MooseTexture = SDL_CreateTexture(renderer, yuv_format, SDL_TEXTUREACCESS_STREAMING, MOOSEPIC_W, MOOSEPIC_H); - if (MooseTexture == NULL) { + if (!MooseTexture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError()); quit(5); } @@ -452,7 +479,7 @@ int main(int argc, char **argv) for (i = 0; i < MOOSEFRAMES_COUNT; i++) { /* Create RGB SDL_Surface */ SDL_Surface *mooseRGBSurface = SDL_CreateSurface(MOOSEPIC_W, MOOSEPIC_H, SDL_PIXELFORMAT_RGB24); - if (mooseRGBSurface == NULL) { + if (!mooseRGBSurface) { quit(6); } diff --git a/test/testpen.c b/test/testpen.c new file mode 100644 index 00000000..c0db5605 --- /dev/null +++ b/test/testpen.c @@ -0,0 +1,530 @@ +/* + Simple DirectMedia Layer + 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 + 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 +#include +#include +#include +#include + +#define WIDTH 1600 +#define HEIGHT 1200 + +#define VERBOSE 0 + +#define ALWAYS_SHOW_PRESSURE_BOX 1 + +static SDLTest_CommonState *state; +static int quitting = 0; + +static float last_x, last_y; +static float last_xtilt, last_ytilt, last_pressure, last_distance, last_rotation; +static int last_button; +static int last_touching; /* tip touches surface */ +static int last_was_eraser; + +static SDL_Texture *offscreen_texture = NULL; + +static void DrawScreen(SDL_Renderer *renderer) +{ + float xdelta, ydelta, endx, endy; + /* off-screen texture to render into */ + SDL_Texture *window_texture; + const float X = 128.0f, Y = 128.0f; /* mid-point in the off-screen texture */ + SDL_FRect dest_rect; + float tilt_vec_x = SDL_sinf(last_xtilt * SDL_PI_F / 180.0f); + float tilt_vec_y = SDL_sinf(last_ytilt * SDL_PI_F / 180.0f); + int color = last_button + 1; + + if (!renderer) { + return; + } + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff); + SDL_RenderClear(renderer); + + if (offscreen_texture == NULL) { + offscreen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, (int)(X * 2.0f), (int)(Y * 2.0f)); + } + + /* Render into off-screen texture so we can do pixel-precise rendering later */ + window_texture = SDL_GetRenderTarget(renderer); + SDL_SetRenderTarget(renderer, offscreen_texture); + + /* Rendering starts here */ + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff); + SDL_RenderClear(renderer); + + SDL_SetRenderDrawColor(renderer, 0xa0, 0xa0, 0xa0, 0xff); + if (last_touching) { + SDL_FRect rect; + + rect.x = 0; + rect.y = 0; + rect.w = 2.0f * X - 1.0f; + rect.h = 2.0f * Y - 1.0f; + + SDL_RenderRect(renderer, &rect); + } else { + /* Show where the pen is rotating when it isn't touching the surface. + Otherwise we draw the rotation angle below together with pressure information. */ + float rot_vecx = SDL_sinf(last_rotation / 180.0f * SDL_PI_F); + float rot_vecy = -SDL_cosf(last_rotation / 180.0f * SDL_PI_F); + float px = X + rot_vecx * 100.0f; + float py = Y + rot_vecy * 100.0f; + float px2 = X + rot_vecx * 80.0f; + float py2 = Y + rot_vecy * 80.0f; + + SDL_RenderLine(renderer, + px, py, + px2 + rot_vecy * 20.0f, + py2 - rot_vecx * 20.0f); + SDL_RenderLine(renderer, + px, py, + px2 - rot_vecy * 20.0f, + py2 + rot_vecx * 20.0f); + } + + if (last_was_eraser) { + SDL_FRect rect; + + rect.x = X - 10.0f; + rect.y = Y - 10.0f; + rect.w = 21.0f; + rect.h = 21.0f; + + SDL_SetRenderDrawColor(renderer, 0x00, 0xff, 0xff, 0xff); + SDL_RenderFillRect(renderer, &rect); + } else { + float distance = last_distance * 50.0f; + + SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff); + SDL_RenderLine(renderer, + X - 10.0f - distance, Y, + X - distance, Y); + SDL_RenderLine(renderer, + X + 10.0f + distance, Y, + X + distance, Y); + SDL_RenderLine(renderer, + X, Y - 10.0f - distance, + X, Y - distance); + SDL_RenderLine(renderer, + X, Y + 10.0f + distance, + X, Y + distance); + + } + + /* Draw a cone based on the direction the pen is leaning as if it were shining a light. */ + /* Colour derived from pens, intensity based on pressure: */ + SDL_SetRenderDrawColor(renderer, + (color & 0x01) ? 0xff : 0, + (color & 0x02) ? 0xff : 0, + (color & 0x04) ? 0xff : 0, + (int)(0xff)); + + xdelta = -tilt_vec_x * 100.0f; + ydelta = -tilt_vec_y * 100.0f; + endx = X + xdelta; + endy = Y + ydelta; + SDL_RenderLine(renderer, X, Y, endx, endy); + + SDL_SetRenderDrawColor(renderer, + (color & 0x01) ? 0xff : 0, + (color & 0x02) ? 0xff : 0, + (color & 0x04) ? 0xff : 0, + (int)(0xff * last_pressure)); + /* Cone base width based on pressure: */ + SDL_RenderLine(renderer, X, Y, endx + (ydelta * last_pressure / 3.0f), endy - (xdelta * last_pressure / 3.0f)); + SDL_RenderLine(renderer, X, Y, endx - (ydelta * last_pressure / 3.0f), endy + (xdelta * last_pressure / 3.0f)); + + /* If tilt is very small (or zero, for pens that don't have tilt), add some extra lines, rotated by the current rotation value */ + if (ALWAYS_SHOW_PRESSURE_BOX || (fabs(tilt_vec_x) < 0.2f && fabs(tilt_vec_y) < 0.2f)) { + int rot; + float pressure = last_pressure * 80.0f; + + /* Four times, rotated 90 degrees, so that we get a box */ + for (rot = 0; rot < 4; ++rot) { + + float vecx = SDL_cosf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F); + float vecy = SDL_sinf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F); + + float px = X + vecx * pressure; + float py = Y + vecy * pressure; + + SDL_RenderLine(renderer, + px + vecy * 10.0f, py - vecx * 10.0f, + px - vecy * 10.0f, py + vecx * 10.0f); + + if (rot == 3) { + int r = 0; + for (; r >= 0; r -= 2) { + float delta = 10.0f - ((float) r); + + SDL_RenderLine(renderer, + px + vecy * delta, py - vecx * delta, + px + (vecx * pressure * 0.4f), + py + (vecy * pressure * 0.4f)); + SDL_RenderLine(renderer, + px - vecy * delta, py + vecx * delta, + px + (vecx * pressure * 0.4f), + py + (vecy * pressure * 0.4f)); + } + } + } + } + + SDL_SetRenderTarget(renderer, window_texture); + /* Now render to pixel-precise position */ + dest_rect.x = last_x - X; + dest_rect.y = last_y - Y; + dest_rect.w = X * 2.0f; + dest_rect.h = Y * 2.0f; + SDL_RenderTexture(renderer, offscreen_texture, NULL, &dest_rect); + SDL_RenderPresent(renderer); +} + +static void dump_state(void) +{ + int i; + int pens_nr; + + /* Make sure this also works with a NULL parameter */ + SDL_PenID* pens = SDL_GetPens(NULL); + if (pens) { + SDL_free(pens); + } + + pens = SDL_GetPens(&pens_nr); + if (!pens) { + SDL_Log("Couldn't get pens: %s\n", SDL_GetError()); + return; + } + SDL_Log("Found %d pens (terminated by %u)\n", pens_nr, (unsigned) pens[pens_nr]); + + for (i = 0; i < pens_nr; ++i) { + SDL_PenID penid = pens[i]; + SDL_GUID guid = SDL_GetPenGUID(penid); + char guid_str[33]; + float axes[SDL_PEN_NUM_AXES]; + float x, y; + int k; + SDL_PenCapabilityInfo info; + Uint32 status = SDL_GetPenStatus(penid, &x, &y, axes, SDL_PEN_NUM_AXES); + Uint32 capabilities = SDL_GetPenCapabilities(penid, &info); + char *type; + char *buttons_str; + + SDL_GUIDToString(guid, guid_str, 33); + + switch (SDL_GetPenType(penid)) { + case SDL_PEN_TYPE_ERASER: + type = "Eraser"; + break; + case SDL_PEN_TYPE_PEN: + type = "Pen"; + break; + case SDL_PEN_TYPE_PENCIL: + type = "Pencil"; + break; + case SDL_PEN_TYPE_BRUSH: + type = "Brush"; + break; + case SDL_PEN_TYPE_AIRBRUSH: + type = "Airbrush"; + break; + default: + type = "Unknown (bug?)"; + } + + switch (info.num_buttons) { + case SDL_PEN_INFO_UNKNOWN: + SDL_asprintf(&buttons_str, "? buttons"); + break; + case 1: + SDL_asprintf(&buttons_str, "1 button"); + break; + default: + SDL_asprintf(&buttons_str, "%d button", info.num_buttons); + break; + } + + SDL_Log("%s %lu: [%s] attached=%d, %s [cap= %08lx:%08lx =status] '%s'\n", + type, + (unsigned long) penid, guid_str, + SDL_PenConnected(penid), /* should always be SDL_TRUE during iteration */ + buttons_str, + (unsigned long) capabilities, + (unsigned long) status, + SDL_GetPenName(penid)); + SDL_free(buttons_str); + SDL_Log(" pos=(%.2f, %.2f)", x, y); + for (k = 0; k < SDL_PEN_NUM_AXES; ++k) { + SDL_bool supported = capabilities & SDL_PEN_AXIS_CAPABILITY(k); + if (supported) { + if (k == SDL_PEN_AXIS_XTILT || k == SDL_PEN_AXIS_YTILT) { + if (info.max_tilt == SDL_PEN_INFO_UNKNOWN) { + SDL_Log(" axis %d: %.3f (max tilt unknown)", k, axes[k]); + } else { + SDL_Log(" axis %d: %.3f (tilt -%.1f..%.1f)", k, axes[k], + info.max_tilt, info.max_tilt); + } + } else { + SDL_Log(" axis %d: %.3f", k, axes[k]); + } + } else { + SDL_Log(" axis %d: unsupported (%.3f)", k, axes[k]); + } + } + } + SDL_free(pens); +} + +static void update_axes(float *axes) +{ + last_xtilt = axes[SDL_PEN_AXIS_XTILT]; + last_ytilt = axes[SDL_PEN_AXIS_YTILT]; + last_pressure = axes[SDL_PEN_AXIS_PRESSURE]; + last_distance = axes[SDL_PEN_AXIS_DISTANCE]; + last_rotation = axes[SDL_PEN_AXIS_ROTATION]; +} + +static void update_axes_from_touch(const float pressure) +{ + last_xtilt = 0; + last_ytilt = 0; + last_pressure = pressure; + last_distance = 0; + last_rotation = 0; +} + +static void process_event(SDL_Event event) +{ + SDLTest_CommonEvent(state, &event, &quitting); + + switch (event.type) { + case SDL_EVENT_KEY_DOWN: + { + dump_state(); + break; + } + case SDL_EVENT_MOUSE_MOTION: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: +#if VERBOSE + { + float x, y; + SDL_GetMouseState(&x, &y); + if (event.type == SDL_EVENT_MOUSE_MOTION) { + SDL_Log("[%lu] mouse motion: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f) delta (%.2f, %.2f)\n", + event.motion.timestamp, + event.motion.which, + event.motion.x, event.motion.y, + event.motion.xrel, event.motion.yrel, + x, y); + } else { + SDL_Log("[%lu] mouse button: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f)\n", + event.button.timestamp, + event.button.which, + event.button.x, event.button.y, + x, y); + } + } +#endif + if (event.motion.which != SDL_PEN_MOUSEID && event.motion.which != SDL_TOUCH_MOUSEID) { + SDL_ShowCursor(); + } break; + + case SDL_EVENT_PEN_MOTION: + { + SDL_PenMotionEvent *ev = &event.pmotion; + + SDL_HideCursor(); + last_x = ev->x; + last_y = ev->y; + update_axes(ev->axes); + last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK; +#if VERBOSE + SDL_Log("[%lu] pen motion: %s %u at (%.4f, %.4f); pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f [buttons=%02x]\n", + (unsigned long) ev->timestamp, + last_was_eraser ? "eraser" : "pen", + (unsigned int)ev->which, ev->x, ev->y, last_pressure, last_xtilt, last_ytilt, last_distance, + ev->pen_state); +#endif + } break; + + case SDL_EVENT_PEN_UP: + case SDL_EVENT_PEN_DOWN: { + SDL_PenTipEvent *ev = &event.ptip; + last_x = ev->x; + last_y = ev->y; + update_axes(ev->axes); + last_was_eraser = ev->tip == SDL_PEN_TIP_ERASER; + last_button = ev->pen_state & 0xf; /* button mask */ + last_touching = (event.type == SDL_EVENT_PEN_DOWN); + } break; + + case SDL_EVENT_PEN_BUTTON_UP: + case SDL_EVENT_PEN_BUTTON_DOWN: + { + SDL_PenButtonEvent *ev = &event.pbutton; + + SDL_HideCursor(); + last_x = ev->x; + last_y = ev->y; + update_axes(ev->axes); + if (last_pressure > 0.0f && !last_touching) { + SDL_LogWarn(SDL_LOG_CATEGORY_TEST, + "[%lu] : reported pressure %.5f even though pen is not touching surface", + (unsigned long) ev->timestamp, last_pressure); + + } + last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK; + last_button = ev->pen_state & 0xf; /* button mask */ + if ((ev->pen_state & SDL_PEN_DOWN_MASK) && !last_touching) { + SDL_LogWarn(SDL_LOG_CATEGORY_TEST, + "[%lu] : reported flags %x (SDL_PEN_FLAG_DOWN_MASK) despite not receiving SDL_EVENT_PEN_DOWN", + (unsigned long) ev->timestamp, ev->pen_state); + + } + if (!(ev->pen_state & SDL_PEN_DOWN_MASK) && last_touching) { + SDL_LogWarn(SDL_LOG_CATEGORY_TEST, + "[%lu] : reported flags %x (no SDL_PEN_FLAG_DOWN_MASK) despite receiving SDL_EVENT_PEN_DOWN without SDL_EVENT_PEN_UP afterwards", + (unsigned long) ev->timestamp, ev->pen_state); + + } +#if VERBOSE + SDL_Log("[%lu] pen button: %s %u at (%.4f, %.4f); BUTTON %d reported %s with event %s [pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f]\n", + (unsigned long) ev->timestamp, + last_was_eraser ? "eraser" : "pen", + (unsigned int)ev->which, ev->x, ev->y, + ev->button, + (ev->state == SDL_PRESSED) ? "PRESSED" + : ((ev->state == SDL_RELEASED) ? "RELEASED" : "--invalid--"), + event.type == SDL_EVENT_PEN_BUTTON_UP ? "PENBUTTONUP" : "PENBUTTONDOWN", + last_pressure, last_xtilt, last_ytilt, last_distance); +#endif + } break; + + case SDL_EVENT_WINDOW_PEN_ENTER: + SDL_Log("[%lu] Pen %lu entered window %lx", + (unsigned long) event.window.timestamp, + (unsigned long) event.window.data1, + (unsigned long) event.window.windowID); + break; + + case SDL_EVENT_WINDOW_PEN_LEAVE: + SDL_Log("[%lu] Pen %lu left window %lx", + (unsigned long) event.window.timestamp, + (unsigned long) event.window.data1, + (unsigned long) event.window.windowID); + break; + +#if VERBOSE + case SDL_EVENT_WINDOW_MOUSE_ENTER: + SDL_Log("[%lu] Mouse entered window %lx", + (unsigned long) event.window.timestamp, + (unsigned long) event.window.windowID); + break; + + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + SDL_Log("[%lu] Mouse left window %lx", + (unsigned long) event.window.timestamp, + (unsigned long) event.window.windowID); + break; +#endif + + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_UP: + { + SDL_TouchFingerEvent *ev = &event.tfinger; + int w, h; + SDL_HideCursor(); + SDL_GetWindowSize(SDL_GetWindowFromID(ev->windowID), &w, &h); + last_x = ev->x * w; + last_y = ev->y * h; + update_axes_from_touch(ev->pressure); + last_was_eraser = SDL_FALSE; + last_button = 0; + last_touching = (ev->type != SDL_EVENT_FINGER_UP); +#if VERBOSE + SDL_Log("[%lu] finger %s: %s (touchId: %" SDL_PRIs64 ", fingerId: %" SDL_PRIs64 ") at (%.4f, %.4f); pressure=%.3f\n", + (unsigned long) ev->timestamp, + ev->type == SDL_EVENT_FINGER_DOWN ? "down" : (ev->type == SDL_EVENT_FINGER_MOTION ? "motion" : "up"), + SDL_GetTouchDeviceName(ev->touchId), + ev->touchId, + ev->fingerId, + last_x, last_y, last_pressure); +#endif + } break; + + default: + break; + } +} + +static void loop(void) +{ + SDL_Event event; + int i; + + for (i = 0; i < state->num_windows; ++i) { + if (state->renderers[i]) { + DrawScreen(state->renderers[i]); + } + } + + if (SDL_WaitEventTimeout(&event, 10)) { + process_event(event); + } + while (SDL_PollEvent(&event)) { + process_event(event); + } +} + +int main(int argc, char *argv[]) +{ + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); + if (!state) { + return 1; + } + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); + + state->window_title = "Pressure-Sensitive Pen Test"; + state->window_w = WIDTH; + state->window_h = HEIGHT; + state->skip_renderer = SDL_FALSE; + + if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) { + SDLTest_CommonQuit(state); + return 1; + } + + while (!quitting) { + loop(); + } + + SDLTest_CommonQuit(state); + return 0; +} diff --git a/test/testplatform.c b/test/testplatform.c index d310fca2..a3cec434 100644 --- a/test/testplatform.c +++ b/test/testplatform.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -365,7 +365,7 @@ static int Test64Bit(SDL_bool verbose) LL_Test *t; int failed = 0; - for (t = LL_Tests; t->routine != NULL; t++) { + for (t = LL_Tests; t->routine; t++) { unsigned long long result = 0; unsigned int *al = (unsigned int *)&t->a; unsigned int *bl = (unsigned int *)&t->b; @@ -447,7 +447,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testpopup.c b/test/testpopup.c index a8ecabe0..32595e3a 100644 --- a/test/testpopup.c +++ b/test/testpopup.c @@ -1,5 +1,5 @@ /* -Copyright (C) 1997-2023 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 @@ -188,9 +188,13 @@ static void loop(void) SDLTest_CommonEvent(state, &event, &done); } + if (done) { + return; + } + /* Show the tooltip if the delay period has elapsed */ if (SDL_GetTicks() > tooltip_timer) { - if (tooltip.win == NULL) { + if (!tooltip.win) { create_popup(&tooltip, SDL_FALSE); } } @@ -240,7 +244,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testpower.c b/test/testpower.c index f9baefb8..715c6aef 100644 --- a/test/testpower.c +++ b/test/testpower.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testqsort.c b/test/testqsort.c index b4a17c2d..f410a616 100644 --- a/test/testqsort.c +++ b/test/testqsort.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testrelative.c b/test/testrelative.c index c02518e8..0854aee9 100644 --- a/test/testrelative.c +++ b/test/testrelative.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testrendercopyex.c b/test/testrendercopyex.c index ffb52295..f349247a 100644 --- a/test/testrendercopyex.c +++ b/test/testrendercopyex.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -120,7 +120,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testrendertarget.c b/test/testrendertarget.c index abaf09c6..e68127a7 100644 --- a/test/testrendertarget.c +++ b/test/testrendertarget.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -141,7 +141,7 @@ Draw(DrawState *s) SDL_GetRenderViewport(s->renderer, &viewport); target = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, viewport.w, viewport.h); - if (target == NULL) { + if (!target) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create render target texture: %s\n", SDL_GetError()); return SDL_FALSE; } @@ -217,7 +217,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } for (i = 1; i < argc;) { diff --git a/test/testresample.c b/test/testresample.c index 83661d34..760d66ff 100644 --- a/test/testresample.c +++ b/test/testresample.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -41,7 +41,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -116,7 +116,7 @@ int main(int argc, char **argv) /* write out a WAV header... */ io = SDL_RWFromFile(file_out, "wb"); - if (io == NULL) { + if (!io) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "fopen('%s') failed: %s\n", file_out, SDL_GetError()); ret = 5; goto end; diff --git a/test/testrumble.c b/test/testrumble.c index a3c6dee3..619f1fb0 100644 --- a/test/testrumble.c +++ b/test/testrumble.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -29,7 +29,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND static SDL_Haptic *haptic; /** - * \brief The entry point of this force feedback demo. + * The entry point of this force feedback demo. * \param[in] argc Number of arguments. * \param[in] argv Array of argc arguments. */ @@ -42,7 +42,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -88,7 +88,7 @@ int main(int argc, char **argv) SDL_Log("%d Haptic devices detected.\n", SDL_NumHaptics()); if (SDL_NumHaptics() > 0) { /* We'll just use index or the first force feedback device found */ - if (name == NULL) { + if (!name) { i = (index != -1) ? index : 0; } /* Try to find matching device */ @@ -107,7 +107,7 @@ int main(int argc, char **argv) } haptic = SDL_HapticOpen(i); - if (haptic == NULL) { + if (!haptic) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create the haptic device: %s\n", SDL_GetError()); return 1; @@ -146,7 +146,7 @@ int main(int argc, char **argv) SDL_Delay(2000); /* Quit */ - if (haptic != NULL) { + if (haptic) { SDL_HapticClose(haptic); } diff --git a/test/testrwlock.c b/test/testrwlock.c index cada79e5..c3c8ebb0 100644 --- a/test/testrwlock.c +++ b/test/testrwlock.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -33,22 +33,21 @@ static void DoWork(const int workticks) /* "Work" */ const SDL_threadID tid = SDL_ThreadID(); const SDL_bool is_reader = tid != mainthread; const char *typestr = is_reader ? "Reader" : "Writer"; - int rc; SDL_Log("%s Thread %lu: ready to work\n", typestr, (unsigned long) tid); - rc = is_reader ? SDL_LockRWLockForReading(rwlock) : SDL_LockRWLockForWriting(rwlock); - if (rc < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't lock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError()); + if (is_reader) { + SDL_LockRWLockForReading(rwlock); } else { - SDL_Log("%s Thread %lu: start work!\n", typestr, (unsigned long) tid); - SDL_Delay(workticks); - SDL_Log("%s Thread %lu: work done!\n", typestr, (unsigned long) tid); - if (SDL_UnlockRWLock(rwlock) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't unlock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError()); - } - /* If this sleep isn't done, then threads may starve */ - SDL_Delay(10); + SDL_LockRWLockForWriting(rwlock); } + + SDL_Log("%s Thread %lu: start work!\n", typestr, (unsigned long) tid); + SDL_Delay(workticks); + SDL_Log("%s Thread %lu: work done!\n", typestr, (unsigned long) tid); + SDL_UnlockRWLock(rwlock); + + /* If this sleep isn't done, then threads may starve */ + SDL_Delay(10); } static int SDLCALL @@ -68,7 +67,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -142,7 +141,7 @@ int main(int argc, char *argv[]) SDL_AtomicSet(&doterminate, 0); rwlock = SDL_CreateRWLock(); - if (rwlock == NULL) { + if (!rwlock) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create rwlock: %s\n", SDL_GetError()); SDL_Quit(); SDLTest_CommonDestroyState(state); diff --git a/test/testscale.c b/test/testscale.c index 16acb3f8..a69c9c1d 100644 --- a/test/testscale.c +++ b/test/testscale.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testsem.c b/test/testsem.c index 8da58613..8d595d39 100644 --- a/test/testsem.c +++ b/test/testsem.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -261,7 +261,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testsensor.c b/test/testsensor.c index be200fdd..0ab36b59 100644 --- a/test/testsensor.c +++ b/test/testsensor.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -38,7 +38,7 @@ static const char *GetSensorTypeString(SDL_SensorType type) static void HandleSensorEvent(SDL_SensorEvent *event) { SDL_Sensor *sensor = SDL_GetSensorFromInstanceID(event->which); - if (sensor == NULL) { + if (!sensor) { SDL_Log("Couldn't get sensor for sensor event\n"); return; } @@ -64,7 +64,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -97,7 +97,7 @@ int main(int argc, char **argv) if (SDL_GetSensorInstanceType(sensors[i]) != SDL_SENSOR_UNKNOWN) { SDL_Sensor *sensor = SDL_OpenSensor(sensors[i]); - if (sensor == NULL) { + if (!sensor) { SDL_Log("Couldn't open sensor %" SDL_PRIu32 ": %s\n", sensors[i], SDL_GetError()); } else { ++num_opened; diff --git a/test/testshader.c b/test/testshader.c index 7d896f28..6b50bca6 100644 --- a/test/testshader.c +++ b/test/testshader.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -115,36 +115,36 @@ static ShaderData shaders[NUM_SHADERS] = { "}" }, }; -static PFNGLATTACHOBJECTARBPROC glAttachObjectARB; -static PFNGLCOMPILESHADERARBPROC glCompileShaderARB; -static PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB; -static PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB; -static PFNGLDELETEOBJECTARBPROC glDeleteObjectARB; -static PFNGLGETINFOLOGARBPROC glGetInfoLogARB; -static PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB; -static PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB; -static PFNGLLINKPROGRAMARBPROC glLinkProgramARB; -static PFNGLSHADERSOURCEARBPROC glShaderSourceARB; -static PFNGLUNIFORM1IARBPROC glUniform1iARB; -static PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB; +static PFNGLATTACHOBJECTARBPROC pglAttachObjectARB; +static PFNGLCOMPILESHADERARBPROC pglCompileShaderARB; +static PFNGLCREATEPROGRAMOBJECTARBPROC pglCreateProgramObjectARB; +static PFNGLCREATESHADEROBJECTARBPROC pglCreateShaderObjectARB; +static PFNGLDELETEOBJECTARBPROC pglDeleteObjectARB; +static PFNGLGETINFOLOGARBPROC pglGetInfoLogARB; +static PFNGLGETOBJECTPARAMETERIVARBPROC pglGetObjectParameterivARB; +static PFNGLGETUNIFORMLOCATIONARBPROC pglGetUniformLocationARB; +static PFNGLLINKPROGRAMARBPROC pglLinkProgramARB; +static PFNGLSHADERSOURCEARBPROC pglShaderSourceARB; +static PFNGLUNIFORM1IARBPROC pglUniform1iARB; +static PFNGLUSEPROGRAMOBJECTARBPROC pglUseProgramObjectARB; static SDL_bool CompileShader(GLhandleARB shader, const char *source) { GLint status = 0; - glShaderSourceARB(shader, 1, &source, NULL); - glCompileShaderARB(shader); - glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status); + pglShaderSourceARB(shader, 1, &source, NULL); + pglCompileShaderARB(shader); + pglGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status); if (status == 0) { GLint length = 0; char *info; - glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); + pglGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); info = (char *)SDL_malloc((size_t)length + 1); - if (info == NULL) { + if (!info) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); } else { - glGetInfoLogARB(shader, length, NULL, info); + pglGetInfoLogARB(shader, length, NULL, info); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile shader:\n%s\n%s", source, info); SDL_free(info); } @@ -158,21 +158,21 @@ static SDL_bool LinkProgram(ShaderData *data) { GLint status = 0; - glAttachObjectARB(data->program, data->vert_shader); - glAttachObjectARB(data->program, data->frag_shader); - glLinkProgramARB(data->program); + pglAttachObjectARB(data->program, data->vert_shader); + pglAttachObjectARB(data->program, data->frag_shader); + pglLinkProgramARB(data->program); - glGetObjectParameterivARB(data->program, GL_OBJECT_LINK_STATUS_ARB, &status); + pglGetObjectParameterivARB(data->program, GL_OBJECT_LINK_STATUS_ARB, &status); if (status == 0) { GLint length = 0; char *info; - glGetObjectParameterivARB(data->program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); + pglGetObjectParameterivARB(data->program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); info = (char *)SDL_malloc((size_t)length + 1); - if (info == NULL) { + if (!info) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); } else { - glGetInfoLogARB(data->program, length, NULL, info); + pglGetInfoLogARB(data->program, length, NULL, info); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to link program:\n%s", info); SDL_free(info); } @@ -191,16 +191,16 @@ static SDL_bool CompileShaderProgram(ShaderData *data) glGetError(); /* Create one program object to rule them all */ - data->program = glCreateProgramObjectARB(); + data->program = pglCreateProgramObjectARB(); /* Create the vertex shader */ - data->vert_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); + data->vert_shader = pglCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); if (!CompileShader(data->vert_shader, data->vert_source)) { return SDL_FALSE; } /* Create the fragment shader */ - data->frag_shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + data->frag_shader = pglCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); if (!CompileShader(data->frag_shader, data->frag_source)) { return SDL_FALSE; } @@ -211,26 +211,26 @@ static SDL_bool CompileShaderProgram(ShaderData *data) } /* Set up some uniform variables */ - glUseProgramObjectARB(data->program); + pglUseProgramObjectARB(data->program); for (i = 0; i < num_tmus_bound; ++i) { char tex_name[5]; (void)SDL_snprintf(tex_name, SDL_arraysize(tex_name), "tex%d", i); - location = glGetUniformLocationARB(data->program, tex_name); + location = pglGetUniformLocationARB(data->program, tex_name); if (location >= 0) { - glUniform1iARB(location, i); + pglUniform1iARB(location, i); } } - glUseProgramObjectARB(0); + pglUseProgramObjectARB(0); - return (glGetError() == GL_NO_ERROR) ? SDL_TRUE : SDL_FALSE; + return (glGetError() == GL_NO_ERROR); } static void DestroyShaderProgram(ShaderData *data) { if (shaders_supported) { - glDeleteObjectARB(data->vert_shader); - glDeleteObjectARB(data->frag_shader); - glDeleteObjectARB(data->program); + pglDeleteObjectARB(data->vert_shader); + pglDeleteObjectARB(data->frag_shader); + pglDeleteObjectARB(data->program); } } @@ -244,30 +244,30 @@ static SDL_bool InitShaders(void) SDL_GL_ExtensionSupported("GL_ARB_shading_language_100") && SDL_GL_ExtensionSupported("GL_ARB_vertex_shader") && SDL_GL_ExtensionSupported("GL_ARB_fragment_shader")) { - glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)SDL_GL_GetProcAddress("glAttachObjectARB"); - glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)SDL_GL_GetProcAddress("glCompileShaderARB"); - glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)SDL_GL_GetProcAddress("glCreateProgramObjectARB"); - glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)SDL_GL_GetProcAddress("glCreateShaderObjectARB"); - glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)SDL_GL_GetProcAddress("glDeleteObjectARB"); - glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)SDL_GL_GetProcAddress("glGetInfoLogARB"); - glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)SDL_GL_GetProcAddress("glGetObjectParameterivARB"); - glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)SDL_GL_GetProcAddress("glGetUniformLocationARB"); - glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)SDL_GL_GetProcAddress("glLinkProgramARB"); - glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)SDL_GL_GetProcAddress("glShaderSourceARB"); - glUniform1iARB = (PFNGLUNIFORM1IARBPROC)SDL_GL_GetProcAddress("glUniform1iARB"); - glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)SDL_GL_GetProcAddress("glUseProgramObjectARB"); - if (glAttachObjectARB && - glCompileShaderARB && - glCreateProgramObjectARB && - glCreateShaderObjectARB && - glDeleteObjectARB && - glGetInfoLogARB && - glGetObjectParameterivARB && - glGetUniformLocationARB && - glLinkProgramARB && - glShaderSourceARB && - glUniform1iARB && - glUseProgramObjectARB) { + pglAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)SDL_GL_GetProcAddress("glAttachObjectARB"); + pglCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)SDL_GL_GetProcAddress("glCompileShaderARB"); + pglCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)SDL_GL_GetProcAddress("glCreateProgramObjectARB"); + pglCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)SDL_GL_GetProcAddress("glCreateShaderObjectARB"); + pglDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)SDL_GL_GetProcAddress("glDeleteObjectARB"); + pglGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)SDL_GL_GetProcAddress("glGetInfoLogARB"); + pglGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)SDL_GL_GetProcAddress("glGetObjectParameterivARB"); + pglGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)SDL_GL_GetProcAddress("glGetUniformLocationARB"); + pglLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)SDL_GL_GetProcAddress("glLinkProgramARB"); + pglShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)SDL_GL_GetProcAddress("glShaderSourceARB"); + pglUniform1iARB = (PFNGLUNIFORM1IARBPROC)SDL_GL_GetProcAddress("glUniform1iARB"); + pglUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)SDL_GL_GetProcAddress("glUseProgramObjectARB"); + if (pglAttachObjectARB && + pglCompileShaderARB && + pglCreateProgramObjectARB && + pglCreateShaderObjectARB && + pglDeleteObjectARB && + pglGetInfoLogARB && + pglGetObjectParameterivARB && + pglGetUniformLocationARB && + pglLinkProgramARB && + pglShaderSourceARB && + pglUniform1iARB && + pglUseProgramObjectARB) { shaders_supported = SDL_TRUE; } } @@ -327,7 +327,7 @@ SDL_GL_LoadTexture(SDL_Surface *surface, GLfloat *texcoord) texcoord[3] = (GLfloat)surface->h / h; /* Max Y */ image = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_RGBA32); - if (image == NULL) { + if (!image) { return 0; } @@ -418,7 +418,7 @@ static void DrawGLScene(SDL_Window *window, GLuint texture, GLfloat *texcoord) glBindTexture(GL_TEXTURE_2D, texture); glColor3f(1.0f, 1.0f, 1.0f); if (shaders_supported) { - glUseProgramObjectARB(shaders[current_shader].program); + pglUseProgramObjectARB(shaders[current_shader].program); } glBegin(GL_QUADS); /* start drawing a polygon (4 sided) */ @@ -433,7 +433,7 @@ static void DrawGLScene(SDL_Window *window, GLuint texture, GLfloat *texcoord) glEnd(); /* done with the polygon */ if (shaders_supported) { - glUseProgramObjectARB(0); + pglUseProgramObjectARB(0); } glDisable(GL_TEXTURE_2D); @@ -454,7 +454,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -489,7 +489,7 @@ int main(int argc, char **argv) /* Create a 640x480 OpenGL screen */ window = SDL_CreateWindow("Shader Demo", 640, 480, SDL_WINDOW_OPENGL); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL window: %s\n", SDL_GetError()); SDL_Quit(); exit(2); @@ -505,7 +505,7 @@ int main(int argc, char **argv) surface = SDL_LoadBMP(filename); SDL_free(filename); - if (surface == NULL) { + if (!surface) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load icon.bmp: %s\n", SDL_GetError()); SDL_Quit(); exit(3); diff --git a/test/testshape.c b/test/testshape.c index 07831979..302be4ea 100644 --- a/test/testshape.c +++ b/test/testshape.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -17,10 +17,42 @@ #define SHAPED_WINDOW_DIMENSION 640 +/** An enum denoting the specific type of contents present in an SDL_WindowShapeParams union. */ +typedef enum +{ + /** The default mode, a binarized alpha cutoff of 1. */ + ShapeModeDefault, + /** A binarized alpha cutoff with a given integer value. */ + ShapeModeBinarizeAlpha, + /** A binarized alpha cutoff with a given integer value, but with the opposite comparison. */ + ShapeModeReverseBinarizeAlpha, + /** A color key is applied. */ + ShapeModeColorKey +} WindowShapeMode; + +/** A union containing parameters for shaped windows. */ +typedef union +{ + /** A cutoff alpha value for binarization of the window shape's alpha channel. */ + Uint8 binarizationCutoff; + SDL_Color colorKey; +} SDL_WindowShapeParams; + +/** A struct that tags the SDL_WindowShapeParams union with an enum describing the type of its contents. */ +typedef struct SDL_WindowShapeMode +{ + /** The mode of these window-shape parameters. */ + WindowShapeMode mode; + /** Window-shape parameters. */ + SDL_WindowShapeParams parameters; +} SDL_WindowShapeMode; + typedef struct LoadedPicture { SDL_Surface *surface; - SDL_Texture *texture; + int num_textures; + SDL_Texture **textures; + SDL_Texture **shape_textures; SDL_WindowShapeMode mode; const char *name; struct LoadedPicture *next; @@ -29,7 +61,6 @@ typedef struct LoadedPicture static Uint8 *g_bitmap = NULL; static int g_bitmap_w = 0, g_bitmap_h = 0; static SDL_Surface *g_shape_surface = NULL; -static SDL_Texture *g_shape_texture = NULL; static void log_usage(SDLTest_CommonState *state, char *progname) { static const char *options[] = { "sample1.bmp [sample2.bmp [sample3.bmp ...]]", NULL }; @@ -42,8 +73,7 @@ static void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shap int x = 0; int y = 0; Uint8 r = 0, g = 0, b = 0, alpha = 0; - Uint8 *pixel = NULL; - Uint32 pixel_value = 0, mask_value = 0; + Uint32 mask_value = 0; size_t bytes_per_scanline = (size_t)(shape->w + (ppb - 1)) / ppb; Uint8 *bitmap_scanline; SDL_Color key; @@ -58,23 +88,10 @@ static void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shap bitmap_scanline = bitmap + y * bytes_per_scanline; for (x = 0; x < shape->w; x++) { alpha = 0; - pixel_value = 0; - pixel = (Uint8 *)(shape->pixels) + (y * shape->pitch) + (x * shape->format->BytesPerPixel); - switch (shape->format->BytesPerPixel) { - case (1): - pixel_value = *pixel; - break; - case (2): - pixel_value = *(Uint16 *)pixel; - break; - case (3): - pixel_value = *(Uint32 *)pixel & (~shape->format->Amask); - break; - case (4): - pixel_value = *(Uint32 *)pixel; - break; + if (SDLTest_ReadSurfacePixel(shape, x, y, &r, &g, &b, &alpha) != 0) { + continue; } - SDL_GetRGBA(pixel_value, shape->format, &r, &g, &b, &alpha); + switch (mode.mode) { case (ShapeModeDefault): mask_value = (alpha >= 1 ? 1 : 0); @@ -99,16 +116,23 @@ static void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shap } } -static int SDL3_SetWindowShape(SDL_Window *window, SDL_Surface *shape, SDL_WindowShapeMode *shape_mode) +static int SDL3_SetWindowShape(LoadedPicture *picture, SDL_WindowShapeMode *shape_mode) { if (g_bitmap) { SDL_free(g_bitmap); g_bitmap = NULL; } - if (g_shape_texture) { - SDL_DestroyTexture(g_shape_texture); - g_shape_texture = NULL; + if (!picture) { + return SDL_SetError("picture"); + } + + if (picture->shape_textures[0]) { + int i; + for (i = 0; i < picture->num_textures; i++) { + SDL_DestroyTexture(picture->shape_textures[i]); + picture->shape_textures[i] = NULL; + } } if (g_shape_surface) { @@ -116,22 +140,18 @@ static int SDL3_SetWindowShape(SDL_Window *window, SDL_Surface *shape, SDL_Windo g_shape_surface = NULL; } - if (shape == NULL) { - return SDL_SetError("shape"); - } - - if (shape_mode == NULL) { + if (!shape_mode) { return SDL_SetError("shape_mode"); } - g_bitmap_w = shape->w; - g_bitmap_h = shape->h; - g_bitmap = (Uint8*) SDL_malloc(shape->w * shape->h); - if (g_bitmap == NULL) { - return SDL_OutOfMemory(); + g_bitmap_w = picture->surface->w; + g_bitmap_h = picture->surface->h; + g_bitmap = (Uint8*) SDL_malloc(picture->surface->w * picture->surface->h); + if (!g_bitmap) { + return -1; } - SDL_CalculateShapeBitmap(*shape_mode, shape, g_bitmap, 1); + SDL_CalculateShapeBitmap(*shape_mode, picture->surface, g_bitmap, 1); g_shape_surface = SDL_CreateSurface(g_bitmap_w, g_bitmap_h, SDL_PIXELFORMAT_ABGR8888); if (g_shape_surface) { @@ -153,14 +173,14 @@ static int SDL3_SetWindowShape(SDL_Window *window, SDL_Surface *shape, SDL_Windo return 0; } -static void render(SDL_Renderer *renderer, SDL_Texture *texture) +static void render(int index, SDL_Renderer *renderer, LoadedPicture *picture) { /* Clear render-target to blue. */ SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xff, 0xff); SDL_RenderClear(renderer); /* Render the texture. */ - SDL_RenderTexture(renderer, texture, NULL, NULL); + SDL_RenderTexture(renderer, picture->textures[index], NULL, NULL); /* Apply the shape */ if (g_shape_surface) { @@ -184,19 +204,19 @@ static void render(SDL_Renderer *renderer, SDL_Texture *texture) SDL_SetRenderDrawColor(renderer, r, g, b, a); } } else { - if (g_shape_texture == NULL) { + if (!picture->shape_textures[index]) { SDL_BlendMode bm; - g_shape_texture = SDL_CreateTextureFromSurface(renderer, g_shape_surface); + picture->shape_textures[index] = SDL_CreateTextureFromSurface(renderer, g_shape_surface); /* if Alpha is 0, set all to 0, else leave unchanged. */ bm = SDL_ComposeCustomBlendMode( SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD); - SDL_SetTextureBlendMode(g_shape_texture, bm); + SDL_SetTextureBlendMode(picture->shape_textures[index], bm); } - SDL_RenderTexture(renderer, g_shape_texture, NULL, NULL); + SDL_RenderTexture(renderer, picture->shape_textures[index], NULL, NULL); } } @@ -210,13 +230,11 @@ int main(int argc, char **argv) LoadedPicture *picture_linked_list = NULL; LoadedPicture **pictures = NULL; int i; + int j; const SDL_DisplayMode *mode; SDL_PixelFormat *format = NULL; - SDL_Window *window = NULL; - SDL_Renderer *renderer = NULL; SDL_Color black = { 0, 0, 0, 0xff }; - SDL_Event event; - int should_exit = 0; + int done = 0; int current_picture; int button_down; Uint32 pixelFormat = 0; @@ -225,11 +243,14 @@ int main(int argc, char **argv) int rc; /* Initialize test framework */ - state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); + if (!state) { return 1; } + state->window_flags |= SDL_WINDOW_TRANSPARENT; + state->window_flags &= ~SDL_WINDOW_RESIZABLE; + rc = 0; /* SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); */ @@ -261,14 +282,16 @@ int main(int argc, char **argv) i += consumed; } if (!num_pictures) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Shape requires at least one bitmap file as argument."); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "testshape requires at least one bitmap file as argument."); log_usage(state, argv[0]); - exit(-1); + rc = -1; + goto ret; } - if (SDL_Init(SDL_INIT_VIDEO) == -1) { + if (!SDLTest_CommonInit(state)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL video."); - exit(-2); + rc = -2; + goto ret; } mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay()); @@ -307,48 +330,42 @@ int main(int argc, char **argv) } } - window = SDL_CreateWindow("SDL_Shape test", SHAPED_WINDOW_DIMENSION, SHAPED_WINDOW_DIMENSION, SDL_WINDOW_TRANSPARENT); - if (window == NULL) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create shaped window for SDL_Shape."); - rc = -4; - goto ret; - } - renderer = SDL_CreateRenderer(window, NULL, 0); - if (renderer == NULL) { - SDL_DestroyWindow(window); - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create rendering context for SDL_Shape window."); - rc = -4; - goto ret; - } - for (i = 0; i < num_pictures; i++) { - pictures[i]->texture = NULL; - } - for (i = 0; i < num_pictures; i++) { - pictures[i]->texture = SDL_CreateTextureFromSurface(renderer, pictures[i]->surface); - if (pictures[i]->texture == NULL) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create texture for SDL_shape."); - rc = -6; + pictures[i]->textures = SDL_calloc(state->num_windows, sizeof(SDL_Texture *)); + pictures[i]->shape_textures = SDL_calloc(state->num_windows, sizeof(SDL_Texture *)); + if (!pictures[i]->textures || !pictures[i]->shape_textures) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create textures array(s)."); + rc = -4; goto ret; } + + for (j = 0; j < state->num_windows; j++) { + pictures[i]->textures[j] = SDL_CreateTextureFromSurface(state->renderers[j], pictures[i]->surface); + if (!pictures[i]->textures[j]) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create textures for SDL_shape."); + rc = -5; + goto ret; + } + } } - should_exit = 0; + done = 0; current_picture = 0; button_down = 0; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Changing to shaped bmp: %s", pictures[current_picture]->name); - SDL_QueryTexture(pictures[current_picture]->texture, &pixelFormat, &access, &w, &h); - /* We want to set the window size in pixels */ - SDL_SetWindowSize(window, (int)SDL_ceilf(w / mode->pixel_density), (int)SDL_ceilf(h / mode->pixel_density)); - SDL3_SetWindowShape(window, pictures[current_picture]->surface, &pictures[current_picture]->mode); - while (should_exit == 0) { + for (i = 0; i < state->num_windows; i++) { + SDL_QueryTexture(pictures[current_picture]->textures[i], &pixelFormat, &access, &w, &h); + /* We want to set the window size in pixels */ + SDL_SetWindowSize(state->windows[i], (int)SDL_ceilf(w / mode->pixel_density), (int)SDL_ceilf(h / mode->pixel_density)); + } + SDL3_SetWindowShape(pictures[current_picture], &pictures[current_picture]->mode); + while (!done) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + SDLTest_CommonEvent(state, &event, &done); if (event.type == SDL_EVENT_KEY_DOWN) { button_down = 1; - if (event.key.keysym.sym == SDLK_ESCAPE) { - should_exit = 1; - break; - } } if (button_down && event.type == SDL_EVENT_KEY_UP) { button_down = 0; @@ -357,16 +374,16 @@ int main(int argc, char **argv) current_picture = 0; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Changing to shaped bmp: %s", pictures[current_picture]->name); - SDL_QueryTexture(pictures[current_picture]->texture, &pixelFormat, &access, &w, &h); - SDL_SetWindowSize(window, (int)SDL_ceilf(w / mode->pixel_density), (int)SDL_ceilf(h / mode->pixel_density)); - SDL3_SetWindowShape(window, pictures[current_picture]->surface, &pictures[current_picture]->mode); - } - if (event.type == SDL_EVENT_QUIT) { - should_exit = 1; - break; + for (i = 0; i < state->num_windows; i++) { + SDL_QueryTexture(pictures[current_picture]->textures[i], &pixelFormat, &access, &w, &h); + SDL_SetWindowSize(state->windows[i], (int)SDL_ceilf(w / mode->pixel_density),(int)SDL_ceilf(h / mode->pixel_density)); + SDL3_SetWindowShape(pictures[current_picture], &pictures[current_picture]->mode); + } } } - render(renderer, pictures[current_picture]->texture); + for (i = 0; i < state->num_windows; i++) { + render(i, state->renderers[i], pictures[current_picture]); + } SDL_Delay(10); } @@ -374,20 +391,16 @@ ret: /* Free the textures + original surfaces backing the textures. */ for (pic_i = picture_linked_list; pic_i; ) { LoadedPicture *next = pic_i->next; - if (pic_i->texture) { - SDL_DestroyTexture(pic_i->texture); + for (j = 0; j < state->num_windows; j++) { + SDL_DestroyTexture(pic_i->textures[i]); } + SDL_free(pic_i->textures); SDL_DestroySurface(pic_i->surface); SDL_free(pic_i); pic_i = next; } SDL_free(pictures); - /* Destroy the renderer. */ - SDL_DestroyRenderer(renderer); - /* Destroy the window. */ - SDL_DestroyWindow(window); - if (g_bitmap) { SDL_free(g_bitmap); g_bitmap = NULL; @@ -397,8 +410,7 @@ ret: g_shape_surface = NULL; } - SDL_Quit(); - SDLTest_CommonDestroyState(state); + SDLTest_CommonQuit(state); return rc; } diff --git a/test/testsprite.c b/test/testsprite.c index 9afa6f0d..c186782a 100644 --- a/test/testsprite.c +++ b/test/testsprite.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -14,10 +14,7 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -48,20 +45,12 @@ static SDL_bool suspend_when_occluded; /* -1: infinite random moves (default); >=0: enables N deterministic moves */ static int iterations = -1; -static int done; - -/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) +void SDL_AppQuit(void) { SDL_free(sprites); SDL_free(positions); SDL_free(velocities); SDLTest_CommonQuit(state); - /* Let 'main()' return normally */ - if (rc != 0) { - exit(rc); - } } static int LoadSprite(const char *file) @@ -395,17 +384,17 @@ static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite) SDL_RenderPresent(renderer); } -static void loop(void) +int SDL_AppEvent(const SDL_Event *event) +{ + return SDLTest_CommonEventMainCallbacks(state, event); +} + +int SDL_AppIterate(void) { Uint64 now; int i; int active_windows = 0; - SDL_Event event; - /* Check for events */ - while (SDL_PollEvent(&event)) { - SDLTest_CommonEvent(state, &event, &done); - } for (i = 0; i < state->num_windows; ++i) { if (state->windows[i] == NULL || (suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) { @@ -414,14 +403,9 @@ static void loop(void) ++active_windows; MoveSprites(state->renderers[i], sprites[i]); } -#ifdef __EMSCRIPTEN__ - if (done) { - emscripten_cancel_main_loop(); - } -#endif /* If all windows are occluded, throttle the event polling to 15hz. */ - if (!done && !active_windows) { + if (!active_windows) { SDL_DelayNS(SDL_NS_PER_SECOND / 15); } @@ -435,9 +419,11 @@ static void loop(void) next_fps_check = now + fps_check_delay; frames = 0; } + + return 0; /* keep going */ } -int main(int argc, char *argv[]) +int SDL_AppInit(int argc, char *argv[]) { int i; Uint64 seed; @@ -448,8 +434,8 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { - return 1; + if (!state) { + return -1; } for (i = 1; i < argc;) { @@ -532,20 +518,20 @@ int main(int argc, char *argv[]) NULL }; SDLTest_CommonLogUsage(state, argv[0], options); - quit(1); + return -1; } i += consumed; } if (!SDLTest_CommonInit(state)) { - quit(2); + return -1; } /* Create the windows, initialize the renderers, and load the textures */ sprites = (SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites)); - if (sprites == NULL) { + if (!sprites) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); - quit(2); + return -1; } for (i = 0; i < state->num_windows; ++i) { SDL_Renderer *renderer = state->renderers[i]; @@ -553,15 +539,15 @@ int main(int argc, char *argv[]) SDL_RenderClear(renderer); } if (LoadSprite(icon) < 0) { - quit(2); + return -1; } /* Allocate memory for the sprite info */ positions = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*positions)); velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities)); - if (positions == NULL || velocities == NULL) { + if (!positions || !velocities) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); - quit(2); + return -1; } /* Position sprites and set their velocities using the fuzzer */ @@ -586,19 +572,10 @@ int main(int argc, char *argv[]) } } - /* Main render loop */ + /* Main render loop in SDL_AppIterate will begin when this function returns. */ frames = 0; next_fps_check = SDL_GetTicks() + fps_check_delay; - done = 0; -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(loop, 0, 1); -#else - while (!done) { - loop(); - } -#endif - - quit(0); return 0; } + diff --git a/test/testspriteminimal.c b/test/testspriteminimal.c index 0d89c5e3..6ffcc46b 100644 --- a/test/testspriteminimal.c +++ b/test/testspriteminimal.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -133,7 +133,7 @@ int main(int argc, char *argv[]) sprite = CreateTexture(renderer, icon_bmp, icon_bmp_len, &sprite_w, &sprite_h); - if (sprite == NULL) { + if (!sprite) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture (%s)", SDL_GetError()); return_code = 3; goto quit; diff --git a/test/teststreaming.c b/test/teststreaming.c index dcd5b442..bb0e02c9 100644 --- a/test/teststreaming.c +++ b/test/teststreaming.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -139,7 +139,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -158,13 +158,13 @@ int main(int argc, char **argv) /* load the moose images */ filename = GetResourceFilename(NULL, "moose.dat"); - if (filename == NULL) { + if (!filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory\n"); return -1; } handle = SDL_RWFromFile(filename, "rb"); SDL_free(filename); - if (handle == NULL) { + if (!handle) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Can't find the file moose.dat !\n"); quit(2); } @@ -173,19 +173,19 @@ int main(int argc, char **argv) /* Create the window and renderer */ window = SDL_CreateWindow("Happy Moose", MOOSEPIC_W * 4, MOOSEPIC_H * 4, SDL_WINDOW_RESIZABLE); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create window: %s\n", SDL_GetError()); quit(3); } renderer = SDL_CreateRenderer(window, NULL, 0); - if (renderer == NULL) { + if (!renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create renderer: %s\n", SDL_GetError()); quit(4); } MooseTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, MOOSEPIC_W, MOOSEPIC_H); - if (MooseTexture == NULL) { + if (!MooseTexture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError()); quit(5); } diff --git a/test/testsurround.c b/test/testsurround.c index 1edd1faf..ea27cb17 100644 --- a/test/testsurround.c +++ b/test/testsurround.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -152,7 +152,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -212,7 +212,7 @@ int main(int argc, char *argv[]) active_channel = 0; stream = SDL_OpenAudioDeviceStream(devices[i], &spec, fill_buffer, NULL); - if (stream == NULL) { + if (!stream) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_OpenAudioDeviceStream() failed: %s\n", SDL_GetError()); continue; } diff --git a/test/testthread.c b/test/testthread.c index 8bcbf4b3..be1a6779 100644 --- a/test/testthread.c +++ b/test/testthread.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -139,7 +139,7 @@ int main(int argc, char *argv[]) alive = 1; thread = SDL_CreateThread(ThreadFunc, "One", "#1"); - if (thread == NULL) { + if (!thread) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread: %s\n", SDL_GetError()); quit(1); } @@ -153,7 +153,7 @@ int main(int argc, char *argv[]) alive = 1; (void)signal(SIGTERM, killed); thread = SDL_CreateThread(ThreadFunc, "Two", "#2"); - if (thread == NULL) { + if (!thread) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread: %s\n", SDL_GetError()); quit(1); } diff --git a/test/testtimer.c b/test/testtimer.c index 278d30b0..6fc02423 100644 --- a/test/testtimer.c +++ b/test/testtimer.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -19,6 +19,34 @@ #define DEFAULT_RESOLUTION 1 +static int test_sdl_delay_within_bounds(void) { + const int testDelay = 100; + const int marginOfError = 25; + Uint64 result; + Uint64 result2; + Sint64 difference; + + SDLTest_ResetAssertSummary(); + + /* Get ticks count - should be non-zero by now */ + result = SDL_GetTicks(); + SDLTest_AssertPass("Call to SDL_GetTicks()"); + SDLTest_AssertCheck(result > 0, "Check result value, expected: >0, got: %" SDL_PRIu64, result); + + /* Delay a bit longer and measure ticks and verify difference */ + SDL_Delay(testDelay); + SDLTest_AssertPass("Call to SDL_Delay(%d)", testDelay); + result2 = SDL_GetTicks(); + SDLTest_AssertPass("Call to SDL_GetTicks()"); + SDLTest_AssertCheck(result2 > 0, "Check result value, expected: >0, got: %" SDL_PRIu64, result2); + difference = result2 - result; + SDLTest_AssertCheck(difference > (testDelay - marginOfError), "Check difference, expected: >%d, got: %" SDL_PRIu64, testDelay - marginOfError, difference); + /* Disabled because this might fail on non-interactive systems. */ + SDLTest_AssertCheck(difference < (testDelay + marginOfError), "Check difference, expected: <%d, got: %" SDL_PRIu64, testDelay + marginOfError, difference); + + return SDLTest_AssertSummaryToTestResult() == TEST_RESULT_PASSED ? 0 : 1; +} + static int ticks = 0; static Uint32 SDLCALL @@ -43,10 +71,12 @@ int main(int argc, char *argv[]) Uint64 start, now; Uint64 start_perf, now_perf; SDLTest_CommonState *state; + SDL_bool run_interactive_tests = SDL_TRUE; + int return_code = 0; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -59,7 +89,10 @@ int main(int argc, char *argv[]) consumed = SDLTest_CommonArg(state, i); if (!consumed) { - if (desired < 0) { + if (SDL_strcmp(argv[i], "--no-interactive") == 0) { + run_interactive_tests = SDL_FALSE; + consumed = 1; + } else if (desired < 0) { char *endptr; desired = SDL_strtoul(argv[i], &endptr, 0); @@ -69,7 +102,7 @@ int main(int argc, char *argv[]) } } if (consumed <= 0) { - static const char *options[] = { "[interval]", NULL }; + static const char *options[] = { "[--no-interactive]", "[interval]", NULL }; SDLTest_CommonLogUsage(state, argv[0], options); return 1; } @@ -162,7 +195,11 @@ int main(int argc, char *argv[]) now = SDL_GetTicks(); SDL_Log("Delay 1 second = %d ms in ticks, %f ms according to performance counter\n", (int)(now - start), (double)((now_perf - start_perf) * 1000) / SDL_GetPerformanceFrequency()); + if (run_interactive_tests) { + return_code = test_sdl_delay_within_bounds(); + } + SDLTest_CommonDestroyState(state); SDL_Quit(); - return 0; + return return_code; } diff --git a/test/testurl.c b/test/testurl.c index e39674b3..8b4f1caa 100644 --- a/test/testurl.c +++ b/test/testurl.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/testutils.c b/test/testutils.c index f49b38e1..74ae6149 100644 --- a/test/testutils.c +++ b/test/testutils.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright 2022 Collabora Ltd. This software is provided 'as-is', without any express or implied @@ -28,15 +28,14 @@ GetNearbyFilename(const char *file) base = SDL_GetBasePath(); - if (base != NULL) { + if (base) { SDL_RWops *rw; size_t len = SDL_strlen(base) + SDL_strlen(file) + 1; path = SDL_malloc(len); - if (path == NULL) { + if (!path) { SDL_free(base); - SDL_OutOfMemory(); return NULL; } @@ -53,11 +52,7 @@ GetNearbyFilename(const char *file) SDL_free(path); } - path = SDL_strdup(file); - if (path == NULL) { - SDL_OutOfMemory(); - } - return path; + return SDL_strdup(file); } /** @@ -72,17 +67,10 @@ GetNearbyFilename(const char *file) char * GetResourceFilename(const char *user_specified, const char *def) { - if (user_specified != NULL) { - char *ret = SDL_strdup(user_specified); - - if (ret == NULL) { - SDL_OutOfMemory(); - } - - return ret; - } else { - return GetNearbyFilename(def); + if (user_specified) { + return SDL_strdup(user_specified); } + return GetNearbyFilename(def); } /** @@ -105,18 +93,23 @@ LoadTexture(SDL_Renderer *renderer, const char *file, SDL_bool transparent, path = GetNearbyFilename(file); - if (path != NULL) { + if (path) { file = path; } temp = SDL_LoadBMP(file); - if (temp == NULL) { + if (!temp) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", file, SDL_GetError()); } else { /* Set transparent pixel as the pixel at (0,0) */ if (transparent) { if (temp->format->palette) { - SDL_SetSurfaceColorKey(temp, SDL_TRUE, *(Uint8 *)temp->pixels); + const Uint8 bpp = temp->format->BitsPerPixel; + const Uint8 mask = (1 << bpp) - 1; + if (SDL_PIXELORDER(temp->format->format) == SDL_BITMAPORDER_4321) + SDL_SetSurfaceColorKey(temp, SDL_TRUE, (*(Uint8 *)temp->pixels) & mask); + else + SDL_SetSurfaceColorKey(temp, SDL_TRUE, ((*(Uint8 *)temp->pixels) >> (8 - bpp)) & mask); } else { switch (temp->format->BitsPerPixel) { case 15: @@ -137,16 +130,16 @@ LoadTexture(SDL_Renderer *renderer, const char *file, SDL_bool transparent, } } - if (width_out != NULL) { + if (width_out) { *width_out = temp->w; } - if (height_out != NULL) { + if (height_out) { *height_out = temp->h; } texture = SDL_CreateTextureFromSurface(renderer, temp); - if (texture == NULL) { + if (!texture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s\n", SDL_GetError()); } } diff --git a/test/testutils.h b/test/testutils.h index 614d6b53..5cbbf45a 100644 --- a/test/testutils.h +++ b/test/testutils.h @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 Sam Lantinga + Copyright (C) 1997-2024 Sam Lantinga Copyright 2022 Collabora Ltd. This software is provided 'as-is', without any express or implied diff --git a/test/testver.c b/test/testver.c index 313ea8c8..1cfe86d2 100644 --- a/test/testver.c +++ b/test/testver.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/testvideocapture.c b/test/testvideocapture.c new file mode 100644 index 00000000..d14fd485 --- /dev/null +++ b/test/testvideocapture.c @@ -0,0 +1,770 @@ +/* + 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 + 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 "SDL3/SDL_main.h" +#include "SDL3/SDL.h" +#include "SDL3/SDL_test.h" +#include "SDL3/SDL_video_capture.h" +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +static const char *usage = "\ + \n\ + =========================================================================\n\ + \n\ +Use keyboards:\n\ + o: open first video capture device. (close previously opened)\n\ + l: switch to, and list video capture devices\n\ + i: information about status (Init, Playing, Stopped)\n\ + f: formats and resolutions available\n\ + s: start / stop capture\n\ + h: display help\n\ + esc: exit \n\ + \n\ + =========================================================================\n\ + \n\ +"; + +typedef struct { + Uint64 next_check; + int frame_counter; + int check_delay; + double last_fps; +} measure_fps_t; + +static void +update_fps(measure_fps_t *m) +{ + Uint64 now = SDL_GetTicks(); + Uint64 deadline; + m->frame_counter++; + if (m->check_delay == 0) { + m->check_delay = 1500; + } + deadline = m->next_check; + if (now >= deadline) { + /* Print out some timing information */ + const Uint64 then = m->next_check - m->check_delay; + m->last_fps = ((double) m->frame_counter * 1000) / (now - then); + m->next_check = now + m->check_delay; + m->frame_counter = 0; + } +} + +#if defined(__linux__) && !defined(__ANDROID__) +static void load_average(float *val) +{ + FILE *fp = 0; + char line[1024]; + fp = fopen("/proc/loadavg", "rt"); + if (fp) { + char *s = fgets(line, sizeof(line), fp); + if (s) { + SDL_sscanf(s, "%f", val); + } + fclose(fp); + } +} +#endif + + +struct data_capture_t { + SDL_VideoCaptureDevice *device; + SDL_VideoCaptureSpec obtained; + int stopped; + SDL_VideoCaptureFrame frame_current; + measure_fps_t fps_capture; + SDL_Texture *texture; + int texture_updated; +}; + +#define SAVE_CAPTURE_STATE(x) \ + data_capture_tab[(x)].device = device; \ + data_capture_tab[(x)].obtained = obtained; \ + data_capture_tab[(x)].stopped = stopped; \ + data_capture_tab[(x)].frame_current = frame_current; \ + data_capture_tab[(x)].fps_capture = fps_capture; \ + data_capture_tab[(x)].texture = texture; \ + data_capture_tab[(x)].texture_updated = texture_updated; \ + + +#define RESTORE_CAPTURE_STATE(x) \ + device = data_capture_tab[(x)].device; \ + obtained = data_capture_tab[(x)].obtained; \ + stopped = data_capture_tab[(x)].stopped; \ + frame_current = data_capture_tab[(x)].frame_current; \ + fps_capture = data_capture_tab[(x)].fps_capture; \ + texture = data_capture_tab[(x)].texture; \ + texture_updated = data_capture_tab[(x)].texture_updated; \ + + + + + +static SDL_VideoCaptureDeviceID get_instance_id(int index) { + int ret = 0; + int num = 0; + SDL_VideoCaptureDeviceID *devices; + devices = SDL_GetVideoCaptureDevices(&num); + if (devices) { + if (index >= 0 && index < num) { + ret = devices[index]; + } + SDL_free(devices); + } + + if (ret == 0) { +/* SDL_Log("invalid index"); */ + } + + return ret; +} + + + +int main(int argc, char **argv) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Event evt; + int quit = 0; + + SDLTest_CommonState *state; + + int current_dev = 0; + measure_fps_t fps_main; + + + SDL_FRect r_playstop = { 50, 50, 120, 50 }; + SDL_FRect r_close = { 50 + (120 + 50) * 1, 50, 120, 50 }; + + SDL_FRect r_open = { 50 + (120 + 50) * 2, 50, 120, 50 }; + + SDL_FRect r_format = { 50 + (120 + 50) * 3, 50, 120, 50 }; + SDL_FRect r_listdev = { 50 + (120 + 50) * 4, 50, 120, 50 }; + + SDL_VideoCaptureDevice *device; + SDL_VideoCaptureSpec obtained; + int stopped = 0; + SDL_VideoCaptureFrame frame_current; + measure_fps_t fps_capture; + SDL_Texture *texture = NULL; + int texture_updated = 0; + + struct data_capture_t data_capture_tab[16]; + const int data_capture_tab_size = SDL_arraysize(data_capture_tab); + + SDL_zero(fps_main); + SDL_zero(fps_capture); + SDL_zero(frame_current); + SDL_zeroa(data_capture_tab); + + /* Set 0 to disable TouchEvent to be duplicated as MouseEvent with SDL_TOUCH_MOUSEID */ + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + /* Set 0 to disable MouseEvent to be duplicated as TouchEvent with SDL_MOUSE_TOUCHID */ + SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); + + { + int i; + for (i = 0; i < data_capture_tab_size; i++) { + data_capture_tab[i].device = NULL; + } + } + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + /* Parse commandline */ + { + int i; + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed <= 0) { + static const char *options[] = {NULL}; + SDLTest_CommonLogUsage(state, argv[0], options); + SDLTest_CommonDestroyState(state); + return 1; + } + + i += consumed; + } + } + + SDL_Log("%s", usage); + + /* Load the SDL library */ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { /* FIXME: SDL_INIT_JOYSTICK needed for add/removing devices at runtime */ + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); + return 1; + } + + window = SDL_CreateWindow("Local Video", 1000, 800, 0); + if (window == NULL) { + SDL_Log("Couldn't create window: %s", SDL_GetError()); + return 1; + } + + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + + renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (renderer == NULL) { + /* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */ + return 1; + } + + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO); + + device = SDL_OpenVideoCapture(0); + + if (!device) { + SDL_Log("Error SDL_OpenVideoCapture: %s", SDL_GetError()); + } + + { + /* List formats */ + int i, num = SDL_GetNumVideoCaptureFormats(device); + for (i = 0; i < num; i++) { + Uint32 format; + SDL_GetVideoCaptureFormat(device, i, &format); + SDL_Log("format %d/%d: %s", i, num, SDL_GetPixelFormatName(format)); + { + int w, h; + int j, num2 = SDL_GetNumVideoCaptureFrameSizes(device, format); + for (j = 0; j < num2; j++) { + SDL_GetVideoCaptureFrameSize(device, format, j, &w, &h); + SDL_Log(" framesizes %d/%d : %d x %d", j, num2, w, h); + } + } + } + } + + /* Set Spec */ + { + int ret; + /* forced_format */ + SDL_VideoCaptureSpec desired; + SDL_zero(desired); + desired.width = 640 * 2; + desired.height = 360 * 2; + desired.format = SDL_PIXELFORMAT_NV12; + ret = SDL_SetVideoCaptureSpec(device, &desired, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE); + + if (ret < 0) { + SDL_SetVideoCaptureSpec(device, NULL, &obtained, 0); + } + } + + SDL_Log("Open capture video device. Obtained spec: size=%d x %d format=%s", + obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format)); + + { + SDL_VideoCaptureSpec spec; + if (SDL_GetVideoCaptureSpec(device, &spec) == 0) { + SDL_Log("Read spec: size=%d x %d format=%s", + spec.width, spec.height, SDL_GetPixelFormatName(spec.format)); + } else { + SDL_Log("Error read spec: %s", SDL_GetError()); + } + } + + if (SDL_StartVideoCapture(device) < 0) { + SDL_Log("error SDL_StartVideoCapture(): %s", SDL_GetError()); + } + + while (!quit) { + + SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255); + SDL_RenderClear(renderer); + + SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 255); + + SDL_RenderFillRect(renderer, &r_playstop); + SDL_RenderFillRect(renderer, &r_close); + SDL_RenderFillRect(renderer, &r_open); + SDL_RenderFillRect(renderer, &r_format); + SDL_RenderFillRect(renderer, &r_listdev); + + SDL_SetRenderDrawColor(renderer, 0xcc, 0xcc, 0xcc, 255); + + SDLTest_DrawString(renderer, r_playstop.x + 5, r_playstop.y + 5, "play stop"); + SDLTest_DrawString(renderer, r_close.x + 5, r_close.y + 5, "close"); + SDLTest_DrawString(renderer, r_open.x + 5, r_open.y + 5, "open dev"); + SDLTest_DrawString(renderer, r_format.x + 5, r_format.y + 5, "formats"); + + { + char buf[256]; + SDL_snprintf(buf, 256, "device %d", current_dev); + SDLTest_DrawString(renderer, r_listdev.x + 5, r_listdev.y + 5, buf); + } + + while (SDL_PollEvent(&evt)) { + SDL_FRect *r = NULL; + SDL_FPoint pt; + int sym = 0; + + pt.x = 0; + pt.y = 0; + + SDL_ConvertEventToRenderCoordinates(renderer, &evt); + + switch (evt.type) + { + case SDL_EVENT_KEY_DOWN: + { + sym = evt.key.keysym.sym; + break; + } + case SDL_EVENT_QUIT: + { + quit = 1; + SDL_Log("Ctlr+C : Quit!"); + } + break; + + case SDL_EVENT_FINGER_DOWN: + { + pt.x = evt.tfinger.x; + pt.y = evt.tfinger.y; + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + pt.x = evt.button.x; + pt.y = evt.button.y; + } + break; + } + + if (pt.x != 0 && pt.y != 0) { + if (SDL_PointInRectFloat(&pt, &r_playstop)) { + r = &r_playstop; + sym = SDLK_s; + } + if (SDL_PointInRectFloat(&pt, &r_close)) { + r = &r_close; + sym = SDLK_c; + } + if (SDL_PointInRectFloat(&pt, &r_open)) { + r = &r_open; + sym = SDLK_o; + } + + if (SDL_PointInRectFloat(&pt, &r_format)) { + r = &r_format; + sym = SDLK_f; + } + if (SDL_PointInRectFloat(&pt, &r_listdev)) { + r = &r_listdev; + sym = SDLK_l; + } + } + + + if (r) { + SDL_SetRenderDrawColor(renderer, 0x33, 0, 0, 255); + SDL_RenderFillRect(renderer, r); + } + + + if (sym == SDLK_c) { + if (frame_current.num_planes) { + SDL_ReleaseVideoCaptureFrame(device, &frame_current); + } + SDL_CloseVideoCapture(device); + device = NULL; + SDL_Log("Close"); + } + + if (sym == SDLK_o) { + if (device) { + SDL_Log("Close previous .."); + if (frame_current.num_planes) { + SDL_ReleaseVideoCaptureFrame(device, &frame_current); + } + SDL_CloseVideoCapture(device); + } + + texture_updated = 0; + + SDL_ClearError(); + + SDL_Log("Try to open:%s", SDL_GetVideoCaptureDeviceName(get_instance_id(current_dev))); + + obtained.width = 640 * 2; + obtained.height = 360 * 2; + device = SDL_OpenVideoCaptureWithSpec(get_instance_id(current_dev), &obtained, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE); + + /* spec may have changed because of re-open */ + if (texture) { + SDL_DestroyTexture(texture); + texture = NULL; + } + + SDL_Log("Open device:%p %s", (void*)device, SDL_GetError()); + stopped = 0; + } + + if (sym == SDLK_l) { + int num = 0; + SDL_VideoCaptureDeviceID *devices; + int i; + devices = SDL_GetVideoCaptureDevices(&num); + + SDL_Log("Num devices : %d", num); + for (i = 0; i < num; i++) { + SDL_Log("Device %d/%d : %s", i, num, SDL_GetVideoCaptureDeviceName(devices[i])); + } + SDL_free(devices); + + SAVE_CAPTURE_STATE(current_dev); + + current_dev += 1; + if (current_dev >= num || current_dev >= (int) SDL_arraysize(data_capture_tab)) { + current_dev = 0; + } + + RESTORE_CAPTURE_STATE(current_dev); + SDL_Log("--> select dev %d / %d", current_dev, num); + } + + if (sym == SDLK_i) { + SDL_VideoCaptureStatus status = SDL_GetVideoCaptureStatus(device); + if (status == SDL_VIDEO_CAPTURE_STOPPED) { SDL_Log("STOPPED"); } + if (status == SDL_VIDEO_CAPTURE_PLAYING) { SDL_Log("PLAYING"); } + if (status == SDL_VIDEO_CAPTURE_INIT) { SDL_Log("INIT"); } + } + + if (sym == SDLK_s) { + if (stopped) { + SDL_Log("Stop"); + SDL_StopVideoCapture(device); + } else { + SDL_Log("Start"); + SDL_StartVideoCapture(device); + } + stopped = !stopped; + } + + if (sym == SDLK_f) { + SDL_Log("List formats"); + + if (!device) { + device = SDL_OpenVideoCapture(get_instance_id(current_dev)); + } + + /* List formats */ + { + int i, num = SDL_GetNumVideoCaptureFormats(device); + for (i = 0; i < num; i++) { + Uint32 format; + SDL_GetVideoCaptureFormat(device, i, &format); + SDL_Log("format %d/%d : %s", i, num, SDL_GetPixelFormatName(format)); + { + int w, h; + int j, num2 = SDL_GetNumVideoCaptureFrameSizes(device, format); + for (j = 0; j < num2; j++) { + SDL_GetVideoCaptureFrameSize(device, format, j, &w, &h); + SDL_Log(" framesizes %d/%d : %d x %d", j, num2, w, h); + } + } + } + } + } + if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) { + quit = 1; + SDL_Log("Key : Escape!"); + } + + if (sym == SDLK_h || sym == SDLK_F1) { + SDL_Log("%s", usage); + } + } + + + SAVE_CAPTURE_STATE(current_dev); + + { + int i, n = SDL_arraysize(data_capture_tab); + for (i = 0; i < n; i++) { + RESTORE_CAPTURE_STATE(i); + + if (!device) { + /* device has been closed */ + frame_current.num_planes = 0; + texture_updated = 0; + } else { + int ret; + SDL_VideoCaptureFrame frame_next; + SDL_zero(frame_next); + + ret = SDL_AcquireVideoCaptureFrame(device, &frame_next); + if (ret < 0) { + SDL_Log("dev[%d] err SDL_AcquireVideoCaptureFrame: %s", i, SDL_GetError()); + } +#if 1 + if (frame_next.num_planes) { + SDL_Log("dev[%d] frame: %p at %" SDL_PRIu64, i, (void*)frame_next.data[0], frame_next.timestampNS); + } +#endif + + if (frame_next.num_planes) { + + update_fps(&fps_capture); + + if (frame_current.num_planes) { + ret = SDL_ReleaseVideoCaptureFrame(device, &frame_current); + if (ret < 0) { + SDL_Log("dev[%d] err SDL_ReleaseVideoCaptureFrame: %s", i, SDL_GetError()); + } + } + frame_current = frame_next; + texture_updated = 0; + } + } + + SAVE_CAPTURE_STATE(i); + } + } + + + RESTORE_CAPTURE_STATE(current_dev); + + + + /* Moving square */ + SDL_SetRenderDrawColor(renderer, 0, 0xff, 0, 255); + { + SDL_FRect r; + static float x = 0; + x += 10; + if (x > 1000) { + x = 0; + } + r.x = x; + r.y = 100; + r.w = r.h = 10; + SDL_RenderFillRect(renderer, &r); + } + + SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 255); + + + SAVE_CAPTURE_STATE(current_dev); + + { + int i, n = SDL_arraysize(data_capture_tab); + for (i = 0; i < n; i++) { + RESTORE_CAPTURE_STATE(i); + + /* Update SDL_Texture with last video frame (only once per new frame) */ + if (frame_current.num_planes && texture_updated == 0) { + + /* Create texture with appropriate format (for DMABUF or not) */ + if (texture == NULL) { + Uint32 format = obtained.format; + texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, obtained.width, obtained.height); + if (texture == NULL) { + SDL_Log("Couldn't create texture: %s", SDL_GetError()); + return 1; + } + } + + { + /* Use software data */ + if (frame_current.num_planes == 1) { + SDL_UpdateTexture(texture, NULL, + frame_current.data[0], frame_current.pitch[0]); + } else if (frame_current.num_planes == 2) { + SDL_UpdateNVTexture(texture, NULL, + frame_current.data[0], frame_current.pitch[0], + frame_current.data[1], frame_current.pitch[1]); + } else if (frame_current.num_planes == 3) { + SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.pitch[0], + frame_current.data[1], frame_current.pitch[1], + frame_current.data[2], frame_current.pitch[2]); + } + texture_updated = 1; + } + } + + SAVE_CAPTURE_STATE(i); + } + } + + + RESTORE_CAPTURE_STATE(current_dev); + + { + int i, n = SDL_arraysize(data_capture_tab); + int win_w, win_h; + int total_texture_updated = 0; + int curr_texture_updated = 0; + for (i = 0; i < n; i++) { + if (data_capture_tab[i].texture_updated) { + total_texture_updated += 1; + } + } + + SDL_GetRenderOutputSize(renderer, &win_w, &win_h); + + + for (i = 0; i < n; i++) { + RESTORE_CAPTURE_STATE(i); + /* RenderCopy the SDL_Texture */ + if (texture_updated == 1) { + /* Scale texture to fit the screen */ + + int tw, th; + int w; + SDL_FRect d; + SDL_QueryTexture(texture, NULL, NULL, &tw, &th); + + w = win_w / total_texture_updated; + + if (tw > w - 20) { + float scale = (float) (w - 20) / (float) tw; + tw = w - 20; + th = (int)((float) th * scale); + } + d.x = (float)(10 + curr_texture_updated * w); + d.y = (float)(win_h - th); + d.w = (float)tw; + d.h = (float)(th - 10); + SDL_RenderTexture(renderer, texture, NULL, &d); + + curr_texture_updated += 1; + } + } + + } + + RESTORE_CAPTURE_STATE(current_dev); + + + /* display status and FPS */ + if (!device) { +#ifdef __IOS__ + const float x_offset = 500; +#else + const float x_offset = 0; +#endif + char buf[256]; + SDL_snprintf(buf, 256, "Device %d (%s) is not opened", current_dev, SDL_GetVideoCaptureDeviceName(get_instance_id(current_dev))); + SDLTest_DrawString(renderer, x_offset + 10, 10, buf); + } else { +#ifdef __IOS__ + const float x_offset = 500; +#else + const float x_offset = 0; +#endif + const char *status = "no status"; + char buf[256]; + + if (device) { + SDL_VideoCaptureStatus s = SDL_GetVideoCaptureStatus(device); + if (s == SDL_VIDEO_CAPTURE_INIT) { + status = "init"; + } else if (s == SDL_VIDEO_CAPTURE_PLAYING) { + status = "playing"; + } else if (s == SDL_VIDEO_CAPTURE_STOPPED) { + status = "stopped"; + } else if (s == SDL_VIDEO_CAPTURE_FAIL) { + status = "failed"; + } + + } + + /* capture device, capture fps, capture status */ + SDL_snprintf(buf, 256, "Device %d - %2.2f fps - %s", current_dev, fps_capture.last_fps, status); + SDLTest_DrawString(renderer, x_offset + 10, 10, buf); + + /* capture spec */ + SDL_snprintf(buf, sizeof(buf), "%d x %d %s", obtained.width, obtained.height, SDL_GetPixelFormatName(obtained.format)); + SDLTest_DrawString(renderer, x_offset + 10, 20, buf); + + /* video fps */ + SDL_snprintf(buf, sizeof(buf), "%2.2f fps", fps_main.last_fps); + SDLTest_DrawString(renderer, x_offset + 10, 30, buf); + + } + + /* display last error */ + { + SDLTest_DrawString(renderer, 400, 10, SDL_GetError()); + } + + /* display load average */ +#if defined(__linux__) && !defined(__ANDROID__) + { + float val = 0.0f; + char buf[128]; + load_average(&val); + if (val != 0.0f) { + SDL_snprintf(buf, sizeof(buf), "load avg %2.2f percent", val); + SDLTest_DrawString(renderer, 800, 10, buf); + } + } +#endif + + + SDL_Delay(20); + SDL_RenderPresent(renderer); + + update_fps(&fps_main); + + } + + + + SAVE_CAPTURE_STATE(current_dev); + + { + int i, n = SDL_arraysize(data_capture_tab); + for (i = 0; i < n; i++) { + RESTORE_CAPTURE_STATE(i); + + if (device) { + if (SDL_StopVideoCapture(device) < 0) { + SDL_Log("error SDL_StopVideoCapture(): %s", SDL_GetError()); + } + if (frame_current.num_planes) { + SDL_ReleaseVideoCaptureFrame(device, &frame_current); + } + SDL_CloseVideoCapture(device); + } + + if (texture) { + SDL_DestroyTexture(texture); + } + } + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + + SDL_Quit(); + + SDLTest_CommonDestroyState(state); + + return 0; +} diff --git a/test/testvideocaptureminimal.c b/test/testvideocaptureminimal.c new file mode 100644 index 00000000..67840d78 --- /dev/null +++ b/test/testvideocaptureminimal.c @@ -0,0 +1,206 @@ +/* + 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 + 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 "SDL3/SDL_main.h" +#include "SDL3/SDL.h" +#include "SDL3/SDL_test.h" +#include "SDL3/SDL_video_capture.h" +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +int main(int argc, char **argv) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Event evt; + int quit = 0; + SDLTest_CommonState *state = NULL; + + SDL_VideoCaptureDevice *device = NULL; + SDL_VideoCaptureSpec obtained; + + SDL_VideoCaptureFrame frame_current; + SDL_Texture *texture = NULL; + int texture_updated = 0; + + SDL_zero(evt); + SDL_zero(obtained); + SDL_zero(frame_current); + + /* Set 0 to disable TouchEvent to be duplicated as MouseEvent with SDL_TOUCH_MOUSEID */ + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + /* Set 0 to disable MouseEvent to be duplicated as TouchEvent with SDL_MOUSE_TOUCHID */ + SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + /* Load the SDL library */ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { /* FIXME: SDL_INIT_JOYSTICK needed for add/removing devices at runtime */ + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); + return 1; + } + + window = SDL_CreateWindow("Local Video", 1000, 800, 0); + if (window == NULL) { + SDL_Log("Couldn't create window: %s", SDL_GetError()); + return 1; + } + + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + + renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (renderer == NULL) { + /* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */ + return 1; + } + + device = SDL_OpenVideoCaptureWithSpec(0, NULL, &obtained, SDL_VIDEO_CAPTURE_ALLOW_ANY_CHANGE); + if (!device) { + SDL_Log("No video capture? %s", SDL_GetError()); + return 1; + } + + if (SDL_StartVideoCapture(device) < 0) { + SDL_Log("error SDL_StartVideoCapture(): %s", SDL_GetError()); + return 1; + } + + /* Create texture with appropriate format */ + if (texture == NULL) { + texture = SDL_CreateTexture(renderer, obtained.format, SDL_TEXTUREACCESS_STATIC, obtained.width, obtained.height); + if (texture == NULL) { + SDL_Log("Couldn't create texture: %s", SDL_GetError()); + return 1; + } + } + + while (!quit) { + while (SDL_PollEvent(&evt)) { + int sym = 0; + switch (evt.type) + { + case SDL_EVENT_KEY_DOWN: + { + sym = evt.key.keysym.sym; + break; + } + + case SDL_EVENT_QUIT: + { + quit = 1; + SDL_Log("Ctlr+C : Quit!"); + } + } + + if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) { + quit = 1; + SDL_Log("Key : Escape!"); + } + } + + { + SDL_VideoCaptureFrame frame_next; + SDL_zero(frame_next); + + if (SDL_AcquireVideoCaptureFrame(device, &frame_next) < 0) { + SDL_Log("err SDL_AcquireVideoCaptureFrame: %s", SDL_GetError()); + } +#if 0 + if (frame_next.num_planes) { + SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next.data[0], frame_next.timestampNS); + } +#endif + + if (frame_next.num_planes) { + if (frame_current.num_planes) { + if (SDL_ReleaseVideoCaptureFrame(device, &frame_current) < 0) { + SDL_Log("err SDL_ReleaseVideoCaptureFrame: %s", SDL_GetError()); + } + } + + /* It's not needed to keep the frame once updated the texture is updated. + * But in case of 0-copy, it's needed to have the frame while using the texture. + */ + frame_current = frame_next; + texture_updated = 0; + } + } + + /* Update SDL_Texture with last video frame (only once per new frame) */ + if (frame_current.num_planes && texture_updated == 0) { + /* Use software data */ + if (frame_current.num_planes == 1) { + SDL_UpdateTexture(texture, NULL, + frame_current.data[0], frame_current.pitch[0]); + } else if (frame_current.num_planes == 2) { + SDL_UpdateNVTexture(texture, NULL, + frame_current.data[0], frame_current.pitch[0], + frame_current.data[1], frame_current.pitch[1]); + } else if (frame_current.num_planes == 3) { + SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.pitch[0], + frame_current.data[1], frame_current.pitch[1], + frame_current.data[2], frame_current.pitch[2]); + } + texture_updated = 1; + } + + SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255); + SDL_RenderClear(renderer); + { + int win_w, win_h, tw, th, w; + SDL_FRect d; + SDL_QueryTexture(texture, NULL, NULL, &tw, &th); + SDL_GetRenderOutputSize(renderer, &win_w, &win_h); + w = win_w; + if (tw > w - 20) { + float scale = (float) (w - 20) / (float) tw; + tw = w - 20; + th = (int)((float) th * scale); + } + d.x = (float)(10 ); + d.y = (float)(win_h - th); + d.w = (float)tw; + d.h = (float)(th - 10); + SDL_RenderTexture(renderer, texture, NULL, &d); + } + SDL_Delay(10); + SDL_RenderPresent(renderer); + } + + if (SDL_StopVideoCapture(device) < 0) { + SDL_Log("error SDL_StopVideoCapture(): %s", SDL_GetError()); + } + if (frame_current.num_planes) { + SDL_ReleaseVideoCaptureFrame(device, &frame_current); + } + SDL_CloseVideoCapture(device); + + if (texture) { + SDL_DestroyTexture(texture); + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + SDLTest_CommonDestroyState(state); + + return 0; +} diff --git a/test/testviewport.c b/test/testviewport.c index 006d3c24..abca0897 100644 --- a/test/testviewport.c +++ b/test/testviewport.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -163,7 +163,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } @@ -191,7 +191,7 @@ int main(int argc, char *argv[]) sprite = LoadTexture(state->renderers[0], "icon.bmp", SDL_TRUE, &sprite_w, &sprite_h); - if (sprite == NULL) { + if (!sprite) { quit(2); } diff --git a/test/testvulkan.c b/test/testvulkan.c index c20e4bc9..cad7acaa 100644 --- a/test/testvulkan.c +++ b/test/testvulkan.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -222,36 +222,15 @@ static void createInstance(void) { VkApplicationInfo appInfo = { 0 }; VkInstanceCreateInfo instanceCreateInfo = { 0 }; - const char **extensions = NULL; - unsigned extensionCount = 0; VkResult result; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.apiVersion = VK_API_VERSION_1_0; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.pApplicationInfo = &appInfo; - if (!SDL_Vulkan_GetInstanceExtensions(&extensionCount, NULL)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_Vulkan_GetInstanceExtensions(): %s\n", - SDL_GetError()); - quit(2); - } - extensions = (const char **)SDL_malloc(sizeof(const char *) * extensionCount); - if (extensions == NULL) { - SDL_OutOfMemory(); - quit(2); - } - if (!SDL_Vulkan_GetInstanceExtensions(&extensionCount, extensions)) { - SDL_free((void *)extensions); - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_Vulkan_GetInstanceExtensions(): %s\n", - SDL_GetError()); - quit(2); - } - instanceCreateInfo.enabledExtensionCount = extensionCount; - instanceCreateInfo.ppEnabledExtensionNames = extensions; + + instanceCreateInfo.ppEnabledExtensionNames = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount); result = vkCreateInstance(&instanceCreateInfo, NULL, &vulkanContext->instance); - SDL_free((void *)extensions); if (result != VK_SUCCESS) { vulkanContext->instance = VK_NULL_HANDLE; SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -282,6 +261,7 @@ static void createSurface(void) { if (!SDL_Vulkan_CreateSurface(vulkanContext->window, vulkanContext->instance, + NULL, &vulkanContext->surface)) { vulkanContext->surface = VK_NULL_HANDLE; SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Vulkan_CreateSurface(): %s\n", SDL_GetError()); @@ -313,8 +293,7 @@ static void findPhysicalDevice(void) quit(2); } physicalDevices = (VkPhysicalDevice *)SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount); - if (physicalDevices == NULL) { - SDL_OutOfMemory(); + if (!physicalDevices) { quit(2); } result = vkEnumeratePhysicalDevices(vulkanContext->instance, &physicalDeviceCount, physicalDevices); @@ -347,10 +326,9 @@ static void findPhysicalDevice(void) SDL_free(queueFamiliesProperties); queueFamiliesPropertiesAllocatedSize = queueFamiliesCount; queueFamiliesProperties = (VkQueueFamilyProperties *)SDL_malloc(sizeof(VkQueueFamilyProperties) * queueFamiliesPropertiesAllocatedSize); - if (queueFamiliesProperties == NULL) { + if (!queueFamiliesProperties) { SDL_free(physicalDevices); SDL_free(deviceExtensions); - SDL_OutOfMemory(); quit(2); } } @@ -409,10 +387,9 @@ static void findPhysicalDevice(void) SDL_free(deviceExtensions); deviceExtensionsAllocatedSize = deviceExtensionCount; deviceExtensions = SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize); - if (deviceExtensions == NULL) { + if (!deviceExtensions) { SDL_free(physicalDevices); SDL_free(queueFamiliesProperties); - SDL_OutOfMemory(); quit(2); } } @@ -570,7 +547,6 @@ static void getSurfaceFormats(void) vulkanContext->surfaceFormats = (VkSurfaceFormatKHR *)SDL_malloc(sizeof(VkSurfaceFormatKHR) * vulkanContext->surfaceFormatsAllocatedCount); if (!vulkanContext->surfaceFormats) { vulkanContext->surfaceFormatsCount = 0; - SDL_OutOfMemory(); quit(2); } } @@ -603,7 +579,6 @@ static void getSwapchainImages(void) } vulkanContext->swapchainImages = SDL_malloc(sizeof(VkImage) * vulkanContext->swapchainImageCount); if (!vulkanContext->swapchainImages) { - SDL_OutOfMemory(); quit(2); } result = vkGetSwapchainImagesKHR(vulkanContext->device, @@ -783,7 +758,6 @@ static void createFences(void) vulkanContext->fences = SDL_malloc(sizeof(VkFence) * vulkanContext->swapchainImageCount); if (!vulkanContext->fences) { - SDL_OutOfMemory(); quit(2); } for (i = 0; i < vulkanContext->swapchainImageCount; i++) { @@ -938,7 +912,7 @@ static void initVulkan(void) SDL_Vulkan_LoadLibrary(NULL); vulkanContexts = (VulkanContext *)SDL_calloc(state->num_windows, sizeof(VulkanContext)); - if (vulkanContexts == NULL) { + if (!vulkanContexts) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); quit(2); } @@ -1099,7 +1073,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } diff --git a/test/testwaylandcustom.c b/test/testwaylandcustom.c new file mode 100644 index 00000000..19196f21 --- /dev/null +++ b/test/testwaylandcustom.c @@ -0,0 +1,334 @@ +/* + 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 + 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 + +#include +#include +#include + +#include "icon.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define NUM_SPRITES 100 +#define MAX_SPEED 1 + +static SDL_Window *window; +static SDL_Renderer *renderer; +static SDL_Texture *sprite; +static SDL_FRect positions[NUM_SPRITES]; +static SDL_FRect velocities[NUM_SPRITES]; +static int sprite_w, sprite_h; +static int done; + +static SDL_Texture *CreateTexture(SDL_Renderer *r, unsigned char *data, unsigned int len, int *w, int *h) +{ + SDL_Texture *texture = NULL; + SDL_Surface *surface; + SDL_RWops *src = SDL_RWFromConstMem(data, len); + if (src) { + surface = SDL_LoadBMP_RW(src, SDL_TRUE); + if (surface) { + /* Treat white as transparent */ + SDL_SetSurfaceColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 255, 255, 255)); + + texture = SDL_CreateTextureFromSurface(r, surface); + *w = surface->w; + *h = surface->h; + SDL_DestroySurface(surface); + } + } + return texture; +} + +static void MoveSprites(void) +{ + int i; + int window_w; + int window_h; + SDL_FRect *position, *velocity; + + /* Get the window size */ + SDL_GetWindowSizeInPixels(window, &window_w, &window_h); + + /* Draw a gray background */ + SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); + SDL_RenderClear(renderer); + + /* Move the sprite, bounce at the wall, and draw */ + for (i = 0; i < NUM_SPRITES; ++i) { + position = &positions[i]; + velocity = &velocities[i]; + position->x += velocity->x; + if ((position->x < 0) || (position->x >= (window_w - sprite_w))) { + velocity->x = -velocity->x; + position->x += velocity->x; + } + position->y += velocity->y; + if ((position->y < 0) || (position->y >= (window_h - sprite_h))) { + velocity->y = -velocity->y; + position->y += velocity->y; + } + + /* Blit the sprite onto the screen */ + SDL_RenderTexture(renderer, sprite, NULL, position); + } + + /* Update the screen! */ + SDL_RenderPresent(renderer); +} + +static int InitSprites(void) +{ + /* Create the sprite texture and initialize the sprite positions */ + sprite = CreateTexture(renderer, icon_bmp, icon_bmp_len, &sprite_w, &sprite_h); + + if (!sprite) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite texture"); + return -1; + } + + srand((unsigned int)time(NULL)); + for (int i = 0; i < NUM_SPRITES; ++i) { + positions[i].x = (float)(rand() % (WINDOW_WIDTH - sprite_w)); + positions[i].y = (float)(rand() % (WINDOW_HEIGHT - sprite_h)); + positions[i].w = (float)sprite_w; + positions[i].h = (float)sprite_h; + velocities[i].x = 0.0f; + velocities[i].y = 0.0f; + while (!velocities[i].x && !velocities[i].y) { + velocities[i].x = (float)((rand() % (MAX_SPEED * 2 + 1)) - MAX_SPEED); + velocities[i].y = (float)((rand() % (MAX_SPEED * 2 + 1)) - MAX_SPEED); + } + } + + return 0; +} + +/* Encapsulated in a struct to silence shadow variable warnings */ +static struct _state +{ + /* These are owned by SDL and must not be destroyed! */ + struct wl_display *wl_display; + struct wl_surface *wl_surface; + + /* These are owned by the application and need to be cleaned up on exit. */ + struct wl_registry *wl_registry; + struct xdg_wm_base *xdg_wm_base; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; +} state; + +static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + xdg_surface_ack_configure(state.xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) +{ + /* NOP */ +} + +static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + done = 1; +} + +static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + /* NOP */ +} + +static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) +{ + /* NOP */ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, + .configure_bounds = xdg_toplevel_configure_bounds, + .wm_capabilities = xdg_toplevel_wm_capabilities +}; + +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + xdg_wm_base_pong(state.xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +static void registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) +{ + if (SDL_strcmp(interface, xdg_wm_base_interface.name) == 0) { + state.xdg_wm_base = wl_registry_bind(state.wl_registry, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(state.xdg_wm_base, &xdg_wm_base_listener, NULL); + } +} + +static void registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) +{ + /* NOP */ +} + +static const struct wl_registry_listener wl_registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +int main(int argc, char **argv) +{ + int ret = -1; + SDL_PropertiesID props; + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + return -1; + } + + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Video driver must be 'wayland', not '%s'", SDL_GetCurrentVideoDriver()); + goto exit; + } + + /* Create a window with the custom surface role property set. */ + props = SDL_CreateProperties(); + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_TRUE); /* Roleless surface */ + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_TRUE); /* OpenGL enabled */ + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, WINDOW_WIDTH); /* Default width */ + SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, WINDOW_HEIGHT); /* Default height */ + SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, SDL_TRUE); /* Handle DPI scaling internally */ + SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, "Wayland custom surface role test"); /* Default title */ + + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (!window) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Window creation failed"); + goto exit; + } + + /* Create the renderer */ + renderer = SDL_CreateRenderer(window, NULL, 0); + if (!renderer) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Renderer creation failed"); + goto exit; + } + + /* Get the display object and use it to create a registry object, which will enumerate the xdg_wm_base protocol. */ + state.wl_display = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + state.wl_registry = wl_display_get_registry(state.wl_display); + wl_registry_add_listener(state.wl_registry, &wl_registry_listener, NULL); + + /* Roundtrip to enumerate registry objects. */ + wl_display_roundtrip(state.wl_display); + + if (!state.xdg_wm_base) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "'xdg_wm_base' protocol not found!"); + goto exit; + } + + /* Get the wl_surface object from the SDL_Window, and create a toplevel window with it. */ + state.wl_surface = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + + /* Create the xdg_surface from the wl_surface. */ + state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.wl_surface); + xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, NULL); + + /* Create the xdg_toplevel from the xdg_surface. */ + state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface); + xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title(state.xdg_toplevel, SDL_GetWindowTitle(window)); + + /* Initialize the sprites. */ + if (InitSprites() < 0) { + goto exit; + } + + while (!done) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_KEY_DOWN) { + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + done = 1; + break; + case SDLK_EQUALS: + /* Ctrl+ enlarges the window */ + if (event.key.keysym.mod & SDL_KMOD_CTRL) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + SDL_SetWindowSize(window, w * 2, h * 2); + } + break; + case SDLK_MINUS: + /* Ctrl- shrinks the window */ + if (event.key.keysym.mod & SDL_KMOD_CTRL) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + SDL_SetWindowSize(window, w / 2, h / 2); + } + break; + default: + break; + } + } + } + + /* Draw the sprites */ + MoveSprites(); + } + + ret = 0; + +exit: + /* The display and surface handles obtained from SDL are owned by SDL and must *NOT* be destroyed here! */ + if (state.xdg_toplevel) { + xdg_toplevel_destroy(state.xdg_toplevel); + state.xdg_toplevel = NULL; + } + if (state.xdg_surface) { + xdg_surface_destroy(state.xdg_surface); + state.xdg_surface = NULL; + } + if (state.xdg_wm_base) { + xdg_wm_base_destroy(state.xdg_wm_base); + state.xdg_wm_base = NULL; + } + if (state.wl_registry) { + wl_registry_destroy(state.wl_registry); + state.wl_registry = NULL; + } + + /* Destroy the SDL resources */ + if (sprite) { + SDL_DestroyTexture(sprite); + sprite = NULL; + } + if (renderer) { + SDL_DestroyRenderer(renderer); + renderer = NULL; + } + if (window) { + SDL_DestroyWindow(window); + window = NULL; + } + + SDL_Quit(); + return ret; +} diff --git a/test/testwm.c b/test/testwm.c index 9116700a..b400e23d 100644 --- a/test/testwm.c +++ b/test/testwm.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -34,7 +34,17 @@ static const char *cursorNames[] = { "sizeALL", "NO", "hand", + "window top left", + "window top", + "window top right", + "window right", + "window bottom right", + "window bottom", + "window bottom left", + "window left" }; +SDL_COMPILE_TIME_ASSERT(cursorNames, SDL_arraysize(cursorNames) == SDL_NUM_SYSTEM_CURSORS); + static int system_cursor = -1; static SDL_Cursor *cursor = NULL; static SDL_bool relative_mode = SDL_FALSE; @@ -205,7 +215,7 @@ static void loop(void) } if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { SDL_Window *window = SDL_GetMouseFocus(); - if (highlighted_mode != NULL && window != NULL) { + if (highlighted_mode && window) { SDL_memcpy(&state->fullscreen_mode, highlighted_mode, sizeof(state->fullscreen_mode)); SDL_SetWindowFullscreenMode(window, highlighted_mode); } @@ -215,7 +225,7 @@ static void loop(void) for (i = 0; i < state->num_windows; ++i) { SDL_Window *window = state->windows[i]; SDL_Renderer *renderer = state->renderers[i]; - if (window != NULL && renderer != NULL) { + if (window && renderer) { float y = 0.0f; SDL_Rect viewport; SDL_FRect menurect; @@ -251,23 +261,18 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); - if (state == NULL) { + if (!state) { return 1; } /* Enable standard application logging */ SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); - SDL_assert(SDL_arraysize(cursorNames) == SDL_NUM_SYSTEM_CURSORS); - if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) { SDLTest_CommonQuit(state); return 1; } - SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, SDL_TRUE); - SDL_SetEventEnabled(SDL_EVENT_DROP_TEXT, SDL_TRUE); - for (i = 0; i < state->num_windows; ++i) { SDL_Renderer *renderer = state->renderers[i]; SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); diff --git a/test/testyuv.c b/test/testyuv.c index afe3cac5..960d2321 100644 --- a/test/testyuv.c +++ b/test/testyuv.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -73,7 +73,7 @@ static SDL_bool verify_yuv_data(Uint32 format, const Uint8 *yuv, int yuv_pitch, SDL_bool result = SDL_FALSE; rgb = (Uint8 *)SDL_malloc(size); - if (rgb == NULL) { + if (!rgb) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory"); return SDL_FALSE; } @@ -124,7 +124,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch) int yuv1_pitch, yuv2_pitch; int result = -1; - if (pattern == NULL || yuv1 == NULL || yuv2 == NULL) { + if (!pattern || !yuv1 || !yuv2) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces"); goto done; } @@ -263,7 +263,7 @@ int main(int argc, char **argv) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; } @@ -372,7 +372,7 @@ int main(int argc, char **argv) bmp = SDL_LoadBMP(filename); original = SDL_ConvertSurfaceFormat(bmp, SDL_PIXELFORMAT_RGB24); SDL_DestroySurface(bmp); - if (original == NULL) { + if (!original) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); return 3; } @@ -384,7 +384,7 @@ int main(int argc, char **argv) pitch = CalculateYUVPitch(yuv_format, original->w); converted = SDL_CreateSurface(original->w, original->h, rgb_format); - if (converted == NULL) { + if (!converted) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s\n", SDL_GetError()); return 3; } @@ -397,13 +397,13 @@ int main(int argc, char **argv) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %" SDL_PRIu64 " ms, %.2fms each\n", iterations, (now - then), (float)(now - then) / iterations); window = SDL_CreateWindow("YUV test", original->w, original->h, 0); - if (window == NULL) { + if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError()); return 4; } renderer = SDL_CreateRenderer(window, NULL, 0); - if (renderer == NULL) { + if (!renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError()); return 4; } diff --git a/test/testyuv_cvt.c b/test/testyuv_cvt.c index 3bed5617..80e873e2 100644 --- a/test/testyuv_cvt.c +++ b/test/testyuv_cvt.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/testyuv_cvt.h b/test/testyuv_cvt.h index 781b632e..fa736be3 100644 --- a/test/testyuv_cvt.h +++ b/test/testyuv_cvt.h @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 diff --git a/test/torturethread.c b/test/torturethread.c index c2a0dd1a..fd524b43 100644 --- a/test/torturethread.c +++ b/test/torturethread.c @@ -1,5 +1,5 @@ /* - Copyright (C) 1997-2023 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 @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { + if (!state) { return 1; }