From 91dddfee51b2798dcc6aec8a7b53329779de3e56 Mon Sep 17 00:00:00 2001 From: Ankith Date: Sun, 31 Aug 2025 14:23:50 +0530 Subject: [PATCH 1/7] Add wayland & sdl2-compat specific skips --- buildconfig/stubs/pygame/display.pyi | 3 +++ buildconfig/stubs/pygame/window.pyi | 12 +++++++++++- test/display_test.py | 9 +++++++++ test/event_test.py | 10 ++++++++-- test/surface_test.py | 10 ++++++++++ test/window_test.py | 11 +++++++++-- 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index c46189eadc..077c3e0c49 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -745,6 +745,9 @@ def set_window_position(position: Point) -> None: still be able to move the window after this call. See also :func:`pygame.display.get_window_position()`. + .. note:: This function is not supported on some video drivers (like wayland) + and a :exc:`pygame.error` exception may be raised in such cases. + .. versionadded:: 2.5.0 """ diff --git a/buildconfig/stubs/pygame/window.pyi b/buildconfig/stubs/pygame/window.pyi index f671a8ebb0..f92bb3f9b5 100644 --- a/buildconfig/stubs/pygame/window.pyi +++ b/buildconfig/stubs/pygame/window.pyi @@ -222,6 +222,9 @@ class Window: Setting the always-on-top mode requires SDL 2.0.16+. + .. note:: Setting this property is not supported on some video drivers (like wayland) + and a :exc:`pygame.error` exception may be raised in such cases. + .. versionadded:: 2.3.1 """ @@ -296,12 +299,19 @@ class Window: The position may be a tuple of (x, y) coordinates or ``WINDOWPOS_CENTERED`` or ``WINDOWPOS_UNDEFINED``. The origin is the topleft of the main display. + + .. note:: Setting this property is not supported on some video drivers (like wayland) + and a :exc:`pygame.error` exception may be raised in such cases. """ @position.setter def position(self, value: Union[int, Point]) -> None: ... opacity: float - """Get or set the window opacity, between 0.0 (fully transparent) and 1.0 (fully opaque).""" + """Get or set the window opacity, between 0.0 (fully transparent) and 1.0 (fully opaque). + + .. note:: Setting this property is not supported on some video drivers (like wayland) + and a :exc:`pygame.error` exception may be raised in such cases. + """ @property def opengl(self) -> bool: diff --git a/test/display_test.py b/test/display_test.py index 3d7b91641f..4836f251cb 100644 --- a/test/display_test.py +++ b/test/display_test.py @@ -8,6 +8,10 @@ from pygame import display from pygame.tests.test_utils import question +pygame.display.init() +is_wayland = pygame.display.get_driver() == "wayland" +pygame.display.quit() + class DisplayModuleTest(unittest.TestCase): default_caption = "pygame window" @@ -71,6 +75,7 @@ def test_get_active(self): os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, "requires the SDL_VIDEODRIVER to be a non-null value", ) + @unittest.skipIf(is_wayland, "broken on wayland") def test_get_active_iconify(self): """Test the get_active function after an iconify""" @@ -411,6 +416,7 @@ def test_gl_set_attribute(self): os.environ.get("SDL_VIDEODRIVER") in [pygame.NULL_VIDEODRIVER, "android"], "iconify is only supported on some video drivers/platforms", ) + @unittest.skipIf(is_wayland, "broken on wayland") def test_iconify(self): pygame.display.set_mode((640, 480)) @@ -519,6 +525,7 @@ def test_quit__multiple(self): pygame.version.SDL >= (2, 32, 50), "set_gamma is removed in SDL3, does not work in sdl2-compat either", ) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_set_gamma(self): pygame.display.set_mode((1, 1)) @@ -537,6 +544,7 @@ def test_set_gamma(self): pygame.version.SDL >= (2, 32, 50), "set_gamma is removed in SDL3, does not work in sdl2-compat either", ) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_set_gamma__tuple(self): pygame.display.set_mode((1, 1)) @@ -690,6 +698,7 @@ def test_toggle_fullscreen(self): (test_surf.get_width(), test_surf.get_height()), width_height ) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_get_set_window_position(self): pygame.display.set_mode((500, 500)) pygame.display.set_window_position((420, 360)) diff --git a/test/event_test.py b/test/event_test.py index f4ce259232..73da8a85ec 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -5,6 +5,10 @@ import pygame +pygame.display.init() +is_wayland = pygame.display.get_driver() == "wayland" +pygame.display.quit() + EVENT_TYPES = ( # pygame.NOEVENT, # pygame.ACTIVEEVENT, @@ -837,8 +841,9 @@ def test_pump(self): @unittest.skipIf( os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, - 'requires the SDL_VIDEODRIVER to be a non-null value', + "requires the SDL_VIDEODRIVER to be a non-null value", ) + @unittest.skipIf(is_wayland, "broken on wayland") def test_set_grab__and_get_symmetric(self): """Ensure event grabbing can be enabled and disabled. @@ -905,8 +910,9 @@ def test_get_blocked__event_sequence(self): @unittest.skipIf( os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, - 'requires the SDL_VIDEODRIVER to be a non-null value', + "requires the SDL_VIDEODRIVER to be a non-null value", ) + @unittest.skipIf(is_wayland, "broken on wayland") def test_get_grab(self): """Ensure get_grab() works as expected""" surf = pygame.display.set_mode((10, 10)) diff --git a/test/surface_test.py b/test/surface_test.py index 4272aee0bc..087086c3b6 100644 --- a/test/surface_test.py +++ b/test/surface_test.py @@ -376,6 +376,11 @@ def test_subsurface_rle2(self): self.assertTrue(s1.get_flags() & pygame.RLEACCELOK) self.assertTrue(not s2.get_flags() & pygame.RLEACCELOK) + @unittest.skipIf( + pygame.version.SDL >= (2, 32, 50), + "fails on SDL3, does not work in sdl2-compat either. See: " + "https://github.com/libsdl-org/sdl2-compat/issues/476", + ) def test_solarwolf_rle_usage(self): """Test for error/crash when calling set_colorkey() followed by convert twice in succession. Code originally taken @@ -404,6 +409,11 @@ def optimize(img): finally: pygame.display.quit() + @unittest.skipIf( + pygame.version.SDL >= (2, 32, 50), + "fails on SDL3, does not work in sdl2-compat either. See: " + "https://github.com/libsdl-org/sdl2-compat/issues/476", + ) def test_solarwolf_rle_usage_2(self): """Test for RLE status after setting alpha""" diff --git a/test/window_test.py b/test/window_test.py index 693da90ac9..b8dd1f0056 100644 --- a/test/window_test.py +++ b/test/window_test.py @@ -11,6 +11,10 @@ IS_PYPY = "PyPy" == platform.python_implementation() +pygame.display.init() +is_wayland = pygame.display.get_driver() == "wayland" +pygame.display.quit() + class WindowTypeTest(unittest.TestCase): DEFAULT_TITLE = "pygame window" @@ -102,6 +106,7 @@ def test_always_on_top(self): SDL < (2, 0, 16), "requires SDL 2.0.16+", ) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_always_on_top_set(self): self.win.always_on_top = True self.assertTrue(self.win.always_on_top) @@ -149,6 +154,7 @@ def test_size(self): self.win.size = (640, 480) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_position(self): new_pos = (self.win.position[0] + 20, self.win.position[1] + 10) self.win.position = new_pos @@ -160,8 +166,8 @@ def test_position(self): self.assertRaises(TypeError, lambda: setattr(self.win, "position", 123)) # test set position when init - win = Window(position=(20, 48)) - self.assertTupleEqual((20, 48), win.position) + win = Window(position=new_pos) + self.assertTupleEqual(new_pos, win.position) win.destroy() self.assertRaises(TypeError, lambda: Window(position=123)) @@ -255,6 +261,7 @@ def test_opacity(self): os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER, "requires the SDL_VIDEODRIVER to be a non-null value", ) + @unittest.skipIf(is_wayland, "not supported on wayland") def test_opacity_set(self): self.win.opacity = 0.5 self.assertEqual(self.win.opacity, 0.5) From 5f2b99e453b63134ae04cd37e5d7da50377f6995 Mon Sep 17 00:00:00 2001 From: Ankith Date: Mon, 18 Aug 2025 14:44:55 +0530 Subject: [PATCH 2/7] SDL3: event: runtime issue fixes --- src_c/_pygame.h | 59 ++- src_c/constants.c | 4 +- src_c/event.c | 68 ++- src_c/include/SDL_gesture.h | 924 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1020 insertions(+), 35 deletions(-) create mode 100644 src_c/include/SDL_gesture.h diff --git a/src_c/_pygame.h b/src_c/_pygame.h index c9d88a98b2..61d6b2ff15 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -50,6 +50,13 @@ #include "stdbool.h" #if SDL_VERSION_ATLEAST(3, 0, 0) + +#include "include/SDL_gesture.h" + +#define SDL_DOLLARGESTURE GESTURE_DOLLARGESTURE +#define SDL_DOLLARRECORD GESTURE_DOLLARRECORD +#define SDL_MULTIGESTURE GESTURE_MULTIGESTURE + #define PG_ShowCursor SDL_ShowCursor #define PG_HideCursor SDL_HideCursor #define PG_CursorVisible SDL_CursorVisible @@ -68,12 +75,6 @@ #define PG_AUDIO_ALLOW_CHANNELS_CHANGE 0 #define PG_AUDIO_ALLOW_ANY_CHANGE 0 -// Todo: deal with multigesture.. See -// https://github.com/pygame-community/pygame-ce/issues/2420 -#define PG_MULTIGESTURE 0 - -#define PG_JOYBALLMOTION 0 - #define PG_CreateSurface SDL_CreateSurface #define PG_CreateSurfaceFrom SDL_CreateSurfaceFrom #define PG_ConvertSurface SDL_ConvertSurface @@ -183,10 +184,6 @@ PG_GetSurfaceFormat(SDL_Surface *surf) #define PG_AUDIO_ALLOW_CHANNELS_CHANGE SDL_AUDIO_ALLOW_CHANNELS_CHANGE #define PG_AUDIO_ALLOW_ANY_CHANGE SDL_AUDIO_ALLOW_ANY_CHANGE -#define PG_MULTIGESTURE SDL_MULTIGESTURE - -#define PG_JOYBALLMOTION SDL_JOYBALLMOTION - #define PG_CreateSurface(width, height, format) \ SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, format) #define PG_CreateSurfaceFrom(width, height, format, pixels, pitch) \ @@ -425,6 +422,40 @@ typedef enum { PGM_BUTTON_KEEP = 0x80 } PygameMouseFlags; +#if SDL_VERSION_ATLEAST(3, 0, 0) +typedef enum { + PGE_WINDOWSHOWN = SDL_EVENT_WINDOW_SHOWN, + PGE_WINDOWHIDDEN = SDL_EVENT_WINDOW_HIDDEN, + PGE_WINDOWEXPOSED = SDL_EVENT_WINDOW_EXPOSED, + PGE_WINDOWMOVED = SDL_EVENT_WINDOW_MOVED, + PGE_WINDOWRESIZED = SDL_EVENT_WINDOW_RESIZED, + PGE_WINDOWSIZECHANGED = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, + PGE_WINDOWMINIMIZED = SDL_EVENT_WINDOW_MINIMIZED, + PGE_WINDOWMAXIMIZED = SDL_EVENT_WINDOW_MAXIMIZED, + PGE_WINDOWRESTORED = SDL_EVENT_WINDOW_RESTORED, + PGE_WINDOWENTER = SDL_EVENT_WINDOW_MOUSE_ENTER, + PGE_WINDOWLEAVE = SDL_EVENT_WINDOW_MOUSE_LEAVE, + PGE_WINDOWFOCUSGAINED = SDL_EVENT_WINDOW_FOCUS_GAINED, + PGE_WINDOWFOCUSLOST = SDL_EVENT_WINDOW_FOCUS_LOST, + PGE_WINDOWCLOSE = SDL_EVENT_WINDOW_CLOSE_REQUESTED, + PGE_WINDOWTAKEFOCUS = -1, /* No SDL3 equivalent */ + PGE_WINDOWHITTEST = SDL_EVENT_WINDOW_HIT_TEST, + PGE_WINDOWICCPROFCHANGED = SDL_EVENT_WINDOW_ICCPROF_CHANGED, + PGE_WINDOWDISPLAYCHANGED = SDL_EVENT_WINDOW_DISPLAY_CHANGED, +} PygameWindowEventCode; +/* +TODO: expose these window events in pygame API + SDL_EVENT_WINDOW_METAL_VIEW_RESIZED, + SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED, + SDL_EVENT_WINDOW_SAFE_AREA_CHANGED, + SDL_EVENT_WINDOW_OCCLUDED, + SDL_EVENT_WINDOW_ENTER_FULLSCREEN, + SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, + SDL_EVENT_WINDOW_DESTROYED, + SDL_EVENT_WINDOW_HDR_STATE_CHANGED, +*/ +#endif + typedef enum { /* Any SDL_* events here are for backward compatibility. */ SDL_NOEVENT = 0, @@ -440,6 +471,9 @@ typedef enum { PGE_MIDIIN, PGE_MIDIOUT, +/* These PGE events are only needed on SDL2: SDL3 has dedicated events for + * these */ +#if !SDL_VERSION_ATLEAST(3, 0, 0) /* DO NOT CHANGE THE ORDER OF EVENTS HERE */ PGE_WINDOWSHOWN, PGE_WINDOWHIDDEN, @@ -459,6 +493,7 @@ typedef enum { PGE_WINDOWHITTEST, PGE_WINDOWICCPROFCHANGED, PGE_WINDOWDISPLAYCHANGED, +#endif /* Here we define PGPOST_* events, events that act as a one-to-one * proxy for SDL events (and some extra events too!), the proxy is used @@ -494,10 +529,8 @@ typedef enum { PGPOST_CONTROLLERTOUCHPADMOTION, PGPOST_CONTROLLERTOUCHPADUP, PGPOST_CONTROLLERSENSORUPDATE, -#if !SDL_VERSION_ATLEAST(3, 0, 0) PGPOST_DOLLARGESTURE, PGPOST_DOLLARRECORD, -#endif PGPOST_DROPFILE, PGPOST_DROPTEXT, PGPOST_DROPBEGIN, @@ -522,9 +555,7 @@ typedef enum { PGPOST_MOUSEBUTTONDOWN, PGPOST_MOUSEBUTTONUP, PGPOST_MOUSEWHEEL, -#if !SDL_VERSION_ATLEAST(3, 0, 0) PGPOST_MULTIGESTURE, -#endif PGPOST_NOEVENT, PGPOST_QUIT, PGPOST_RENDER_TARGETS_RESET, diff --git a/src_c/constants.c b/src_c/constants.c index cbbaf5c97d..3ed987be38 100644 --- a/src_c/constants.c +++ b/src_c/constants.c @@ -260,7 +260,7 @@ MODINIT_DEFINE(constants) DEC_CONST(MOUSEBUTTONDOWN); DEC_CONST(MOUSEBUTTONUP); DEC_CONST(JOYAXISMOTION); - DEC_CONSTS(JOYBALLMOTION, PG_JOYBALLMOTION); + DEC_CONST(JOYBALLMOTION); DEC_CONST(JOYHATMOTION); DEC_CONST(JOYBUTTONDOWN); DEC_CONST(JOYBUTTONUP); @@ -302,7 +302,7 @@ MODINIT_DEFINE(constants) DEC_CONST(FINGERMOTION); DEC_CONST(FINGERDOWN); DEC_CONST(FINGERUP); - DEC_CONSTS(MULTIGESTURE, PG_MULTIGESTURE); + DEC_CONST(MULTIGESTURE); DEC_CONST(AUDIODEVICEADDED); DEC_CONST(AUDIODEVICEREMOVED); DEC_CONST(MOUSEWHEEL); diff --git a/src_c/event.c b/src_c/event.c index e01c26c601..232b6d331e 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -25,6 +25,7 @@ */ #define PYGAMEAPI_EVENT_INTERNAL +#define SDL_GESTURE_IMPLEMENTATION 1 #include "pygame.h" #include "pgcompat.h" @@ -401,10 +402,8 @@ _pg_pgevent_proxify_helper(Uint32 type, Uint8 proxify) _PG_HANDLE_PROXIFY(CONTROLLERTOUCHPADMOTION); _PG_HANDLE_PROXIFY(CONTROLLERTOUCHPADUP); _PG_HANDLE_PROXIFY(CONTROLLERSENSORUPDATE); -#if !SDL_VERSION_ATLEAST(3, 0, 0) _PG_HANDLE_PROXIFY(DOLLARGESTURE); _PG_HANDLE_PROXIFY(DOLLARRECORD); -#endif _PG_HANDLE_PROXIFY(DROPFILE); _PG_HANDLE_PROXIFY(DROPTEXT); _PG_HANDLE_PROXIFY(DROPBEGIN); @@ -427,9 +426,7 @@ _pg_pgevent_proxify_helper(Uint32 type, Uint8 proxify) _PG_HANDLE_PROXIFY(MOUSEBUTTONDOWN); _PG_HANDLE_PROXIFY(MOUSEBUTTONUP); _PG_HANDLE_PROXIFY(MOUSEWHEEL); -#if !SDL_VERSION_ATLEAST(3, 0, 0) _PG_HANDLE_PROXIFY(MULTIGESTURE); -#endif _PG_HANDLE_PROXIFY(NOEVENT); _PG_HANDLE_PROXIFY(QUIT); _PG_HANDLE_PROXIFY(RENDER_TARGETS_RESET); @@ -493,12 +490,8 @@ _pg_pgevent_type(SDL_Event *event) * Currently this only includes WINDOWEVENT, but can be expanded in the * future. */ -#if SDL_VERSION_ATLEAST(3, 0, 0) -static bool SDLCALL -#else -static int SDLCALL -#endif -_pg_filter_blocked_events(void *_, SDL_Event *event) +static bool +_pg_event_psuedo_block(SDL_Event *event) { #if SDL_VERSION_ATLEAST(3, 0, 0) if (event->type >= SDL_EVENT_WINDOW_FIRST && @@ -506,6 +499,19 @@ _pg_filter_blocked_events(void *_, SDL_Event *event) #else if (event->type == SDL_WINDOWEVENT) { #endif + return true; + } + return false; +} + +#if SDL_VERSION_ATLEAST(3, 0, 0) +static bool SDLCALL +#else +static int SDLCALL +#endif +_pg_filter_blocked_events(void *_, SDL_Event *event) +{ + if (_pg_event_psuedo_block(event)) { return PG_EventEnabled(_pg_pgevent_proxify(_pg_pgevent_type(event))); } return 1; @@ -732,14 +738,15 @@ pg_event_filter(void *_, SDL_Event *event) return RAISE(pgExc_SDLError, SDL_GetError()), 0; */ } - /* TODO: + /* * Any event that gets blocked here will not be visible to the event * watchers. So things like WINDOWEVENT should never be blocked here. - * This is taken care of in SDL2 codepaths already but needs to also - * be verified in SDL3 porting. * If the user requests a block on WINDOWEVENTs we are going to handle * it specially and call it a "pseudo-block", where the filtering will * happen in a _pg_filter_blocked_events call. */ + if (_pg_event_psuedo_block(event)) { + return 1; + } return PG_EventEnabled(_pg_pgevent_proxify(event->type)); } @@ -773,6 +780,9 @@ static PyObject * pgEvent_AutoQuit(PyObject *self, PyObject *_null) { if (_pg_event_is_init) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + Gesture_Quit(); +#endif PG_LOCK_EVFILTER_MUTEX if (_pg_repeat_timer) { SDL_RemoveTimer(_pg_repeat_timer); @@ -805,6 +815,11 @@ pgEvent_AutoInit(PyObject *self, PyObject *_null) } #endif SDL_SetEventFilter(pg_event_filter, NULL); +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (Gesture_Init() != 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#endif } _pg_event_is_init = 1; Py_RETURN_NONE; @@ -936,10 +951,8 @@ _pg_name_from_eventtype(int type) return "FingerDown"; case SDL_FINGERUP: return "FingerUp"; -#if !SDL_VERSION_ATLEAST(3, 0, 0) case SDL_MULTIGESTURE: return "MultiGesture"; -#endif case SDL_MOUSEWHEEL: return "MouseWheel"; case SDL_TEXTINPUT: @@ -1142,7 +1155,6 @@ dict_from_event(SDL_Event *event) state = SDL_APPACTIVE; break; default: - assert(event->window.event == SDL_WINDOWEVENT_RESTORED); gain = 1; state = SDL_APPACTIVE; } @@ -1294,9 +1306,27 @@ dict_from_event(SDL_Event *event) _pg_insobj(dict, "pressure", PyFloat_FromDouble(event->tfinger.dy)); break; -#if !SDL_VERSION_ATLEAST(3, 0, 0) case SDL_MULTIGESTURE: - /* https://wiki.libsdl.org/SDL_MultiGestureEvent */ +#if SDL_VERSION_ATLEAST(3, 0, 0) + _pg_insobj(dict, "touch_id", + PyLong_FromLongLong( + ((Gesture_MultiGestureEvent *)event)->touchID)); + _pg_insobj( + dict, "x", + PyFloat_FromDouble(((Gesture_MultiGestureEvent *)event)->x)); + _pg_insobj( + dict, "y", + PyFloat_FromDouble(((Gesture_MultiGestureEvent *)event)->y)); + _pg_insobj(dict, "rotated", + PyFloat_FromDouble( + ((Gesture_MultiGestureEvent *)event)->dTheta)); + _pg_insobj(dict, "pinched", + PyFloat_FromDouble( + ((Gesture_MultiGestureEvent *)event)->dDist)); + _pg_insobj(dict, "num_fingers", + PyLong_FromLong( + ((Gesture_MultiGestureEvent *)event)->numFingers)); +#else _pg_insobj(dict, "touch_id", PyLong_FromLongLong(event->mgesture.touchId)); _pg_insobj(dict, "x", PyFloat_FromDouble(event->mgesture.x)); @@ -1307,8 +1337,8 @@ dict_from_event(SDL_Event *event) PyFloat_FromDouble(event->mgesture.dDist)); _pg_insobj(dict, "num_fingers", PyLong_FromLong(event->mgesture.numFingers)); - break; #endif + break; case SDL_MOUSEWHEEL: /* https://wiki.libsdl.org/SDL_MouseWheelEvent */ #ifndef NO_SDL_MOUSEWHEEL_FLIPPED diff --git a/src_c/include/SDL_gesture.h b/src_c/include/SDL_gesture.h new file mode 100644 index 0000000000..5566ea3cb6 --- /dev/null +++ b/src_c/include/SDL_gesture.h @@ -0,0 +1,924 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + 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. +*/ + +/* Touch gestures were removed from SDL3, so this is the SDL2 implementation + * copied in here, and tweaked a little. */ + +#ifndef INCL_SDL_GESTURE_H +#define INCL_SDL_GESTURE_H + +#if !defined(SDL_MAJOR_VERSION) +#error Please include SDL.h before including this header. +#elif SDL_MAJOR_VERSION < 2 +#error This header requires SDL2 or later. +#elif SDL_MAJOR_VERSION == 2 +/* building against SDL2? Just use the built-in SDL2 implementation. */ +#define Gesture_Init() (0) +#define Gesture_Quit() +#define Gesture_ID SDL_GestureID +#define Gesture_LoadDollarTemplates SDL_LoadDollarTemplates +#define Gesture_RecordGesture SDL_RecordGesture +#define Gesture_SaveAllDollarTemplates SDL_SaveAllDollarTemplates +#define Gesture_SaveDollarTemplate SDL_SaveDollarTemplate +#define GESTURE_DOLLARGESTURE SDL_DOLLARGESTURE +#define GESTURE_DOLLARRECORD SDL_DOLLARRECORD +#define GESTURE_MULTIGESTURE SDL_MULTIGESTURE +#define Gesture_MultiGestureEvent SDL_MultiGestureEvent +#define Gesture_DollarGestureEvent SDL_DollarGestureEvent +#else + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef Sint64 Gesture_ID; + +/* events... */ + +/* generally you shouldn't hardcode event type numbers--and doubly so in + the reserved range!--but these match SDL2 and SDL3 promises to preserve + these values to help sdl2-compat. */ +#define GESTURE_DOLLARGESTURE 0x800 +#define GESTURE_DOLLARRECORD 0x801 +#define GESTURE_MULTIGESTURE 0x802 + +typedef struct Gesture_MultiGestureEvent { + Uint32 type; + Uint32 reserved; + Uint64 timestamp; + SDL_TouchID touchID; + float dTheta; + float dDist; + float x; + float y; + Uint16 numFingers; + Uint16 padding; +} Gesture_MultiGestureEvent; + +typedef struct Gesture_DollarGestureEvent { + Uint32 type; + Uint32 reserved; + Uint64 timestamp; + SDL_TouchID touchID; + Gesture_ID gestureId; + Uint32 numFingers; + float error; + float x; + float y; +} Gesture_DollarGestureEvent; + +/* Function prototypes */ + +/** + * Call this once, AFTER SDL_Init, to set up the Gesture API. + * + * \returns 0 on success, -1 on error. Call SDL_GetError() for specifics. + */ +extern int SDLCALL +Gesture_Init(void); + +/** + * Call this once, BEFORE SDL_Quit, to clean up the Gesture API. + */ +extern void SDLCALL +Gesture_Quit(void); + +/** + * Begin recording a gesture on a specified touch device or all touch devices. + * + * If the parameter `touchID` is -1 (i.e., all devices), this function will + * always return 1, regardless of whether there actually are any devices. + * + * \param touchID the touch device id, or -1 for all touch devices + * \returns 1 on success or 0 if the specified device could not be found. + */ +extern int SDLCALL +Gesture_RecordGesture(SDL_TouchID touchID); + +/** + * Save all currently loaded Dollar Gesture templates. + * + * \param dst a SDL_IOStream to save to + * \returns the number of saved templates on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa Gesture_LoadDollarTemplates + * \sa Gesture_SaveDollarTemplate + */ +extern int SDLCALL +Gesture_SaveAllDollarTemplates(SDL_IOStream *dst); + +/** + * Save a currently loaded Dollar Gesture template. + * + * \param gestureId a gesture id + * \param dst a SDL_IOStream to save to + * \returns 1 on success or 0 on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadDollarTemplates + * \sa SDL_SaveAllDollarTemplates + */ +extern int SDLCALL +Gesture_SaveDollarTemplate(Gesture_ID gestureId, SDL_IOStream *dst); + +/** + * Load Dollar Gesture templates from a file. + * + * \param touchID a touch id + * \param src a SDL_IOStream to load from + * \returns the number of loaded templates on success or a negative error code + * (or 0) on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SaveAllDollarTemplates + * \sa SDL_SaveDollarTemplate + */ +extern int SDLCALL +Gesture_LoadDollarTemplates(SDL_TouchID touchID, SDL_IOStream *src); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#if defined(SDL_GESTURE_IMPLEMENTATION) + +#define GESTURE_MAX_DOLLAR_PATH_SIZE 1024 +#define GESTURE_DOLLARNPOINTS 64 +#define GESTURE_DOLLARSIZE 256 +#define GESTURE_PHI 0.618033989 + +typedef struct { + float length; + int numPoints; + SDL_FPoint p[GESTURE_MAX_DOLLAR_PATH_SIZE]; +} GestureDollarPath; + +typedef struct { + SDL_FPoint path[GESTURE_DOLLARNPOINTS]; + unsigned long hash; +} GestureDollarTemplate; + +typedef struct { + SDL_TouchID touchID; + SDL_FPoint centroid; + GestureDollarPath dollarPath; + Uint16 numDownFingers; + int numDollarTemplates; + GestureDollarTemplate *dollarTemplate; + bool recording; +} GestureTouch; + +static GestureTouch *GestureTouches = NULL; +static int GestureNumTouches = 0; +static bool GestureRecordAll = false; + +static void +GestureProcessEvent(const SDL_Event *event); + +static bool SDLCALL +GestureEventWatch(void *userdata, SDL_Event *event) +{ + GestureProcessEvent(event); + return true; +} + +int +Gesture_Init(void) +{ + Gesture_Quit(); + SDL_AddEventWatch(GestureEventWatch, NULL); + return 0; +} + +static GestureTouch * +GestureAddTouch(const SDL_TouchID touchID) +{ + GestureTouch *gestureTouch = (GestureTouch *)SDL_realloc( + GestureTouches, (GestureNumTouches + 1) * sizeof(GestureTouch)); + if (gestureTouch == NULL) { + SDL_OutOfMemory(); + return NULL; + } + + GestureTouches = gestureTouch; + SDL_zero(GestureTouches[GestureNumTouches]); + GestureTouches[GestureNumTouches].touchID = touchID; + return &GestureTouches[GestureNumTouches++]; +} + +/* pygame-ce specific patch: + * This has been commented out to silence compiler warnings of this function + * being unused. */ +#if 0 +static int GestureDelTouch(const SDL_TouchID touchID) +{ + int i; + for (i = 0; i < GestureNumTouches; i++) { + if (GestureTouches[i].touchID == touchID) { + break; + } + } + + if (i == GestureNumTouches) { + /* not found */ + return -1; + } + + SDL_free(GestureTouches[i].dollarTemplate); + SDL_zero(GestureTouches[i]); + + GestureNumTouches--; + if (i != GestureNumTouches) { + SDL_copyp(&GestureTouches[i], &GestureTouches[GestureNumTouches]); + } + return 0; +} +#endif + +static GestureTouch * +GestureGetTouch(const SDL_TouchID touchID) +{ + int i; + for (i = 0; i < GestureNumTouches; i++) { + /* printf("%i ?= %i\n",GestureTouches[i].touchID,touchID); */ + if (GestureTouches[i].touchID == touchID) { + return &GestureTouches[i]; + } + } + return NULL; +} + +int +Gesture_RecordGesture(SDL_TouchID touchID) +{ + SDL_TouchID *devices; + int i; + + devices = SDL_GetTouchDevices(NULL); + if (devices) { + /* make sure we know about all the devices SDL3 knows about, since we + * aren't connected as tightly as we were in SDL2. */ + for (i = 0; devices[i]; i++) { + if (!GestureGetTouch(devices[i])) { + GestureAddTouch(devices[i]); + } + } + SDL_free(devices); + } + + if (touchID != 0) { + GestureRecordAll = true; /* !!! FIXME: this is never set back to false + anywhere, that's probably a bug. */ + for (i = 0; i < GestureNumTouches; i++) { + GestureTouches[i].recording = true; + } + } + else { + GestureTouch *touch = GestureGetTouch(touchID); + if (!touch) { + return 0; /* bogus touchid */ + } + touch->recording = true; + } + + return 1; +} + +void +Gesture_Quit(void) +{ + SDL_RemoveEventWatch(GestureEventWatch, NULL); + SDL_free(GestureTouches); + GestureTouches = NULL; + GestureNumTouches = 0; + GestureRecordAll = false; +} + +static unsigned long +GestureHashDollar(SDL_FPoint *points) +{ + unsigned long hash = 5381; + int i; + for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) { + hash = ((hash << 5) + hash) + (unsigned long)points[i].x; + hash = ((hash << 5) + hash) + (unsigned long)points[i].y; + } + return hash; +} + +static int +GestureSaveTemplate(GestureDollarTemplate *templ, SDL_IOStream *dst) +{ + const size_t bytes = sizeof(templ->path[0]) * GESTURE_DOLLARNPOINTS; + + if (dst == NULL) { + return 0; + } + + /* No Longer storing the Hash, rehash on load */ + /* if (SDL_IOWrite(dst, &(templ->hash), sizeof(templ->hash)) != + * sizeof(templ->hash)) return 0; */ + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + if (SDL_WriteIO(dst, templ->path, bytes) != bytes) { + return 0; + } +#else + { + GestureDollarTemplate copy = *templ; + SDL_FPoint *p = copy.path; + int i; + for (i = 0; i < GESTURE_DOLLARNPOINTS; i++, p++) { + p->x = SDL_SwapFloatLE(p->x); + p->y = SDL_SwapFloatLE(p->y); + } + + if (SDL_WriteIO(dst, copy.path, bytes) != bytes) { + return 0; + } + } +#endif + + return 1; +} + +SDL_DECLSPEC int SDLCALL +Gesture_SaveAllDollarTemplates(SDL_IOStream *dst) +{ + int i, j, rtrn = 0; + for (i = 0; i < GestureNumTouches; i++) { + GestureTouch *touch = &GestureTouches[i]; + for (j = 0; j < touch->numDollarTemplates; j++) { + rtrn += GestureSaveTemplate(&touch->dollarTemplate[j], dst); + } + } + return rtrn; +} + +SDL_DECLSPEC int SDLCALL +Gesture_SaveDollarTemplate(Gesture_ID gestureId, SDL_IOStream *dst) +{ + int i, j; + for (i = 0; i < GestureNumTouches; i++) { + GestureTouch *touch = &GestureTouches[i]; + for (j = 0; j < touch->numDollarTemplates; j++) { + if (touch->dollarTemplate[j].hash == gestureId) { + return GestureSaveTemplate(&touch->dollarTemplate[j], dst); + } + } + } + return SDL_SetError("Unknown gestureId"); +} + +/* path is an already sampled set of points +Returns the index of the gesture on success, or -1 */ +static int +GestureAddDollar_one(GestureTouch *inTouch, SDL_FPoint *path) +{ + GestureDollarTemplate *dollarTemplate; + GestureDollarTemplate *templ; + int index; + + index = inTouch->numDollarTemplates; + dollarTemplate = (GestureDollarTemplate *)SDL_realloc( + inTouch->dollarTemplate, (index + 1) * sizeof(GestureDollarTemplate)); + if (dollarTemplate == NULL) { + return SDL_OutOfMemory(); + } + inTouch->dollarTemplate = dollarTemplate; + + templ = &inTouch->dollarTemplate[index]; + SDL_memcpy(templ->path, path, GESTURE_DOLLARNPOINTS * sizeof(SDL_FPoint)); + templ->hash = GestureHashDollar(templ->path); + inTouch->numDollarTemplates++; + + return index; +} + +static int +GestureAddDollar(GestureTouch *inTouch, SDL_FPoint *path) +{ + int index = -1; + int i = 0; + if (inTouch == NULL) { + if (GestureNumTouches == 0) { + return SDL_SetError("no gesture touch devices registered"); + } + for (i = 0; i < GestureNumTouches; i++) { + inTouch = &GestureTouches[i]; + index = GestureAddDollar_one(inTouch, path); + if (index < 0) { + return -1; + } + } + /* Use the index of the last one added. */ + return index; + } + return GestureAddDollar_one(inTouch, path); +} + +SDL_DECLSPEC int SDLCALL +Gesture_LoadDollarTemplates(SDL_TouchID touchID, SDL_IOStream *src) +{ + int i, loaded = 0; + GestureTouch *touch = NULL; + if (src == NULL) { + return 0; + } + if (touchID >= 0) { + for (i = 0; i < GestureNumTouches; i++) { + if (GestureTouches[i].touchID == touchID) { + touch = &GestureTouches[i]; + } + } + if (touch == NULL) { + return SDL_SetError("given touch id not found"); + } + } + + while (1) { + GestureDollarTemplate templ; + const size_t bytes = sizeof(templ.path[0]) * GESTURE_DOLLARNPOINTS; + + if (SDL_ReadIO(src, templ.path, bytes) < bytes) { + if (loaded == 0) { + return SDL_SetError( + "could not read any dollar gesture from rwops"); + } + break; + } + +#if SDL_BYTEORDER != SDL_LIL_ENDIAN + for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) { + SDL_FPoint *p = &templ.path[i]; + p->x = SDL_SwapFloatLE(p->x); + p->y = SDL_SwapFloatLE(p->y); + } +#endif + + if (touchID >= 0) { + /* printf("Adding loaded gesture to 1 touch\n"); */ + if (GestureAddDollar(touch, templ.path) >= 0) { + loaded++; + } + } + else { + /* printf("Adding to: %i touches\n",GestureNumTouches); */ + for (i = 0; i < GestureNumTouches; i++) { + touch = &GestureTouches[i]; + /* printf("Adding loaded gesture to + touches\n"); */ + /* TODO: What if this fails? */ + GestureAddDollar(touch, templ.path); + } + loaded++; + } + } + + return loaded; +} + +static float +GestureDollarDifference(SDL_FPoint *points, SDL_FPoint *templ, float ang) +{ + /* SDL_FPoint p[GESTURE_DOLLARNPOINTS]; */ + float dist = 0; + SDL_FPoint p; + int i; + for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) { + p.x = points[i].x * SDL_cosf(ang) - points[i].y * SDL_sinf(ang); + p.y = points[i].x * SDL_sinf(ang) + points[i].y * SDL_cosf(ang); + dist += SDL_sqrtf((p.x - templ[i].x) * (p.x - templ[i].x) + + (p.y - templ[i].y) * (p.y - templ[i].y)); + } + return dist / GESTURE_DOLLARNPOINTS; +} + +static float +GestureBestDollarDifference(SDL_FPoint *points, SDL_FPoint *templ) +{ + /*------------BEGIN DOLLAR BLACKBOX------------------ + -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT- + -"http://depts.washington.edu/aimgroup/proj/dollar/" + */ + double ta = -SDL_PI_D / 4; + double tb = SDL_PI_D / 4; + double dt = SDL_PI_D / 90; + float x1 = (float)(GESTURE_PHI * ta + (1 - GESTURE_PHI) * tb); + float f1 = GestureDollarDifference(points, templ, x1); + float x2 = (float)((1 - GESTURE_PHI) * ta + GESTURE_PHI * tb); + float f2 = GestureDollarDifference(points, templ, x2); + while (SDL_fabs(ta - tb) > dt) { + if (f1 < f2) { + tb = x2; + x2 = x1; + f2 = f1; + x1 = (float)(GESTURE_PHI * ta + (1 - GESTURE_PHI) * tb); + f1 = GestureDollarDifference(points, templ, x1); + } + else { + ta = x1; + x1 = x2; + f1 = f2; + x2 = (float)((1 - GESTURE_PHI) * ta + GESTURE_PHI * tb); + f2 = GestureDollarDifference(points, templ, x2); + } + } + /* + if (f1 <= f2) + printf("Min angle (x1): %f\n",x1); + else if (f1 > f2) + printf("Min angle (x2): %f\n",x2); + */ + return SDL_min(f1, f2); +} + +/* `path` contains raw points, plus (possibly) the calculated length */ +static int +GestureDollarNormalize(const GestureDollarPath *path, SDL_FPoint *points, + bool is_recording) +{ + int i; + float interval; + float dist; + int numPoints = 0; + SDL_FPoint centroid; + float xmin, xmax, ymin, ymax; + float ang; + float w, h; + float length = path->length; + + /* Calculate length if it hasn't already been done */ + if (length <= 0) { + for (i = 1; i < path->numPoints; i++) { + const float dx = path->p[i].x - path->p[i - 1].x; + const float dy = path->p[i].y - path->p[i - 1].y; + length += SDL_sqrtf(dx * dx + dy * dy); + } + } + + /* Resample */ + interval = length / (GESTURE_DOLLARNPOINTS - 1); + dist = interval; + + centroid.x = 0; + centroid.y = 0; + + /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); + */ + for (i = 1; i < path->numPoints; i++) { + const float d = SDL_sqrtf((path->p[i - 1].x - path->p[i].x) * + (path->p[i - 1].x - path->p[i].x) + + (path->p[i - 1].y - path->p[i].y) * + (path->p[i - 1].y - path->p[i].y)); + /* printf("d = %f dist = %f/%f\n",d,dist,interval); */ + while (dist + d > interval) { + points[numPoints].x = + path->p[i - 1].x + + ((interval - dist) / d) * (path->p[i].x - path->p[i - 1].x); + points[numPoints].y = + path->p[i - 1].y + + ((interval - dist) / d) * (path->p[i].y - path->p[i - 1].y); + centroid.x += points[numPoints].x; + centroid.y += points[numPoints].y; + numPoints++; + + dist -= interval; + } + dist += d; + } + if (numPoints < GESTURE_DOLLARNPOINTS - 1) { + if (is_recording) { + SDL_SetError("ERROR: NumPoints = %i", numPoints); + } + return 0; + } + /* copy the last point */ + points[GESTURE_DOLLARNPOINTS - 1] = path->p[path->numPoints - 1]; + numPoints = GESTURE_DOLLARNPOINTS; + + centroid.x /= numPoints; + centroid.y /= numPoints; + + /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */ + /* Rotate Points so point 0 is left of centroid and solve for the bounding + * box */ + xmin = centroid.x; + xmax = centroid.x; + ymin = centroid.y; + ymax = centroid.y; + + ang = SDL_atan2f(centroid.y - points[0].y, centroid.x - points[0].x); + + for (i = 0; i < numPoints; i++) { + const float px = points[i].x; + const float py = points[i].y; + points[i].x = (px - centroid.x) * SDL_cosf(ang) - + (py - centroid.y) * SDL_sinf(ang) + centroid.x; + points[i].y = (px - centroid.x) * SDL_sinf(ang) + + (py - centroid.y) * SDL_cosf(ang) + centroid.y; + + if (points[i].x < xmin) { + xmin = points[i].x; + } + if (points[i].x > xmax) { + xmax = points[i].x; + } + if (points[i].y < ymin) { + ymin = points[i].y; + } + if (points[i].y > ymax) { + ymax = points[i].y; + } + } + + /* Scale points to GESTURE_DOLLARSIZE, and translate to the origin */ + w = xmax - xmin; + h = ymax - ymin; + + for (i = 0; i < numPoints; i++) { + points[i].x = (points[i].x - centroid.x) * GESTURE_DOLLARSIZE / w; + points[i].y = (points[i].y - centroid.y) * GESTURE_DOLLARSIZE / h; + } + return numPoints; +} + +static float +GestureDollarRecognize(const GestureDollarPath *path, int *bestTempl, + GestureTouch *touch) +{ + SDL_FPoint points[GESTURE_DOLLARNPOINTS]; + int i; + float bestDiff = 10000; + + SDL_memset(points, 0, sizeof(points)); + + GestureDollarNormalize(path, points, false); + + /* PrintPath(points); */ + *bestTempl = -1; + for (i = 0; i < touch->numDollarTemplates; i++) { + const float diff = + GestureBestDollarDifference(points, touch->dollarTemplate[i].path); + if (diff < bestDiff) { + bestDiff = diff; + *bestTempl = i; + } + } + return bestDiff; +} + +static void +GestureSendMulti(GestureTouch *touch, float dTheta, float dDist) +{ + if (SDL_EventEnabled(GESTURE_MULTIGESTURE)) { + Gesture_MultiGestureEvent mgesture; + mgesture.type = GESTURE_MULTIGESTURE; + mgesture.timestamp = 0; + mgesture.touchID = touch->touchID; + mgesture.x = touch->centroid.x; + mgesture.y = touch->centroid.y; + mgesture.dTheta = dTheta; + mgesture.dDist = dDist; + mgesture.numFingers = touch->numDownFingers; + SDL_PushEvent((SDL_Event *)&mgesture); + } +} + +static void +GestureSendDollar(GestureTouch *touch, Gesture_ID gestureId, float error) +{ + if (SDL_EventEnabled(GESTURE_DOLLARGESTURE)) { + Gesture_DollarGestureEvent dgesture; + dgesture.type = GESTURE_DOLLARGESTURE; + dgesture.timestamp = 0; + dgesture.touchID = touch->touchID; + dgesture.x = touch->centroid.x; + dgesture.y = touch->centroid.y; + dgesture.gestureId = gestureId; + dgesture.error = error; + /* A finger came up to trigger this event. */ + dgesture.numFingers = touch->numDownFingers + 1; + SDL_PushEvent((SDL_Event *)&dgesture); + } +} + +static void +GestureSendDollarRecord(GestureTouch *touch, Gesture_ID gestureId) +{ + if (SDL_EventEnabled(GESTURE_DOLLARRECORD)) { + Gesture_DollarGestureEvent dgesture; + dgesture.type = GESTURE_DOLLARRECORD; + dgesture.timestamp = 0; + dgesture.touchID = touch->touchID; + dgesture.gestureId = gestureId; + SDL_PushEvent((SDL_Event *)&dgesture); + } +} + +static void +GestureProcessEvent(const SDL_Event *event) +{ + float x, y; + int index; + int i; + float pathDx, pathDy; + SDL_FPoint lastP; + SDL_FPoint lastCentroid; + float lDist; + float Dist; + float dtheta; + float dDist; + + if (event->type == SDL_EVENT_FINGER_MOTION || + event->type == SDL_EVENT_FINGER_DOWN || + event->type == SDL_EVENT_FINGER_UP) { + GestureTouch *inTouch = GestureGetTouch(event->tfinger.touchID); + + if (inTouch == NULL) { /* we maybe didn't see this one before. */ + inTouch = GestureAddTouch(event->tfinger.touchID); + if (!inTouch) { + return; /* oh well. */ + } + } + + x = event->tfinger.x; + y = event->tfinger.y; + + /* Finger Up */ + if (event->type == SDL_EVENT_FINGER_UP) { + SDL_FPoint path[GESTURE_DOLLARNPOINTS]; + inTouch->numDownFingers--; + + if (inTouch->recording) { + inTouch->recording = false; + GestureDollarNormalize(&inTouch->dollarPath, path, true); + /* PrintPath(path); */ + if (GestureRecordAll) { + index = GestureAddDollar(NULL, path); + for (i = 0; i < GestureNumTouches; i++) { + GestureTouches[i].recording = false; + } + } + else { + index = GestureAddDollar(inTouch, path); + } + + if (index >= 0) { + GestureSendDollarRecord( + inTouch, inTouch->dollarTemplate[index].hash); + } + else { + GestureSendDollarRecord(inTouch, -1); + } + } + else { + int bestTempl = -1; + const float error = GestureDollarRecognize( + &inTouch->dollarPath, &bestTempl, inTouch); + if (bestTempl >= 0) { + /* Send Event */ + const unsigned long gestureId = + inTouch->dollarTemplate[bestTempl].hash; + GestureSendDollar(inTouch, gestureId, error); + /* printf ("%s\n",);("Dollar error: %f\n",error); */ + } + } + + /* inTouch->gestureLast[j] = + * inTouch->gestureLast[inTouch->numDownFingers]; */ + if (inTouch->numDownFingers > 0) { + inTouch->centroid.x = + (inTouch->centroid.x * (inTouch->numDownFingers + 1) - x) / + inTouch->numDownFingers; + inTouch->centroid.y = + (inTouch->centroid.y * (inTouch->numDownFingers + 1) - y) / + inTouch->numDownFingers; + } + } + else if (event->type == SDL_EVENT_FINGER_MOTION) { + const float dx = event->tfinger.dx; + const float dy = event->tfinger.dy; + GestureDollarPath *path = &inTouch->dollarPath; + if (path->numPoints < GESTURE_MAX_DOLLAR_PATH_SIZE) { + path->p[path->numPoints].x = inTouch->centroid.x; + path->p[path->numPoints].y = inTouch->centroid.y; + pathDx = (path->p[path->numPoints].x - + path->p[path->numPoints - 1].x); + pathDy = (path->p[path->numPoints].y - + path->p[path->numPoints - 1].y); + path->length += + (float)SDL_sqrt(pathDx * pathDx + pathDy * pathDy); + path->numPoints++; + } + + lastP.x = x - dx; + lastP.y = y - dy; + lastCentroid = inTouch->centroid; + + inTouch->centroid.x += dx / inTouch->numDownFingers; + inTouch->centroid.y += dy / inTouch->numDownFingers; + /* printf("Centrid : + * (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */ + if (inTouch->numDownFingers > 1) { + SDL_FPoint lv; /* Vector from centroid to last x,y position */ + SDL_FPoint + v; /* Vector from centroid to current x,y position */ + /* lv = inTouch->gestureLast[j].cv; */ + lv.x = lastP.x - lastCentroid.x; + lv.y = lastP.y - lastCentroid.y; + lDist = SDL_sqrtf(lv.x * lv.x + lv.y * lv.y); + /* printf("lDist = %f\n",lDist); */ + v.x = x - inTouch->centroid.x; + v.y = y - inTouch->centroid.y; + /* inTouch->gestureLast[j].cv = v; */ + Dist = SDL_sqrtf(v.x * v.x + v.y * v.y); + /* SDL_cosf(dTheta) = (v . lv)/(|v| * |lv|) */ + + /* Normalize Vectors to simplify angle calculation */ + lv.x /= lDist; + lv.y /= lDist; + v.x /= Dist; + v.y /= Dist; + dtheta = SDL_atan2f(lv.x * v.y - lv.y * v.x, + lv.x * v.x + lv.y * v.y); + + dDist = (Dist - lDist); + if (lDist == 0) { + /* To avoid impossible values */ + dDist = 0; + dtheta = 0; + } + + /* inTouch->gestureLast[j].dDist = dDist; + inTouch->gestureLast[j].dtheta = dtheta; + + printf("dDist = %f, dTheta = %f\n",dDist,dtheta); + gdtheta = gdtheta*.9 + dtheta*.1; + gdDist = gdDist*.9 + dDist*.1 + knob.r += dDist/numDownFingers; + knob.ang += dtheta; + printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist); + printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */ + GestureSendMulti(inTouch, dtheta, dDist); + } + else { + /* inTouch->gestureLast[j].dDist = 0; + inTouch->gestureLast[j].dtheta = 0; + inTouch->gestureLast[j].cv.x = 0; + inTouch->gestureLast[j].cv.y = 0; */ + } + /* inTouch->gestureLast[j].f.p.x = x; + inTouch->gestureLast[j].f.p.y = y; + break; + pressure? */ + } + else if (event->type == SDL_EVENT_FINGER_DOWN) { + inTouch->numDownFingers++; + inTouch->centroid.x = + (inTouch->centroid.x * (inTouch->numDownFingers - 1) + x) / + inTouch->numDownFingers; + inTouch->centroid.y = + (inTouch->centroid.y * (inTouch->numDownFingers - 1) + y) / + inTouch->numDownFingers; + /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y, + inTouch->centroid.x,inTouch->centroid.y); */ + + inTouch->dollarPath.length = 0; + inTouch->dollarPath.p[0].x = x; + inTouch->dollarPath.p[0].y = y; + inTouch->dollarPath.numPoints = 1; + } + } +} + +#endif /* defined(SDL_GESTURE_IMPLEMENTATION) */ +#endif /* SDL version > 2 */ +#endif /* INCL_SDL_GESTURE_H */ + +/* vi: set sts=4 ts=4 sw=4 expandtab: */ From aa6aac6e695a615542fd1f770509fa3a0a26759e Mon Sep 17 00:00:00 2001 From: Ankith Date: Thu, 21 Aug 2025 14:14:56 +0530 Subject: [PATCH 3/7] SDL3: display+window: runtime fixes --- src_c/_pygame.h | 8 ++ src_c/display.c | 214 ++++++++++++++++++++++++++++++++---------------- src_c/window.c | 31 ++++++- 3 files changed, 180 insertions(+), 73 deletions(-) diff --git a/src_c/_pygame.h b/src_c/_pygame.h index 61d6b2ff15..311a4bd9c0 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -167,6 +167,8 @@ PG_GetSurfaceFormat(SDL_Surface *surf) #define PG_GetSurfaceClipRect SDL_GetSurfaceClipRect +#define PG_GL_SetSwapInterval SDL_GL_SetSwapInterval + #else /* ~SDL_VERSION_ATLEAST(3, 0, 0)*/ #define PG_ShowCursor() SDL_ShowCursor(SDL_ENABLE) #define PG_HideCursor() SDL_ShowCursor(SDL_DISABLE) @@ -375,6 +377,12 @@ PG_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect) *rect = surface->clip_rect; return true; } + +static inline bool +PG_GL_SetSwapInterval(int interval) +{ + return SDL_GL_SetSwapInterval(interval) == 0; +} #endif /* DictProxy is useful for event posting with an arbitrary dict. Maintains diff --git a/src_c/display.c b/src_c/display.c index 959d29769c..622a02c19c 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -279,7 +279,11 @@ pg_get_init(PyObject *self, PyObject *_null) static PyObject * pg_get_active(PyObject *self, PyObject *_null) { - SDL_WindowFlags flags = SDL_GetWindowFlags(pg_GetDefaultWindow()); + SDL_Window *win = pg_GetDefaultWindow(); + if (!win) { + Py_RETURN_FALSE; + } + SDL_WindowFlags flags = SDL_GetWindowFlags(win); #if SDL_VERSION_ATLEAST(3, 0, 0) return PyBool_FromLong(!(flags & SDL_WINDOW_HIDDEN) && @@ -460,7 +464,12 @@ pg_GetVideoInfo(pg_VideoInfo *info) } else { #if SDL_VERSION_ATLEAST(3, 0, 0) - if ((mode_ptr = SDL_GetCurrentDisplayMode(0))) { + SDL_DisplayID primary_display = SDL_GetPrimaryDisplay(); + if (primary_display == 0) { + PyErr_SetString(pgExc_SDLError, SDL_GetError()); + return (pg_VideoInfo *)NULL; + } + if ((mode_ptr = SDL_GetCurrentDisplayMode(primary_display))) { info->current_w = mode_ptr->w; info->current_h = mode_ptr->h; formatenum = mode_ptr->format; @@ -793,7 +802,7 @@ pg_get_surface(PyObject *self, PyObject *_null) static PyObject * pg_gl_set_attribute(PyObject *self, PyObject *arg) { - int flag, value, result; + int flag, value; VIDEO_INIT_CHECK(); if (!PyArg_ParseTuple(arg, "ii", &flag, &value)) { return NULL; @@ -801,25 +810,35 @@ pg_gl_set_attribute(PyObject *self, PyObject *arg) if (flag == -1) { /*an undefined/unsupported val, ignore*/ Py_RETURN_NONE; } - result = SDL_GL_SetAttribute(flag, value); - if (result == -1) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_GL_SetAttribute(flag, value)) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#else + if (SDL_GL_SetAttribute(flag, value) == -1) { return RAISE(pgExc_SDLError, SDL_GetError()); } +#endif Py_RETURN_NONE; } static PyObject * pg_gl_get_attribute(PyObject *self, PyObject *arg) { - int flag, value, result; + int flag, value; VIDEO_INIT_CHECK(); if (!PyArg_ParseTuple(arg, "i", &flag)) { return NULL; } - result = SDL_GL_GetAttribute(flag, &value); - if (result == -1) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_GL_GetAttribute(flag, &value)) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#else + if (SDL_GL_GetAttribute(flag, &value) == -1) { return RAISE(pgExc_SDLError, SDL_GetError()); } +#endif return PyLong_FromLong(value); } @@ -1118,12 +1137,12 @@ PG_CreateWindowCompat(const char *title, int x, int y, int w, int h, } #if SDL_VERSION_ATLEAST(3, 0, 0) -/* Returns 0 on success, negative on failure. */ -static int +/* Returns true on success, false on failure. */ +static bool PG_SetWindowFullscreen(SDL_Window *window, bool fullscreen, bool non_desktop_fullscreen) { - int ret = -1; + bool ret = false; SDL_DisplayMode **modes = NULL; SDL_DisplayMode *chosen_mode = NULL; if (!SDL_SetWindowFullscreen(window, fullscreen)) { @@ -1152,11 +1171,24 @@ PG_SetWindowFullscreen(SDL_Window *window, bool fullscreen, } } - ret = 0; + SDL_SyncWindow(window); + ret = true; end: SDL_free(modes); return ret; } +#else +static bool +PG_SetWindowFullscreen(SDL_Window *window, bool fullscreen, + bool non_desktop_fullscreen) +{ + int flags = 0; + if (fullscreen) { + flags = non_desktop_fullscreen ? SDL_WINDOW_FULLSCREEN + : SDL_WINDOW_FULLSCREEN_DESKTOP; + } + return (SDL_SetWindowFullscreen(window, flags) == 0) ? true : false; +} #endif static PyObject * @@ -1235,6 +1267,16 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } } +#if SDL_VERSION_ATLEAST(3, 0, 0) + /* In SDL2, display == 0 meant primary display, so compat code for it */ + if (display == 0) { + display = SDL_GetPrimaryDisplay(); + if (display == 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + } +#endif + if ((vsync == -1) && ((flags & PGS_OPENGL) == 0)) { return RAISE(PyExc_ValueError, "requested adaptive vsync without OpenGL"); @@ -1437,11 +1479,16 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) if (flags & PGS_SCALED && !(flags & PGS_FULLSCREEN)) { SDL_Rect display_bounds; int fractional_scaling = SDL_FALSE; - +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_GetDisplayUsableBounds(display, &display_bounds)) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#else if (0 != SDL_GetDisplayUsableBounds(display, &display_bounds)) { return RAISE(pgExc_SDLError, SDL_GetError()); } +#endif if (SDL_GetHintBoolean("SDL_HINT_RENDER_SCALE_QUALITY", SDL_FALSE)) { @@ -1530,9 +1577,9 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) * changes if the window is fullscreen * See https://github.com/pygame/pygame/issues/2711 */ #if SDL_VERSION_ATLEAST(3, 0, 0) - if (0 != PG_SetWindowFullscreen( - win, sdl_flags & SDL_WINDOW_FULLSCREEN, - non_desktop_fullscreen)) { + if (!PG_SetWindowFullscreen(win, + sdl_flags & SDL_WINDOW_FULLSCREEN, + non_desktop_fullscreen)) { return RAISE(pgExc_SDLError, SDL_GetError()); } #else @@ -1592,7 +1639,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) vsync to be always on or always off, or vsync is on by default for the whole desktop because of wayland GL compositing. */ if (vsync == -1) { - if (SDL_GL_SetSwapInterval(-1) != 0) { + if (!PG_GL_SetSwapInterval(-1)) { PyErr_SetString(pgExc_SDLError, "adaptive vsync for OpenGL not " "available"); @@ -1602,7 +1649,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } } else if (vsync == 1) { - if (SDL_GL_SetSwapInterval(1) != 0) { + if (!PG_GL_SetSwapInterval(1)) { PyErr_SetString(pgExc_SDLError, "regular vsync for OpenGL not " "available"); @@ -1611,7 +1658,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } } else { - SDL_GL_SetSwapInterval(0); + PG_GL_SetSwapInterval(0); } } else { @@ -1849,6 +1896,9 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds) } } } +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(win); +#endif /*return the window's surface (screen)*/ Py_INCREF(surface); @@ -1947,6 +1997,9 @@ pg_set_window_position(PyObject *self, PyObject *arg) if (win) { /* Will raise errors with SDL 3, deal with it during the porting */ SDL_SetWindowPosition(win, x, y); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(win); +#endif } Py_RETURN_NONE; @@ -1973,7 +2026,16 @@ pg_mode_ok(PyObject *self, PyObject *args, PyObject *kwds) &display_index)) { return NULL; } -#if !SDL_VERSION_ATLEAST(3, 0, 0) + +#if SDL_VERSION_ATLEAST(3, 0, 0) + /* In SDL2, display == 0 meant primary display, so compat code for it */ + if (display_index == 0) { + display_index = SDL_GetPrimaryDisplay(); + if (display_index == 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + } +#else /* Display ID is not bounded by number of displays in SDL3 */ if (display_index < 0 || display_index >= SDL_GetNumVideoDisplays()) { return RAISE(PyExc_ValueError, @@ -2000,6 +2062,15 @@ pg_mode_ok(PyObject *self, PyObject *args, PyObject *kwds) } #if SDL_VERSION_ATLEAST(3, 0, 0) + /* Compat with SDL2 behaviour */ + const SDL_DisplayMode *curmode; + if (!(curmode = SDL_GetCurrentDisplayMode(display_index))) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + if (curmode->w == desired.w && curmode->h == desired.h) { + return PyLong_FromLong(SDL_BITSPERPIXEL(curmode->format)); + } + if (!SDL_GetClosestFullscreenDisplayMode(display_index, desired.w, desired.h, desired.refresh_rate, 1, &closest)) { @@ -2039,7 +2110,15 @@ pg_list_modes(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } -#if !SDL_VERSION_ATLEAST(3, 0, 0) +#if SDL_VERSION_ATLEAST(3, 0, 0) + /* In SDL2, display == 0 meant primary display, so compat code for it */ + if (display_index == 0) { + display_index = SDL_GetPrimaryDisplay(); + if (display_index == 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + } +#else /* Display ID is not bounded by number of displays in SDL3 */ if (display_index < 0 || display_index >= SDL_GetNumVideoDisplays()) { return RAISE(PyExc_ValueError, @@ -2051,19 +2130,31 @@ pg_list_modes(PyObject *self, PyObject *args, PyObject *kwds) #pragma PG_WARN(Ignoring flags) #if SDL_VERSION_ATLEAST(3, 0, 0) + const SDL_DisplayMode *curmode; + if (!(curmode = SDL_GetCurrentDisplayMode(display_index))) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } if (bpp == 0) { - const SDL_DisplayMode *curmode; - if (!(curmode = SDL_GetCurrentDisplayMode(display_index))) { - return RAISE(pgExc_SDLError, SDL_GetError()); - } bpp = SDL_BITSPERPIXEL(curmode->format); } SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display_index, &nummodes); - if (nummodes < 0) { + if (!modes || nummodes < 0) { return RAISE(pgExc_SDLError, SDL_GetError()); } + + /* SDL3 can return empty list here but SDL2 didn't. In that case, use + * curmode. */ + if (nummodes == 0) { + SDL_free(modes); + modes = SDL_malloc(sizeof(SDL_DisplayMode **)); + if (!modes) { + return PyErr_NoMemory(); + } + modes[0] = (SDL_DisplayMode *)curmode; + nummodes = 1; + } #else if (bpp == 0) { SDL_DisplayMode curmode; @@ -2700,6 +2791,9 @@ pg_iconify(PyObject *self, PyObject *_null) return RAISE(pgExc_SDLError, "No open window"); } SDL_MinimizeWindow(win); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(win); +#endif return PyBool_FromLong(1); } @@ -3035,7 +3129,6 @@ static PyObject * pg_toggle_fullscreen(PyObject *self, PyObject *_null) { SDL_Window *win = pg_GetDefaultWindow(); - int result; SDL_WindowFlags flags; int window_w, window_h, w, h, window_display, x, y; pgSurfaceObject *display_surface; @@ -3164,8 +3257,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) /* TOGGLE FULLSCREEN OFF */ if (state->unscaled_render) { - result = SDL_SetWindowFullscreen(win, 0); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 0, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } } @@ -3179,8 +3271,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) if (scale < 1) { scale = 1; } - result = SDL_SetWindowFullscreen(win, 0); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 0, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } SDL_SetWindowSize(win, w * scale, h * scale); @@ -3220,8 +3311,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) /* this is literally the only place where state->toggle_windowed_w * should ever be read. We only use it because with GL, there is no * display surface we can query for dimensions. */ - result = SDL_SetWindowFullscreen(win, 0); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 0, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } SDL_GL_MakeCurrent(win, state->gl_context); @@ -3257,8 +3347,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) else if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) { #endif - result = SDL_SetWindowFullscreen(win, 0); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 0, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } display_surface->surf = SDL_GetWindowSurface(win); @@ -3287,15 +3376,11 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) if (win == NULL) { return RAISE(pgExc_SDLError, SDL_GetError()); } - else { - result = 0; - } display_surface->surf = SDL_GetWindowSurface(win); pg_SetDefaultWindow(win); } else { - result = SDL_SetWindowFullscreen(win, 0); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 0, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } display_surface->surf = SDL_GetWindowSurface(win); @@ -3330,24 +3415,12 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) state->fullscreen_backup_y = y; if (state->unscaled_render) { -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = PG_SetWindowFullscreen(win, 1, 0); -#else - result = - SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); -#endif - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 1, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } } else if (pg_renderer != NULL) { -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = PG_SetWindowFullscreen(win, 1, 0); -#else - result = - SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); -#endif - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 1, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } if (is_renderer_software && subsystem == SDL_SYSWM_X11) { @@ -3380,13 +3453,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) #endif } else if (state->using_gl) { -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = PG_SetWindowFullscreen(win, 1, 0); -#else - result = - SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); -#endif - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 1, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } SDL_GL_MakeCurrent(win, state->gl_context); @@ -3411,13 +3478,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) } } else if (w == display_mode->w && h == display_mode->h) { -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = PG_SetWindowFullscreen(win, 1, 0); -#else - result = - SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); -#endif - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 1, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } display_surface->surf = SDL_GetWindowSurface(win); @@ -3434,8 +3495,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) return PyLong_FromLong(-1); } else { - result = SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN); - if (result != 0) { + if (!PG_SetWindowFullscreen(win, 1, 1)) { return RAISE(pgExc_SDLError, SDL_GetError()); } display_surface->surf = SDL_GetWindowSurface(win); @@ -3447,7 +3507,7 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) if (win == NULL) { return RAISE(pgExc_SDLError, SDL_GetError()); } - if (0 != SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN)) { + if (!PG_SetWindowFullscreen(win, 1, 1)) { return RAISE(pgExc_SDLError, SDL_GetError()); } display_surface->surf = SDL_GetWindowSurface(win); @@ -3461,7 +3521,10 @@ pg_toggle_fullscreen(PyObject *self, PyObject *_null) } } } - return PyLong_FromLong(result != 0); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(win); +#endif + return PyLong_FromLong(1); } /* This API is provisional, and, not finalised, and should not be documented @@ -3539,6 +3602,9 @@ pg_display_resize_event(PyObject *self, PyObject *event) /* do not do anything that would invalidate a display surface! */ return PyLong_FromLong(-1); } +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(win); +#endif Py_RETURN_FALSE; } @@ -3771,7 +3837,11 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) int clicked_button_id; +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_ShowMessageBox(&msgbox_data, &clicked_button_id)) { +#else if (SDL_ShowMessageBox(&msgbox_data, &clicked_button_id)) { +#endif PyErr_SetString(pgExc_SDLError, SDL_GetError()); goto error; } diff --git a/src_c/window.c b/src_c/window.c index 074c514b35..e6dfd0bc1c 100644 --- a/src_c/window.c +++ b/src_c/window.c @@ -301,9 +301,16 @@ _resize_event_watch(void *userdata, SDL_Event *event) static PyObject * window_set_windowed(pgWindowObject *self, PyObject *_null) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_SetWindowFullscreen(self->_win, 0)) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + SDL_SyncWindow(self->_win); +#else if (SDL_SetWindowFullscreen(self->_win, 0)) { return RAISE(pgExc_SDLError, SDL_GetError()); } +#endif Py_RETURN_NONE; } @@ -339,6 +346,7 @@ pg_window_set_fullscreen(SDL_Window *window, int desktop) goto end; } + SDL_SyncWindow(window); ret = 1; end: SDL_free(modes); @@ -371,7 +379,7 @@ window_set_fullscreen(pgWindowObject *self, PyObject *args, PyObject *kwargs) static PyObject * window_focus(pgWindowObject *self, PyObject *args, PyObject *kwargs) { - SDL_bool input_only = SDL_FALSE; + int input_only = SDL_FALSE; char *kwids[] = {"input_only", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|p", kwids, &input_only)) { return NULL; @@ -423,6 +431,9 @@ static PyObject * window_restore(pgWindowObject *self, PyObject *_null) { SDL_RestoreWindow(self->_win); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif Py_RETURN_NONE; } @@ -430,6 +441,9 @@ static PyObject * window_maximize(pgWindowObject *self, PyObject *_null) { SDL_MaximizeWindow(self->_win); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif Py_RETURN_NONE; } @@ -437,6 +451,9 @@ static PyObject * window_minimize(pgWindowObject *self, PyObject *_null) { SDL_MinimizeWindow(self->_win); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif Py_RETURN_NONE; } @@ -739,6 +756,9 @@ window_set_size(pgWindowObject *self, PyObject *arg, void *v) } SDL_SetWindowSize(self->_win, w, h); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif if (self->surf) { /* Ensure that the underlying surf is immediately updated, instead of * relying on the event callback */ @@ -791,6 +811,9 @@ window_set_minimum_size(pgWindowObject *self, PyObject *arg, void *v) } SDL_SetWindowMinimumSize(self->_win, w, h); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif return 0; } @@ -831,6 +854,9 @@ window_set_maximum_size(pgWindowObject *self, PyObject *arg, void *v) } SDL_SetWindowMaximumSize(self->_win, w, h); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif return 0; } @@ -862,6 +888,9 @@ window_set_position(pgWindowObject *self, PyObject *arg, void *v) } SDL_SetWindowPosition(self->_win, x, y); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_SyncWindow(self->_win); +#endif return 0; } From c20f1980c67eebc2d94a1570ed6f0197d832fc00 Mon Sep 17 00:00:00 2001 From: Ankith Date: Thu, 21 Aug 2025 14:18:11 +0530 Subject: [PATCH 4/7] SDL3: surface+pixelarray+font+draw: runtime fixes --- docs/reST/c_api/surface.rst | 2 +- src_c/_pygame.h | 44 ++++++++++++-- src_c/draw.c | 2 +- src_c/font.c | 6 +- src_c/pixelarray_methods.c | 2 +- src_c/pixelcopy.c | 6 +- src_c/surface.c | 117 +++++++++++++++++++++++++++--------- src_c/surface_fill.c | 3 +- 8 files changed, 136 insertions(+), 46 deletions(-) diff --git a/docs/reST/c_api/surface.rst b/docs/reST/c_api/surface.rst index a394d629f0..6cbc91f0e3 100644 --- a/docs/reST/c_api/surface.rst +++ b/docs/reST/c_api/surface.rst @@ -56,4 +56,4 @@ Header file: src_c/include/pygame.h by the blit. The C version of the :py:meth:`pygame.Surface.blit` method. - Return ``1`` on success, ``0`` on an exception. + Return ``0`` on success, ``1`` on an exception. diff --git a/src_c/_pygame.h b/src_c/_pygame.h index 311a4bd9c0..0cbb058331 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -77,9 +77,17 @@ #define PG_CreateSurface SDL_CreateSurface #define PG_CreateSurfaceFrom SDL_CreateSurfaceFrom -#define PG_ConvertSurface SDL_ConvertSurface #define PG_ConvertSurfaceFormat SDL_ConvertSurface +/* Convert surface using palette and format of dst */ +static inline SDL_Surface * +PG_ConvertSurface(SDL_Surface *surface, SDL_Surface *dst) +{ + return SDL_ConvertSurfaceAndColorspace(surface, dst->format, + SDL_GetSurfacePalette(dst), + SDL_GetSurfaceColorspace(dst), 0); +} + #define PG_PixelFormatEnum SDL_PixelFormat #define PG_SurfaceHasRLE SDL_SurfaceHasRLE @@ -106,7 +114,17 @@ PG_UnlockMutex(SDL_mutex *mutex) return 0; } -#define PG_SURF_BitsPerPixel(surf) SDL_BITSPERPIXEL(surf->format) +static inline int +PG_SURF_BitsPerPixel(SDL_Surface *surf) +{ + if (SDL_BYTESPERPIXEL(surf->format) == 4) { + /* Compat with SDL2: for 24-bit images SDL3 returns 24 when SDL2 + * returned 32 */ + return 32; + } + return SDL_BITSPERPIXEL(surf->format); +} + #define PG_SURF_BytesPerPixel(surf) SDL_BYTESPERPIXEL(surf->format) #define PG_FORMAT_BitsPerPixel(format) format->bits_per_pixel #define PG_FORMAT_BytesPerPixel(format) format->bytes_per_pixel @@ -119,11 +137,23 @@ PG_UnlockMutex(SDL_mutex *mutex) #define PG_PixelFormat const SDL_PixelFormatDetails +static inline SDL_Palette * +PG_GetSurfacePalette(SDL_Surface *surf) +{ + SDL_Palette *ret = SDL_GetSurfacePalette(surf); + if (!ret && SDL_ISPIXELFORMAT_INDEXED(surf->format)) { + /* Palette doesn't exist but is expected, so create it. + * SDL will associate the newly created palette with the surface */ + ret = SDL_CreateSurfacePalette(surf); + } + return ret; +} + static inline bool PG_GetSurfaceDetails(SDL_Surface *surf, PG_PixelFormat **format_p, SDL_Palette **palette_p) { - *palette_p = SDL_GetSurfacePalette(surf); + *palette_p = PG_GetSurfacePalette(surf); *format_p = SDL_GetPixelFormatDetails(surf->format); return *format_p != NULL; } @@ -134,7 +164,6 @@ PG_GetSurfaceFormat(SDL_Surface *surf) return SDL_GetPixelFormatDetails(surf->format); } -#define PG_GetSurfacePalette SDL_GetSurfacePalette #define PG_SetPaletteColors SDL_SetPaletteColors #define PG_SetSurfacePalette SDL_SetSurfacePalette #define PG_SetSurfaceColorKey SDL_SetSurfaceColorKey @@ -190,10 +219,15 @@ PG_GetSurfaceFormat(SDL_Surface *surf) SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, format) #define PG_CreateSurfaceFrom(width, height, format, pixels, pitch) \ SDL_CreateRGBSurfaceWithFormatFrom(pixels, width, height, 0, pitch, format) -#define PG_ConvertSurface(src, fmt) SDL_ConvertSurface(src, fmt, 0) #define PG_ConvertSurfaceFormat(src, pixel_format) \ SDL_ConvertSurfaceFormat(src, pixel_format, 0) +static inline SDL_Surface * +PG_ConvertSurface(SDL_Surface *surface, SDL_Surface *dst) +{ + return SDL_ConvertSurface(surface, dst->format, 0); +} + #define PG_PixelFormatEnum SDL_PixelFormatEnum #define PG_SoftStretchNearest(src, srcrect, dst, dstrect) \ diff --git a/src_c/draw.c b/src_c/draw.c index 7684d19493..5c682bcc4c 100644 --- a/src_c/draw.c +++ b/src_c/draw.c @@ -1334,7 +1334,7 @@ flood_fill(PyObject *self, PyObject *arg, PyObject *kwargs) if (pgSurface_Check(colorobj)) { pat_surfobj = ((pgSurfaceObject *)colorobj); - pattern = PG_ConvertSurface(pat_surfobj->surf, surf->format); + pattern = PG_ConvertSurface(pat_surfobj->surf, surf); if (pattern == NULL) { return RAISE(PyExc_RuntimeError, "error converting pattern surf"); diff --git a/src_c/font.c b/src_c/font.c index 11b7b20530..8a201ae2a4 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -717,11 +717,7 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) resolve to Render_Solid, that needs to be explicitly handled. */ if (surf != NULL && bg_rgba_obj != Py_None) { SDL_SetColorKey(surf, 0, 0); -#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) - SDL_Palette *palette = SDL_GetSurfacePalette(surf); -#else - SDL_Palette *palette = surf->format->palette; -#endif + SDL_Palette *palette = PG_GetSurfacePalette(surf); if (palette) { palette->colors[0].r = backg.r; palette->colors[0].g = backg.g; diff --git a/src_c/pixelarray_methods.c b/src_c/pixelarray_methods.c index 6eb031d637..6f22a0c228 100644 --- a/src_c/pixelarray_methods.c +++ b/src_c/pixelarray_methods.c @@ -137,7 +137,7 @@ _make_surface(pgPixelArrayObject *array, PyObject *args) } /* Ensure the new surface has the same format as the original */ - new_surf = PG_ConvertSurface(temp_surf, surf->format); + new_surf = PG_ConvertSurface(temp_surf, surf); if (temp_surf != surf) { SDL_FreeSurface(temp_surf); } diff --git a/src_c/pixelcopy.c b/src_c/pixelcopy.c index e9f1a327c9..1e59752f07 100644 --- a/src_c/pixelcopy.c +++ b/src_c/pixelcopy.c @@ -1197,11 +1197,7 @@ make_surface(PyObject *self, PyObject *arg) pgBuffer_Release(&pg_view); return RAISE(pgExc_SDLError, SDL_GetError()); } -#if SDL_VERSION_ATLEAST(3, 0, 0) - SDL_Palette *palette = SDL_GetSurfacePalette(surf); -#else - SDL_Palette *palette = surf->format->palette; -#endif + SDL_Palette *palette = PG_GetSurfacePalette(surf); if (SDL_ISPIXELFORMAT_INDEXED(PG_SURF_FORMATENUM(surf))) { /* Give the surface something other than an all white palette. * */ diff --git a/src_c/surface.c b/src_c/surface.c index f118a4db48..dc446dd376 100644 --- a/src_c/surface.c +++ b/src_c/surface.c @@ -1623,7 +1623,7 @@ surf_copy(pgSurfaceObject *self, PyObject *_null) SURF_INIT_CHECK(surf) pgSurface_Prep(self); - newsurf = PG_ConvertSurface(surf, surf->format); + newsurf = PG_ConvertSurface(surf, surf); pgSurface_Unprep(self); final = surf_subtype_new(Py_TYPE(self), newsurf, 1); @@ -1683,7 +1683,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args) if (argobject) { if (pgSurface_Check(argobject)) { src = pgSurface_AsSurface(argobject); - newsurf = PG_ConvertSurface(surf, src->format); + newsurf = PG_ConvertSurface(surf, src); } else { /* will be updated later, initialize to make static analyzer happy @@ -1837,7 +1837,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args) if (argobject) { if (pgSurface_Check(argobject)) { src = pgSurface_AsSurface(argobject); - newsurf = PG_ConvertSurface(surf, src->format); + newsurf = PG_ConvertSurface(surf, src); } else { /* will be updated later, initialize to make static analyzer happy @@ -1961,7 +1961,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args) SDL_SetPixelFormatPalette(&format, palette); } } - newsurf = PG_ConvertSurface(surf, &format); + newsurf = SDL_ConvertSurface(surf, &format, 0); SDL_SetSurfaceBlendMode(newsurf, SDL_BLENDMODE_NONE); SDL_FreePalette(palette); } @@ -3767,7 +3767,7 @@ surf_premul_alpha(pgSurfaceObject *self, PyObject *_null) pgSurface_Prep(self); // Make a copy of the surface first - newsurf = PG_ConvertSurface(surf, surf->format); + newsurf = PG_ConvertSurface(surf, surf); if ((surf->w > 0 && surf->h > 0)) { // If the surface has no pixels we don't need to premul @@ -4461,6 +4461,76 @@ surface_do_overlap(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, return dstoffset < span || dstoffset > src->pitch - span; } +int +PG_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, + SDL_Rect *dstrect) +{ +#if SDL_VERSION_ATLEAST(3, 0, 0) + /* SDL3 doesn't modify dstrect, so compat for that. + * Below logic taken from SDL2 source with slight modifications */ + SDL_Rect r_src, r_dst; + + r_src.x = 0; + r_src.y = 0; + r_src.w = src->w; + r_src.h = src->h; + + if (dstrect) { + r_dst.x = dstrect->x; + r_dst.y = dstrect->y; + } + else { + r_dst.x = 0; + r_dst.y = 0; + } + + /* clip the source rectangle to the source surface */ + if (srcrect) { + SDL_Rect tmp; + if (SDL_IntersectRect(srcrect, &r_src, &tmp) == SDL_FALSE) { + goto end; + } + + /* Shift dstrect, if srcrect origin has changed */ + r_dst.x += tmp.x - srcrect->x; + r_dst.y += tmp.y - srcrect->y; + + /* Update srcrect */ + r_src = tmp; + } + + /* There're no dstrect.w/h parameters. It's the same as srcrect */ + r_dst.w = r_src.w; + r_dst.h = r_src.h; + + /* clip the destination rectangle against the clip rectangle */ + { + SDL_Rect tmp, clip_rect; + SDL_GetSurfaceClipRect(dst, &clip_rect); + if (SDL_IntersectRect(&r_dst, &clip_rect, &tmp) == SDL_FALSE) { + goto end; + } + + /* Update dstrect */ + r_dst = tmp; + } + + if (r_dst.w > 0 && r_dst.h > 0) { + if (dstrect) { /* update output parameter */ + *dstrect = r_dst; + } + return SDL_BlitSurface(src, srcrect, dst, dstrect) ? 0 : -1; + } +end: + if (dstrect) { + dstrect->w = dstrect->h = 0; + } + return 0; +#else + return SDL_BlitSurface(src, srcrect, dst, dstrect); +#endif +} + /*this internal blit function is accessible through the C api*/ int pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, @@ -4471,13 +4541,11 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, SDL_Surface *subsurface = NULL; int result, suboffsetx = 0, suboffsety = 0; SDL_Rect orig_clip, sub_clip, dstclip; -#if !SDL_VERSION_ATLEAST(3, 0, 0) Uint8 alpha; -#endif if (!PG_GetSurfaceClipRect(dst, &dstclip)) { PyErr_SetString(pgExc_SDLError, SDL_GetError()); - return 0; + return 1; } /* passthrough blits to the real surface */ @@ -4528,8 +4596,6 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, result = pygame_Blit(src, srcrect, dst, dstrect, blend_flags); /* Py_END_ALLOW_THREADS */ } -// TODO SDL3: port the below bit of code. Skipping for initial surface port. -#if !SDL_VERSION_ATLEAST(3, 0, 0) /* can't blit alpha to 8bit, crashes SDL */ else if (PG_SURF_BytesPerPixel(dst) == 1 && (SDL_ISPIXELFORMAT_ALPHA(PG_SURF_FORMATENUM(src)) || @@ -4539,17 +4605,22 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, result = pygame_Blit(src, srcrect, dst, dstrect, 0); } else { +#if SDL_VERSION_ATLEAST(3, 0, 0) + const SDL_PixelFormatDetails *fmt = + SDL_GetPixelFormatDetails(src->format); + src = fmt ? SDL_ConvertSurface(src, + SDL_GetPixelFormatForMasks( + fmt->bits_per_pixel, fmt->Rmask, + fmt->Gmask, fmt->Bmask, 0)) + : NULL; + +#else SDL_PixelFormat *fmt = src->format; SDL_PixelFormat newfmt; newfmt.palette = 0; /* Set NULL (or SDL gets confused) */ -#if SDL_VERSION_ATLEAST(3, 0, 0) - newfmt.bits_per_pixel = fmt->bits_per_pixel; - newfmt.bytes_per_pixel = fmt->bytes_per_pixel; -#else newfmt.BitsPerPixel = fmt->BitsPerPixel; newfmt.BytesPerPixel = fmt->BytesPerPixel; -#endif newfmt.Amask = 0; newfmt.Rmask = fmt->Rmask; newfmt.Gmask = fmt->Gmask; @@ -4562,13 +4633,10 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, newfmt.Rloss = fmt->Rloss; newfmt.Gloss = fmt->Gloss; newfmt.Bloss = fmt->Bloss; - src = PG_ConvertSurface(src, &newfmt); - if (src) { -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = SDL_BlitSurface(src, srcrect, dst, dstrect) ? 0 : -1; -#else - result = SDL_BlitSurface(src, srcrect, dst, dstrect); + src = SDL_ConvertSurface(src, &newfmt, 0); #endif + if (src) { + result = PG_BlitSurface(src, srcrect, dst, dstrect); SDL_FreeSurface(src); } else { @@ -4577,7 +4645,6 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, } /* Py_END_ALLOW_THREADS */ } -#endif else if (blend_flags != PYGAME_BLEND_ALPHA_SDL2 && !(pg_EnvShouldBlendAlphaSDL2()) && !SDL_HasColorKey(src) && (PG_SURF_BytesPerPixel(dst) == 4 || @@ -4598,11 +4665,7 @@ pgSurface_Blit(pgSurfaceObject *dstobj, pgSurfaceObject *srcobj, } else { /* Py_BEGIN_ALLOW_THREADS */ -#if SDL_VERSION_ATLEAST(3, 0, 0) - result = SDL_BlitSurface(src, srcrect, dst, dstrect) ? 0 : -1; -#else - result = SDL_BlitSurface(src, srcrect, dst, dstrect); -#endif + result = PG_BlitSurface(src, srcrect, dst, dstrect); /* Py_END_ALLOW_THREADS */ } diff --git a/src_c/surface_fill.c b/src_c/surface_fill.c index 5f3873b2be..e03d67948d 100644 --- a/src_c/surface_fill.c +++ b/src_c/surface_fill.c @@ -1143,7 +1143,8 @@ surface_fill_blend(SDL_Surface *surface, SDL_Rect *rect, Uint32 color, } default: { - result = SDL_SetError("invalid blend flag for this operation"); + SDL_SetError("invalid blend flag for this operation"); + result = -1; break; } } From d37c7a9ccb75c7a3209a8d2633b5ad5403a22793 Mon Sep 17 00:00:00 2001 From: Ankith Date: Sun, 31 Aug 2025 17:57:35 +0530 Subject: [PATCH 5/7] SDL3: mouse+key+rect: runtime fixes --- buildconfig/stubs/pygame/mouse.pyi | 2 ++ src_c/key.c | 6 +++++- src_c/mouse.c | 16 +++++++++++++--- src_c/pgcompat_rect.c | 8 ++++++-- src_c/pgcompat_rect.h | 4 ++-- test/key_test.py | 5 ++++- test/mouse_test.py | 10 ++++++++++ 7 files changed, 42 insertions(+), 9 deletions(-) diff --git a/buildconfig/stubs/pygame/mouse.pyi b/buildconfig/stubs/pygame/mouse.pyi index e8e32d6188..c47d60de90 100644 --- a/buildconfig/stubs/pygame/mouse.pyi +++ b/buildconfig/stubs/pygame/mouse.pyi @@ -292,4 +292,6 @@ def set_relative_mode(enable: bool, /) -> None: ``True`` will exit relative mouse mode. .. versionadded:: 2.4.0 + .. versionchanged:: 2.5.6 calling this function before calling + :func:`pygame.display.set_mode` is deprecated and may error in the future. """ diff --git a/src_c/key.c b/src_c/key.c index 2540e59980..9b249c13c7 100644 --- a/src_c/key.c +++ b/src_c/key.c @@ -389,7 +389,11 @@ static const struct { {1073742054, "right alt"}, /* K_RALT */ {1073742055, "right meta"}, /* K_RGUI, K_RMETA, K_RSUPER */ {1073742081, "alt gr"}, /* K_MODE */ - {1073742094, "AC Back"}, /* K_AC_BACK */ +#if SDL_VERSION_ATLEAST(3, 0, 0) + {1073742106, "AC Back"}, /* K_AC_BACK */ +#else + {1073742094, "AC Back"}, /* K_AC_BACK */ +#endif }; /* Get name from keycode using pygame compat table */ diff --git a/src_c/mouse.c b/src_c/mouse.c index 62d1ea6b77..efe71d1b4a 100644 --- a/src_c/mouse.c +++ b/src_c/mouse.c @@ -324,8 +324,9 @@ mouse_get_visible(PyObject *self, PyObject *_null) #if SDL_VERSION_ATLEAST(3, 0, 0) SDL_Window *win = pg_GetDefaultWindow(); - result = - win ? (PG_CursorVisible() && !SDL_GetWindowRelativeMouseMode(win)) : 0; + + /* If win is NULL, SDL_GetWindowRelativeMouseMode returns false */ + result = (PG_CursorVisible() && !SDL_GetWindowRelativeMouseMode(win)); #else result = (PG_CursorVisible() && !SDL_GetRelativeMouseMode()); #endif @@ -618,8 +619,8 @@ mouse_set_relative_mode(PyObject *self, PyObject *arg) if (mode == -1) { return NULL; } -#if SDL_VERSION_ATLEAST(3, 0, 0) SDL_Window *win = pg_GetDefaultWindow(); +#if SDL_VERSION_ATLEAST(3, 0, 0) if (!win) { return RAISE(pgExc_SDLError, "display.set_mode has not been called yet."); @@ -628,6 +629,15 @@ mouse_set_relative_mode(PyObject *self, PyObject *arg) return RAISE(pgExc_SDLError, SDL_GetError()); } #else + if (!win) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Calling mouse.set_relative_mode before calling " + "display.set_mode has been deprecated and may raise " + "errors in the future.", + 1) == -1) { + return NULL; + } + } if (SDL_SetRelativeMouseMode((SDL_bool)mode)) { return RAISE(pgExc_SDLError, SDL_GetError()); } diff --git a/src_c/pgcompat_rect.c b/src_c/pgcompat_rect.c index 0756da13bc..68548d4bcc 100644 --- a/src_c/pgcompat_rect.c +++ b/src_c/pgcompat_rect.c @@ -1,7 +1,11 @@ #include "pgcompat_rect.h" /* SDL 2.0.22 provides some utility functions for FRects */ -#if !(SDL_VERSION_ATLEAST(2, 0, 22)) +/* SDL3 changed how the edges are handled. Previously right/bottom edges were + * considered excluded from the FRect but now they aren't. + * For now do SDL2 compat, but consider changing this in the future. + * See: https://github.com/pygame-community/pygame-ce/issues/3571 */ +#if !(SDL_VERSION_ATLEAST(2, 0, 22)) || SDL_VERSION_ATLEAST(3, 0, 0) #ifndef CODE_BOTTOM #define CODE_BOTTOM 1 @@ -176,4 +180,4 @@ PG_IntersectFRectAndLine(SDL_FRect *rect, float *X1, float *Y1, float *X2, *Y2 = y2; return SDL_TRUE; } -#endif /* !(SDL_VERSION_ATLEAST(2, 0, 22)) */ +#endif /* !(SDL_VERSION_ATLEAST(2, 0, 22)) || SDL_VERSION_ATLEAST(3, 0, 0) */ diff --git a/src_c/pgcompat_rect.h b/src_c/pgcompat_rect.h index 5bba69dd21..47f7da60ff 100644 --- a/src_c/pgcompat_rect.h +++ b/src_c/pgcompat_rect.h @@ -8,14 +8,14 @@ #endif /* SDL 2.0.22 provides some utility functions for FRects */ -#if !(SDL_VERSION_ATLEAST(2, 0, 22)) +#if !(SDL_VERSION_ATLEAST(2, 0, 22)) || SDL_VERSION_ATLEAST(3, 0, 0) SDL_bool PG_IntersectFRectAndLine(SDL_FRect *rect, float *X1, float *Y1, float *X2, float *Y2); #else #define PG_IntersectFRectAndLine SDL_IntersectFRectAndLine -#endif /* !(SDL_VERSION_ATLEAST(2, 0, 22)) */ +#endif /* !(SDL_VERSION_ATLEAST(2, 0, 22)) || SDL_VERSION_ATLEAST(3, 0, 0) */ #define pg_PyFloat_FromFloat(x) (PyFloat_FromDouble((double)x)) diff --git a/test/key_test.py b/test/key_test.py index 79bd8e1b6f..95176cf57f 100644 --- a/test/key_test.py +++ b/test/key_test.py @@ -8,6 +8,7 @@ # keys that are not tested for const-name match SKIPPED_KEYS = {"K_UNKNOWN"} +SKIPPED_KEYS_NEW = {"K_MODE"} # This is the expected compat output KEY_NAME_COMPAT = { @@ -169,6 +170,7 @@ class KeyModuleTest(unittest.TestCase): @classmethod def setUpClass(cls): + pygame.quit() pygame.init() @classmethod @@ -286,7 +288,8 @@ def test_name_and_key_code(self): # This is a test for an implementation detail of name with use_compat=False # If this test breaks in the future for any key, it is safe to put skips on # failing keys (the implementation detail is documented as being unreliable) - self.assertEqual(pygame.key.key_code(alt_name), const_val) + if const_name not in SKIPPED_KEYS_NEW: + self.assertEqual(pygame.key.key_code(alt_name), const_val) self.assertRaises(TypeError, pygame.key.name, "fizzbuzz") self.assertRaises(TypeError, pygame.key.key_code, pygame.K_a) diff --git a/test/mouse_test.py b/test/mouse_test.py index f7642d6ab6..6a5f2d3df6 100644 --- a/test/mouse_test.py +++ b/test/mouse_test.py @@ -384,6 +384,15 @@ def test_set_visible__invalid_value(self): ) def test_set_relative_mode(self): """Tests that set_relative_mode hides the cursor.""" + for val in (True, False): + if pygame.version.SDL >= (3, 0, 0): + with self.assertRaises(pygame.error): + pygame.mouse.set_relative_mode(val) + else: + with self.assertWarns(DeprecationWarning): + pygame.mouse.set_relative_mode(val) + + pygame.display.set_mode((100, 100)) pygame.mouse.set_visible(True) pygame.mouse.set_relative_mode(True) # sets the mouse invisible visible = pygame.mouse.get_visible() @@ -398,6 +407,7 @@ def test_set_relative_mode(self): ) def test_get_relative_mode(self): """Tests that get_relative_mode correctly reports the relative mode""" + pygame.display.set_mode((100, 100)) pygame.mouse.set_relative_mode(True) self.assertEqual(pygame.mouse.get_relative_mode(), True) pygame.mouse.set_relative_mode(False) From 179b3bc8b45f3d07c6a7692e9de95d4ea9b2eec1 Mon Sep 17 00:00:00 2001 From: Ankith Date: Sun, 31 Aug 2025 18:00:20 +0530 Subject: [PATCH 6/7] SDL3: image+scrap+transform+debug: runtime fixes --- src_c/image.c | 31 ++++++++++--------------------- src_c/scrap.c | 4 ++++ src_c/scrap_sdl2.c | 4 ++++ src_c/transform.c | 6 +++--- src_py/_debug.py | 21 ++++++++++----------- 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src_c/image.c b/src_c/image.c index 74f5d60299..63ccbcd537 100644 --- a/src_c/image.c +++ b/src_c/image.c @@ -454,17 +454,10 @@ tobytes_surf_32bpp(SDL_Surface *surf, SDL_PixelFormat *format_details, { int w, h; -#if SDL_VERSION_ATLEAST(3, 0, 0) - Uint32 Rloss = format_details->Rbits; - Uint32 Gloss = format_details->Gbits; - Uint32 Bloss = format_details->Bbits; - Uint32 Aloss = format_details->Abits; -#else - Uint32 Rloss = format_details->Rloss; - Uint32 Gloss = format_details->Gloss; - Uint32 Bloss = format_details->Bloss; - Uint32 Aloss = format_details->Aloss; -#endif + Uint32 Rloss = PG_FORMAT_R_LOSS(format_details); + Uint32 Gloss = PG_FORMAT_G_LOSS(format_details); + Uint32 Bloss = PG_FORMAT_B_LOSS(format_details); + Uint32 Aloss = PG_FORMAT_A_LOSS(format_details); Uint32 Rmask = format_details->Rmask; Uint32 Gmask = format_details->Gmask; Uint32 Bmask = format_details->Bmask; @@ -565,19 +558,15 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg) if (!format_details) { return RAISE(pgExc_SDLError, SDL_GetError()); } - SDL_Palette *surf_palette = SDL_GetSurfacePalette(surf); - Rloss = format_details->Rbits; - Gloss = format_details->Gbits; - Bloss = format_details->Bbits; - Aloss = format_details->Abits; + SDL_Palette *surf_palette = PG_GetSurfacePalette(surf); #else SDL_PixelFormat *format_details = surf->format; SDL_Palette *surf_palette = surf->format->palette; - Rloss = format_details->Rloss; - Gloss = format_details->Gloss; - Bloss = format_details->Bloss; - Aloss = format_details->Aloss; #endif + Rloss = PG_FORMAT_R_LOSS(format_details); + Gloss = PG_FORMAT_G_LOSS(format_details); + Bloss = PG_FORMAT_B_LOSS(format_details); + Aloss = PG_FORMAT_A_LOSS(format_details); Rmask = format_details->Rmask; Gmask = format_details->Gmask; Bmask = format_details->Bmask; @@ -1722,7 +1711,7 @@ SaveTGA_RW(SDL_Surface *surface, SDL_RWops *out, int rle) } SDL_PixelFormat output_format; - SDL_Palette *surf_palette = SDL_GetSurfacePalette(surface); + SDL_Palette *surf_palette = PG_GetSurfacePalette(surface); #else SDL_PixelFormat *surf_format = surface->format; SDL_Palette *surf_palette = surface->format->palette; diff --git a/src_c/scrap.c b/src_c/scrap.c index 43fd96b915..8f1d2c8fd4 100644 --- a/src_c/scrap.c +++ b/src_c/scrap.c @@ -441,7 +441,11 @@ _scrap_put_text(PyObject *self, PyObject *args) return NULL; } +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (!SDL_SetClipboardText(text)) { +#else if (SDL_SetClipboardText(text)) { +#endif return RAISE(pgExc_SDLError, SDL_GetError()); } diff --git a/src_c/scrap_sdl2.c b/src_c/scrap_sdl2.c index 754edaf1bf..fe75a75213 100644 --- a/src_c/scrap_sdl2.c +++ b/src_c/scrap_sdl2.c @@ -109,7 +109,11 @@ pygame_scrap_put(char *type, Py_ssize_t srclen, char *src) if (strcmp(type, pygame_scrap_plaintext_type) == 0 || strcmp(type, pygame_scrap_utf8text_type) == 0) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + if (SDL_SetClipboardText(src)) { +#else if (SDL_SetClipboardText(src) == 0) { +#endif return 1; } } diff --git a/src_c/transform.c b/src_c/transform.c index 6c9a563c5e..bd09e52375 100644 --- a/src_c/transform.c +++ b/src_c/transform.c @@ -3737,7 +3737,7 @@ surf_average_color(PyObject *self, PyObject *args, PyObject *kwargs) Uint8 r, g, b, a; int x, y, w, h; static char *keywords[] = {"surface", "rect", "consider_alpha", NULL}; - SDL_bool consider_alpha = SDL_FALSE; + int consider_alpha = SDL_FALSE; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|Op", keywords, &pgSurface_Type, &surfobj, &rectobj, @@ -4051,7 +4051,7 @@ surf_box_blur(PyObject *self, PyObject *args, PyObject *kwargs) pgSurfaceObject *dst_surf_obj = NULL; pgSurfaceObject *src_surf_obj; SDL_Surface *new_surf = NULL; - SDL_bool repeat_edge_pixels = SDL_TRUE; + int repeat_edge_pixels = SDL_TRUE; int radius; @@ -4084,7 +4084,7 @@ surf_gaussian_blur(PyObject *self, PyObject *args, PyObject *kwargs) pgSurfaceObject *dst_surf_obj = NULL; pgSurfaceObject *src_surf_obj; SDL_Surface *new_surf = NULL; - SDL_bool repeat_edge_pixels = SDL_TRUE; + int repeat_edge_pixels = SDL_TRUE; int radius; diff --git a/src_py/_debug.py b/src_py/_debug.py index d3d9ee5f5b..a39f599bc4 100644 --- a/src_py/_debug.py +++ b/src_py/_debug.py @@ -63,9 +63,9 @@ def _get_platform_info(): Internal helper to get platform information """ cpu_inst_dict = get_cpu_instruction_sets() - sse2 = 'Yes' if cpu_inst_dict['SSE2'] else 'No' - avx2 = 'Yes' if cpu_inst_dict['AVX2'] else 'No' - neon = 'Yes' if cpu_inst_dict['NEON'] else 'No' + sse2 = "Yes" if cpu_inst_dict["SSE2"] else "No" + avx2 = "Yes" if cpu_inst_dict["AVX2"] else "No" + neon = "Yes" if cpu_inst_dict["NEON"] else "No" ret = f"Platform:\t\t{platform.platform()}\n" ret += f"System:\t\t\t{platform.system()}\n" ret += f"System Version:\t\t{platform.version()}\n" @@ -106,18 +106,17 @@ def default_return(linked=True): get_driver as get_display_driver, get_init as display_init, ) - from pygame.mixer import ( - get_driver as get_mixer_driver, - get_init as mixer_init, - ) + + debug_str, *mixer = attempt_import("pygame.mixer", "get_driver", debug_str) + get_mixer_driver = mixer[1] if mixer[0] else lambda: None + + debug_str, *mixer = attempt_import("pygame.mixer", "get_init", debug_str) + mixer_init = mixer[1] if mixer[0] else lambda: False debug_str, *mixer = attempt_import( "pygame.mixer", "get_sdl_mixer_version", debug_str ) - if not mixer[0]: - get_sdl_mixer_version = default_return - else: - get_sdl_mixer_version = mixer[1] + get_sdl_mixer_version = mixer[1] if mixer[0] else default_return debug_str, *font = attempt_import("pygame.font", "get_sdl_ttf_version", debug_str) if not font[0]: From 1cdc4f077ca6a4ad97f99e9c3c98a55d2bf546bd Mon Sep 17 00:00:00 2001 From: Ankith Date: Mon, 1 Sep 2025 18:17:36 +0530 Subject: [PATCH 7/7] SDL3: skip tests of unported stuff, run on CI --- .github/workflows/build-sdl3.yml | 40 ++++++++++++++------------------ test/controller_tags.py | 8 +++++++ test/gfxdraw_tags.py | 8 +++++++ test/joystick_tags.py | 8 +++++++ test/mixer_music_tags.py | 8 +++++++ test/mixer_tags.py | 8 +++++++ test/render_tags.py | 8 +++++++ test/sndarray_tags.py | 6 +++-- test/touch_tags.py | 8 +++++++ test/video_tags.py | 8 +++++++ 10 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 test/controller_tags.py create mode 100644 test/gfxdraw_tags.py create mode 100644 test/joystick_tags.py create mode 100644 test/mixer_music_tags.py create mode 100644 test/mixer_tags.py create mode 100644 test/render_tags.py create mode 100644 test/touch_tags.py create mode 100644 test/video_tags.py diff --git a/.github/workflows/build-sdl3.yml b/.github/workflows/build-sdl3.yml index 0ee78442b3..ed02c66bed 100644 --- a/.github/workflows/build-sdl3.yml +++ b/.github/workflows/build-sdl3.yml @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false # if a particular matrix build fails, don't skip the rest matrix: - os: [ubuntu-24.04, windows-latest, macos-14] + os: [ubuntu-24.04, windows-latest, macos-15] env: # Pip now forces us to either make a venv or set this flag, so we will do @@ -54,18 +54,15 @@ jobs: steps: - uses: actions/checkout@v5.0.0 - - name: Install pygame deps (linux) + - name: Fix apt issues (linux) if: matrix.os == 'ubuntu-24.04' - run: | - sudo apt-get update --fix-missing - sudo apt-get install libfreetype6-dev libportmidi-dev python3-dev + run: sudo apt-get update --fix-missing - - name: Install pygame deps (mac) - if: matrix.os == 'macos-14' - run: brew install freetype portmidi + - name: Install pygame & SDL deps (mac) + if: matrix.os == 'macos-15' + run: brew install freetype harfbuzz portmidi libtiff webp - # taken from dependencies of the 'libsdl2-dev' package - - name: Install SDL deps (linux) + - name: Install pygame & SDL deps (linux) if: matrix.os == 'ubuntu-24.04' run: > sudo apt-get install libasound2-dev libdbus-1-dev libdecor-0-dev libdrm-dev @@ -73,6 +70,8 @@ jobs: libsamplerate0-dev libsndio-dev libudev-dev libwayland-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev + libfreetype-dev libharfbuzz-dev libportmidi-dev libtiff-dev libwebp-dev + python3-dev # taken from https://wiki.libsdl.org/SDL3/Installation - name: Install SDL3 @@ -93,7 +92,9 @@ jobs: cd SDL_image mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_BUILD_TYPE=Release -DSDLIMAGE_VENDORED=0 \ + -DSDLIMAGE_BACKEND_STB=1 -DSDLIMAGE_BACKEND_IMAGEIO=0 \ + -DSDLIMAGE_AVIF=0 -DSDLIMAGE_JXL=0 -DSDLIMAGE_TIF=1 -DSDLIMAGE_WEBP=1 .. cmake --build . --config Release --parallel sudo cmake --install . --config Release @@ -104,20 +105,15 @@ jobs: cd SDL_ttf mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_BUILD_TYPE=Release -DSDLTTF_VENDORED=0 -DSDLTTF_HARFBUZZ=1 .. cmake --build . --config Release --parallel sudo cmake --install . --config Release - name: Build with SDL3 run: python3 dev.py build --sdl3 - # eventually we need to run all tests, but for now test that importing pygame - # works - - name: Test import works - run: python3 -c 'import pygame' - - # - name: Run tests - # env: - # SDL_VIDEODRIVER: "dummy" - # SDL_AUDIODRIVER: "disk" - # run: python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300 + - name: Run tests + env: + SDL_VIDEODRIVER: "dummy" + SDL_AUDIODRIVER: "disk" + run: python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300 diff --git a/test/controller_tags.py b/test/controller_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/controller_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/gfxdraw_tags.py b/test/gfxdraw_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/gfxdraw_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/joystick_tags.py b/test/joystick_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/joystick_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/mixer_music_tags.py b/test/mixer_music_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/mixer_music_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/mixer_tags.py b/test/mixer_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/mixer_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/render_tags.py b/test/render_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/render_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/sndarray_tags.py b/test/sndarray_tags.py index 5e24b93580..9c5b71ee9e 100644 --- a/test/sndarray_tags.py +++ b/test/sndarray_tags.py @@ -1,6 +1,8 @@ -__tags__ = ["array"] +import pygame -exclude = False +__tags__ = ["array", "sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) try: import numpy diff --git a/test/touch_tags.py b/test/touch_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/touch_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/test/video_tags.py b/test/video_tags.py new file mode 100644 index 0000000000..7048bc8392 --- /dev/null +++ b/test/video_tags.py @@ -0,0 +1,8 @@ +import pygame + +__tags__ = ["sdl3_skip"] + +exclude = pygame.get_sdl_version() >= (3, 0, 0) + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore"))