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 7f8ed1a6b7..174201fefd 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -1,23 +1,27 @@ -from typing import Any, Optional, Union, final +from typing import Any, Dict, Type, Optional, Union, overload -from pygame.typing import SequenceLike +from pygame.typing import SequenceLike, EventLike +from pygame import Window, constants as _c -@final -class Event: + +class Event(EventLike): type: int - __dict__: dict[str, Any] - __hash__: None # type: ignore + + @overload def __init__( - self, type: int, dict: dict[str, Any] = ..., **kwargs: Any + self, type: int, dict: Optional[Dict[str, Any]] = None, **kwargs: Any ) -> None: ... + @overload + def __init__( + self, dict: Optional[Dict[str, Any]] = None, **kwargs: Any + ) -> None: ... + 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: ... - - # this is at the bottom because mypy complains if this declaration comes - # before any uses of the dict[] typehinting because of the same naming - dict: dict[str, Any] + def __eq__(self, other: Any) -> bool: ... _EventTypes = Union[int, SequenceLike[int]] @@ -26,18 +30,424 @@ 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: _EventTypes, 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 event_class(type: int) -> Type[Event]: ... +def init() -> None: ... +def quit() -> None: ... EventType = Event + +# BEGIN Event subclasses +class ActiveEvent(Event): + type: int = _c.ACTIVEEVENT + gain: int + state: int + +class AppTerminating(Event): + type: int = _c.APP_TERMINATING + +class AppLowMemory(Event): + type: int = _c.APP_LOWMEMORY + +class AppWillEnterBackground(Event): + type: int = _c.APP_WILLENTERBACKGROUND + +class AppDidEnterBackground(Event): + type: int = _c.APP_DIDENTERBACKGROUND + +class AppWillEnterForeground(Event): + type: int = _c.APP_WILLENTERFOREGROUND + +class AppDidEnterForeground(Event): + type: int = _c.APP_DIDENTERFOREGROUND + +class ClipboardUpdate(Event): + type: int = _c.CLIPBOARDUPDATE + +class KeyDown(Event): + type: int = _c.KEYDOWN + unicode: str + key: int + mod: int + scancode: int + window: Optional[Window] + +class KeyUp(Event): + type: int = _c.KEYUP + unicode: str + key: int + mod: int + scancode: int + window: Optional[Window] + +class KeyMapChanged(Event): + type: int = _c.KEYMAPCHANGED + +class LocaleChanged(Event): + """Only for SDL 2.0.14+""" + type: int = _c.LOCALECHANGED + +class MouseMotion(Event): + type: int = _c.MOUSEMOTION + pos: tuple[int, int] + rel: tuple[int, int] + buttons: tuple[int, int, int] + touch: bool + window: Optional[Window] + +class MouseButtonDown(Event): + type: int = _c.MOUSEBUTTONDOWN + pos: tuple[int, int] + button: int + touch: bool + window: Optional[Window] + +class MouseButtonUp(Event): + type: int = _c.MOUSEBUTTONUP + pos: tuple[int, int] + button: int + touch: bool + window: Optional[Window] + +class JoyAxisMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" + type: int = _c.JOYAXISMOTION + joy: int + instance_id: int + axis: int + value: float + +class JoyBallMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" + type: int = _c.JOYBALLMOTION + joy: int + instance_id: int + ball: int + rel: tuple[int, int] + +class JoyHatMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" + type: int = _c.JOYHATMOTION + joy: int + instance_id: int + hat: int + value: tuple[int, int] + +class JoyButtonUp(Event): + """Attribute "joy" is depracated, use "instance_id".""" + type: int = _c.JOYBUTTONUP + joy: int + instance_id: int + button: int + +class JoyButtonDown(Event): + """Attribute "joy" is depracated, use "instance_id".""" + type: int = _c.JOYBUTTONDOWN + joy: int + instance_id: int + button: int + +class Quit(Event): + type: int = _c.QUIT + +class SysWMEvent(Event): + """ + Attributes are OS-depended: + hwnd, msg, wparam, lparam - Windows. + event - Unix / OpenBSD + For other OSes and in some cases for Unix / OpenBSD + this event won't have any attributes. + """ + type: int = _c.SYSWMEVENT + hwnd: int + msg: int + wparam: int + lparam: int + event: bytes + +class VideoResize(Event): + type: int = _c.VIDEORESIZE + size: tuple[int, int] + w: int + h: int + +class VideoExpose(Event): + type: int = _c.VIDEOEXPOSE + +class MidiIn(Event): + type: int = _c.MIDIIN + +class MidiOut(Event): + type: int = _c.MIDIOUT + +class NoEvent(Event): + type: int = _c.NOEVENT + +class FingerMotion(Event): + """Attribute "window" avalible only for SDL 2.0.14+""" + type: int = _c.FINGERMOTION + touch_id: int + finger_id: int + x: float + y: float + dx: float + dy: float + pressure: float + window: Optional[Window] + +class FingerDown(Event): + """Attribute "window" avalible only for SDL 2.0.14+""" + type: int = _c.FINGERDOWN + touch_id: int + finger_id: int + x: float + y: float + dx: float + dy: float + pressure: float + window: Optional[Window] + +class FingerUp(Event): + """Attribute "window" avalible only for SDL 2.0.14+""" + type: int = _c.FINGERUP + touch_id: int + finger_id: int + x: float + y: float + dx: float + dy: float + pressure: float + window: Optional[Window] + +class MultiGesture(Event): + type: int = _c.MULTIGESTURE + touch_id: int + x: float + y: float + rotated: float + pinched: float + num_fingers: int + +class MouseWheel(Event): + type: int = _c.MOUSEWHEEL + flipped: bool + x: int + y: int + precise_x: float + precise_y: float + touch: bool + window: Optional[Window] + +class TextInput(Event): + type: int = _c.TEXTINPUT + text: str + window: Optional[Window] + +class TextEditing(Event): + type: int = _c.TEXTEDITING + text: str + start: int + length: int + window: Optional[Window] + +class DropFile(Event): + type: int = _c.DROPFILE + file: str + window: Optional[Window] + +class DropText(Event): + type: int = _c.DROPTEXT + text: str + window: Optional[Window] + +class DropBegin(Event): + type: int = _c.DROPBEGIN + window: Optional[Window] + +class DropComplete(Event): + type: int = _c.DROPCOMPLETE + window: Optional[Window] + +class ControllerAxisMotion(Event): + type: int = _c.CONTROLLERAXISMOTION + instance_id: int + axis: int + value: int + +class ControllerButtonDown(Event): + type: int = _c.CONTROLLERBUTTONDOWN + instance_id: int + button: int + +class ControllerButtonUp(Event): + type: int = _c.CONTROLLERBUTTONUP + instance_id: int + button: int + +class ControllerDeviceAdded(Event): + type: int = _c.CONTROLLERDEVICEADDED + device_index: int + guid: str + +class ControllerDeviceRemoved(Event): + type: int = _c.CONTROLLERDEVICEREMOVED + instance_id: int + +class ControllerDeviceMapped(Event): + type: int = _c.CONTROLLERDEVICEREMAPPED + instance_id: int + +class JoyDeviceAdded(Event): + type: int = _c.JOYDEVICEADDED + device_index: int + guid: str + +class JoyDeviceRemoved(Event): + type: int = _c.JOYDEVICEREMOVED + instance_id: int + +class ControllerTouchpadDown(Event): + """Only for SDL 2.0.14+""" + type: int = _c.CONTROLLERTOUCHPADDOWN + instance_id: int + touch_id: int + finger_id: int + x: float + y: float + pressure: float + +class ControllerTouchpadMotion(Event): + """Only for SDL 2.0.14+""" + type: int = _c.CONTROLLERTOUCHPADMOTION + instance_id: int + touch_id: int + finger_id: int + x: float + y: float + pressure: float + +class ControllerTouchpadUp(Event): + """Only for SDL 2.0.14+""" + type: int = _c.CONTROLLERTOUCHPADUP + instance_id: int + touch_id: int + finger_id: int + x: float + y: float + pressure: float + +class ControllerSensorUpdate(Event): + """Only for SDL 2.0.14+""" + type: int = _c.CONTROLLERSENSORUPDATE + +class AudioDeviceAdded(Event): + type: int = _c.AUDIODEVICEADDED + which: int + iscapture: int + +class AudioDeviceRemoved(Event): + type: int = _c.AUDIODEVICEREMOVED + which: int + iscapture: int + +class RenderTargetsReset(Event): + type: int = _c.RENDER_TARGETS_RESET + +class RenderDeviceReset(Event): + type: int = _c.RENDER_DEVICE_RESET + +class WindowShown(Event): + type: int = _c.WINDOWSHOWN + window: Optional[Window] + +class WindowHidden(Event): + type: int = _c.WINDOWHIDDEN + window: Optional[Window] + +class WindowExposed(Event): + type: int = _c.WINDOWEXPOSED + window: Optional[Window] + +class WindowMoved(Event): + type: int = _c.WINDOWMOVED + x: int + y: int + window: Optional[Window] + +class WindowResized(Event): + type: int = _c.WINDOWRESIZED + x: int + y: int + window: Optional[Window] + +class WindowSizeChanged(Event): + type: int = _c.WINDOWSIZECHANGED + x: int + y: int + window: Optional[Window] + +class WindowMinimized(Event): + type: int = _c.WINDOWMINIMIZED + window: Optional[Window] + +class WindowMaximized(Event): + type: int = _c.WINDOWMAXIMIZED + window: Optional[Window] + +class WindowRestored(Event): + type: int = _c.WINDOWRESTORED + window: Optional[Window] + +class WindowEnter(Event): + type: int = _c.WINDOWENTER + window: Optional[Window] + +class WindowLeave(Event): + type: int = _c.WINDOWLEAVE + window: Optional[Window] + +class WindowFocusGained(Event): + type: int = _c.WINDOWFOCUSGAINED + window: Optional[Window] + +class WindowFocusLost(Event): + type: int = _c.WINDOWFOCUSLOST + window: Optional[Window] + +class WindowClose(Event): + type: int = _c.WINDOWCLOSE + window: Optional[Window] + +class WindowTakeFocus(Event): + type: int = _c.WINDOWTAKEFOCUS + window: Optional[Window] + +class WindowHitTest(Event): + type: int = _c.WINDOWHITTEST + window: Optional[Window] + +class WindowICCProfChanged(Event): + type: int = _c.WINDOWICCPROFCHANGED + window: Optional[Window] + +class WindowDisplayChanged(Event): + type: int = _c.WINDOWDISPLAYCHANGED + display_index: int + window: Optional[Window] + +class UserEvent(Event): + type: int = _c.USEREVENT + +# END Event subclasses diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi index 2897a338fe..5a2f2703e6 100644 --- a/buildconfig/stubs/pygame/typing.pyi +++ b/buildconfig/stubs/pygame/typing.pyi @@ -15,7 +15,7 @@ __all__ = [ from abc import abstractmethod from collections.abc import Callable from os import PathLike as _PathProtocol -from typing import IO, Union, TypeVar, Protocol +from typing import IO, Union, Optional, TypeVar, Protocol, Any, Iterable from pygame.color import Color from pygame.rect import Rect, FRect @@ -31,13 +31,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 @@ -63,6 +64,17 @@ RectLike = Union[ ] +class EventLike(Protocol): + type: int + + def __init__( + self, dict: Optional[dict[str, Any]] = None, **kwargs: Any + ) -> None: ... + + @property + def dict(self) -> dict[str, Any]: ... + + # cleanup namespace del ( abstractmethod, @@ -72,6 +84,8 @@ del ( IO, Callable, 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 186fa37717..3a1a5edb10 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -317,8 +317,8 @@ 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(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 @@ -463,6 +463,17 @@ On Android, the following events can be generated .. ## pygame.event.custom_type ## +.. function:: event_class + + | :sl:`returns related event class to event type` + | :sg:`event_class(type: int, /) -> type[Event]` + + Returns an event class that is correlated with the given event type. If the class to a given event type is not found, + but the type is within the range of valid values for the event type, a new subclass will be created. + + .. versionadded:: 2.6.0 + .. ## pygame.event.event_class ## + .. class:: Event | :sl:`pygame object for representing events` @@ -478,6 +489,8 @@ On Android, the following events can be generated .. versionchanged:: 2.1.4 This class is also available through the ``pygame.Event`` alias. + .. versionchanged:: 2.6.0 This class can be subclassed to create user-defined event types. + .. note:: From version 2.1.3 ``EventType`` is an alias for ``Event``. Beforehand, ``Event`` was a function that returned ``EventType`` instances. Use of @@ -515,4 +528,88 @@ On Android, the following events can be generated .. ## pygame.event.Event ## +.. table:: List of Event subclasses available as aliases for event types importable as ``pygame.event.{Class name}``. + :widths: auto + + ============================= ============================= ========================== + Class name Alias to Notes + ============================= ============================= ========================== + ``ActiveEvent`` ``ACTIVEEVENT`` + ``AppTerminating`` ``APP_TERMINATING`` + ``AppLowMemory`` ``APP_LOWMEMORY`` + ``AppWillEnterBackground`` ``APP_WILLENTERBACKGROUND`` + ``AppDidEnterBackground`` ``APP_DIDENTERBACKGROUND`` + ``AppWillEnterForeground`` ``APP_WILLENTERFOREGROUND`` + ``AppDidEnterForeground`` ``APP_DIDENTERFOREGROUND`` + ``ClipboardUpdate`` ``CLIPBOARDUPDATE`` + ``KeyDown`` ``KEYDOWN`` + ``KeyUp`` ``KEYUP`` + ``KeyMapChanged`` ``KEYMAPCHANGED`` + ``LocaleChanged`` ``LOCALECHANGED`` Only for SDL 2.0.14+ + ``MouseMotion`` ``MOUSEMOTION`` + ``MouseButtonDown`` ``MOUSEBUTTONDOWN`` + ``MouseButtonUp`` ``MOUSEBUTTONUP`` + ``JoyAxisMotion`` ``JOYAXISMOTION`` + ``JoyBallMotion`` ``JOYBALLMOTION`` + ``JoyHatMotion`` ``JOYHATMOTION`` + ``JoyButtonUp`` ``JOYBUTTONUP`` + ``JoyButtonDown`` ``JOYBUTTONDOWN`` + ``Quit`` ``QUIT`` + ``SysWMEvent`` ``SYSWMEVENT`` + ``VideoResize`` ``VIDEORESIZE`` + ``VideoExpose`` ``VIDEOEXPOSE`` + ``MidiIn`` ``MIDIIN`` + ``MidiOut`` ``MIDIOUT`` + ``NoEvent`` ``NOEVENT`` + ``FingerMotion`` ``FINGERMOTION`` + ``FingerDown`` ``FINGERDOWN`` + ``FingerUp`` ``FINGERUP`` + ``MultiGesture`` ``MULTIGESTURE`` + ``MouseWheel`` ``MOUSEWHEEL`` + ``TextInput`` ``TEXTINPUT`` + ``TextEditing`` ``TEXTEDITING`` + ``DropFile`` ``DROPFILE`` + ``DropText`` ``DROPTEXT`` + ``DropBegin`` ``DROPBEGIN`` + ``DropComplete`` ``DROPCOMPLETE`` + ``ControllerAxisMotion`` ``CONTROLLERAXISMOTION`` + ``ControllerButtonDown`` ``CONTROLLERBUTTONDOWN`` + ``ControllerButtonUp`` ``CONTROLLERBUTTONUP`` + ``ControllerDeviceAdded`` ``CONTROLLERDEVICEADDED`` + ``ControllerDeviceRemoved`` ``CONTROLLERDEVICEREMOVED`` + ``ControllerDeviceMapped`` ``CONTROLLERDEVICEREMAPPED`` + ``JoyDeviceAdded`` ``JOYDEVICEADDED`` + ``JoyDeviceRemoved`` ``JOYDEVICEREMOVED`` + ``ControllerTouchpadDown`` ``CONTROLLERTOUCHPADDOWN`` Only for SDL 2.0.14+ + ``ControllerTouchpadMotion`` ``CONTROLLERTOUCHPADMOTION`` Only for SDL 2.0.14+ + ``ControllerTouchpadUp`` ``CONTROLLERTOUCHPADUP`` Only for SDL 2.0.14+ + ``ControllerSensorUpdate`` ``CONTROLLERSENSORUPDATE`` Only for SDL 2.0.14+ + ``AudioDeviceAdded`` ``AUDIODEVICEADDED`` + ``AudioDeviceRemoved`` ``AUDIODEVICEREMOVED`` + ``RenderTargetsReset`` ``RENDER_TARGETS_RESET`` + ``RenderDeviceReset`` ``RENDER_DEVICE_RESET`` + ``WindowShown`` ``WINDOWSHOWN`` + ``WindowHidden`` ``WINDOWHIDDEN`` + ``WindowExposed`` ``WINDOWEXPOSED`` + ``WindowMoved`` ``WINDOWMOVED`` + ``WindowResized`` ``WINDOWRESIZED`` + ``WindowSizeChanged`` ``WINDOWSIZECHANGED`` + ``WindowMinimized`` ``WINDOWMINIMIZED`` + ``WindowMaximized`` ``WINDOWMAXIMIZED`` + ``WindowRestored`` ``WINDOWRESTORED`` + ``WindowEnter`` ``WINDOWENTER`` + ``WindowLeave`` ``WINDOWLEAVE`` + ``WindowFocusGained`` ``WINDOWFOCUSGAINED`` + ``WindowFocusLost`` ``WINDOWFOCUSLOST`` + ``WindowClose`` ``WINDOWCLOSE`` + ``WindowTakeFocus`` ``WINDOWTAKEFOCUS`` + ``WindowHitTest`` ``WINDOWHITTEST`` + ``WindowICCProfChanged`` ``WINDOWICCPROFCHANGED`` + ``WindowDisplayChanged`` ``WINDOWDISPLAYCHANGED`` + ============================= ============================= ========================== + +.. note:: + + While instantiating these subclasses, don't pass the ``type`` argument to ``{Class name}.__init__()`` + .. ## pygame.event ## 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 63% rename from src_c/event.c rename to src_c/_event.c index c7838a0ee8..566ccb59a3 100644 --- a/src_c/event.c +++ b/src_c/_event.c @@ -31,8 +31,6 @@ #include "doc/event_doc.h" -#include "structmember.h" - #ifndef PG_SDL3 // The system message code is only tested on windows, so only // include it there for now. @@ -51,11 +49,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 @@ -94,11 +87,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 */ @@ -183,10 +187,11 @@ _pg_unicode_from_event(SDL_Event *event) int capitalize = (capsheld && !shiftheld) || (shiftheld && !capsheld); #if SDL_VERSION_ATLEAST(3, 0, 0) - if (event->key.mod & KMOD_CTRL) { + if (event->key.mod & KMOD_CTRL) #else - if (event->key.keysym.mod & KMOD_CTRL) { + if (event->key.keysym.mod & KMOD_CTRL) #endif + { /* Control Key held, send control-key related unicode. */ if (key >= SDLK_a && key <= SDLK_z) return key - SDLK_a + 1; @@ -308,10 +313,11 @@ _pg_get_event_unicode(SDL_Event *event) int i; for (i = 0; i < MAX_SCAN_UNICODE; i++) { #if SDL_VERSION_ATLEAST(3, 0, 0) - if (scanunicode[i].key == event->key.scancode) { + if (scanunicode[i].key == event->key.scancode) #else - if (scanunicode[i].key == event->key.keysym.scancode) { + if (scanunicode[i].key == event->key.keysym.scancode) #endif + { if (event->type == SDL_KEYUP) { /* mark the position as free real estate for other * events to occupy. */ @@ -520,17 +526,20 @@ pg_event_filter(void *_, SDL_Event *event) #if SDL_VERSION_ATLEAST(3, 0, 0) if (event->type >= SDL_EVENT_WINDOW_FIRST && - event->type <= SDL_EVENT_WINDOW_LAST) { + event->type <= SDL_EVENT_WINDOW_LAST) #else - if (event->type == SDL_WINDOWEVENT) { + if (event->type == SDL_WINDOWEVENT) #endif + { /* DON'T filter SDL_WINDOWEVENTs here. If we delete events, they * won't be available to low-level SDL2 either.*/ + switch ( #if SDL_VERSION_ATLEAST(3, 0, 0) - switch (event->type) { + event->type #else - switch (event->window.event) { + event->window.event #endif + ) { case SDL_WINDOWEVENT_RESIZED: SDL_FilterEvents(_pg_remove_pending_VIDEORESIZE, &newevent); @@ -564,9 +573,10 @@ pg_event_filter(void *_, SDL_Event *event) PG_LOCK_EVFILTER_MUTEX #if SDL_VERSION_ATLEAST(3, 0, 0) - pressed_keys[event->key.scancode] = 1; + input_buffer[INPUT_BUFFER_PRESSED_OFFSET + event->key.scancode] = 1; #else - pressed_keys[event->key.keysym.scancode] = 1; + input_buffer[INPUT_BUFFER_PRESSED_OFFSET + + event->key.keysym.scancode] = 1; #endif if (pg_key_repeat_delay > 0) { if (_pg_repeat_timer) @@ -598,14 +608,16 @@ pg_event_filter(void *_, SDL_Event *event) else if (event->type == SDL_KEYUP) { PG_LOCK_EVFILTER_MUTEX #if SDL_VERSION_ATLEAST(3, 0, 0) - released_keys[event->key.scancode] = 1; + input_buffer[INPUT_BUFFER_RELEASED_OFFSET + event->key.scancode] = 1; if (_pg_repeat_timer && - _pg_repeat_event.key.scancode == event->key.scancode) { + _pg_repeat_event.key.scancode == event->key.scancode) #else - released_keys[event->key.keysym.scancode] = 1; - if (_pg_repeat_timer && _pg_repeat_event.key.keysym.scancode == - event->key.keysym.scancode) { + 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) #endif + { SDL_RemoveTimer(_pg_repeat_timer); _pg_repeat_timer = 0; } @@ -616,11 +628,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; @@ -719,11 +733,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; @@ -749,222 +758,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"; - case SDL_LOCALECHANGED: - return "LocaleChanged"; - 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"; -#if !SDL_VERSION_ATLEAST(3, 0, 0) - case SDL_MULTIGESTURE: - return "MultiGesture"; -#endif - 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"; - case SDL_CONTROLLERTOUCHPADDOWN: - return "ControllerTouchpadDown"; - case SDL_CONTROLLERTOUCHPADMOTION: - return "ControllerTouchpadMotion"; - case SDL_CONTROLLERTOUCHPADUP: - return "ControllerTouchpadUp"; - case SDL_CONTROLLERSENSORUPDATE: - return "ControllerSensorUpdate"; - 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 @@ -1008,7 +828,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; @@ -1017,27 +837,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(); @@ -1054,10 +855,11 @@ dict_from_event(SDL_Event *event) break; case SDL_ACTIVEEVENT: #if SDL_VERSION_ATLEAST(3, 0, 0) - switch (event->window.data2) { + switch (event->window.data2) #else - switch (event->window.event) { + switch (event->window.event) #endif + { case SDL_WINDOWEVENT_ENTER: gain = 1; state = SDL_APPMOUSEFOCUS; @@ -1266,12 +1068,12 @@ dict_from_event(SDL_Event *event) PyFloat_FromDouble((double)event->wheel.preciseY)); #else /* ~SDL_VERSION_ATLEAST(2, 0, 18) */ - /* fallback to regular x and y when SDL version used does not - * support precise fields */ - _pg_insobj(dict, "precise_x", - PyFloat_FromDouble((double)event->wheel.x)); - _pg_insobj(dict, "precise_y", - PyFloat_FromDouble((double)event->wheel.y)); + /* fallback to regular x and y when SDL version used does not + * support precise fields */ + _pg_insobj(dict, "precise_x", + PyFloat_FromDouble((double)event->wheel.x)); + _pg_insobj(dict, "precise_y", + PyFloat_FromDouble((double)event->wheel.y)); #endif /* ~SDL_VERSION_ATLEAST(2, 0, 18) */ _pg_insobj( @@ -1497,12 +1299,12 @@ dict_from_event(SDL_Event *event) } PyObject *pgWindow; #if SDL_VERSION_ATLEAST(3, 0, 0) - if (!window || - !(pgWindow = SDL_GetPointerProperty(SDL_GetWindowProperties(window), - "pg_window", NULL))) { + if (!window || !(pgWindow = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), "pg_window", NULL))) #else - if (!window || !(pgWindow = SDL_GetWindowData(window, "pg_window"))) { + if (!window || !(pgWindow = SDL_GetWindowData(window, "pg_window"))) #endif + { pgWindow = Py_None; } Py_INCREF(pgWindow); @@ -1510,232 +1312,110 @@ dict_from_event(SDL_Event *event) return dict; } -/* event object internals */ - -static void -pg_event_dealloc(PyObject *self) +static PyObject * +pgEvent_GetType(void) { - pgEventObject *e = (pgEventObject *)self; - Py_XDECREF(e->dict); - Py_TYPE(self)->tp_free(self); -} + if (!_event_class) + return RAISE(PyExc_RuntimeError, "event type is currently unknown"); -#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; + Py_INCREF(_event_class); + return _event_class; } -int -pg_EventSetAttr(PyObject *o, PyObject *name, PyObject *value) -{ - /* 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 - -PyObject * -pg_event_str(PyObject *self) -{ - pgEventObject *e = (pgEventObject *)self; - return PyUnicode_FromFormat("", e->type, - _pg_name_from_eventtype(e->type), e->dict); -} - -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, -}; - -static PyTypeObject pgEvent_Type; -#define pgEvent_Check(x) ((x)->ob_type == &pgEvent_Type) -#define OFF(x) offsetof(pgEventObject, x) + PyObject *e_typeo = pgEvent_GetType(); + if (!e_typeo) + return NULL; -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 */ -}; + PyObject *num = PyLong_FromLong(e_type); + if (!num) + goto finalize; -/* - * eventA == eventB - * eventA != eventB - */ -static PyObject * -pg_event_richcompare(PyObject *o1, PyObject *o2, int opid) -{ - pgEventObject *e1, *e2; + args = PyTuple_New(1); - 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) { @@ -1797,10 +1477,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(); } @@ -1845,10 +1522,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; } @@ -1889,413 +1571,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); + SDL_Event ev; + int e_type = PyLong_AsLong(obj); - PG_PEEP_EVENT_ALL(filtered_events, filtered_index, SDL_ADDEVENT); - - 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 @@ -2307,11 +1662,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: @@ -2322,113 +1686,79 @@ 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; - 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); - } + PG_SetEventEnabled(_pg_pgevent_proxify(e_type), + e_flag ? SDL_TRUE : SDL_FALSE); + + // Never block events that are needed for proecesing. + if (e_type == PGE_KEYREPEAT) + PG_SetEventEnabled(e_type, SDL_TRUE); #if !SDL_VERSION_ATLEAST(3, 0, 0) - /* Never block SDL_WINDOWEVENT on SDL2, we need them for translation */ - PG_SetEventEnabled(SDL_WINDOWEVENT, SDL_TRUE); + else if (e_type == SDL_WINDOWEVENT) + PG_SetEventEnabled(e_type, SDL_TRUE); #endif - /* Never block PGE_KEYREPEAT too, its needed for pygame internal use */ - PG_SetEventEnabled(PGE_KEYREPEAT, SDL_TRUE); + else + PG_SetEventEnabled(e_type, e_flag ? SDL_TRUE : SDL_FALSE); + 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[] = { @@ -2437,48 +1767,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. @@ -2493,44 +1810,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 05bf8a5228..0f3bde5214 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -192,19 +192,6 @@ PG_UnlockMutex(SDL_mutex *mutex) #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, @@ -462,10 +449,6 @@ typedef enum { /* * event module internals */ -struct pgEventObject { - PyObject_HEAD int type; - PyObject *dict; -}; /* * surface module internals @@ -520,7 +503,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 b07f18e4e3..febc5ac4b5 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..e294cbae6d 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(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" @@ -14,6 +14,7 @@ #define DOC_EVENT_GETGRAB "get_grab() -> bool\ntest if the program is sharing input devices" #define DOC_EVENT_POST "post(event, /) -> bool\nplace a new event on the queue" #define DOC_EVENT_CUSTOMTYPE "custom_type() -> int\nmake custom user event type" +#define DOC_EVENT_EVENTCLASS "event_class(type: int, /) -> type[Event]\nreturns related event class to event type" #define DOC_EVENT_EVENT "Event(type, dict) -> Event\nEvent(type, **attributes) -> Event\npygame object for representing events" #define DOC_EVENT_EVENT_TYPE "type -> int\nevent type identifier." #define DOC_EVENT_EVENT_DICT "__dict__ -> dict\nevent attribute dictionary" 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 b3548a66ee..d42671c83d 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 bb94eddcb2..a0e5b67ba8 100644 --- a/src_c/meson.build +++ b/src_c/meson.build @@ -39,9 +39,9 @@ display = py.extension_module( ) endif -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 a44d859209..bd92e84937 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 */ @@ -292,8 +280,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; @@ -404,9 +406,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}; @@ -432,14 +433,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__ @@ -476,7 +484,7 @@ time_set_timer(PyObject *self, PyObject *args, PyObject *kwargs) } #endif - 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..fb97f00c0e --- /dev/null +++ b/src_py/event.py @@ -0,0 +1,696 @@ +"pygame module for interacting with events and queues" + +from __future__ import annotations + +import gc + +from typing import Any, Dict, Type, Union, overload +from pygame.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, +) + +import pygame as pg + + +_is_init = False +_custom_event = pg.USEREVENT + 1 + + +def event_name(type: int) -> str: + """ + event_name(type) -> string + + get the string name from an event id + """ + + if type in _events_map: + return _events_map[type].__name__ + 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") + + +_events_map: Dict[int, Type["Event"]] = {} + + +def _unknown_event_factory(ev_type: int) -> Type[Event]: + if ev_type >= pg.USEREVENT: + + class UserEvent(Event): + type = ev_type + _unknown: bool = True + + return UserEvent + + class UnknownEvent(Event): + type = ev_type + _unknown: bool = True + + return UnknownEvent + + +def event_class(type: int) -> type[EventLike]: + _check_ev_type(type) + + if type not in _events_map: + _events_map[type] = _unknown_event_factory(type) + return _events_map[type] + + +class _EventMeta(type): + def _create_of_type(cls, type: int, *args, **kwds): + return event_class(type)(*args, **kwds) + + def __call__(cls, *args: Any, **kwds: Any) -> Any: + if cls is Event: + return cls._create_of_type(*args, **kwds) + return super(_EventMeta, cls).__call__(*args, **kwds) + + +class Event(metaclass=_EventMeta): + """ + Event(type, dict) -> Event + Event(type, **attributes) -> Event + + pygame object for representing events + """ + + type: int = -1 + + @overload + def __init__( + self, type: int, dict: dict[str, Any] | None = None, **kwargs: Any + ): ... + @overload + def __init__(self, dict: dict[str, Any] | None = None, **kwargs: Any): ... + + def __init__(self, *args, **kwargs) -> None: + self.__init(*args, **kwargs) + + def __init(self, dict: dict[str, Any] | None = None, **kwargs: Any): + if dict is None: + dict = kwargs + else: + dict.update(kwargs) + + if "type" in dict: + raise ValueError("redundant type field in event dict") + + self._dict = dict + + def __init_subclass__(cls) -> None: + if getattr(cls, "type", -1) == -1: + cls.type = custom_type() + _events_map[cls.type] = 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): + if getattr(self, "_unknown", False): + return ( + f"<{type(self).__module__}.." + f"{type(self).__name__}({self.type} {self.dict})>" + ) + + return f"<{type(self).__module__}.{type(self).__qualname__}({self.dict})>" + + @property + def dict(self): + return self._dict + + def __getattr__(self, name: str) -> Any: + if name not in self._dict: + raise AttributeError( + f"{self.__class__.__name__} object has no attribute {name!r}" + ) + 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 == "type": + raise AttributeError( + "attribute 'type' of 'Event' or its subclass object is protected" + ) + + if name in ("_dict", "dict"): + super().__setattr__(name, value) + else: + self._dict[name] = value + + def __delattr__(self, name: str) -> None: + del self._dict[name] + + def __dir__(self): + ret = super().__dir__() + ret = (*ret, *self._dict.keys()) + return ret + + +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 + _events_map.clear() + _events_map.update(_extra_cache) + _quit() + + _is_init = False + + +def custom_type(): + """ + custom_type() -> int + + make custom user event type + """ + global _custom_event + + if _custom_event >= pg.NUMEVENTS: + raise pg.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], + 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] + + 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 + + +_extra_cache = {} + + +def _create_class(type: int, name: str, note: str | None): + ret = _EventMeta(name, (Event,), {"type": type, "__doc__": note}) + _extra_cache[type] = ret + return ret + + +# To regenerate class definitions: +# 1) Paste class declarations from buildconfig/stubs/pygame/event.pyi above +# 2) Run the code below and paste the output. + +# def const_find(vl, guess: str): +# keys: list[str] = [key for key, value in pg.constants.__dict__.items() if value == vl] +# if len(keys) < 1: +# raise ValueError(f"{guess}:{vl} not found int the dict") +# elif len(keys) == 1: +# return keys[0] +# for k in keys: +# if k.replace("_", "") == guess.upper(): +# return k +# raise ValueError(f"{guess}:{vl} unresolved: {keys}") + +# for cls in _events_map.values(): +# if cls.__name__ == "UserEvent": continue +# print( +# f"{cls.__name__} = _create_class(pg.{const_find(cls.type, cls.__name__)}, " \ +# f"{cls.__qualname__!r}, {cls.__doc__!r})" +# ) + + +ActiveEvent = _create_class(pg.ACTIVEEVENT, "ActiveEvent", None) +AppTerminating = _create_class(pg.APP_TERMINATING, "AppTerminating", None) +AppLowMemory = _create_class(pg.APP_LOWMEMORY, "AppLowMemory", None) +AppWillEnterBackground = _create_class( + pg.APP_WILLENTERBACKGROUND, "AppWillEnterBackground", None +) +AppDidEnterBackground = _create_class( + pg.APP_DIDENTERBACKGROUND, "AppDidEnterBackground", None +) +AppWillEnterForeground = _create_class( + pg.APP_WILLENTERFOREGROUND, "AppWillEnterForeground", None +) +AppDidEnterForeground = _create_class( + pg.APP_DIDENTERFOREGROUND, "AppDidEnterForeground", None +) +ClipboardUpdate = _create_class(pg.CLIPBOARDUPDATE, "ClipboardUpdate", None) +KeyDown = _create_class(pg.KEYDOWN, "KeyDown", None) +KeyUp = _create_class(pg.KEYUP, "KeyUp", None) +KeyMapChanged = _create_class(pg.KEYMAPCHANGED, "KeyMapChanged", None) +LocaleChanged = _create_class(pg.LOCALECHANGED, "LocaleChanged", "Only for SDL 2.0.14+") +MouseMotion = _create_class(pg.MOUSEMOTION, "MouseMotion", None) +MouseButtonDown = _create_class(pg.MOUSEBUTTONDOWN, "MouseButtonDown", None) +MouseButtonUp = _create_class(pg.MOUSEBUTTONUP, "MouseButtonUp", None) +JoyAxisMotion = _create_class( + pg.JOYAXISMOTION, + "JoyAxisMotion", + 'Attribute "joy" is depracated, use "instance_id".', +) +JoyBallMotion = _create_class( + pg.JOYBALLMOTION, + "JoyBallMotion", + 'Attribute "joy" is depracated, use "instance_id".', +) +JoyHatMotion = _create_class( + pg.JOYHATMOTION, "JoyHatMotion", 'Attribute "joy" is depracated, use "instance_id".' +) +JoyButtonUp = _create_class( + pg.JOYBUTTONUP, "JoyButtonUp", 'Attribute "joy" is depracated, use "instance_id".' +) +JoyButtonDown = _create_class( + pg.JOYBUTTONDOWN, + "JoyButtonDown", + 'Attribute "joy" is depracated, use "instance_id".', +) +Quit = _create_class(pg.QUIT, "Quit", None) +SysWMEvent = _create_class( + pg.SYSWMEVENT, + "SysWMEvent", + "\n Attributes are OS-depended:\n hwnd, msg, wparam, lparam - Windows.\n" + " event - Unix / OpenBSD\n For other OSes and in some cases for Unix / OpenBSD\n" + " this event won't have any attributes.\n ", +) +VideoResize = _create_class(pg.VIDEORESIZE, "VideoResize", None) +VideoExpose = _create_class(pg.VIDEOEXPOSE, "VideoExpose", None) +MidiIn = _create_class(pg.MIDIIN, "MidiIn", None) +MidiOut = _create_class(pg.MIDIOUT, "MidiOut", None) +NoEvent = _create_class(pg.NOEVENT, "NoEvent", None) +FingerMotion = _create_class( + pg.FINGERMOTION, "FingerMotion", 'Attribute "window" avalible only for SDL 2.0.14+' +) +FingerDown = _create_class( + pg.FINGERDOWN, "FingerDown", 'Attribute "window" avalible only for SDL 2.0.14+' +) +FingerUp = _create_class( + pg.FINGERUP, "FingerUp", 'Attribute "window" avalible only for SDL 2.0.14+' +) +MultiGesture = _create_class(pg.MULTIGESTURE, "MultiGesture", None) +MouseWheel = _create_class(pg.MOUSEWHEEL, "MouseWheel", None) +TextInput = _create_class(pg.TEXTINPUT, "TextInput", None) +TextEditing = _create_class(pg.TEXTEDITING, "TextEditing", None) +DropFile = _create_class(pg.DROPFILE, "DropFile", None) +DropText = _create_class(pg.DROPTEXT, "DropText", None) +DropBegin = _create_class(pg.DROPBEGIN, "DropBegin", None) +DropComplete = _create_class(pg.DROPCOMPLETE, "DropComplete", None) +ControllerAxisMotion = _create_class( + pg.CONTROLLERAXISMOTION, "ControllerAxisMotion", None +) +ControllerButtonDown = _create_class( + pg.CONTROLLERBUTTONDOWN, "ControllerButtonDown", None +) +ControllerButtonUp = _create_class(pg.CONTROLLERBUTTONUP, "ControllerButtonUp", None) +ControllerDeviceAdded = _create_class( + pg.CONTROLLERDEVICEADDED, "ControllerDeviceAdded", None +) +ControllerDeviceRemoved = _create_class( + pg.CONTROLLERDEVICEREMOVED, "ControllerDeviceRemoved", None +) +ControllerDeviceMapped = _create_class( + pg.CONTROLLERDEVICEREMAPPED, "ControllerDeviceMapped", None +) +JoyDeviceAdded = _create_class(pg.JOYDEVICEADDED, "JoyDeviceAdded", None) +JoyDeviceRemoved = _create_class(pg.JOYDEVICEREMOVED, "JoyDeviceRemoved", None) +ControllerTouchpadDown = _create_class( + pg.CONTROLLERTOUCHPADDOWN, "ControllerTouchpadDown", "Only for SDL 2.0.14+" +) +ControllerTouchpadMotion = _create_class( + pg.CONTROLLERTOUCHPADMOTION, "ControllerTouchpadMotion", "Only for SDL 2.0.14+" +) +ControllerTouchpadUp = _create_class( + pg.CONTROLLERTOUCHPADUP, "ControllerTouchpadUp", "Only for SDL 2.0.14+" +) +ControllerSensorUpdate = _create_class( + pg.CONTROLLERSENSORUPDATE, "ControllerSensorUpdate", "Only for SDL 2.0.14+" +) +AudioDeviceAdded = _create_class(pg.AUDIODEVICEADDED, "AudioDeviceAdded", None) +AudioDeviceRemoved = _create_class(pg.AUDIODEVICEREMOVED, "AudioDeviceRemoved", None) +RenderTargetsReset = _create_class(pg.RENDER_TARGETS_RESET, "RenderTargetsReset", None) +RenderDeviceReset = _create_class(pg.RENDER_DEVICE_RESET, "RenderDeviceReset", None) +WindowShown = _create_class(pg.WINDOWSHOWN, "WindowShown", None) +WindowHidden = _create_class(pg.WINDOWHIDDEN, "WindowHidden", None) +WindowExposed = _create_class(pg.WINDOWEXPOSED, "WindowExposed", None) +WindowMoved = _create_class(pg.WINDOWMOVED, "WindowMoved", None) +WindowResized = _create_class(pg.WINDOWRESIZED, "WindowResized", None) +WindowSizeChanged = _create_class(pg.WINDOWSIZECHANGED, "WindowSizeChanged", None) +WindowMinimized = _create_class(pg.WINDOWMINIMIZED, "WindowMinimized", None) +WindowMaximized = _create_class(pg.WINDOWMAXIMIZED, "WindowMaximized", None) +WindowRestored = _create_class(pg.WINDOWRESTORED, "WindowRestored", None) +WindowEnter = _create_class(pg.WINDOWENTER, "WindowEnter", None) +WindowLeave = _create_class(pg.WINDOWLEAVE, "WindowLeave", None) +WindowFocusGained = _create_class(pg.WINDOWFOCUSGAINED, "WindowFocusGained", None) +WindowFocusLost = _create_class(pg.WINDOWFOCUSLOST, "WindowFocusLost", None) +WindowClose = _create_class(pg.WINDOWCLOSE, "WindowClose", None) +WindowTakeFocus = _create_class(pg.WINDOWTAKEFOCUS, "WindowTakeFocus", None) +WindowHitTest = _create_class(pg.WINDOWHITTEST, "WindowHitTest", None) +WindowICCProfChanged = _create_class( + pg.WINDOWICCPROFCHANGED, "WindowICCProfChanged", None +) +WindowDisplayChanged = _create_class( + pg.WINDOWDISPLAYCHANGED, "WindowDisplayChanged", None +) + + +class UserEvent(Event): + """ + User defined event. To create your own, subclass Event instead. + You can test if an event is UserEvent by calling: + isinstance(event, pygame.event.UserEvent) + """ + + type: int = pg.USEREVENT + + def __instancecheck__(self, instance: Any, /) -> bool: + return ( + isinstance(instance, Event) and pg.USEREVENT <= instance.type < pg.NUMEVENTS + ) + + +_extra_cache[pg.USEREVENT] = UserEvent + + +__all__ = [ + "Event", + "EventType", + "event_class", + "pump", + "get", + "poll", + "wait", + "peek", + "clear", + "event_name", + "set_blocked", + "set_allowed", + "get_blocked", + "set_grab", + "get_grab", + "post", + "custom_type", + "init", + "quit", + "ActiveEvent", + "AppTerminating", + "AppLowMemory", + "AppWillEnterBackground", + "AppDidEnterBackground", + "AppWillEnterForeground", + "AppDidEnterForeground", + "ClipboardUpdate", + "KeyDown", + "KeyUp", + "KeyMapChanged", + "LocaleChanged", + "MouseMotion", + "MouseButtonDown", + "MouseButtonUp", + "JoyAxisMotion", + "JoyBallMotion", + "JoyHatMotion", + "JoyButtonUp", + "JoyButtonDown", + "Quit", + "SysWMEvent", + "VideoResize", + "VideoExpose", + "MidiIn", + "MidiOut", + "NoEvent", + "FingerMotion", + "FingerDown", + "FingerUp", + "MultiGesture", + "MouseWheel", + "TextInput", + "TextEditing", + "DropFile", + "DropText", + "DropBegin", + "DropComplete", + "ControllerAxisMotion", + "ControllerButtonDown", + "ControllerButtonUp", + "ControllerDeviceAdded", + "ControllerDeviceRemoved", + "ControllerDeviceMapped", + "JoyDeviceAdded", + "JoyDeviceRemoved", + "ControllerTouchpadDown", + "ControllerTouchpadMotion", + "ControllerTouchpadUp", + "ControllerSensorUpdate", + "AudioDeviceAdded", + "AudioDeviceRemoved", + "RenderTargetsReset", + "RenderDeviceReset", + "WindowShown", + "WindowHidden", + "WindowExposed", + "WindowMoved", + "WindowResized", + "WindowSizeChanged", + "WindowMinimized", + "WindowMaximized", + "WindowRestored", + "WindowEnter", + "WindowLeave", + "WindowFocusGained", + "WindowFocusLost", + "WindowClose", + "WindowTakeFocus", + "WindowHitTest", + "WindowICCProfChanged", + "WindowDisplayChanged", + "UserEvent", +] 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 2897a338fe..5a2f2703e6 100644 --- a/src_py/typing.py +++ b/src_py/typing.py @@ -15,7 +15,7 @@ from abc import abstractmethod from collections.abc import Callable from os import PathLike as _PathProtocol -from typing import IO, Union, TypeVar, Protocol +from typing import IO, Union, Optional, TypeVar, Protocol, Any, Iterable from pygame.color import Color from pygame.rect import Rect, FRect @@ -31,13 +31,14 @@ 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 @@ -63,6 +64,17 @@ def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... ] +class EventLike(Protocol): + type: int + + def __init__( + self, dict: Optional[dict[str, Any]] = None, **kwargs: Any + ) -> None: ... + + @property + def dict(self) -> dict[str, Any]: ... + + # cleanup namespace del ( abstractmethod, @@ -72,6 +84,8 @@ def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... IO, Callable, Union, + Optional, TypeVar, Protocol, + Any, ) diff --git a/test/event_test.py b/test/event_test.py index 43e4783eee..6d1c71f098 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -118,7 +118,7 @@ def test_Event(self): self.assertIn(attr, d) # redundant type field as kwarg - self.assertRaises(ValueError, pygame.event.Event, 10, type=100) + self.assertRaises(TypeError, pygame.event.Event, 10, type=100) def test_as_str(self): # Bug reported on Pygame mailing list July 24, 2011: @@ -250,13 +250,6 @@ def test_clear(self): self.assertRaises(TypeError, pygame.event.get, ["a", "b", "c"]) def test_peek(self): - pygame.event.peek() - pygame.event.peek(None) - pygame.event.peek(None, True) - - pygame.event.peek(pump=False) - pygame.event.peek(pump=True) - pygame.event.peek(eventtype=None) pygame.event.peek(eventtype=[pygame.KEYUP, pygame.KEYDOWN]) pygame.event.peek(eventtype=pygame.USEREVENT, pump=False) @@ -294,6 +287,20 @@ def test_custom_type(self): self.assertEqual(len(queue), 1) self.assertEqual(queue[0].type, atype) + def test_subclass(self): + class MyEvent(pygame.event.Event): + pass + + self.assertEqual(MyEvent.type, pygame.event.custom_type() - 1) + + event = MyEvent() + self.assertEqual(event.type, MyEvent.type) + self.assertIs(MyEvent, pygame.event.event_class(MyEvent.type)) + + d = {"arg": "val"} + event = MyEvent(d) + self.assertIs(event.dict, d) + def test_custom_type__end_boundary(self): """Ensure custom_type() raises error when no more custom types. @@ -485,6 +492,17 @@ def test_post_same_reference(self): self.assertEqual(e.type, event.type) self.assertIs(e.dict, event.dict) + def test_post_same_object(self): + pygame.event.clear() + + for ev_type in EVENT_TYPES: + event = pygame.event.Event(ev_type, EVENT_TEST_PARAMS[ev_type]) + pygame.event.post(event) + + self.assertIs( + pygame.event.get(ev_type)[0], event, race_condition_notification + ) + def test_post_blocked(self): """ Test blocked events are not posted. Also test whether post() @@ -761,11 +779,6 @@ def test_peek__empty_queue(self): """Ensure peek() works correctly on an empty queue.""" pygame.event.clear() - # Ensure all events can be checked. - peeked = pygame.event.peek() - - self.assertFalse(peeked) - # Ensure events can be checked individually. for event_type in EVENT_TYPES: peeked = pygame.event.peek(event_type) @@ -919,6 +932,25 @@ def test_poll(self): self.assertEqual(pygame.event.poll().type, e3.type) self.assertEqual(pygame.event.poll().type, pygame.NOEVENT) + def test_event_class(self): + for ev_type in EVENT_TYPES: + self.assertEqual(pygame.event.event_class(ev_type).type, ev_type) + + classes = [ + (pygame.KEYDOWN, pygame.event.KeyDown), + (pygame.KEYUP, pygame.event.KeyUp), + (pygame.NOEVENT, pygame.event.NoEvent), + ] + + for ev_id, ev_cls in classes: + self.assertIs(ev_cls, pygame.event.event_class(ev_id)) + self.assertEqual(ev_cls.type, ev_id) + + for ev_id in EVENT_TYPES: + self.assertFalse( + getattr(pygame.event.event_class(ev_id), "_unknown", False) + ) + class EventModuleTestsWithTiming(unittest.TestCase): __tags__ = ["timing"] diff --git a/test/midi_test.py b/test/midi_test.py index f4189a2351..2b7bf8c20e 100644 --- a/test/midi_test.py +++ b/test/midi_test.py @@ -398,10 +398,7 @@ def test_midis2events(self): midi_event = midi_events[i] midi_event_data = midi_event[MIDI_DATA] - # Can't directly check event instance as pygame.event.Event is - # a function. - # self.assertIsInstance(pg_event, pygame.event.Event) - self.assertEqual(pg_event.__class__.__name__, "Event") + self.assertIsInstance(pg_event, pygame.event.Event) self.assertEqual(pg_event.type, pygame.MIDIIN) self.assertEqual(pg_event.status, midi_event_data[MD_STATUS]) self.assertEqual(pg_event.data1, midi_event_data[MD_DATA1]) diff --git a/test/time_test.py b/test/time_test.py index e58bff78f6..d996186e17 100644 --- a/test/time_test.py +++ b/test/time_test.py @@ -285,7 +285,7 @@ def test_multiple_timers(self): set_time, set_dict, repeat, stop = events_tests_dict[event.type] # measure time for each event, should almost match expected time self.assertAlmostEqual( - (now_time - times[event.type]) * 1000, set_time, delta=4 + (now_time - times[event.type]) * 1000, set_time, delta=6 ) # the dict attribute should be the same reference, if it exists