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..6bd917951e --- /dev/null +++ b/buildconfig/stubs/pygame/_event.pyi @@ -0,0 +1,29 @@ +from typing import ( + Any, + List, + Optional, + Union, + Type, +) + +from .typing import SequenceLike, EventLike + +_EventTypes = Union[int, SequenceLike[int]] + +def pump() -> None: ... +def get( + eventtype: Optional[_EventTypes] = None, + pump: Any = True, + exclude: Optional[_EventTypes] = None, +) -> List[EventLike]: ... +def poll() -> EventLike: ... +def wait(timeout: int = 0) -> EventLike: ... +def peek(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> bool: ... +def clear(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> None: ... +def set_blocked(type: Optional[_EventTypes], /) -> None: ... +def set_allowed(type: Optional[_EventTypes], /) -> None: ... +def get_blocked(type: _EventTypes, /) -> bool: ... +def set_grab(grab: bool, /) -> None: ... +def get_grab() -> bool: ... +def post(event: EventLike, /) -> bool: ... +def register_event_class(cls: Type[EventLike]): ... diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index 1987f96de9..3d9d2214a7 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -4,24 +4,12 @@ from typing import ( List, Optional, Union, - final, ) -from pygame.typing import SequenceLike +from pygame.typing import SequenceLike, EventLike -@final -class Event: - type: int - dict: Dict[str, Any] - __dict__: Dict[str, Any] - __hash__: None # type: ignore - def __init__( - self, type: int, dict: Dict[str, Any] = ..., **kwargs: Any - ) -> None: ... - def __getattribute__(self, name: str) -> Any: ... - def __setattr__(self, name: str, value: Any) -> None: ... - def __delattr__(self, name: str) -> None: ... - def __bool__(self) -> bool: ... +class Event(EventLike): + ... _EventTypes = Union[int, SequenceLike[int]] @@ -35,7 +23,7 @@ def poll() -> Event: ... def wait(timeout: int = 0) -> Event: ... def peek(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> bool: ... def clear(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> None: ... -def event_name(type: int, /) -> str: ... +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: ... @@ -43,5 +31,7 @@ def set_grab(grab: bool, /) -> None: ... def get_grab() -> bool: ... def post(event: Event, /) -> bool: ... def custom_type() -> int: ... +def init(): ... +def quit(): ... EventType = Event diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi index ca88863a59..0c084b3276 100644 --- a/buildconfig/stubs/pygame/typing.pyi +++ b/buildconfig/stubs/pygame/typing.pyi @@ -14,7 +14,7 @@ __all__ = [ import sys from abc import abstractmethod -from typing import IO, Callable, Tuple, Union, TypeVar, Protocol +from typing import IO, Any, Callable, Dict, Optional, Tuple, Union, TypeVar, Protocol from pygame.color import Color from pygame.rect import Rect, FRect @@ -72,6 +72,25 @@ RectLike = Union[ ] +class EventLike(Protocol): + # __dict__: Dict[str, Any] + def __init__( + self, type: int, dict: Optional[Dict[str, Any]] = None, **kwargs: Any + ) -> None: ... + def __new__(cls, *args, **kwargs) -> "EventLike": ... + def __getattribute__(self, name: str) -> Any: ... + def __setattr__(self, name: str, value: Any) -> None: ... + def __delattr__(self, name: str) -> None: ... + def __int__(self) -> int: ... + def __bool__(self) -> bool: ... + def __eq__(self, other) -> bool: ... + + @property + def type(self) -> int: ... + @property + def dict(self) -> Dict[str, Any]: ... + + # cleanup namespace del ( sys, diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index 650dccd25b..a28d3a2537 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -13,25 +13,32 @@ The extension module :py:mod:`pygame.event`. Header file: src_c/include/pygame.h +.. c:type:: pgEventData -.. c:type:: pgEventObject - - The :py:class:`pygame.event.EventType` object C struct. + Struct holding information about the event object. .. c:member:: int type The event type code. -.. c:type:: pgEvent_Type + .. c:member:: PyObject* dict + + Dict object of the event, might be NULL. + +.. c:function:: PyObject* pgEvent_GetType(void) + + Return a python class that is currently set to be the event class - 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 +46,25 @@ 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``. +.. c:function:: PyObject* pgEvent_FromEventData(pgEventData) + + Return an event object constructed from pgEventData struct. + + On error returns NULL and sets python exception. + +.. c:function:: pgEventData pgEvent_GetEventData(PyObject *) + + Return a pgEventData struct containing information about event extracted from the python object. + + Beware: on error this doesn't retun any special sentiel value if error ocurred, only sets python exception, so use ``PyErr_Ocurred()`` for error checking. + Remember to call :c:func:`pgEvent_FreeEventData` after usage to avoid memory leaks. + +.. c:function:: void pgEvent_FreeEventData(pgEventData) + + Free resources held by pgEventData (decrefs dict). + + .. ## pgEvent_FreeEventData ## + .. c:function:: char* pgEvent_GetKeyDownInfo(void) Return an array of bools (using char) of length SDL_NUM_SCANCODES diff --git a/src_c/event.c b/src_c/_event.c similarity index 83% rename from src_c/event.c rename to src_c/_event.c index 59132e3330..0962ba692a 100644 --- a/src_c/event.c +++ b/src_c/_event.c @@ -49,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 @@ -98,6 +93,8 @@ static char released_keys[SDL_NUM_SCANCODES] = {0}; static char pressed_mouse_buttons[5] = {0}; static char released_mouse_buttons[5] = {0}; +static PyObject *_event_class = NULL; + #ifdef __EMSCRIPTEN__ /* these macros are no-op here */ #define PG_LOCK_EVFILTER_MUTEX @@ -638,11 +635,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; @@ -730,164 +722,6 @@ pg_post_event(Uint32 type, PyObject *dict) return ret; } -static char * -_pg_name_from_eventtype(int type) -{ - switch (type) { - case SDL_ACTIVEEVENT: - return "ActiveEvent"; - case SDL_APP_TERMINATING: - return "AppTerminating"; - case SDL_APP_LOWMEMORY: - return "AppLowMemory"; - case SDL_APP_WILLENTERBACKGROUND: - return "AppWillEnterBackground"; - case SDL_APP_DIDENTERBACKGROUND: - return "AppDidEnterBackground"; - case SDL_APP_WILLENTERFOREGROUND: - return "AppWillEnterForeground"; - case SDL_APP_DIDENTERFOREGROUND: - return "AppDidEnterForeground"; - case SDL_CLIPBOARDUPDATE: - return "ClipboardUpdate"; - case SDL_KEYDOWN: - return "KeyDown"; - case SDL_KEYUP: - return "KeyUp"; - case SDL_KEYMAPCHANGED: - return "KeyMapChanged"; -#if SDL_VERSION_ATLEAST(2, 0, 14) - case SDL_LOCALECHANGED: - return "LocaleChanged"; -#endif - case SDL_MOUSEMOTION: - return "MouseMotion"; - case SDL_MOUSEBUTTONDOWN: - return "MouseButtonDown"; - case SDL_MOUSEBUTTONUP: - return "MouseButtonUp"; - case SDL_JOYAXISMOTION: - return "JoyAxisMotion"; - case SDL_JOYBALLMOTION: - return "JoyBallMotion"; - case SDL_JOYHATMOTION: - return "JoyHatMotion"; - case SDL_JOYBUTTONUP: - return "JoyButtonUp"; - case SDL_JOYBUTTONDOWN: - return "JoyButtonDown"; - case SDL_QUIT: - return "Quit"; - case SDL_SYSWMEVENT: - return "SysWMEvent"; - case SDL_VIDEORESIZE: - return "VideoResize"; - case SDL_VIDEOEXPOSE: - return "VideoExpose"; - case PGE_MIDIIN: - return "MidiIn"; - case PGE_MIDIOUT: - return "MidiOut"; - case SDL_NOEVENT: - return "NoEvent"; - case SDL_FINGERMOTION: - return "FingerMotion"; - case SDL_FINGERDOWN: - return "FingerDown"; - case SDL_FINGERUP: - return "FingerUp"; - case SDL_MULTIGESTURE: - return "MultiGesture"; - case SDL_MOUSEWHEEL: - return "MouseWheel"; - case SDL_TEXTINPUT: - return "TextInput"; - case SDL_TEXTEDITING: - return "TextEditing"; - case SDL_DROPFILE: - return "DropFile"; - case SDL_DROPTEXT: - return "DropText"; - case SDL_DROPBEGIN: - return "DropBegin"; - case SDL_DROPCOMPLETE: - return "DropComplete"; - case SDL_CONTROLLERAXISMOTION: - return "ControllerAxisMotion"; - case SDL_CONTROLLERBUTTONDOWN: - return "ControllerButtonDown"; - case SDL_CONTROLLERBUTTONUP: - return "ControllerButtonUp"; - case SDL_CONTROLLERDEVICEADDED: - return "ControllerDeviceAdded"; - case SDL_CONTROLLERDEVICEREMOVED: - return "ControllerDeviceRemoved"; - case SDL_CONTROLLERDEVICEREMAPPED: - return "ControllerDeviceMapped"; - case SDL_JOYDEVICEADDED: - return "JoyDeviceAdded"; - case SDL_JOYDEVICEREMOVED: - return "JoyDeviceRemoved"; -#if SDL_VERSION_ATLEAST(2, 0, 14) - case SDL_CONTROLLERTOUCHPADDOWN: - return "ControllerTouchpadDown"; - case SDL_CONTROLLERTOUCHPADMOTION: - return "ControllerTouchpadMotion"; - case SDL_CONTROLLERTOUCHPADUP: - return "ControllerTouchpadUp"; - case SDL_CONTROLLERSENSORUPDATE: - return "ControllerSensorUpdate"; -#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ - case SDL_AUDIODEVICEADDED: - return "AudioDeviceAdded"; - case SDL_AUDIODEVICEREMOVED: - return "AudioDeviceRemoved"; - case SDL_RENDER_TARGETS_RESET: - return "RenderTargetsReset"; - case SDL_RENDER_DEVICE_RESET: - return "RenderDeviceReset"; - case PGE_WINDOWSHOWN: - return "WindowShown"; - case PGE_WINDOWHIDDEN: - return "WindowHidden"; - case PGE_WINDOWEXPOSED: - return "WindowExposed"; - case PGE_WINDOWMOVED: - return "WindowMoved"; - case PGE_WINDOWRESIZED: - return "WindowResized"; - case PGE_WINDOWSIZECHANGED: - return "WindowSizeChanged"; - case PGE_WINDOWMINIMIZED: - return "WindowMinimized"; - case PGE_WINDOWMAXIMIZED: - return "WindowMaximized"; - case PGE_WINDOWRESTORED: - return "WindowRestored"; - case PGE_WINDOWENTER: - return "WindowEnter"; - case PGE_WINDOWLEAVE: - return "WindowLeave"; - case PGE_WINDOWFOCUSGAINED: - return "WindowFocusGained"; - case PGE_WINDOWFOCUSLOST: - return "WindowFocusLost"; - case PGE_WINDOWCLOSE: - return "WindowClose"; - case PGE_WINDOWTAKEFOCUS: - return "WindowTakeFocus"; - case PGE_WINDOWHITTEST: - return "WindowHitTest"; - case PGE_WINDOWICCPROFCHANGED: - return "WindowICCProfChanged"; - case PGE_WINDOWDISPLAYCHANGED: - return "WindowDisplayChanged"; - } - if (type >= PGE_USEREVENT && type < PG_NUMEVENTS) - return "UserEvent"; - return "Unknown"; -} - /* Helper for adding objects to dictionaries. Check for errors with PyErr_Occurred() */ static void @@ -1339,232 +1173,114 @@ 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) +static PyObject * +pgEvent_FromEventData(pgEventData e_data) { - pgEventObject *e = (pgEventObject *)self; - return PyUnicode_FromFormat("", e->type, - _pg_name_from_eventtype(e->type), e->dict); -} + PyObject *ret = NULL; + PyObject *args = NULL; -static int -_pg_event_nonzero(pgEventObject *self) -{ - return self->type != SDL_NOEVENT; -} + PyObject *e_type = pgEvent_GetType(); + if (!e_type) + return NULL; -static PyNumberMethods pg_event_as_number = { - .nb_bool = (inquiry)_pg_event_nonzero, -}; + PyObject *num = PyLong_FromLong(e_data.type); + if (!num) + goto finalize; -static PyTypeObject pgEvent_Type; -#define pgEvent_Check(x) ((x)->ob_type == &pgEvent_Type) -#define OFF(x) offsetof(pgEventObject, x) + if (e_data.dict) + args = PyTuple_New(2); + else + args = PyTuple_New(1); -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 */ -}; + if (!args) { + Py_DECREF(num); + goto finalize; + } -/* - * eventA == eventB - * eventA != eventB - */ -static PyObject * -pg_event_richcompare(PyObject *o1, PyObject *o2, int opid) -{ - pgEventObject *e1, *e2; - - if (!pgEvent_Check(o1) || !pgEvent_Check(o2)) { - goto Unimplemented; - } - - 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); + if (e_data.dict) { + Py_INCREF(e_data.dict); + PyTuple_SetItem(args, 1, e_data.dict); } -Unimplemented: - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + ret = PyObject_Call(e_type, args, NULL); + +finalize: + Py_DECREF(e_type); + Py_XDECREF(args); + return ret; } -static int -pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) +static pgEventData +pgEvent_GetEventData(PyObject *event) { - int type; - PyObject *dict = NULL; + pgEventData data = {0}; + data.dict = PyObject_GetAttrString(event, "dict"); - if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { - return -1; - } + if (PyErr_Occurred()) + PyErr_Clear(); - if (type < 0 || type >= PG_NUMEVENTS) { - PyErr_SetString(PyExc_ValueError, "event type out of range"); - return -1; + PyObject *e_typeo = PyObject_GetAttrString(event, "type"); + if (!e_typeo) { + Py_XDECREF(data.dict); + data.dict = NULL; + goto finalize; } - 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 (PyDict_GetItemString(dict, "type")) { - PyErr_SetString(PyExc_ValueError, - "redundant type field in event dict"); - Py_DECREF(dict); - return -1; - } + data.type = PyLong_AsLong(e_typeo); - self->type = _pg_pgevent_deproxify(type); - self->dict = dict; - return 0; +finalize: + return data; } -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 void +pgEvent_FreeEventData(pgEventData e_data) +{ + Py_XDECREF(e_data.dict); + e_data.dict = NULL; +} static PyObject * pgEvent_New(SDL_Event *event) { - pgEventObject *e; - e = PyObject_New(pgEventObject, &pgEvent_Type); - if (!e) - return PyErr_NoMemory(); + pgEventData e = {0}; if (event) { - e->type = _pg_pgevent_deproxify(event->type); - e->dict = dict_from_event(event); + e.type = _pg_pgevent_deproxify(event->type); + e.dict = dict_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(); - } - return (PyObject *)e; -} -/* event module functions */ + PyObject *ret = pgEvent_FromEventData(e); + pgEvent_FreeEventData(e); + return ret; +} -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) { @@ -2118,11 +1834,21 @@ 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)) { + pgEventData e = pgEvent_GetEventData(obj); + + if (PyErr_Occurred()) + return NULL; + + int res = pg_post_event(e.type, e.dict); + pgEvent_FreeEventData(e); + + switch (res) { case 0: Py_RETURN_FALSE; case 1: @@ -2231,13 +1957,22 @@ pg_event_get_blocked(PyObject *self, PyObject *obj) } static PyObject * -pg_event_custom_type(PyObject *self, PyObject *_null) +pg_event_register_event_class(PyObject *self, PyObject *obj) { - if (_custom_event < PG_NUMEVENTS) - return PyLong_FromLong(_custom_event++); - else - return RAISE(pgExc_SDLError, - "pygame.event.custom_type made too many event types."); + if (!(PyType_Check(obj) && PyCallable_Check(obj))) + return RAISE(PyExc_ValueError, "expected a type"); + + Py_INCREF(obj); + Py_XDECREF(_event_class); + _event_class = obj; + Py_RETURN_NONE; +} + +void +pg_event_free(PyObject *self) +{ + Py_XDECREF(_event_class); + _event_class = NULL; } static PyMethodDef _event_methods[] = { @@ -2246,8 +1981,6 @@ 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}, @@ -2269,25 +2002,20 @@ static PyMethodDef _event_methods[] = { 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}, + {"register_event_class", (PyCFunction)pg_event_register_event_class, + METH_O}, {NULL, NULL, 0, NULL}}; -MODINIT_DEFINE(event) +MODINIT_DEFINE(_event) { PyObject *module, *apiobj; static void *c_api[PYGAMEAPI_EVENT_NUMSLOTS]; - static struct PyModuleDef _module = {PyModuleDef_HEAD_INIT, - "event", - DOC_EVENT, - -1, - _event_methods, - NULL, - NULL, - NULL, - NULL}; + static struct PyModuleDef _module = { + PyModuleDef_HEAD_INIT, "event", DOC_EVENT, -1, + _event_methods, NULL, NULL, NULL, + (freefunc)pg_event_free}; /* imported needed apis; Do this first so if there is an error the module is not loaded. @@ -2302,33 +2030,15 @@ 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 == 14); + c_api[0] = pgEvent_GetType; c_api[1] = pgEvent_New; c_api[2] = pg_post_event; c_api[3] = pg_post_event_dictproxy; @@ -2338,8 +2048,12 @@ MODINIT_DEFINE(event) c_api[7] = pgEvent_GetKeyUpInfo; c_api[8] = pgEvent_GetMouseButtonDownInfo; c_api[9] = pgEvent_GetMouseButtonUpInfo; + c_api[10] = pgEvent_Check; + c_api[11] = pgEvent_FromEventData; + c_api[12] = pgEvent_GetEventData; + c_api[13] = pgEvent_FreeEventData; - apiobj = encapsulate_api(c_api, "event"); + apiobj = encapsulate_api(c_api, "_event"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { Py_XDECREF(apiobj); Py_DECREF(module); diff --git a/src_c/_pygame.h b/src_c/_pygame.h index 0cbfed872f..7f0092bbc5 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -486,8 +486,8 @@ typedef enum { /* * event module internals */ -struct pgEventObject { - PyObject_HEAD int type; +struct pgEventData { + int type; PyObject *dict; }; @@ -544,7 +544,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 14 #define PYGAMEAPI_WINDOW_NUMSLOTS 1 #define PYGAMEAPI_GEOMETRY_NUMSLOTS 2 diff --git a/src_c/base.c b/src_c/base.c index 573bca3aa2..d8e5887d92 100644 --- a/src_c/base.c +++ b/src_c/base.c @@ -305,6 +305,10 @@ pg_mod_autoquit(const char *modname) funcobj = PyObject_GetAttrString(module, "_internal_mod_quit"); + /* Silence errors */ + if (PyErr_Occurred()) + PyErr_Clear(); + /* If we could not load _internal_mod_quit, load quit function */ if (!funcobj) funcobj = PyObject_GetAttrString(module, "quit"); diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index bcfb0fdc8c..d60d93a403 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -378,37 +378,48 @@ typedef struct { /* * EVENT module */ -typedef struct pgEventObject pgEventObject; +typedef struct pgEventData pgEventData; #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 (*)(Uint32, PyObject *))PYGAMEAPI_GET_SLOT(_event, 2)) #define pg_post_event_dictproxy \ - (*(int (*)(Uint32, pgEventDictProxy *))PYGAMEAPI_GET_SLOT(event, 3)) + (*(int (*)(Uint32, pgEventDictProxy *))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_FromEventData \ + (*(PyObject * (*)(pgEventData)) PYGAMEAPI_GET_SLOT(_event, 11)) + +#define pgEvent_GetEventData \ + (*(pgEventData(*)(PyObject *))PYGAMEAPI_GET_SLOT(_event, 12)) + +#define pgEvent_FreeEventData \ + (*(void (*)(pgEventData))PYGAMEAPI_GET_SLOT(_event, 13)) -#define import_pygame_event() IMPORT_PYGAME_MODULE(event) +#define import_pygame_event() IMPORT_PYGAME_MODULE(_event) #endif /* @@ -529,7 +540,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 +554,7 @@ PYGAMEAPI_EXTERN_SLOTS(joystick); PYGAMEAPI_EXTERN_SLOTS(display); PYGAMEAPI_EXTERN_SLOTS(surface); PYGAMEAPI_EXTERN_SLOTS(surflock); -PYGAMEAPI_EXTERN_SLOTS(event); +PYGAMEAPI_EXTERN_SLOTS(_event); PYGAMEAPI_EXTERN_SLOTS(rwobject); PYGAMEAPI_EXTERN_SLOTS(pixelarray); PYGAMEAPI_EXTERN_SLOTS(color); diff --git a/src_c/meson.build b/src_c/meson.build index ca10cca737..973cd49f55 100644 --- a/src_c/meson.build +++ b/src_c/meson.build @@ -47,9 +47,9 @@ endif # TODO: support SDL3 if sdl_api != 3 -event = py.extension_module( - 'event', - 'event.c', +_event = py.extension_module( + '_event', + '_event.c', c_args: warnings_error, dependencies: pg_base_deps, install: true, diff --git a/src_c/static.c b/src_c/static.c index 97229dd633..34050b56fd 100644 --- a/src_c/static.c +++ b/src_c/static.c @@ -117,7 +117,7 @@ PyInit_mouse(void); PyMODINIT_FUNC PyInit_key(void); PyMODINIT_FUNC -PyInit_event(void); +PyInit__event(void); PyMODINIT_FUNC PyInit_joystick(void); @@ -313,7 +313,7 @@ PyInit_pygame_static() load_submodule("pygame", PyInit_mask(), "mask"); load_submodule("pygame", PyInit_mouse(), "mouse"); - load_submodule("pygame", PyInit_event(), "event"); + load_submodule("pygame", PyInit__event(), "_event"); load_submodule("pygame", PyInit_joystick(), "joystick"); load_submodule("pygame", PyInit_pg_mixer(), "mixer"); @@ -396,7 +396,7 @@ PyInit_pygame_static() #include "joystick.c" -#include "event.c" +#include "_event.c" #include "mouse.c" diff --git a/src_c/time.c b/src_c/time.c index 66dc4fc6f3..ff12021378 100644 --- a/src_c/time.c +++ b/src_c/time.c @@ -395,7 +395,7 @@ time_set_timer(PyObject *self, PyObject *args, PyObject *kwargs) int ticks, loops = 0; PyObject *obj, *ev_dict = NULL; int ev_type; - pgEventObject *e; + pgEventData e; pgSetTimerErr ecode = PG_TIMER_NO_ERROR; static char *kwids[] = {"event", "millis", "loops", NULL}; @@ -421,14 +421,24 @@ 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) { + e = pgEvent_GetEventData(obj); + + if (PyErr_Occurred()) + return NULL; + + ev_type = e.type; + ev_dict = e.dict; + + pgEvent_FreeEventData(e); + } + else { + return RAISE( + PyExc_TypeError, + "first argument must be an event type or event object"); + } } #ifndef __EMSCRIPTEN__ diff --git a/src_py/event.py b/src_py/event.py new file mode 100644 index 0000000000..97b031e8c8 --- /dev/null +++ b/src_py/event.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +from pygame._event import * # pylint: disable=wildcard-import,unused-wildcard-import; lgtm[py/polluting-import] +from pygame._event import _internal_mod_init as _init, _internal_mod_quit as _quit +from pygame.constants import USEREVENT, NUMEVENTS +from pygame.base import error +import pygame as pg + + +_is_init = False +_custom_event = USEREVENT + 1 +_NAMES_MAPPING = { + pg.ACTIVEEVENT: "ActiveEvent", + pg.APP_TERMINATING: "AppTerminating", + pg.APP_LOWMEMORY: "AppLowMemory", + pg.APP_WILLENTERBACKGROUND: "AppWillEnterBackground", + pg.APP_DIDENTERBACKGROUND: "AppDidEnterBackground", + pg.APP_WILLENTERFOREGROUND: "AppWillEnterForeground", + pg.APP_DIDENTERFOREGROUND: "AppDidEnterForeground", + pg.CLIPBOARDUPDATE: "ClipboardUpdate", + pg.KEYDOWN: "KeyDown", + pg.KEYUP: "KeyUp", + pg.KEYMAPCHANGED: "KeyMapChanged", + pg.LOCALECHANGED: "LocaleChanged", + pg.MOUSEMOTION: "MouseMotion", + pg.MOUSEBUTTONDOWN: "MouseButtonDown", + pg.MOUSEBUTTONUP: "MouseButtonUp", + pg.JOYAXISMOTION: "JoyAxisMotion", + pg.JOYBALLMOTION: "JoyBallMotion", + pg.JOYHATMOTION: "JoyHatMotion", + pg.JOYBUTTONUP: "JoyButtonUp", + pg.JOYBUTTONDOWN: "JoyButtonDown", + pg.QUIT: "Quit", + pg.SYSWMEVENT: "SysWMEvent", + pg.VIDEORESIZE: "VideoResize", + pg.VIDEOEXPOSE: "VideoExpose", + pg.MIDIIN: "MidiIn", + pg.MIDIOUT: "MidiOut", + pg.NOEVENT: "NoEvent", + pg.FINGERMOTION: "FingerMotion", + pg.FINGERDOWN: "FingerDown", + pg.FINGERUP: "FingerUp", + pg.MULTIGESTURE: "MultiGesture", + pg.MOUSEWHEEL: "MouseWheel", + pg.TEXTINPUT: "TextInput", + pg.TEXTEDITING: "TextEditing", + pg.DROPFILE: "DropFile", + pg.DROPTEXT: "DropText", + pg.DROPBEGIN: "DropBegin", + pg.DROPCOMPLETE: "DropComplete", + pg.CONTROLLERAXISMOTION: "ControllerAxisMotion", + pg.CONTROLLERBUTTONDOWN: "ControllerButtonDown", + pg.CONTROLLERBUTTONUP: "ControllerButtonUp", + pg.CONTROLLERDEVICEADDED: "ControllerDeviceAdded", + pg.CONTROLLERDEVICEREMOVED: "ControllerDeviceRemoved", + pg.CONTROLLERDEVICEREMAPPED: "ControllerDeviceMapped", + pg.JOYDEVICEADDED: "JoyDeviceAdded", + pg.JOYDEVICEREMOVED: "JoyDeviceRemoved", + pg.CONTROLLERTOUCHPADDOWN: "ControllerTouchpadDown", + pg.CONTROLLERTOUCHPADMOTION: "ControllerTouchpadMotion", + pg.CONTROLLERTOUCHPADUP: "ControllerTouchpadUp", + pg.CONTROLLERSENSORUPDATE: "ControllerSensorUpdate", + pg.AUDIODEVICEADDED: "AudioDeviceAdded", + pg.AUDIODEVICEREMOVED: "AudioDeviceRemoved", + pg.RENDER_TARGETS_RESET: "RenderTargetsReset", + pg.RENDER_DEVICE_RESET: "RenderDeviceReset", + pg.WINDOWSHOWN: "WindowShown", + pg.WINDOWHIDDEN: "WindowHidden", + pg.WINDOWEXPOSED: "WindowExposed", + pg.WINDOWMOVED: "WindowMoved", + pg.WINDOWRESIZED: "WindowResized", + pg.WINDOWSIZECHANGED: "WindowSizeChanged", + pg.WINDOWMINIMIZED: "WindowMinimized", + pg.WINDOWMAXIMIZED: "WindowMaximized", + pg.WINDOWRESTORED: "WindowRestored", + pg.WINDOWENTER: "WindowEnter", + pg.WINDOWLEAVE: "WindowLeave", + pg.WINDOWFOCUSGAINED: "WindowFocusGained", + pg.WINDOWFOCUSLOST: "WindowFocusLost", + pg.WINDOWCLOSE: "WindowClose", + pg.WINDOWTAKEFOCUS: "WindowTakeFocus", + pg.WINDOWHITTEST: "WindowHitTest", + pg.WINDOWICCPROFCHANGED: "WindowICCProfChanged", + pg.WINDOWDISPLAYCHANGED: "WindowDisplayChanged", +} + + +def event_name(type: int) -> str: + if type in _NAMES_MAPPING: + return _NAMES_MAPPING[type] + if USEREVENT <= type < NUMEVENTS: + return "UserEvent" + return "Unknown" + + +class Event: + """ + Event(type, dict) -> Event + Event(type, **attributes) -> Event + pygame object for representing events + """ + + type: int + dict: dict[str, ...] + + def __init__(self, type: int, dict: dict[str, ...] | None = None, **kwargs): + if not isinstance(type, int): + raise TypeError("event type must be an integer") + + if not 0 <= type < NUMEVENTS: + raise ValueError("event type out of range") + + dict = dict if dict is not None else {} + dict.update(kwargs) + + if "type" in dict: + raise ValueError("redundant type field in event dict") + + self._type = type + self._dict = dict + + def __new__(cls, *args, **kwargs): + if "type" in kwargs: + raise ValueError("redundant type field in event dict") + return super().__new__(cls) + + def __int__(self): + return self.type + + def __bool__(self): + return self.type != pg.NOEVENT + + def __eq__(self, other: Event): + if not isinstance(other, Event): + return NotImplemented + return self.type == other.type and self.dict == other.dict + + def __repr__(self): + return f" int\nmake custom user event type""" + global _custom_event + + if _custom_event >= NUMEVENTS: + raise error("pygame.event.custom_type made too many event types.") + + _custom_event += 1 + return _custom_event - 1 diff --git a/src_py/meson.build b/src_py/meson.build index 541c54cd69..2978d633b2 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -8,6 +8,7 @@ python_sources = files( 'camera.py', 'colordict.py', 'cursors.py', + 'event.py', 'freetype.py', 'ftfont.py', 'locals.py', diff --git a/src_py/typing.py b/src_py/typing.py index ca88863a59..0c084b3276 100644 --- a/src_py/typing.py +++ b/src_py/typing.py @@ -14,7 +14,7 @@ import sys from abc import abstractmethod -from typing import IO, Callable, Tuple, Union, TypeVar, Protocol +from typing import IO, Any, Callable, Dict, Optional, Tuple, Union, TypeVar, Protocol from pygame.color import Color from pygame.rect import Rect, FRect @@ -72,6 +72,25 @@ def rect(self) -> Union["RectLike", Callable[[], "RectLike"]]: ... ] +class EventLike(Protocol): + # __dict__: Dict[str, Any] + def __init__( + self, type: int, dict: Optional[Dict[str, Any]] = None, **kwargs: Any + ) -> None: ... + def __new__(cls, *args, **kwargs) -> "EventLike": ... + def __getattribute__(self, name: str) -> Any: ... + def __setattr__(self, name: str, value: Any) -> None: ... + def __delattr__(self, name: str) -> None: ... + def __int__(self) -> int: ... + def __bool__(self) -> bool: ... + def __eq__(self, other) -> bool: ... + + @property + def type(self) -> int: ... + @property + def dict(self) -> Dict[str, Any]: ... + + # cleanup namespace del ( sys,