diff --git a/buildconfig/Setup.Android.SDL2.in b/buildconfig/Setup.Android.SDL2.in index fc556ad024..5ef285a55e 100644 --- a/buildconfig/Setup.Android.SDL2.in +++ b/buildconfig/Setup.Android.SDL2.in @@ -43,7 +43,7 @@ base src_c/base.c $(SDL) $(DEBUG) color src_c/color.c $(SDL) $(DEBUG) constants src_c/constants.c $(SDL) $(DEBUG) display src_c/display.c $(SDL) $(DEBUG) -event src_c/event.c $(SDL) $(DEBUG) +_event src_c/_event.c $(SDL) $(DEBUG) key src_c/key.c $(SDL) $(DEBUG) mouse src_c/mouse.c $(SDL) $(DEBUG) rect src_c/rect.c src_c/pgcompat_rect.c $(SDL) $(DEBUG) diff --git a/buildconfig/Setup.Emscripten.SDL2.in b/buildconfig/Setup.Emscripten.SDL2.in index 28e86f1e42..a6b97f2271 100644 --- a/buildconfig/Setup.Emscripten.SDL2.in +++ b/buildconfig/Setup.Emscripten.SDL2.in @@ -47,7 +47,7 @@ controller src_c/void.c controller_old src_c/void.c display src_c/void.c draw src_c/void.c -event src_c/void.c +_event src_c/void.c font src_c/void.c gfxdraw src_c/void.c joystick src_c/void.c diff --git a/buildconfig/Setup.SDL2.in b/buildconfig/Setup.SDL2.in index 011b8d1404..9fe68925f6 100644 --- a/buildconfig/Setup.SDL2.in +++ b/buildconfig/Setup.SDL2.in @@ -54,7 +54,7 @@ base src_c/base.c $(SDL) $(DEBUG) color src_c/color.c $(SDL) $(DEBUG) constants src_c/constants.c $(SDL) $(DEBUG) display src_c/display.c $(SDL) $(DEBUG) -event src_c/event.c $(SDL) $(DEBUG) +_event src_c/_event.c $(SDL) $(DEBUG) key src_c/key.c $(SDL) $(DEBUG) mouse src_c/mouse.c $(SDL) $(DEBUG) rect src_c/rect.c src_c/pgcompat_rect.c $(SDL) $(DEBUG) diff --git a/buildconfig/stubs/pygame/_event.pyi b/buildconfig/stubs/pygame/_event.pyi new file mode 100644 index 0000000000..e01d6db86b --- /dev/null +++ b/buildconfig/stubs/pygame/_event.pyi @@ -0,0 +1,21 @@ +from __future__ import annotations + +from .typing import SequenceLike, EventLike + +_EventTypes = int | SequenceLike[int] + +def pump(dopump: bool, /) -> None: ... +def _get(type: int, /) -> EventLike | None: ... +def _peek(type: int, /) -> bool: ... +def _proxify_event_type(type: int, /) -> int: ... +def video_check() -> None: ... +def poll() -> EventLike: ... +def wait(timeout: int = 0) -> EventLike: ... +def set_grab(grab: bool, /) -> None: ... +def get_grab() -> bool: ... +def allowed_get(type: int, /) -> bool: ... +def allowed_set(type: int, val: bool, /) -> None: ... +def post(event: EventLike, /) -> bool: ... +def register_event_class(cls: type[EventLike]) -> None: ... +def _internal_mod_init() -> None: ... +def _internal_mod_quit() -> None: ... diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index 1987f96de9..3ce4731876 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -1,27 +1,19 @@ -from typing import ( - Any, - Dict, - List, - Optional, - Union, - final, -) +from typing import Any, Union, Optional, Dict -from pygame.typing import SequenceLike +from pygame.typing import SequenceLike, EventLike -@final -class Event: - type: int - dict: Dict[str, Any] - __dict__: Dict[str, Any] - __hash__: None # type: ignore + +class Event(EventLike): def __init__( - self, type: int, dict: Dict[str, Any] = ..., **kwargs: Any + self, type: int, dict: Optional[Dict[str, Any]] = None, **kwargs: Any ) -> None: ... + def __new__(cls, *args: Any, **kwargs: Any) -> "Event": ... def __getattribute__(self, name: str) -> Any: ... def __setattr__(self, name: str, value: Any) -> None: ... def __delattr__(self, name: str) -> None: ... + def __int__(self) -> int: ... def __bool__(self) -> bool: ... + def __eq__(self, other: Any) -> bool: ... _EventTypes = Union[int, SequenceLike[int]] @@ -30,18 +22,20 @@ def get( eventtype: Optional[_EventTypes] = None, pump: Any = True, exclude: Optional[_EventTypes] = None, -) -> List[Event]: ... -def poll() -> Event: ... -def wait(timeout: int = 0) -> Event: ... -def peek(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> bool: ... +) -> list[EventLike]: ... +def poll() -> EventLike: ... +def wait(timeout: int = 0) -> EventLike: ... +def peek(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> Union[bool, EventLike]: ... def clear(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> None: ... -def event_name(type: int, /) -> str: ... -def set_blocked(type: Optional[_EventTypes], /) -> None: ... -def set_allowed(type: Optional[_EventTypes], /) -> None: ... -def get_blocked(type: _EventTypes, /) -> bool: ... +def event_name(type: int) -> str: ... +def set_blocked(type: Optional[_EventTypes], *args: int) -> None: ... +def set_allowed(type: Optional[_EventTypes], *args: int) -> None: ... +def get_blocked(type: _EventTypes, *args: int) -> bool: ... def set_grab(grab: bool, /) -> None: ... def get_grab() -> bool: ... -def post(event: Event, /) -> bool: ... +def post(event: EventLike, /) -> bool: ... def custom_type() -> int: ... +def init() -> None: ... +def quit() -> None: ... EventType = Event diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi index ca88863a59..b5271c37d0 100644 --- a/buildconfig/stubs/pygame/typing.pyi +++ b/buildconfig/stubs/pygame/typing.pyi @@ -14,7 +14,18 @@ __all__ = [ import sys from abc import abstractmethod -from typing import IO, Callable, Tuple, Union, TypeVar, Protocol +from typing import ( + IO, + Callable, + Tuple, + Dict, + Union, + Optional, + TypeVar, + Protocol, + Any, + Iterable, +) from pygame.color import Color from pygame.rect import Rect, FRect @@ -40,13 +51,14 @@ _T_co = TypeVar("_T_co", covariant=True) class SequenceLike(Protocol[_T_co]): """ - Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + Variant of the standard `Sequence` ABC that only requires `__getitem__`. """ @abstractmethod def __getitem__(self, index: int, /) -> _T_co: ... - @abstractmethod - def __len__(self) -> int: ... + + +IterableLike = Union[SequenceLike[_T_co], Iterable[_T_co]] # Modify typehints when it is possible to annotate sizes @@ -72,6 +84,16 @@ RectLike = Union[ ] +class EventLike(Protocol): + def __init__(self, type: int, /, **kwargs: Any) -> None: ... + def __new__(cls, *args: Any, **kwargs: Any) -> "EventLike": ... + + @property + def type(self) -> int: ... + @property + def dict(self) -> Dict[str, Any]: ... + + # cleanup namespace del ( sys, @@ -82,7 +104,10 @@ del ( IO, Callable, Tuple, + Dict, Union, + Optional, TypeVar, Protocol, + Any, ) diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index 650dccd25b..bdca06f6d6 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -13,25 +13,20 @@ The extension module :py:mod:`pygame.event`. Header file: src_c/include/pygame.h +.. c:function:: PyObject* pgEvent_GetType(void) -.. c:type:: pgEventObject + Return a python class that is currently set to be the event class - The :py:class:`pygame.event.EventType` object C struct. - - .. c:member:: int type - - The event type code. - -.. c:type:: pgEvent_Type - - The pygame event object type :py:class:`pygame.event.EventType`. + If the class is not known at the time (called before ``pygame._event.register_event_class``) + this function will return NULL and set the error. .. c:function:: int pgEvent_Check(PyObject *x) Return true if *x* is a pygame event instance Will return false if *x* is a subclass of event. - This is a macro. No check is made that *x* is not ``NULL``. + Will return -1 if python error is set while checking. + No check is made that *x* is not ``NULL``. .. c:function:: PyObject* pgEvent_New(SDL_Event *event) @@ -39,6 +34,21 @@ Header file: src_c/include/pygame.h If *event* is ``NULL`` then create an empty event object. On failure raise a Python exception and return ``NULL``. + .. note:: + This is a destructive operation, so don't use passed SDL_Event afterwards. + +.. c:function:: PyObject* pgEvent_FromTypeAndDict(int e_type, PyObject *dict) + + Instantiates a new Event object created from the given event type and a dict. + + On error returns NULL and sets python exception. + +.. c:function:: int pgEvent_GetEventType(PyObject *) + + Returns an event type extracted from the python object. + + On error this retuns -1. + .. c:function:: char* pgEvent_GetKeyDownInfo(void) Return an array of bools (using char) of length SDL_NUM_SCANCODES @@ -59,23 +69,19 @@ Header file: src_c/include/pygame.h Return an array of bools (using char) of length 5 with the most recent button releases. -.. c:function:: int pg_post_event(Uint32 type, PyObject *dict) +.. c:function:: int pg_post_event(Uint32 type, PyObject *obj) Posts a pygame event that is an ``SDL_USEREVENT`` on the SDL side. This - function takes a python dict, which can be NULL too. - This function does not need GIL to be held if dict is NULL, but needs GIL + function takes a python dict or Event instance, which can be NULL too. + This function does not need GIL to be held if obj is NULL, but needs GIL otherwise. Just like the SDL ``SDL_PushEvent`` function, returns 1 on success, 0 if the event was not posted due to it being blocked, and -1 on failure. -.. c:function:: int pg_post_event_dictproxy(Uint32 type, pgEventDictProxy *dict_proxy) - - Posts a pygame event that is an ``SDL_USEREVENT`` on the SDL side, can also - optionally take a dictproxy instance. Using this dictproxy API is especially - useful when multiple events that need to be posted share the same dict - attribute, like in the case of event timers. This way, the number of python - increfs and decrefs are reduced, and callers of this function don't need to - hold GIL for every event posted, the GIL only needs to be held during the - creation of the dictproxy instance, and when it is freed. - Just like the SDL ``SDL_PushEvent`` function, returns 1 on success, 0 if the - event was not posted due to it being blocked, and -1 on failure. + .. ## pg_post_event ## + +.. c:function:: int pg_post_event_steal(Uint32 type, PyObject *obj) + + Nearly the same as :c:func:`pg_post_event`, but with two differences. + 1) This doesn't need GIL held at all when called. + 2) This steals the reference to obj, instead of borrowing it. diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index b6c8349948..2dc09c449f 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -317,8 +317,9 @@ On Android, the following events can be generated .. function:: peek | :sl:`test if event types are waiting on the queue` - | :sg:`peek(eventtype=None) -> bool` - | :sg:`peek(eventtype=None, pump=True) -> bool` + | :sg:`peek() -> Event instance` + | :sg:`peek(eventtype) -> bool` + | :sg:`peek(eventtype, pump=True) -> bool` Returns ``True`` if there are any events of the given type waiting on the queue. If a sequence of event types is passed, this will return ``True`` if @@ -326,6 +327,11 @@ On Android, the following events can be generated If ``pump`` is ``True`` (the default), then :func:`pygame.event.pump()` will be called. + If ``eventtype`` is unspecified, or ``None``, then this function will return the top-most event instead. + + .. note:: + There is no guarantee that the event got with :func:`pygame.event.get()` immediately after calling this function will be the same. + .. versionchangedold:: 1.9.5 Added ``pump`` argument .. ## pygame.event.peek ## diff --git a/docs/reST/ref/typing.rst b/docs/reST/ref/typing.rst index bd08af9580..a12308b102 100644 --- a/docs/reST/ref/typing.rst +++ b/docs/reST/ref/typing.rst @@ -76,4 +76,18 @@ type aliases for proper typehint annotations. * Any object with a ``.rect`` attribute which is a ``RectLike`` or a function returning a ``RectLike`` + .. data:: EventLike + + A protocol representing an event that is undestood by pygame-ce. + + * ``pygame.Event(type, dict, arg=val)`` + * A python class that implements EventLike protocol: + + :: + + class MyEvent: + def __init__(self, type: int, **kwargs): + self.type = type + self.dict = kwargs + .. ## pygame.typing ## diff --git a/src_c/event.c b/src_c/_event.c similarity index 61% rename from src_c/event.c rename to src_c/_event.c index 59132e3330..2208fbc72a 100644 --- a/src_c/event.c +++ b/src_c/_event.c @@ -31,8 +31,6 @@ #include "doc/event_doc.h" -#include "structmember.h" - // The system message code is only tested on windows, so only // include it there for now. #include @@ -49,11 +47,6 @@ #define PG_GET_LIST_LEN 128 -/* _custom_event stores the next custom user event type that will be - * returned by pygame.event.custom_type() */ -#define _PGE_CUSTOM_EVENT_INIT PGE_USEREVENT + 1 - -static int _custom_event = _PGE_CUSTOM_EVENT_INIT; static int _pg_event_is_init = 0; /* Length of our unicode string in bytes. We need 1 to 3 bytes to store @@ -92,11 +85,22 @@ static SDL_TimerID _pg_repeat_timer = 0; static SDL_Event _pg_repeat_event; static SDL_Event _pg_last_keydown_event = {0}; +#define INPUT_BUFFER_SIZE SDL_NUM_SCANCODES + SDL_NUM_SCANCODES + 5 + 5 +#define INPUT_BUFFER_PRESSED_OFFSET 0 +#define INPUT_BUFFER_RELEASED_OFFSET \ + INPUT_BUFFER_PRESSED_OFFSET + SDL_NUM_SCANCODES +#define INPUT_BUFFER_MOUSE_PRESSED_OFFSET \ + INPUT_BUFFER_RELEASED_OFFSET + SDL_NUM_SCANCODES +#define INPUT_BUFFER_MOUSE_RELEASED_OFFSET \ + INPUT_BUFFER_MOUSE_PRESSED_OFFSET + 5 + +static_assert(INPUT_BUFFER_MOUSE_RELEASED_OFFSET + 5 == INPUT_BUFFER_SIZE, + "mismatched buffer ranges definition"); + /* Not used as text, acts as an array of bools */ -static char pressed_keys[SDL_NUM_SCANCODES] = {0}; -static char released_keys[SDL_NUM_SCANCODES] = {0}; -static char pressed_mouse_buttons[5] = {0}; -static char released_mouse_buttons[5] = {0}; +static char input_buffer[INPUT_BUFFER_SIZE] = {0}; + +static PyObject *_event_class = NULL; #ifdef __EMSCRIPTEN__ /* these macros are no-op here */ @@ -500,7 +504,8 @@ pg_event_filter(void *_, SDL_Event *event) return 0; PG_LOCK_EVFILTER_MUTEX - pressed_keys[event->key.keysym.scancode] = 1; + input_buffer[INPUT_BUFFER_PRESSED_OFFSET + + event->key.keysym.scancode] = 1; if (pg_key_repeat_delay > 0) { if (_pg_repeat_timer) SDL_RemoveTimer(_pg_repeat_timer); @@ -530,7 +535,8 @@ pg_event_filter(void *_, SDL_Event *event) else if (event->type == SDL_KEYUP) { PG_LOCK_EVFILTER_MUTEX - released_keys[event->key.keysym.scancode] = 1; + input_buffer[INPUT_BUFFER_RELEASED_OFFSET + + event->key.keysym.scancode] = 1; if (_pg_repeat_timer && _pg_repeat_event.key.keysym.scancode == event->key.keysym.scancode) { SDL_RemoveTimer(_pg_repeat_timer); @@ -543,11 +549,13 @@ pg_event_filter(void *_, SDL_Event *event) event->type == SDL_MOUSEBUTTONUP) { if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button - 1 < 5) { - pressed_mouse_buttons[event->button.button - 1] = 1; + input_buffer[INPUT_BUFFER_MOUSE_PRESSED_OFFSET + + event->button.button - 1] = 1; } else if (event->type == SDL_MOUSEBUTTONUP && event->button.button - 1 < 5) { - released_mouse_buttons[event->button.button - 1] = 1; + input_buffer[INPUT_BUFFER_MOUSE_RELEASED_OFFSET + + event->button.button - 1] = 1; } if (event->button.button & PGM_BUTTON_KEEP) event->button.button ^= PGM_BUTTON_KEEP; @@ -638,11 +646,6 @@ pgEvent_AutoQuit(PyObject *self, PyObject *_null) _pg_repeat_timer = 0; } PG_UNLOCK_EVFILTER_MUTEX - /* The main reason for _custom_event to be reset here is so we - * can have a unit test that checks if pygame.event.custom_type() - * stops returning new types when they are finished, without that - * test preventing further tests from getting a custom event type.*/ - _custom_event = _PGE_CUSTOM_EVENT_INIT; } _pg_event_is_init = 0; Py_RETURN_NONE; @@ -668,224 +671,33 @@ pgEvent_AutoInit(PyObject *self, PyObject *_null) Py_RETURN_NONE; } -/* Posts a pygame event that is an ``SDL_USEREVENT`` on the SDL side, can also - * optionally take a dictproxy instance. Using this dictproxy API is especially - * useful when multiple events that need to be posted share the same dict - * attribute, like in the case of event timers. This way, the number of python - * increfs and decrefs are reduced, and callers of this function don't need to - * hold GIL for every event posted, the GIL only needs to be held during the - * creation of the dictproxy instance, and when it is freed. - * Just like the SDL ``SDL_PushEvent`` function, returns 1 on success, 0 if the - * event was not posted due to it being blocked, and -1 on failure. */ +/* Similar to pg_post_event, but it steals the reference to obj and does not + * need GIL to be held at all.*/ static int -pg_post_event_dictproxy(Uint32 type, pgEventDictProxy *dict_proxy) +pg_post_event_steal(int type, PyObject *obj) { - int ret; SDL_Event event = {0}; - event.type = _pg_pgevent_proxify(type); - event.user.data1 = (void *)dict_proxy; - - ret = SDL_PushEvent(&event); - if (ret == 1 && dict_proxy) { - /* successfully posted event with dictproxy */ - SDL_AtomicLock(&dict_proxy->lock); - dict_proxy->num_on_queue++; - SDL_AtomicUnlock(&dict_proxy->lock); - } - - return ret; + event.user.data1 = (void *)obj; + return SDL_PushEvent(&event); } /* This function posts an SDL "UserEvent" event, can also optionally take a - * python dict. This function does not need GIL to be held if dict is NULL, but - * needs GIL otherwise */ + * dict or an Event instance. This function does not need GIL to be held if obj + * is NULL, but needs GIL otherwise + */ static int -pg_post_event(Uint32 type, PyObject *dict) +pg_post_event(int type, PyObject *obj) { - int ret; - if (!dict) { - return pg_post_event_dictproxy(type, NULL); - } + if (obj) + Py_INCREF(obj); - pgEventDictProxy *dict_proxy = - (pgEventDictProxy *)malloc(sizeof(pgEventDictProxy)); - if (!dict_proxy) { - return SDL_SetError("insufficient memory (internal malloc failed)"); - } + int ret = pg_post_event_steal(type, obj); - Py_INCREF(dict); - dict_proxy->dict = dict; - /* initially set to 0 - unlocked state */ - dict_proxy->lock = 0; - dict_proxy->num_on_queue = 0; - /* So that event function handling this frees it */ - dict_proxy->do_free_at_end = 1; - - ret = pg_post_event_dictproxy(type, dict_proxy); - if (ret != 1) { - Py_DECREF(dict); - free(dict_proxy); - } - return ret; -} + if (ret != 1 && obj) + Py_DECREF(obj); -static char * -_pg_name_from_eventtype(int type) -{ - switch (type) { - case SDL_ACTIVEEVENT: - return "ActiveEvent"; - case SDL_APP_TERMINATING: - return "AppTerminating"; - case SDL_APP_LOWMEMORY: - return "AppLowMemory"; - case SDL_APP_WILLENTERBACKGROUND: - return "AppWillEnterBackground"; - case SDL_APP_DIDENTERBACKGROUND: - return "AppDidEnterBackground"; - case SDL_APP_WILLENTERFOREGROUND: - return "AppWillEnterForeground"; - case SDL_APP_DIDENTERFOREGROUND: - return "AppDidEnterForeground"; - case SDL_CLIPBOARDUPDATE: - return "ClipboardUpdate"; - case SDL_KEYDOWN: - return "KeyDown"; - case SDL_KEYUP: - return "KeyUp"; - case SDL_KEYMAPCHANGED: - return "KeyMapChanged"; -#if SDL_VERSION_ATLEAST(2, 0, 14) - case SDL_LOCALECHANGED: - return "LocaleChanged"; -#endif - case SDL_MOUSEMOTION: - return "MouseMotion"; - case SDL_MOUSEBUTTONDOWN: - return "MouseButtonDown"; - case SDL_MOUSEBUTTONUP: - return "MouseButtonUp"; - case SDL_JOYAXISMOTION: - return "JoyAxisMotion"; - case SDL_JOYBALLMOTION: - return "JoyBallMotion"; - case SDL_JOYHATMOTION: - return "JoyHatMotion"; - case SDL_JOYBUTTONUP: - return "JoyButtonUp"; - case SDL_JOYBUTTONDOWN: - return "JoyButtonDown"; - case SDL_QUIT: - return "Quit"; - case SDL_SYSWMEVENT: - return "SysWMEvent"; - case SDL_VIDEORESIZE: - return "VideoResize"; - case SDL_VIDEOEXPOSE: - return "VideoExpose"; - case PGE_MIDIIN: - return "MidiIn"; - case PGE_MIDIOUT: - return "MidiOut"; - case SDL_NOEVENT: - return "NoEvent"; - case SDL_FINGERMOTION: - return "FingerMotion"; - case SDL_FINGERDOWN: - return "FingerDown"; - case SDL_FINGERUP: - return "FingerUp"; - case SDL_MULTIGESTURE: - return "MultiGesture"; - case SDL_MOUSEWHEEL: - return "MouseWheel"; - case SDL_TEXTINPUT: - return "TextInput"; - case SDL_TEXTEDITING: - return "TextEditing"; - case SDL_DROPFILE: - return "DropFile"; - case SDL_DROPTEXT: - return "DropText"; - case SDL_DROPBEGIN: - return "DropBegin"; - case SDL_DROPCOMPLETE: - return "DropComplete"; - case SDL_CONTROLLERAXISMOTION: - return "ControllerAxisMotion"; - case SDL_CONTROLLERBUTTONDOWN: - return "ControllerButtonDown"; - case SDL_CONTROLLERBUTTONUP: - return "ControllerButtonUp"; - case SDL_CONTROLLERDEVICEADDED: - return "ControllerDeviceAdded"; - case SDL_CONTROLLERDEVICEREMOVED: - return "ControllerDeviceRemoved"; - case SDL_CONTROLLERDEVICEREMAPPED: - return "ControllerDeviceMapped"; - case SDL_JOYDEVICEADDED: - return "JoyDeviceAdded"; - case SDL_JOYDEVICEREMOVED: - return "JoyDeviceRemoved"; -#if SDL_VERSION_ATLEAST(2, 0, 14) - case SDL_CONTROLLERTOUCHPADDOWN: - return "ControllerTouchpadDown"; - case SDL_CONTROLLERTOUCHPADMOTION: - return "ControllerTouchpadMotion"; - case SDL_CONTROLLERTOUCHPADUP: - return "ControllerTouchpadUp"; - case SDL_CONTROLLERSENSORUPDATE: - return "ControllerSensorUpdate"; -#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ - case SDL_AUDIODEVICEADDED: - return "AudioDeviceAdded"; - case SDL_AUDIODEVICEREMOVED: - return "AudioDeviceRemoved"; - case SDL_RENDER_TARGETS_RESET: - return "RenderTargetsReset"; - case SDL_RENDER_DEVICE_RESET: - return "RenderDeviceReset"; - case PGE_WINDOWSHOWN: - return "WindowShown"; - case PGE_WINDOWHIDDEN: - return "WindowHidden"; - case PGE_WINDOWEXPOSED: - return "WindowExposed"; - case PGE_WINDOWMOVED: - return "WindowMoved"; - case PGE_WINDOWRESIZED: - return "WindowResized"; - case PGE_WINDOWSIZECHANGED: - return "WindowSizeChanged"; - case PGE_WINDOWMINIMIZED: - return "WindowMinimized"; - case PGE_WINDOWMAXIMIZED: - return "WindowMaximized"; - case PGE_WINDOWRESTORED: - return "WindowRestored"; - case PGE_WINDOWENTER: - return "WindowEnter"; - case PGE_WINDOWLEAVE: - return "WindowLeave"; - case PGE_WINDOWFOCUSGAINED: - return "WindowFocusGained"; - case PGE_WINDOWFOCUSLOST: - return "WindowFocusLost"; - case PGE_WINDOWCLOSE: - return "WindowClose"; - case PGE_WINDOWTAKEFOCUS: - return "WindowTakeFocus"; - case PGE_WINDOWHITTEST: - return "WindowHitTest"; - case PGE_WINDOWICCPROFCHANGED: - return "WindowICCProfChanged"; - case PGE_WINDOWDISPLAYCHANGED: - return "WindowDisplayChanged"; - } - if (type >= PGE_USEREVENT && type < PG_NUMEVENTS) - return "UserEvent"; - return "Unknown"; + return ret; } /* Helper for adding objects to dictionaries. Check for errors with @@ -917,7 +729,7 @@ get_joy_device_index(int instance_id) } static PyObject * -dict_from_event(SDL_Event *event) +dict_or_obj_from_event(SDL_Event *event) { PyObject *dict = NULL, *tuple, *obj; int hx, hy; @@ -926,27 +738,8 @@ dict_from_event(SDL_Event *event) /* check if a proxy event or userevent was posted */ if (event->type >= PGPOST_EVENTBEGIN) { - int to_free; - pgEventDictProxy *dict_proxy = (pgEventDictProxy *)event->user.data1; - if (!dict_proxy) { - /* the field being NULL implies empty dict */ - return PyDict_New(); - } - - /* spinlocks must be held and released as quickly as possible */ - SDL_AtomicLock(&dict_proxy->lock); - dict = dict_proxy->dict; - dict_proxy->num_on_queue--; - to_free = dict_proxy->num_on_queue <= 0 && dict_proxy->do_free_at_end; - SDL_AtomicUnlock(&dict_proxy->lock); - - if (to_free) { - free(dict_proxy); - } - else { - Py_INCREF(dict); - } - return dict; + // This steals reference to obj from SDL_Event. + return (PyObject *)event->user.data1; } dict = PyDict_New(); @@ -1339,232 +1132,110 @@ dict_from_event(SDL_Event *event) return dict; } -/* event object internals */ - -static void -pg_event_dealloc(PyObject *self) -{ - pgEventObject *e = (pgEventObject *)self; - Py_XDECREF(e->dict); - Py_TYPE(self)->tp_free(self); -} - -#ifdef PYPY_VERSION -/* Because pypy does not work with the __dict__ tp_dictoffset. */ -PyObject * -pg_EventGetAttr(PyObject *o, PyObject *attr_name) -{ - /* Try e->dict first, if not try the generic attribute. */ - PyObject *result = PyDict_GetItem(((pgEventObject *)o)->dict, attr_name); - if (!result) { - return PyObject_GenericGetAttr(o, attr_name); - } - return result; -} - -int -pg_EventSetAttr(PyObject *o, PyObject *name, PyObject *value) +static PyObject * +pgEvent_GetType(void) { - /* if the variable is in the dict, deal with it there. - else if it's a normal attribute set it there. - else if it's not an attribute, or in the dict, set it in the dict. - */ - int dictResult; - int setInDict = 0; - PyObject *result = PyDict_GetItem(((pgEventObject *)o)->dict, name); - - if (result) { - setInDict = 1; - } - else { - result = PyObject_GenericGetAttr(o, name); - if (!result) { - PyErr_Clear(); - setInDict = 1; - } - } - - if (setInDict) { - dictResult = PyDict_SetItem(((pgEventObject *)o)->dict, name, value); - if (dictResult) { - return -1; - } - return 0; - } - else { - return PyObject_GenericSetAttr(o, name, value); - } -} -#endif + if (!_event_class) + return RAISE(PyExc_RuntimeError, "event type is currently unknown"); -PyObject * -pg_event_str(PyObject *self) -{ - pgEventObject *e = (pgEventObject *)self; - return PyUnicode_FromFormat("", e->type, - _pg_name_from_eventtype(e->type), e->dict); + Py_INCREF(_event_class); + return _event_class; } -static int -_pg_event_nonzero(pgEventObject *self) +static PyObject * +pgEvent_FromTypeAndDict(int e_type, PyObject *dict) { - return self->type != SDL_NOEVENT; -} + PyObject *ret = NULL; + PyObject *args = NULL; -static PyNumberMethods pg_event_as_number = { - .nb_bool = (inquiry)_pg_event_nonzero, -}; + PyObject *e_typeo = pgEvent_GetType(); + if (!e_typeo) + return NULL; -static PyTypeObject pgEvent_Type; -#define pgEvent_Check(x) ((x)->ob_type == &pgEvent_Type) -#define OFF(x) offsetof(pgEventObject, x) + PyObject *num = PyLong_FromLong(e_type); + if (!num) + goto finalize; -static PyMemberDef pg_event_members[] = { - {"__dict__", T_OBJECT, OFF(dict), READONLY}, - {"type", T_INT, OFF(type), READONLY}, - {"dict", T_OBJECT, OFF(dict), READONLY}, - {NULL} /* Sentinel */ -}; + args = PyTuple_New(1); -/* - * eventA == eventB - * eventA != eventB - */ -static PyObject * -pg_event_richcompare(PyObject *o1, PyObject *o2, int opid) -{ - pgEventObject *e1, *e2; - - if (!pgEvent_Check(o1) || !pgEvent_Check(o2)) { - goto Unimplemented; + if (!args) { + Py_DECREF(num); + goto finalize; } - e1 = (pgEventObject *)o1; - e2 = (pgEventObject *)o2; - switch (opid) { - case Py_EQ: - return PyBool_FromLong( - e1->type == e2->type && - PyObject_RichCompareBool(e1->dict, e2->dict, Py_EQ) == 1); - case Py_NE: - return PyBool_FromLong( - e1->type != e2->type || - PyObject_RichCompareBool(e1->dict, e2->dict, Py_NE) == 1); - default: - break; - } + PyTuple_SetItem(args, 0, num); + + ret = PyObject_Call(e_typeo, args, dict); -Unimplemented: - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; +finalize: + Py_DECREF(e_typeo); + Py_XDECREF(args); + return ret; } static int -pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) +pgEvent_GetEventType(PyObject *event) { - int type; - PyObject *dict = NULL; + PyObject *e_typeo = PyObject_GetAttrString(event, "type"); - if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { + if (!e_typeo) { return -1; } - if (type < 0 || type >= PG_NUMEVENTS) { - PyErr_SetString(PyExc_ValueError, "event type out of range"); - return -1; - } + long e_type = PyLong_AsLong(e_typeo); + Py_DECREF(e_typeo); - if (!dict) { - if (kwargs) { - dict = kwargs; - Py_INCREF(dict); - } - else { - dict = PyDict_New(); - if (!dict) { - PyErr_NoMemory(); - return -1; - } - } - } - else { - if (kwargs) { - if (PyDict_Update(dict, kwargs) == -1) { - return -1; - } - } - /* So that dict is a new reference */ - Py_INCREF(dict); + if (PyErr_Occurred()) { + return -1; } - if (PyDict_GetItemString(dict, "type")) { - PyErr_SetString(PyExc_ValueError, - "redundant type field in event dict"); - Py_DECREF(dict); - return -1; + if (e_type < 0 || e_type >= PG_NUMEVENTS) { + RAISERETURN(PyExc_ValueError, "event type out of range", -1) } - self->type = _pg_pgevent_deproxify(type); - self->dict = dict; - return 0; + return e_type; } -static PyTypeObject pgEvent_Type = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event.Event", - .tp_basicsize = sizeof(pgEventObject), - .tp_dealloc = pg_event_dealloc, - .tp_repr = pg_event_str, - .tp_as_number = &pg_event_as_number, -#ifdef PYPY_VERSION - .tp_getattro = pg_EventGetAttr, - .tp_setattro = pg_EventSetAttr, -#else - .tp_getattro = PyObject_GenericGetAttr, - .tp_setattro = PyObject_GenericSetAttr, -#endif - .tp_doc = DOC_EVENT_EVENT, - .tp_richcompare = pg_event_richcompare, - .tp_members = pg_event_members, - .tp_dictoffset = offsetof(pgEventObject, dict), - .tp_init = (initproc)pg_event_init, - .tp_new = PyType_GenericNew, -}; - static PyObject * pgEvent_New(SDL_Event *event) { - pgEventObject *e; - e = PyObject_New(pgEventObject, &pgEvent_Type); - if (!e) - return PyErr_NoMemory(); + Uint32 e_type; + PyObject *obj_or_dict = NULL; if (event) { - e->type = _pg_pgevent_deproxify(event->type); - e->dict = dict_from_event(event); + e_type = _pg_pgevent_deproxify(event->type); + obj_or_dict = dict_or_obj_from_event(event); } else { - e->type = SDL_NOEVENT; - e->dict = PyDict_New(); + e_type = SDL_NOEVENT; } - if (!e->dict) { - Py_TYPE(e)->tp_free(e); - return PyErr_NoMemory(); + + if (!obj_or_dict || + PyObject_IsInstance(obj_or_dict, (PyObject *)&PyDict_Type)) { + if (PyErr_Occurred()) + return NULL; + + PyObject *ret = pgEvent_FromTypeAndDict(e_type, obj_or_dict); + Py_XDECREF(obj_or_dict); + return ret; } - return (PyObject *)e; -} -/* event module functions */ + return obj_or_dict; +} -static PyObject * -event_name(PyObject *self, PyObject *arg) +static int +pgEvent_Check(PyObject *obj) { - int type; - if (!PyArg_ParseTuple(arg, "i", &type)) - return NULL; - - return PyUnicode_FromString(_pg_name_from_eventtype(type)); + PyObject *e_type = pgEvent_GetType(); + if (!e_type) + return -1; + int res = PyObject_IsInstance(obj, e_type); + Py_DECREF(e_type); + return res; } +/* event module functions */ + static PyObject * set_grab(PyObject *self, PyObject *arg) { @@ -1611,10 +1282,7 @@ _pg_event_pump(int dopump) if (dopump) { /* This needs to be reset just before calling pump, e.g. on calls to * pygame.event.get(), but not on pygame.event.get(pump=False). */ - memset(pressed_keys, 0, sizeof(pressed_keys)); - memset(released_keys, 0, sizeof(released_keys)); - memset(pressed_mouse_buttons, 0, sizeof(pressed_mouse_buttons)); - memset(released_mouse_buttons, 0, sizeof(released_mouse_buttons)); + memset(input_buffer, 0, sizeof(input_buffer)); SDL_PumpEvents(); } @@ -1656,10 +1324,15 @@ _pg_event_wait(SDL_Event *event, int timeout) } static PyObject * -pg_event_pump(PyObject *self, PyObject *_null) +pg_event_pump(PyObject *self, PyObject *obj) { VIDEO_INIT_CHECK(); - _pg_event_pump(1); + int dopump = PyObject_IsTrue(obj); + + if (dopump < 0) + return NULL; + + _pg_event_pump(dopump); Py_RETURN_NONE; } @@ -1700,413 +1373,86 @@ pg_event_wait(PyObject *self, PyObject *args, PyObject *kwargs) return pgEvent_New(&event); } -static int -_pg_eventtype_from_seq(PyObject *seq, int ind) -{ - int val = 0; - if (!pg_IntFromObjIndex(seq, ind, &val)) { - PyErr_SetString(PyExc_TypeError, - "type sequence must contain valid event types"); - return -1; - } - if (val < 0 || val >= PG_NUMEVENTS) { - PyErr_SetString(PyExc_ValueError, "event type out of range"); - return -1; - } - return val; -} - -static PyObject * -_pg_eventtype_as_seq(PyObject *obj, Py_ssize_t *len) -{ - *len = 1; - if (PySequence_Check(obj)) { - *len = PySequence_Size(obj); - /* The returned object gets decref'd later, so incref now */ - Py_INCREF(obj); - return obj; - } - else if (PyLong_Check(obj)) - return Py_BuildValue("(O)", obj); - else - return RAISE(PyExc_TypeError, - "event type must be numeric or a sequence"); -} - -static void -_pg_flush_events(Uint32 type) -{ - if (type == MAX_UINT32) - SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); - else { - SDL_FlushEvent(type); - SDL_FlushEvent(_pg_pgevent_proxify(type)); - } -} - -static PyObject * -pg_event_clear(PyObject *self, PyObject *args, PyObject *kwargs) -{ - Py_ssize_t len; - int loop, type; - PyObject *seq, *obj = NULL; - int dopump = 1; - - static char *kwids[] = {"eventtype", "pump", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Op", kwids, &obj, - &dopump)) - return NULL; - - VIDEO_INIT_CHECK(); - _pg_event_pump(dopump); - - if (obj == NULL || obj == Py_None) { - _pg_flush_events(MAX_UINT32); - } - else { - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) /* error aldready set */ - return NULL; - - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) { - Py_DECREF(seq); - return NULL; /* PyErr aldready set */ - } - _pg_flush_events(type); - } - Py_DECREF(seq); - } - Py_RETURN_NONE; -} - -static int -_pg_event_append_to_list(PyObject *list, SDL_Event *event) -{ - /* The caller of this function must handle decref of list on error */ - PyObject *e = pgEvent_New(event); - if (!e) /* Exception already set. */ - return 0; - - if (PyList_Append(list, e)) { - Py_DECREF(e); - return 0; /* Exception already set. */ - } - Py_DECREF(e); - return 1; -} - char * pgEvent_GetKeyDownInfo(void) { - return pressed_keys; + return input_buffer + INPUT_BUFFER_PRESSED_OFFSET; } char * pgEvent_GetKeyUpInfo(void) { - return released_keys; + return input_buffer + INPUT_BUFFER_RELEASED_OFFSET; } char * pgEvent_GetMouseButtonDownInfo(void) { - return pressed_mouse_buttons; + return input_buffer + INPUT_BUFFER_MOUSE_PRESSED_OFFSET; } char * pgEvent_GetMouseButtonUpInfo(void) { - return released_mouse_buttons; + return input_buffer + INPUT_BUFFER_MOUSE_RELEASED_OFFSET; } static PyObject * -_pg_get_all_events_except(PyObject *obj) +_pg_get_event(PyObject *self, PyObject *obj) { - SDL_Event event; - Py_ssize_t len; - int loop, type, ret; - PyObject *seq, *list; - - SDL_Event *filtered_events; - int filtered_index = 0; - int filtered_events_len = 16; - - SDL_Event eventbuf[PG_GET_LIST_LEN]; - - filtered_events = malloc(sizeof(SDL_Event) * filtered_events_len); - if (!filtered_events) - return PyErr_NoMemory(); - - list = PyList_New(0); - if (!list) { - free(filtered_events); - return PyErr_NoMemory(); - } - - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) - goto error; - - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) - goto error; - - do { - ret = PG_PEEP_EVENT(&event, 1, SDL_GETEVENT, type); - if (ret < 0) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } - else if (ret > 0) { - if (filtered_index == filtered_events_len) { - SDL_Event *new_filtered_events = - malloc(sizeof(SDL_Event) * filtered_events_len * 4); - if (new_filtered_events == NULL) { - goto error; - } - memcpy(new_filtered_events, filtered_events, - sizeof(SDL_Event) * filtered_events_len); - filtered_events_len *= 4; - free(filtered_events); - filtered_events = new_filtered_events; - } - filtered_events[filtered_index] = event; - filtered_index++; - } - } while (ret); - do { - ret = PG_PEEP_EVENT(&event, 1, SDL_GETEVENT, - _pg_pgevent_proxify(type)); - if (ret < 0) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } - else if (ret > 0) { - if (filtered_index == filtered_events_len) { - SDL_Event *new_filtered_events = - malloc(sizeof(SDL_Event) * filtered_events_len * 4); - if (new_filtered_events == NULL) { - free(filtered_events); - goto error; - } - memcpy(new_filtered_events, filtered_events, - sizeof(SDL_Event) * filtered_events_len); - filtered_events_len *= 4; - free(filtered_events); - filtered_events = new_filtered_events; - } - filtered_events[filtered_index] = event; - filtered_index++; - } - } while (ret); - } - - do { - len = PG_PEEP_EVENT_ALL(eventbuf, PG_GET_LIST_LEN, SDL_GETEVENT); - if (len == -1) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } - - for (loop = 0; loop < len; loop++) { - if (!_pg_event_append_to_list(list, &eventbuf[loop])) - goto error; - } - } while (len == PG_GET_LIST_LEN); - - PG_PEEP_EVENT_ALL(filtered_events, filtered_index, SDL_ADDEVENT); + SDL_Event ev; + int e_type = PyLong_AsLong(obj); - free(filtered_events); - Py_DECREF(seq); - return list; + if (e_type == -1 && PyErr_Occurred()) + return NULL; -error: - /* While doing a goto here, PyErr must be set */ - free(filtered_events); - Py_DECREF(list); - Py_XDECREF(seq); - return NULL; + int ret; + if (e_type == -1) + ret = PG_PEEP_EVENT_ALL(&ev, 1, SDL_GETEVENT); + else + ret = PG_PEEP_EVENT(&ev, 1, SDL_GETEVENT, e_type); + if (ret == -1) + return RAISE(pgExc_SDLError, SDL_GetError()); + else if (ret == 0) + Py_RETURN_NONE; + return pgEvent_New(&ev); } static PyObject * -_pg_get_all_events(void) +_pg_peek_event(PyObject *self, PyObject *obj) { - SDL_Event eventbuf[PG_GET_LIST_LEN]; - PyObject *list; - int loop, len = PG_GET_LIST_LEN; - - list = PyList_New(0); - if (!list) - return PyErr_NoMemory(); - - while (len == PG_GET_LIST_LEN) { - len = PG_PEEP_EVENT_ALL(eventbuf, PG_GET_LIST_LEN, SDL_GETEVENT); - if (len == -1) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } + SDL_Event ev; + int e_type = PyLong_AsLong(obj); - for (loop = 0; loop < len; loop++) { - if (!_pg_event_append_to_list(list, &eventbuf[loop])) - goto error; - } - } - return list; - -error: - Py_DECREF(list); - return NULL; -} + if (e_type == -1 && PyErr_Occurred()) + return NULL; -static PyObject * -_pg_get_seq_events(PyObject *obj) -{ - Py_ssize_t len; - SDL_Event event; - int loop, type, ret; - PyObject *seq, *list; - - list = PyList_New(0); - if (!list) - return PyErr_NoMemory(); - - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) - goto error; - - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) - goto error; - - do { - ret = PG_PEEP_EVENT(&event, 1, SDL_GETEVENT, type); - if (ret < 0) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } - else if (ret > 0) { - if (!_pg_event_append_to_list(list, &event)) - goto error; - } - } while (ret); - do { - ret = PG_PEEP_EVENT(&event, 1, SDL_GETEVENT, - _pg_pgevent_proxify(type)); - if (ret < 0) { - PyErr_SetString(pgExc_SDLError, SDL_GetError()); - goto error; - } - else if (ret > 0) { - if (!_pg_event_append_to_list(list, &event)) - goto error; - } - } while (ret); - } - Py_DECREF(seq); - return list; - -error: - /* While doing a goto here, PyErr must be set */ - Py_DECREF(list); - Py_XDECREF(seq); - return NULL; + int ret; + if (e_type == -1) + ret = PG_PEEP_EVENT_ALL(&ev, 1, SDL_PEEKEVENT); + else + ret = PG_PEEP_EVENT(&ev, 1, SDL_PEEKEVENT, e_type); + if (ret == -1) + return RAISE(pgExc_SDLError, SDL_GetError()); + return PyBool_FromLong(ret); } static PyObject * -pg_event_get(PyObject *self, PyObject *args, PyObject *kwargs) +_pg_video_check(PyObject *self, PyObject *_null) { - PyObject *obj_evtype = NULL; - PyObject *obj_exclude = NULL; - int dopump = 1; - - static char *kwids[] = {"eventtype", "pump", "exclude", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OpO", kwids, &obj_evtype, - &dopump, &obj_exclude)) - return NULL; - VIDEO_INIT_CHECK(); - - _pg_event_pump(dopump); - - if (obj_evtype == NULL || obj_evtype == Py_None) { - if (obj_exclude != NULL && obj_exclude != Py_None) { - return _pg_get_all_events_except(obj_exclude); - } - return _pg_get_all_events(); - } - else { - if (obj_exclude != NULL && obj_exclude != Py_None) { - return RAISE( - pgExc_SDLError, - "Invalid combination of excluded and included event type"); - } - return _pg_get_seq_events(obj_evtype); - } + Py_RETURN_NONE; } static PyObject * -pg_event_peek(PyObject *self, PyObject *args, PyObject *kwargs) +_pg_proxify_event_type(PyObject *self, PyObject *obj) { - SDL_Event event; - Py_ssize_t len; - int type, loop, res; - PyObject *seq, *obj = NULL; - int dopump = 1; - - static char *kwids[] = {"eventtype", "pump", NULL}; + int e_type = PyLong_AsLong(obj); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Op", kwids, &obj, - &dopump)) + if (e_type == -1 && PyErr_Occurred()) return NULL; - VIDEO_INIT_CHECK(); - - _pg_event_pump(dopump); - - if (obj == NULL || obj == Py_None) { - res = PG_PEEP_EVENT_ALL(&event, 1, SDL_PEEKEVENT); - if (res < 0) - return RAISE(pgExc_SDLError, SDL_GetError()); - return pgEvent_New(res ? &event : NULL); - } - else { - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) - return NULL; - - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) { - Py_DECREF(seq); - return NULL; - } - res = PG_PEEP_EVENT(&event, 1, SDL_PEEKEVENT, type); - if (res) { - Py_DECREF(seq); - - if (res < 0) - return RAISE(pgExc_SDLError, SDL_GetError()); - Py_RETURN_TRUE; - } - res = PG_PEEP_EVENT(&event, 1, SDL_PEEKEVENT, - _pg_pgevent_proxify(type)); - if (res) { - Py_DECREF(seq); - - if (res < 0) - return RAISE(pgExc_SDLError, SDL_GetError()); - Py_RETURN_TRUE; - } - } - Py_DECREF(seq); - Py_RETURN_FALSE; /* No event type match. */ - } + return PyLong_FromLong(_pg_pgevent_proxify((Uint32)e_type)); } /* You might notice how we do event blocking stuff on proxy events and @@ -2118,11 +1464,20 @@ static PyObject * pg_event_post(PyObject *self, PyObject *obj) { VIDEO_INIT_CHECK(); - if (!pgEvent_Check(obj)) + int is_event = pgEvent_Check(obj); + if (is_event < 0) + return NULL; + else if (!is_event) return RAISE(PyExc_TypeError, "argument must be an Event object"); - pgEventObject *e = (pgEventObject *)obj; - switch (pg_post_event(e->type, e->dict)) { + int e_type = pgEvent_GetEventType(obj); + + if (PyErr_Occurred()) + return NULL; + + int res = pg_post_event(e_type, obj); + + switch (res) { case 0: Py_RETURN_FALSE; case 1: @@ -2133,111 +1488,75 @@ pg_event_post(PyObject *self, PyObject *obj) } static PyObject * -pg_event_set_allowed(PyObject *self, PyObject *obj) +pg_event_allowed_set(PyObject *self, PyObject *args) { - Py_ssize_t len; - int loop, type; - PyObject *seq; VIDEO_INIT_CHECK(); - if (obj == Py_None) { - int i; - for (i = SDL_FIRSTEVENT; i < SDL_LASTEVENT; i++) { - PG_SetEventEnabled(i, SDL_TRUE); - } - } - else { - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) - return NULL; + int e_type, e_flag; + PyObject *e_flago = NULL; - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) { - Py_DECREF(seq); - return NULL; - } - PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_TRUE); - } - Py_DECREF(seq); + if (!PyArg_ParseTuple(args, "iO", &e_type, &e_flago)) + return NULL; + + if (e_type < 0 || e_type >= PG_NUMEVENTS) { + PyErr_SetString(PyExc_ValueError, "event type out of range"); + return NULL; } - Py_RETURN_NONE; -} -static PyObject * -pg_event_set_blocked(PyObject *self, PyObject *obj) -{ - Py_ssize_t len; - int loop, type; - PyObject *seq; - VIDEO_INIT_CHECK(); + e_flag = PyObject_IsTrue(e_flago); - if (obj == Py_None) { - int i; - /* Start at PGPOST_EVENTBEGIN */ - for (i = PGPOST_EVENTBEGIN; i < SDL_LASTEVENT; i++) { - PG_SetEventEnabled(i, SDL_FALSE); - } - } - else { - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) - return NULL; + if (e_flag < 0) + return NULL; + + PG_SetEventEnabled(_pg_pgevent_proxify(e_type), + e_flag ? SDL_TRUE : SDL_FALSE); + + // Never block events that are needed for proecesing. + if (e_type == SDL_WINDOWEVENT || e_type == PGE_KEYREPEAT) + PG_SetEventEnabled(e_type, SDL_TRUE); + else + PG_SetEventEnabled(e_type, e_flag ? SDL_TRUE : SDL_FALSE); - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) { - Py_DECREF(seq); - return NULL; - } - PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_FALSE); - } - Py_DECREF(seq); - } - /* Never block SDL_WINDOWEVENT, we need them for translation */ - PG_SetEventEnabled(SDL_WINDOWEVENT, SDL_TRUE); - /* Never block PGE_KEYREPEAT too, its needed for pygame internal use */ - PG_SetEventEnabled(PGE_KEYREPEAT, SDL_TRUE); Py_RETURN_NONE; } static PyObject * -pg_event_get_blocked(PyObject *self, PyObject *obj) +pg_event_allowed_get(PyObject *self, PyObject *obj) { - Py_ssize_t len; - int loop, type, isblocked = 0; - PyObject *seq; - VIDEO_INIT_CHECK(); - seq = _pg_eventtype_as_seq(obj, &len); - if (!seq) + int e_type = PyLong_AsLong(obj); + + if (PyErr_Occurred()) return NULL; - for (loop = 0; loop < len; loop++) { - type = _pg_eventtype_from_seq(seq, loop); - if (type == -1) { - Py_DECREF(seq); - return NULL; - } - if (PG_EventEnabled(_pg_pgevent_proxify(type)) == SDL_FALSE) { - isblocked = 1; - break; - } + else if (e_type < 0 || e_type >= PG_NUMEVENTS) { + PyErr_SetString(PyExc_ValueError, "event type out of range"); + return NULL; } - Py_DECREF(seq); - return PyBool_FromLong(isblocked); + if (PG_EventEnabled(e_type) == SDL_TRUE) + Py_RETURN_TRUE; + Py_RETURN_FALSE; } static PyObject * -pg_event_custom_type(PyObject *self, PyObject *_null) +pg_event_register_event_class(PyObject *self, PyObject *obj) { - if (_custom_event < PG_NUMEVENTS) - return PyLong_FromLong(_custom_event++); - else - return RAISE(pgExc_SDLError, - "pygame.event.custom_type made too many event types."); + if (!(PyType_Check(obj) && PyCallable_Check(obj))) + return RAISE(PyExc_ValueError, "expected a type"); + + Py_INCREF(obj); + Py_XDECREF(_event_class); + _event_class = obj; + Py_RETURN_NONE; +} + +void +pg_event_free(PyObject *self) +{ + Py_XDECREF(_event_class); + _event_class = NULL; } static PyMethodDef _event_methods[] = { @@ -2246,48 +1565,35 @@ static PyMethodDef _event_methods[] = { {"_internal_mod_quit", (PyCFunction)pgEvent_AutoQuit, METH_NOARGS, "auto quit for event module"}, - {"event_name", event_name, METH_VARARGS, DOC_EVENT_EVENTNAME}, - {"set_grab", set_grab, METH_O, DOC_EVENT_SETGRAB}, {"get_grab", (PyCFunction)get_grab, METH_NOARGS, DOC_EVENT_GETGRAB}, - {"pump", (PyCFunction)pg_event_pump, METH_NOARGS, DOC_EVENT_PUMP}, + {"pump", (PyCFunction)pg_event_pump, METH_O, DOC_EVENT_PUMP}, {"wait", (PyCFunction)pg_event_wait, METH_VARARGS | METH_KEYWORDS, DOC_EVENT_WAIT}, {"poll", (PyCFunction)pg_event_poll, METH_NOARGS, DOC_EVENT_POLL}, - {"clear", (PyCFunction)pg_event_clear, METH_VARARGS | METH_KEYWORDS, - DOC_EVENT_CLEAR}, - {"get", (PyCFunction)pg_event_get, METH_VARARGS | METH_KEYWORDS, - DOC_EVENT_GET}, - {"peek", (PyCFunction)pg_event_peek, METH_VARARGS | METH_KEYWORDS, - DOC_EVENT_PEEK}, {"post", (PyCFunction)pg_event_post, METH_O, DOC_EVENT_POST}, - {"set_allowed", (PyCFunction)pg_event_set_allowed, METH_O, - DOC_EVENT_SETALLOWED}, - {"set_blocked", (PyCFunction)pg_event_set_blocked, METH_O, - DOC_EVENT_SETBLOCKED}, - {"get_blocked", (PyCFunction)pg_event_get_blocked, METH_O, - DOC_EVENT_GETBLOCKED}, - {"custom_type", (PyCFunction)pg_event_custom_type, METH_NOARGS, - DOC_EVENT_CUSTOMTYPE}, + {"allowed_get", (PyCFunction)pg_event_allowed_get, METH_O}, + {"allowed_set", (PyCFunction)pg_event_allowed_set, METH_VARARGS}, + {"register_event_class", (PyCFunction)pg_event_register_event_class, + METH_O}, + {"video_check", (PyCFunction)_pg_video_check, METH_NOARGS}, + {"_get", (PyCFunction)_pg_get_event, METH_O}, + {"_peek", (PyCFunction)_pg_peek_event, METH_O}, + {"_proxify_event_type", (PyCFunction)_pg_proxify_event_type, METH_O}, {NULL, NULL, 0, NULL}}; -MODINIT_DEFINE(event) +MODINIT_DEFINE(_event) { PyObject *module, *apiobj; static void *c_api[PYGAMEAPI_EVENT_NUMSLOTS]; - static struct PyModuleDef _module = {PyModuleDef_HEAD_INIT, - "event", - DOC_EVENT, - -1, - _event_methods, - NULL, - NULL, - NULL, - NULL}; + static struct PyModuleDef _module = { + PyModuleDef_HEAD_INIT, "event", DOC_EVENT, -1, + _event_methods, NULL, NULL, NULL, + (freefunc)pg_event_free}; /* imported needed apis; Do this first so if there is an error the module is not loaded. @@ -2302,44 +1608,29 @@ MODINIT_DEFINE(event) return NULL; } - /* type preparation */ - if (PyType_Ready(&pgEvent_Type) < 0) { - return NULL; - } - /* create the module */ module = PyModule_Create(&_module); if (!module) { return NULL; } - Py_INCREF(&pgEvent_Type); - if (PyModule_AddObject(module, "EventType", (PyObject *)&pgEvent_Type)) { - Py_DECREF(&pgEvent_Type); - Py_DECREF(module); - return NULL; - } - Py_INCREF(&pgEvent_Type); - if (PyModule_AddObject(module, "Event", (PyObject *)&pgEvent_Type)) { - Py_DECREF(&pgEvent_Type); - Py_DECREF(module); - return NULL; - } - /* export the c api */ - assert(PYGAMEAPI_EVENT_NUMSLOTS == 10); - c_api[0] = &pgEvent_Type; + assert(PYGAMEAPI_EVENT_NUMSLOTS == 13); + c_api[0] = pgEvent_GetType; c_api[1] = pgEvent_New; c_api[2] = pg_post_event; - c_api[3] = pg_post_event_dictproxy; + c_api[3] = pg_post_event_steal; c_api[4] = pg_EnableKeyRepeat; c_api[5] = pg_GetKeyRepeat; c_api[6] = pgEvent_GetKeyDownInfo; c_api[7] = pgEvent_GetKeyUpInfo; c_api[8] = pgEvent_GetMouseButtonDownInfo; c_api[9] = pgEvent_GetMouseButtonUpInfo; + c_api[10] = pgEvent_Check; + c_api[11] = pgEvent_FromTypeAndDict; + c_api[12] = pgEvent_GetEventType; - apiobj = encapsulate_api(c_api, "event"); + apiobj = encapsulate_api(c_api, "_event"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { Py_XDECREF(apiobj); Py_DECREF(module); diff --git a/src_c/_pygame.h b/src_c/_pygame.h index 0cbfed872f..8efa3a3f69 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -224,19 +224,6 @@ struct SDL_BlitMap { #endif -/* DictProxy is useful for event posting with an arbitrary dict. Maintains - * state of number of events on queue and whether the owner of this struct - * wants this dict freed. This DictProxy is only to be freed when there are no - * more instances of this DictProxy on the event queue. Access to this is - * safeguarded with a per-proxy spinlock, which is more optimal than having - * to hold GIL in case of event timers */ -typedef struct _pgEventDictProxy { - PyObject *dict; - SDL_SpinLock lock; - int num_on_queue; - Uint8 do_free_at_end; -} pgEventDictProxy; - /* SDL 1.2 constants removed from SDL 2 */ typedef enum { SDL_HWSURFACE = 0, @@ -486,10 +473,6 @@ typedef enum { /* * event module internals */ -struct pgEventObject { - PyObject_HEAD int type; - PyObject *dict; -}; /* * surface module internals @@ -544,7 +527,7 @@ typedef enum { #define PYGAMEAPI_COLOR_NUMSLOTS 5 #define PYGAMEAPI_MATH_NUMSLOTS 2 #define PYGAMEAPI_BASE_NUMSLOTS 29 -#define PYGAMEAPI_EVENT_NUMSLOTS 10 +#define PYGAMEAPI_EVENT_NUMSLOTS 13 #define PYGAMEAPI_WINDOW_NUMSLOTS 1 #define PYGAMEAPI_GEOMETRY_NUMSLOTS 2 diff --git a/src_c/base.c b/src_c/base.c index 573bca3aa2..d8e5887d92 100644 --- a/src_c/base.c +++ b/src_c/base.c @@ -305,6 +305,10 @@ pg_mod_autoquit(const char *modname) funcobj = PyObject_GetAttrString(module, "_internal_mod_quit"); + /* Silence errors */ + if (PyErr_Occurred()) + PyErr_Clear(); + /* If we could not load _internal_mod_quit, load quit function */ if (!funcobj) funcobj = PyObject_GetAttrString(module, "quit"); diff --git a/src_c/doc/event_doc.h b/src_c/doc/event_doc.h index 16b6f1b0a1..fc283d6eee 100644 --- a/src_c/doc/event_doc.h +++ b/src_c/doc/event_doc.h @@ -4,7 +4,7 @@ #define DOC_EVENT_GET "get(eventtype=None) -> Eventlist\nget(eventtype=None, pump=True) -> Eventlist\nget(eventtype=None, pump=True, exclude=None) -> Eventlist\nget events from the queue" #define DOC_EVENT_POLL "poll() -> Event instance\nget a single event from the queue" #define DOC_EVENT_WAIT "wait() -> Event instance\nwait(timeout) -> Event instance\nwait for a single event from the queue" -#define DOC_EVENT_PEEK "peek(eventtype=None) -> bool\npeek(eventtype=None, pump=True) -> bool\ntest if event types are waiting on the queue" +#define DOC_EVENT_PEEK "peek() -> Event instance\npeek(eventtype) -> bool\npeek(eventtype, pump=True) -> bool\ntest if event types are waiting on the queue" #define DOC_EVENT_CLEAR "clear(eventtype=None) -> None\nclear(eventtype=None, pump=True) -> None\nremove all events from the queue" #define DOC_EVENT_EVENTNAME "event_name(type, /) -> string\nget the string name from an event id" #define DOC_EVENT_SETBLOCKED "set_blocked(type, /) -> None\nset_blocked(typelist, /) -> None\nset_blocked(None) -> None\ncontrol which events are blocked on the queue" diff --git a/src_c/doc/typing_doc.h b/src_c/doc/typing_doc.h index 158e27d1ed..ded2831ade 100644 --- a/src_c/doc/typing_doc.h +++ b/src_c/doc/typing_doc.h @@ -6,3 +6,4 @@ #define DOC_TYPING_INTPOINT "" #define DOC_TYPING_COLORLIKE "" #define DOC_TYPING_RECTLIKE "" +#define DOC_TYPING_EVENTLIKE "" diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index bcfb0fdc8c..d9e4765d18 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -378,37 +378,43 @@ typedef struct { /* * EVENT module */ -typedef struct pgEventObject pgEventObject; - #ifndef PYGAMEAPI_EVENT_INTERNAL -#define pgEvent_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(event, 0)) - -#define pgEvent_Check(x) ((x)->ob_type == &pgEvent_Type) +#define pgEvent_GetType (*(PyObject * (*)(void)) PYGAMEAPI_GET_SLOT(_event, 0)) #define pgEvent_New \ - (*(PyObject * (*)(SDL_Event *)) PYGAMEAPI_GET_SLOT(event, 1)) + (*(PyObject * (*)(SDL_Event *)) PYGAMEAPI_GET_SLOT(_event, 1)) #define pg_post_event \ - (*(int (*)(Uint32, PyObject *))PYGAMEAPI_GET_SLOT(event, 2)) + (*(int (*)(int, PyObject *))PYGAMEAPI_GET_SLOT(_event, 2)) -#define pg_post_event_dictproxy \ - (*(int (*)(Uint32, pgEventDictProxy *))PYGAMEAPI_GET_SLOT(event, 3)) +#define pg_post_event_steal \ + (*(int (*)(int, PyObject *))PYGAMEAPI_GET_SLOT(_event, 3)) -#define pg_EnableKeyRepeat (*(int (*)(int, int))PYGAMEAPI_GET_SLOT(event, 4)) +#define pg_EnableKeyRepeat (*(int (*)(int, int))PYGAMEAPI_GET_SLOT(_event, 4)) -#define pg_GetKeyRepeat (*(void (*)(int *, int *))PYGAMEAPI_GET_SLOT(event, 5)) +#define pg_GetKeyRepeat \ + (*(void (*)(int *, int *))PYGAMEAPI_GET_SLOT(_event, 5)) -#define pgEvent_GetKeyDownInfo (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 6)) +#define pgEvent_GetKeyDownInfo \ + (*(char *(*)(void))PYGAMEAPI_GET_SLOT(_event, 6)) -#define pgEvent_GetKeyUpInfo (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 7)) +#define pgEvent_GetKeyUpInfo (*(char *(*)(void))PYGAMEAPI_GET_SLOT(_event, 7)) #define pgEvent_GetMouseButtonDownInfo \ - (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 8)) + (*(char *(*)(void))PYGAMEAPI_GET_SLOT(_event, 8)) #define pgEvent_GetMouseButtonUpInfo \ - (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 9)) + (*(char *(*)(void))PYGAMEAPI_GET_SLOT(_event, 9)) + +#define pgEvent_Check (*(int (*)(PyObject *))PYGAMEAPI_GET_SLOT(_event, 10)) + +#define pgEvent_FromTypeAndDict \ + (*(PyObject * (*)(int, PyObject *)) PYGAMEAPI_GET_SLOT(_event, 11)) + +#define pgEvent_GetEventType \ + (*(int (*)(PyObject *))PYGAMEAPI_GET_SLOT(_event, 12)) -#define import_pygame_event() IMPORT_PYGAME_MODULE(event) +#define import_pygame_event() IMPORT_PYGAME_MODULE(_event) #endif /* @@ -529,7 +535,7 @@ PYGAMEAPI_DEFINE_SLOTS(joystick); PYGAMEAPI_DEFINE_SLOTS(display); PYGAMEAPI_DEFINE_SLOTS(surface); PYGAMEAPI_DEFINE_SLOTS(surflock); -PYGAMEAPI_DEFINE_SLOTS(event); +PYGAMEAPI_DEFINE_SLOTS(_event); PYGAMEAPI_DEFINE_SLOTS(rwobject); PYGAMEAPI_DEFINE_SLOTS(pixelarray); PYGAMEAPI_DEFINE_SLOTS(color); @@ -543,7 +549,7 @@ PYGAMEAPI_EXTERN_SLOTS(joystick); PYGAMEAPI_EXTERN_SLOTS(display); PYGAMEAPI_EXTERN_SLOTS(surface); PYGAMEAPI_EXTERN_SLOTS(surflock); -PYGAMEAPI_EXTERN_SLOTS(event); +PYGAMEAPI_EXTERN_SLOTS(_event); PYGAMEAPI_EXTERN_SLOTS(rwobject); PYGAMEAPI_EXTERN_SLOTS(pixelarray); PYGAMEAPI_EXTERN_SLOTS(color); diff --git a/src_c/meson.build b/src_c/meson.build index ca10cca737..973cd49f55 100644 --- a/src_c/meson.build +++ b/src_c/meson.build @@ -47,9 +47,9 @@ endif # TODO: support SDL3 if sdl_api != 3 -event = py.extension_module( - 'event', - 'event.c', +_event = py.extension_module( + '_event', + '_event.c', c_args: warnings_error, dependencies: pg_base_deps, install: true, diff --git a/src_c/static.c b/src_c/static.c index 97229dd633..34050b56fd 100644 --- a/src_c/static.c +++ b/src_c/static.c @@ -117,7 +117,7 @@ PyInit_mouse(void); PyMODINIT_FUNC PyInit_key(void); PyMODINIT_FUNC -PyInit_event(void); +PyInit__event(void); PyMODINIT_FUNC PyInit_joystick(void); @@ -313,7 +313,7 @@ PyInit_pygame_static() load_submodule("pygame", PyInit_mask(), "mask"); load_submodule("pygame", PyInit_mouse(), "mouse"); - load_submodule("pygame", PyInit_event(), "event"); + load_submodule("pygame", PyInit__event(), "_event"); load_submodule("pygame", PyInit_joystick(), "joystick"); load_submodule("pygame", PyInit_pg_mixer(), "mixer"); @@ -396,7 +396,7 @@ PyInit_pygame_static() #include "joystick.c" -#include "event.c" +#include "_event.c" #include "mouse.c" diff --git a/src_c/time.c b/src_c/time.c index 66dc4fc6f3..8764be0b76 100644 --- a/src_c/time.c +++ b/src_c/time.c @@ -27,6 +27,9 @@ #include "doc/time_doc.h" #define WORST_CLOCK_ACCURACY 12 +#define TIMER_REF_STEP 1 + +#define PG_CHANGE_REFCNT(obj, by) Py_SET_REFCNT(obj, Py_REFCNT(obj) + by) /* Enum containing some error codes used by timer related functions */ typedef enum { @@ -51,8 +54,9 @@ typedef struct pgEventTimer { * instance from the linked list */ intptr_t timer_id; - /* A dictproxy instance */ - pgEventDictProxy *dict_proxy; + /* A python object reference */ + PyObject *obj; + int owned_refs; /* event type of the associated event */ int event_type; @@ -137,27 +141,13 @@ _pg_timer_free(pgEventTimer *timer) } } - if (timer->dict_proxy) { - int is_fully_freed = 0; - - SDL_AtomicLock(&timer->dict_proxy->lock); - /* Fully free dict and dict_proxy only if there are no references to it - * on the event queue. If there are any references, event functions - * will handle cleanups */ - if (timer->dict_proxy->num_on_queue <= 0) { - is_fully_freed = 1; - } - else { - timer->dict_proxy->do_free_at_end = 1; - } - SDL_AtomicUnlock(&timer->dict_proxy->lock); - - if (is_fully_freed) { - PyGILState_STATE gstate = PyGILState_Ensure(); - Py_DECREF(timer->dict_proxy->dict); - PyGILState_Release(gstate); - free(timer->dict_proxy); + if (timer->obj) { + PyGILState_STATE gstate = PyGILState_Ensure(); + Py_DECREF(timer->obj); + if (timer->owned_refs > 0) { + PG_CHANGE_REFCNT(timer->obj, -timer->owned_refs); } + PyGILState_Release(gstate); } free(timer); } @@ -197,29 +187,27 @@ pg_time_autoinit(PyObject *self, PyObject *_null) * but this function can internally hold GIL if needed. * Returns pgSetTimerErr error codes */ static pgSetTimerErr -_pg_add_event_timer(int ev_type, PyObject *ev_dict, int repeat) +_pg_add_event_timer(int ev_type, PyObject *ev_obj, int repeat) { pgEventTimer *new = (pgEventTimer *)malloc(sizeof(pgEventTimer)); if (!new) { return PG_TIMER_MEMORY_ERROR; } - if (ev_dict) { - new->dict_proxy = (pgEventDictProxy *)malloc(sizeof(pgEventDictProxy)); - if (!new->dict_proxy) { - free(new); - return PG_TIMER_MEMORY_ERROR; - } - PyGILState_STATE gstate = PyGILState_Ensure(); - Py_INCREF(ev_dict); - PyGILState_Release(gstate); - new->dict_proxy->dict = ev_dict; - new->dict_proxy->lock = 0; - new->dict_proxy->num_on_queue = 0; - new->dict_proxy->do_free_at_end = 0; + new->obj = ev_obj; + + if (repeat > 0) { + new->owned_refs = repeat; } else { - new->dict_proxy = NULL; + new->owned_refs = TIMER_REF_STEP; + } + + if (ev_obj) { + PyGILState_STATE gstate = PyGILState_Ensure(); + Py_INCREF(ev_obj); // Own reference. + PG_CHANGE_REFCNT(ev_obj, new->owned_refs); + PyGILState_Release(gstate); } /* insert the timer into the doubly linked list at the first index */ @@ -287,8 +275,22 @@ timer_callback(Uint32 interval, void *param) } else { if (SDL_WasInit(SDL_INIT_VIDEO)) { - pg_post_event_dictproxy((Uint32)evtimer->event_type, - evtimer->dict_proxy); + if (evtimer->owned_refs == 0) { + evtimer->owned_refs = TIMER_REF_STEP; + + if (evtimer->obj) { + PyGILState_STATE gstate = PyGILState_Ensure(); + PG_CHANGE_REFCNT(evtimer->obj, evtimer->owned_refs); + PyGILState_Release(gstate); + } + } + + /* TODO: When error handling is created for SDL callbacks, + * update this to support the case of -1. */ + if (pg_post_event_steal((Uint32)evtimer->event_type, + evtimer->obj) == 1) { + evtimer->owned_refs -= 1; + } } else { evtimer->repeat = 0; @@ -393,9 +395,8 @@ static PyObject * time_set_timer(PyObject *self, PyObject *args, PyObject *kwargs) { int ticks, loops = 0; - PyObject *obj, *ev_dict = NULL; + PyObject *obj, *ev_obj = NULL; int ev_type; - pgEventObject *e; pgSetTimerErr ecode = PG_TIMER_NO_ERROR; static char *kwids[] = {"event", "millis", "loops", NULL}; @@ -421,14 +422,21 @@ time_set_timer(PyObject *self, PyObject *args, PyObject *kwargs) return RAISE(PyExc_ValueError, "event type out of range"); } } - else if (pgEvent_Check(obj)) { - e = (pgEventObject *)obj; - ev_type = e->type; - ev_dict = e->dict; - } else { - return RAISE(PyExc_TypeError, - "first argument must be an event type or event object"); + int is_event = pgEvent_Check(obj); + if (is_event) { + ev_type = pgEvent_GetEventType(obj); + + if (PyErr_Occurred()) + return NULL; + + ev_obj = obj; + } + else { + return RAISE( + PyExc_TypeError, + "first argument must be an event type or event object"); + } } #ifndef __EMSCRIPTEN__ @@ -463,7 +471,7 @@ time_set_timer(PyObject *self, PyObject *args, PyObject *kwargs) } } - ecode = _pg_add_event_timer(ev_type, ev_dict, loops); + ecode = _pg_add_event_timer(ev_type, ev_obj, loops); if (ecode != PG_TIMER_NO_ERROR) { goto end; } diff --git a/src_py/event.py b/src_py/event.py new file mode 100644 index 0000000000..e718b9eced --- /dev/null +++ b/src_py/event.py @@ -0,0 +1,462 @@ +"pygame module for interacting with events and queues" + +from __future__ import annotations + +import gc + +from typing import Any, Union +from .typing import EventLike, IterableLike + +from pygame._event import ( + _internal_mod_init as _init, + _internal_mod_quit as _quit, + pump as _pump, + register_event_class as _register_event_class, + allowed_get as _allowed_get, + allowed_set as _allowed_set, + video_check as _video_check, + post, + get_grab, + set_grab, + wait, + poll, + _get, + _peek, + _proxify_event_type, +) + +from pygame.base import error +import pygame as pg + + +_is_init = False +_custom_event = pg.USEREVENT + 1 +_NAMES_MAPPING = { + pg.ACTIVEEVENT: "ActiveEvent", + pg.APP_TERMINATING: "AppTerminating", + pg.APP_LOWMEMORY: "AppLowMemory", + pg.APP_WILLENTERBACKGROUND: "AppWillEnterBackground", + pg.APP_DIDENTERBACKGROUND: "AppDidEnterBackground", + pg.APP_WILLENTERFOREGROUND: "AppWillEnterForeground", + pg.APP_DIDENTERFOREGROUND: "AppDidEnterForeground", + pg.CLIPBOARDUPDATE: "ClipboardUpdate", + pg.KEYDOWN: "KeyDown", + pg.KEYUP: "KeyUp", + pg.KEYMAPCHANGED: "KeyMapChanged", + pg.LOCALECHANGED: "LocaleChanged", + pg.MOUSEMOTION: "MouseMotion", + pg.MOUSEBUTTONDOWN: "MouseButtonDown", + pg.MOUSEBUTTONUP: "MouseButtonUp", + pg.JOYAXISMOTION: "JoyAxisMotion", + pg.JOYBALLMOTION: "JoyBallMotion", + pg.JOYHATMOTION: "JoyHatMotion", + pg.JOYBUTTONUP: "JoyButtonUp", + pg.JOYBUTTONDOWN: "JoyButtonDown", + pg.QUIT: "Quit", + pg.SYSWMEVENT: "SysWMEvent", + pg.VIDEORESIZE: "VideoResize", + pg.VIDEOEXPOSE: "VideoExpose", + pg.MIDIIN: "MidiIn", + pg.MIDIOUT: "MidiOut", + pg.NOEVENT: "NoEvent", + pg.FINGERMOTION: "FingerMotion", + pg.FINGERDOWN: "FingerDown", + pg.FINGERUP: "FingerUp", + pg.MULTIGESTURE: "MultiGesture", + pg.MOUSEWHEEL: "MouseWheel", + pg.TEXTINPUT: "TextInput", + pg.TEXTEDITING: "TextEditing", + pg.DROPFILE: "DropFile", + pg.DROPTEXT: "DropText", + pg.DROPBEGIN: "DropBegin", + pg.DROPCOMPLETE: "DropComplete", + pg.CONTROLLERAXISMOTION: "ControllerAxisMotion", + pg.CONTROLLERBUTTONDOWN: "ControllerButtonDown", + pg.CONTROLLERBUTTONUP: "ControllerButtonUp", + pg.CONTROLLERDEVICEADDED: "ControllerDeviceAdded", + pg.CONTROLLERDEVICEREMOVED: "ControllerDeviceRemoved", + pg.CONTROLLERDEVICEREMAPPED: "ControllerDeviceMapped", + pg.JOYDEVICEADDED: "JoyDeviceAdded", + pg.JOYDEVICEREMOVED: "JoyDeviceRemoved", + pg.CONTROLLERTOUCHPADDOWN: "ControllerTouchpadDown", + pg.CONTROLLERTOUCHPADMOTION: "ControllerTouchpadMotion", + pg.CONTROLLERTOUCHPADUP: "ControllerTouchpadUp", + pg.CONTROLLERSENSORUPDATE: "ControllerSensorUpdate", + pg.AUDIODEVICEADDED: "AudioDeviceAdded", + pg.AUDIODEVICEREMOVED: "AudioDeviceRemoved", + pg.RENDER_TARGETS_RESET: "RenderTargetsReset", + pg.RENDER_DEVICE_RESET: "RenderDeviceReset", + pg.WINDOWSHOWN: "WindowShown", + pg.WINDOWHIDDEN: "WindowHidden", + pg.WINDOWEXPOSED: "WindowExposed", + pg.WINDOWMOVED: "WindowMoved", + pg.WINDOWRESIZED: "WindowResized", + pg.WINDOWSIZECHANGED: "WindowSizeChanged", + pg.WINDOWMINIMIZED: "WindowMinimized", + pg.WINDOWMAXIMIZED: "WindowMaximized", + pg.WINDOWRESTORED: "WindowRestored", + pg.WINDOWENTER: "WindowEnter", + pg.WINDOWLEAVE: "WindowLeave", + pg.WINDOWFOCUSGAINED: "WindowFocusGained", + pg.WINDOWFOCUSLOST: "WindowFocusLost", + pg.WINDOWCLOSE: "WindowClose", + pg.WINDOWTAKEFOCUS: "WindowTakeFocus", + pg.WINDOWHITTEST: "WindowHitTest", + pg.WINDOWICCPROFCHANGED: "WindowICCProfChanged", + pg.WINDOWDISPLAYCHANGED: "WindowDisplayChanged", +} + + +def event_name(type: int) -> str: + """ + event_name(type) -> string + + get the string name from an event id + """ + + if type in _NAMES_MAPPING: + return _NAMES_MAPPING[type] + if pg.USEREVENT <= type < pg.NUMEVENTS: + return "UserEvent" + return "Unknown" + + +def _check_ev_type(ev_type): + if not isinstance(ev_type, int): + raise TypeError("event type must be an integer") + + if not 0 <= ev_type < pg.NUMEVENTS: + raise ValueError("event type out of range") + + +class Event: + """ + Event(type, dict) -> Event + Event(type, **attributes) -> Event + + pygame object for representing events + """ + + def __init__(self, type: int, dict: dict[str, Any] | None = None, **kwargs: Any): + _check_ev_type(type) + + if dict is None: + dict = kwargs + else: + dict.update(kwargs) + + if "type" in dict: + raise ValueError("redundant type field in event dict") + + self._type = type + self._dict = dict + + def __new__(cls, *args: Any, **kwargs: Any): + if "type" in kwargs: + raise ValueError("redundant type field in event dict") + return super().__new__(cls) + + def __int__(self): + return self.type + + def __bool__(self): + return self.type != pg.NOEVENT + + def __eq__(self, other: Any): + if not isinstance(other, Event): + return NotImplemented + return self.type == other.type and self.dict == other.dict + + def __repr__(self): + return f" Any: + return self._dict[name] + + def __getattribute__(self, name: str): + if name == "__dict__": + return super().__getattribute__("_dict") + return super().__getattribute__(name) + + def __setattr__(self, name: str, value: Any): + if name in ("_type", "_dict", "type", "dict"): + super().__setattr__(name, value) + else: + self._dict[name] = value + + def __delattr__(self, name: str) -> None: + del self._dict[name] + + +EventType = Event +_register_event_class(Event) + + +def init(): + global _is_init + + _init() + + _is_init = True + + +def quit(): + global _is_init, _custom_event + + # Clear event queue to avoid memory leak when SDL tries to clear it + # without freeing our resources. + clear(pump=False) + + # The main reason for _custom_event to be reset here is + # so we can have a unit test that checks if pygame.event.custom_type() stops + # returning new types when they are finished, + # without that test preventing further tests from getting a custom event type. + _custom_event = pg.USEREVENT + 1 + _quit() + + _is_init = False + + +def custom_type(): + """ + custom_type() -> int + + make custom user event type + """ + global _custom_event + + if _custom_event >= pg.NUMEVENTS: + raise error("pygame.event.custom_type made too many event types.") + + _custom_event += 1 + return _custom_event - 1 + + +def pump(): + """ + pump() -> None + + internally process pygame event handlers + """ + return _pump(True) + + +def _parse(type: int | IterableLike[int], args: tuple[int, ...]) -> list[int]: + types = [] + + types: list[int] = [] + + if isinstance(type, int): + types.append(type) + else: + types.extend(iter(type)) + + if args: + types.extend(args) + + return types + + +def _setter(val: bool, type: int | IterableLike[int] | None, *args: int): + if type is None: + if args: + raise ValueError("Args aren't supported for type==None.") + for ev in range(pg.NUMEVENTS): + _allowed_set(ev, val) + return + + for t in _parse(type, args): + _check_ev_type(t) + _allowed_set(t, val) + + +def set_blocked(type: int | IterableLike[int] | None, *args: int): + """ + set_blocked(type: int, *args) -> None + set_blocked(type: list) -> None + set_blocked(None) -> None + + control which events are blocked on the queue + """ + + _setter(False, type, *args) + + +def set_allowed(type: int | IterableLike[int] | None, *args: int): + """ + set_allowed(type: int, *args) -> None + set_allowed(type: list) -> None + set_allowed(None) -> None + + control which events are allowed on the queue + """ + + _setter(True, type, *args) + + +def get_blocked(type: int | IterableLike[int], *args: int): + """ + get_blocked(type: int, *args) -> bool + get_blocked(type: list) -> bool + + test if a type of event is blocked from the queue + """ + + for t in _parse(type, args): + _check_ev_type(t) + if not _allowed_get(t): + return True + return False + + +def clear(eventtype: int | IterableLike[int] | None = None, pump: bool = True): + """ + clear(eventtype=None) -> None + clear(eventtype=None, pump=True) -> None + + remove all events from the queue + """ + if eventtype is None or isinstance(eventtype, int): + get(eventtype, pump) + else: + get(list(iter(eventtype)), pump) + + gc.collect() + + +def _get_many( + ev_type: int, to: list | None = None, proxify: bool = True +) -> list[EventLike]: + if to is None: + to = [] + + ev = _get(ev_type) + + while ev: + to.append(ev) + ev = _get(ev_type) + + if proxify: + to = _get_many(_proxify_event_type(ev_type), to, False) + + return to + + +def get( + eventtype: int | IterableLike[int] | None = None, + pump: bool = True, + exclude: int | IterableLike[int] | None = None, +) -> list[EventLike]: + """ + get(eventtype=None) -> Eventlist + get(eventtype=None, pump=True) -> Eventlist + get(eventtype=None, pump=True, exclude=None) -> Eventlist + + get events from the queue + """ + _video_check() + _pump(pump) + + if isinstance(eventtype, int): + eventtype = [eventtype] + + if isinstance(exclude, int): + exclude = [exclude] + + if eventtype is None: + if exclude is None: + # Get all events + return _get_many(-1, proxify=False) + + # Get all events except + excluded = [] + + for ev_type in exclude: + _check_ev_type(ev_type) + _get_many(ev_type, excluded) + + ret = _get_many(-1, proxify=False) + + for ev in excluded: + post(ev) + + del excluded + return ret + + if exclude is not None: + raise pg.error("Invalid combination of excluded and included event type") + + # Get all events of type + ret = [] + + for ev_type in eventtype: + _check_ev_type(ev_type) + _get_many(ev_type, ret) + + return ret + + +def peek( + eventtype: int | IterableLike[int] | None = None, + pump: bool = True, +) -> Union[EventLike, bool]: + """ + peek() -> Event instance + peek(eventtype) -> bool + peek(eventtype, pump=True) -> bool + + test if event types are waiting on the queue + """ + _video_check() + _pump(pump) + + if isinstance(eventtype, int): + eventtype = [eventtype] + + if eventtype is None: + # Can't seek without mutating event queue here, + # Due to PgEvent_New being a destructive operation. + ret = _get(-1) + + if ret is None: + return Event(0) + + post(ret) + return ret + + for ev_type in eventtype: + _check_ev_type(ev_type) + + if _peek(ev_type) or _peek(_proxify_event_type(ev_type)): + return True + return False + + +__all__ = [ + "Event", + "EventType", + "pump", + "get", + "poll", + "wait", + "peek", + "clear", + "event_name", + "set_blocked", + "set_allowed", + "get_blocked", + "set_grab", + "get_grab", + "post", + "custom_type", + "init", + "quit", +] diff --git a/src_py/meson.build b/src_py/meson.build index 541c54cd69..2978d633b2 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -8,6 +8,7 @@ python_sources = files( 'camera.py', 'colordict.py', 'cursors.py', + 'event.py', 'freetype.py', 'ftfont.py', 'locals.py', diff --git a/src_py/typing.py b/src_py/typing.py index ca88863a59..b5271c37d0 100644 --- a/src_py/typing.py +++ b/src_py/typing.py @@ -14,7 +14,18 @@ import sys from abc import abstractmethod -from typing import IO, Callable, Tuple, Union, TypeVar, Protocol +from typing import ( + IO, + Callable, + Tuple, + Dict, + Union, + Optional, + TypeVar, + Protocol, + Any, + Iterable, +) from pygame.color import Color from pygame.rect import Rect, FRect @@ -40,13 +51,14 @@ def __fspath__(self) -> _AnyStr_co: ... class SequenceLike(Protocol[_T_co]): """ - Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`. + Variant of the standard `Sequence` ABC that only requires `__getitem__`. """ @abstractmethod def __getitem__(self, index: int, /) -> _T_co: ... - @abstractmethod - def __len__(self) -> int: ... + + +IterableLike = Union[SequenceLike[_T_co], Iterable[_T_co]] # Modify typehints when it is possible to annotate sizes @@ -72,6 +84,16 @@ def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... ] +class EventLike(Protocol): + def __init__(self, type: int, /, **kwargs: Any) -> None: ... + def __new__(cls, *args: Any, **kwargs: Any) -> "EventLike": ... + + @property + def type(self) -> int: ... + @property + def dict(self) -> Dict[str, Any]: ... + + # cleanup namespace del ( sys, @@ -82,7 +104,10 @@ def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... IO, Callable, Tuple, + Dict, Union, + Optional, TypeVar, Protocol, + Any, )