From 620dd13042dda1780e98ae9cde3bbf74aa79dbf2 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Thu, 16 May 2024 00:09:25 +0200 Subject: [PATCH 01/18] First strokes - dynamic class creation. --- meson_options.txt | 6 ++--- src_c/event.c | 56 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/meson_options.txt b/meson_options.txt index c34007090a..fc5228d30f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -24,12 +24,12 @@ option('midi', type: 'feature', value: 'enabled') # Controls whether to make a "stripped" pygame install. Enabling this disables # the bundling of docs/examples/tests/stubs in the wheels. # The default behaviour is to bundle all of these. -option('stripped', type: 'boolean', value: 'false') +option('stripped', type: 'boolean', value: false) # Controls whether to compile with -Werror (or its msvc equivalent). The default # behaviour is to not do this by default -option('error_on_warns', type: 'boolean', value: 'false') +option('error_on_warns', type: 'boolean', value: false) # Controls whether to error on build if generated docs are missing. Defaults to # false. -option('error_docs_missing', type: 'boolean', value: 'false') +option('error_docs_missing', type: 'boolean', value: false) diff --git a/src_c/event.c b/src_c/event.c index 685dff68ca..25b04e1f4e 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1390,8 +1390,16 @@ PyObject * pg_event_str(PyObject *self) { pgEventObject *e = (pgEventObject *)self; - return PyUnicode_FromFormat("", e->type, - _pg_name_from_eventtype(e->type), e->dict); + char* event_name = _pg_name_from_eventtype(e->type); + if (strcmp(event_name, "UserEvent")==0) + { + if (PyObject_IsTrue(e->dict)) + return PyUnicode_FromFormat("%s(type=%d, dict=%S)", event_name, e->type, e->dict); + return PyUnicode_FromFormat("%s(type=%d)", event_name, e->type); + } + if (PyObject_IsTrue(e->dict)) + return PyUnicode_FromFormat("%sEvent(type=%d, dict=%S)", event_name, e->type, e->dict); + return PyUnicode_FromFormat("%sEvent(type=%d)", event_name, e->type); } static int @@ -1498,9 +1506,25 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) return 0; } +static PyObject * +pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) +{ + PyObject* repr = PyUnicode_FromFormat("%S %S %S", cls, args, kwargs); + printf("subclass: %s\n", PyUnicode_AsUTF8(repr)); + Py_DECREF(repr); + Py_RETURN_NONE; +} + +static PyMethodDef eventobj_methods[] = { + {"__init_subclass__", (PyCFunction)(void (*)(void))pg_event_init_subclass, + METH_VARARGS | METH_KEYWORDS | METH_CLASS}, + {NULL, NULL, 0, NULL} +}; + static PyTypeObject pgEvent_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event.Event", .tp_basicsize = sizeof(pgEventObject), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_dealloc = pg_event_dealloc, .tp_repr = pg_event_str, .tp_as_number = &pg_event_as_number, @@ -1517,8 +1541,35 @@ static PyTypeObject pgEvent_Type = { .tp_dictoffset = offsetof(pgEventObject, dict), .tp_init = (initproc)pg_event_init, .tp_new = PyType_GenericNew, + .tp_methods = eventobj_methods, +}; + +static PyType_Slot pg_event_subclass_slots[] = { + {0} }; +static PyObject * +_pgEvent_CreateSubclass(Uint32 ev_type) +{ + PyType_Spec type_spec = {.name=_pg_name_from_eventtype(ev_type), .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; + PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); + + if (bases == NULL) + return NULL; + + PyObject* type = PyType_FromSpecWithBases(&type_spec, bases); + return type; +} + +static PyObject * +pg_event_class(PyObject *self, PyObject *args) +{ + Uint32 e_type; + if (!PyArg_ParseTuple(args, "I", &e_type)) + return NULL; + return _pgEvent_CreateSubclass(e_type); +} + static PyObject * pgEvent_New(SDL_Event *event) { @@ -2247,6 +2298,7 @@ static PyMethodDef _event_methods[] = { DOC_EVENT_GETBLOCKED}, {"custom_type", (PyCFunction)pg_event_custom_type, METH_NOARGS, DOC_EVENT_CUSTOMTYPE}, + {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, {NULL, NULL, 0, NULL}}; From df2cb28b9b446b416e451d2f8c23cfb1cf5a4686 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Thu, 16 May 2024 15:31:43 +0200 Subject: [PATCH 02/18] Type caching. --- src_c/event.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 25b04e1f4e..ac6e36d04f 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -95,6 +95,8 @@ static SDL_Event _pg_last_keydown_event = {0}; /* 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}; +//!NEW +static PyObject *pg_event_lookup; #ifdef __EMSCRIPTEN__ /* these macros are no-op here */ @@ -1390,16 +1392,18 @@ PyObject * pg_event_str(PyObject *self) { pgEventObject *e = (pgEventObject *)self; + //!NEW> char* event_name = _pg_name_from_eventtype(e->type); - if (strcmp(event_name, "UserEvent")==0) + if (strcmp(event_name, "UserEvent")==0 || strcmp(event_name, "Unknown")==0) { if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("%s(type=%d, dict=%S)", event_name, e->type, e->dict); - return PyUnicode_FromFormat("%s(type=%d)", event_name, e->type); + return PyUnicode_FromFormat("Event(type=%d, dict=%S)", e->type, e->dict); + return PyUnicode_FromFormat("Event(type=%d)", e->type); } if (PyObject_IsTrue(e->dict)) return PyUnicode_FromFormat("%sEvent(type=%d, dict=%S)", event_name, e->type, e->dict); return PyUnicode_FromFormat("%sEvent(type=%d)", event_name, e->type); + //!NEW< } static int @@ -1505,7 +1509,7 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) self->dict = dict; return 0; } - +//!NEW> static PyObject * pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) { @@ -1520,10 +1524,12 @@ static PyMethodDef eventobj_methods[] = { METH_VARARGS | METH_KEYWORDS | METH_CLASS}, {NULL, NULL, 0, NULL} }; +//!NEW< static PyTypeObject pgEvent_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event.Event", .tp_basicsize = sizeof(pgEventObject), + //!NEW .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_dealloc = pg_event_dealloc, .tp_repr = pg_event_str, @@ -1541,9 +1547,11 @@ static PyTypeObject pgEvent_Type = { .tp_dictoffset = offsetof(pgEventObject, dict), .tp_init = (initproc)pg_event_init, .tp_new = PyType_GenericNew, + //!NEW .tp_methods = eventobj_methods, }; +//!NEW> static PyType_Slot pg_event_subclass_slots[] = { {0} }; @@ -1551,7 +1559,14 @@ static PyType_Slot pg_event_subclass_slots[] = { static PyObject * _pgEvent_CreateSubclass(Uint32 ev_type) { - PyType_Spec type_spec = {.name=_pg_name_from_eventtype(ev_type), .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; + char* name = _pg_name_from_eventtype(ev_type); + size_t name_s = sizeof name + 13; // 13 - len("pygame.event.") + char* joined = malloc(name_s); + if (!joined) + return PyErr_NoMemory(); + PyOS_snprintf(joined, name_s, "pygame.event.%s", name); + PyType_Spec type_spec = {.name=joined, .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; + // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); if (bases == NULL) @@ -1565,10 +1580,35 @@ static PyObject * pg_event_class(PyObject *self, PyObject *args) { Uint32 e_type; + PyObject *e_typeo; + PyObject *e_class; + if (!PyArg_ParseTuple(args, "I", &e_type)) return NULL; - return _pgEvent_CreateSubclass(e_type); + + if (!(e_typeo=PyLong_FromLong(e_type))) + return NULL; + + int e_exists = PyDict_Contains(pg_event_lookup, e_typeo); + if (e_exists == -1) { + Py_DECREF(e_typeo); + return NULL; + } else if (e_exists) { + e_class = PyDict_GetItem(pg_event_lookup, e_typeo); + Py_DECREF(e_typeo); + } else { + e_class = _pgEvent_CreateSubclass(e_type); + if (!e_class || PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) + { + Py_DECREF(e_typeo); + Py_XDECREF(e_class); + return NULL; + } + } + + return e_class; } +//!NEW< static PyObject * pgEvent_New(SDL_Event *event) @@ -2298,6 +2338,7 @@ static PyMethodDef _event_methods[] = { DOC_EVENT_GETBLOCKED}, {"custom_type", (PyCFunction)pg_event_custom_type, METH_NOARGS, DOC_EVENT_CUSTOMTYPE}, + //!NEW {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, {NULL, NULL, 0, NULL}}; @@ -2372,6 +2413,24 @@ MODINIT_DEFINE(event) return NULL; } + //!NEW> + if (!pg_event_lookup) { + pg_event_lookup = PyDict_New(); + if (!pg_event_lookup) + { + Py_DECREF(module); + return NULL; + } + } + + PyObject *proxy = PyDictProxy_New(pg_event_lookup); + + if (PyModule_AddObject(module, "_event_classes", proxy)) { + Py_DECREF(proxy); + Py_DECREF(module); + } + //!NEW< + SDL_RegisterEvents(PG_NUMEVENTS - SDL_USEREVENT); return module; } From f38f67d14af4021256b9ca17e1fdd509f387960b Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Fri, 17 May 2024 01:02:12 +0200 Subject: [PATCH 03/18] Event subclasses working. --- docs/reST/c_api/event.rst | 4 +- src_c/_pygame.h | 6 +- src_c/event.c | 199 +++++++++++++++++++++++++++++--------- src_c/time.c | 4 +- 4 files changed, 160 insertions(+), 53 deletions(-) diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index 6f115b6cbe..2053a61bc0 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -39,10 +39,10 @@ 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:: 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. + function takes a python dict/event object, which can be NULL too. This function does not need GIL to be held if dict 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 diff --git a/src_c/_pygame.h b/src_c/_pygame.h index f08b212ce2..1361100460 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -196,14 +196,14 @@ PG_SurfaceHasRLE(SDL_Surface *surface); #endif -/* DictProxy is useful for event posting with an arbitrary dict. Maintains +/* DictProxy is useful for event posting with an arbitrary dict/event object. 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 + * wants this dict/event instance 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; + PyObject *obj; // Either dict or event object. SDL_SpinLock lock; int num_on_queue; Uint8 do_free_at_end; diff --git a/src_c/event.c b/src_c/event.c index ac6e36d04f..5386547d7b 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -309,6 +309,12 @@ _pg_get_event_unicode(SDL_Event *event) case PGPOST_##name: \ return proxify ? PGPOST_##name : PGE_##name +static PyTypeObject pgEvent_Type; +//!NEW +#define pgEvent_CheckExact(x) ((x)->ob_type == &pgEvent_Type) +#define pgEvent_Check(x) (PyObject_IsInstance(x, (PyObject *)&pgEvent_Type)) +#define OFF(x) offsetof(pgEventObject, x) + /* The next three functions are used for proxying SDL events to and from * PGPOST_* events. * @@ -690,13 +696,13 @@ pg_post_event_dictproxy(Uint32 type, pgEventDictProxy *dict_proxy) } /* 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 + * python dict/event object. This function does not need GIL to be held if dict is NULL, but * needs GIL otherwise */ static int -pg_post_event(Uint32 type, PyObject *dict) +pg_post_event(Uint32 type, PyObject *obj) { int ret; - if (!dict) { + if (!obj) { return pg_post_event_dictproxy(type, NULL); } @@ -706,8 +712,8 @@ pg_post_event(Uint32 type, PyObject *dict) return SDL_SetError("insufficient memory (internal malloc failed)"); } - Py_INCREF(dict); - dict_proxy->dict = dict; + Py_INCREF(obj); + dict_proxy->obj = obj; /* initially set to 0 - unlocked state */ dict_proxy->lock = 0; dict_proxy->num_on_queue = 0; @@ -716,7 +722,7 @@ pg_post_event(Uint32 type, PyObject *dict) ret = pg_post_event_dictproxy(type, dict_proxy); if (ret != 1) { - Py_DECREF(dict); + Py_DECREF(obj); free(dict_proxy); } return ret; @@ -927,7 +933,7 @@ dict_from_event(SDL_Event *event) /* spinlocks must be held and released as quickly as possible */ SDL_AtomicLock(&dict_proxy->lock); - dict = dict_proxy->dict; + dict = dict_proxy->obj; dict_proxy->num_on_queue--; to_free = dict_proxy->num_on_queue <= 0 && dict_proxy->do_free_at_end; SDL_AtomicUnlock(&dict_proxy->lock); @@ -1340,6 +1346,21 @@ pg_event_dealloc(PyObject *self) Py_TYPE(self)->tp_free(self); } +//!NEW> +PyObject * +_pg_EventGetAttr(PyObject *o, PyObject *attr_name) +{ + const char *attr = PyUnicode_AsUTF8(attr_name); + if (!attr) + return NULL; + + if (strcmp(attr, "type")==0) { + return PyLong_FromLong(((pgEventObject *) o)->type); + } + return PyObject_GenericGetAttr(o, attr_name); +} +//!NEW< + #ifdef PYPY_VERSION /* Because pypy does not work with the __dict__ tp_dictoffset. */ PyObject * @@ -1348,7 +1369,8 @@ 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); + //!NEW + return _pg_EventGetAttr(o, attr_name); } return result; } @@ -1388,24 +1410,6 @@ pg_EventSetAttr(PyObject *o, PyObject *name, PyObject *value) } #endif -PyObject * -pg_event_str(PyObject *self) -{ - pgEventObject *e = (pgEventObject *)self; - //!NEW> - char* event_name = _pg_name_from_eventtype(e->type); - if (strcmp(event_name, "UserEvent")==0 || strcmp(event_name, "Unknown")==0) - { - if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("Event(type=%d, dict=%S)", e->type, e->dict); - return PyUnicode_FromFormat("Event(type=%d)", e->type); - } - if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("%sEvent(type=%d, dict=%S)", event_name, e->type, e->dict); - return PyUnicode_FromFormat("%sEvent(type=%d)", event_name, e->type); - //!NEW< -} - static int _pg_event_nonzero(pgEventObject *self) { @@ -1416,13 +1420,38 @@ 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 * +pg_event_str(PyObject *self) +{ + pgEventObject *e = (pgEventObject *)self; + //!NEW> + if (!pgEvent_CheckExact(self)) { + PyObject *e_type = (PyObject *) Py_TYPE(self); + PyObject *e_name = PyObject_GetAttrString(e_type, "__name__"); + PyObject *e_module = PyObject_GetAttrString(e_type, "__module__"); + + if (PyObject_IsTrue(e->dict)) + return PyUnicode_FromFormat("%S.%S(%S)", e_module, e_name, e->dict); + return PyUnicode_FromFormat("%S.%S()", e_module, e_name); + } + + char* event_name = _pg_name_from_eventtype(e->type);; + + if (strcmp(event_name, "UserEvent")==0 || strcmp(event_name, "Unknown")==0) { + if (PyObject_IsTrue(e->dict)) + return PyUnicode_FromFormat("Event(%d, %S)", e->type, e->dict); + return PyUnicode_FromFormat("Event(%d)", e->type); + } + if (PyObject_IsTrue(e->dict)) + return PyUnicode_FromFormat("%sEvent(%d, %S)", event_name, e->type, e->dict); + return PyUnicode_FromFormat("%sEvent(%d)", event_name, e->type); + //!NEW< +} static PyMemberDef pg_event_members[] = { {"__dict__", T_OBJECT, OFF(dict), READONLY}, - {"type", T_INT, OFF(type), READONLY}, + //!NEW - changed type->__type + {"__type", T_INT, OFF(type), READONLY}, {"dict", T_OBJECT, OFF(dict), READONLY}, {NULL} /* Sentinel */ }; @@ -1465,10 +1494,21 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) { int type; PyObject *dict = NULL; + //!NEW> + PyObject *self_t = (PyObject *)Py_TYPE(self); + if (PyObject_HasAttrString(self_t, "type")) { + PyObject *type_o = PyObject_GetAttrString(self_t, "type"); + if (!type_o) + return -1; + type = PyLong_AsLong(type_o); - if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { + if (!PyArg_ParseTuple(args, "|O!", &PyDict_Type, &dict)) { + return -1; + } + } else if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { return -1; } + //!NEW< if (type < 0 || type >= PG_NUMEVENTS) { PyErr_SetString(PyExc_ValueError, "event type out of range"); @@ -1509,13 +1549,53 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) self->dict = dict; return 0; } + //!NEW> +int +_register_user_event_class(PyObject *e_class) +{ + int e_type = -1; + PyObject *e_typeo = NULL; + + if (_custom_event < PG_NUMEVENTS) { + e_type = _custom_event; + e_typeo = PyLong_FromLong(_custom_event++); + } + else { + RAISE(pgExc_SDLError, + "Exceeded maximimum number of allowed user-defined types."); + return -1; + } + + if (!(e_typeo)) + return -1; + + if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) + { + Py_XDECREF(e_typeo); + return -1; + } + + Py_DECREF(e_typeo); + return e_type; +} + static PyObject * pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) { - PyObject* repr = PyUnicode_FromFormat("%S %S %S", cls, args, kwargs); - printf("subclass: %s\n", PyUnicode_AsUTF8(repr)); - Py_DECREF(repr); + int e_type = _register_user_event_class((PyObject *)cls); + if (e_type < 0) + return NULL; + + PyObject *value = PyLong_FromLong(e_type); + if (!value) + return NULL; + + if (PyObject_SetAttrString((PyObject *)cls, "type", value) < 0) { + Py_DECREF(value); + return NULL; + } + Py_RETURN_NONE; } @@ -1538,7 +1618,8 @@ static PyTypeObject pgEvent_Type = { .tp_getattro = pg_EventGetAttr, .tp_setattro = pg_EventSetAttr, #else - .tp_getattro = PyObject_GenericGetAttr, + //!NEW + .tp_getattro = _pg_EventGetAttr, .tp_setattro = PyObject_GenericSetAttr, #endif .tp_doc = DOC_EVENT_EVENT, @@ -1546,21 +1627,22 @@ static PyTypeObject pgEvent_Type = { .tp_members = pg_event_members, .tp_dictoffset = offsetof(pgEventObject, dict), .tp_init = (initproc)pg_event_init, - .tp_new = PyType_GenericNew, + .tp_new = PyType_GenericNew, // TODO: hook up pg_event_class //!NEW .tp_methods = eventobj_methods, }; //!NEW> static PyType_Slot pg_event_subclass_slots[] = { - {0} + {Py_tp_getattro, _pg_EventGetAttr}, + {0, NULL} }; static PyObject * _pgEvent_CreateSubclass(Uint32 ev_type) { - char* name = _pg_name_from_eventtype(ev_type); - size_t name_s = sizeof name + 13; // 13 - len("pygame.event.") + const char* name = _pg_name_from_eventtype(ev_type); + size_t name_s = sizeof name + 13; // 13 = len("pygame.event.") char* joined = malloc(name_s); if (!joined) return PyErr_NoMemory(); @@ -1569,11 +1651,25 @@ _pgEvent_CreateSubclass(Uint32 ev_type) // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); - if (bases == NULL) + if (bases == NULL) { + free(joined); return NULL; + } - PyObject* type = PyType_FromSpecWithBases(&type_spec, bases); - return type; + PyObject* cls = PyType_FromSpecWithBases(&type_spec, bases); + + PyObject *value = PyLong_FromLong(ev_type); + if (!value) + return NULL; + + if (PyObject_SetAttrString((PyObject *)cls, "type", value) < 0) { + Py_DECREF(value); + return NULL; + } + free(joined); + // I'm convinced this is leaking, but for unknown reason to me, + // freeing "joined" it will corrupt the name of returned class. + return cls; } static PyObject * @@ -1604,12 +1700,13 @@ pg_event_class(PyObject *self, PyObject *args) Py_XDECREF(e_class); return NULL; } + Py_DECREF(e_typeo); } return e_class; } //!NEW< - +// TODO static PyObject * pgEvent_New(SDL_Event *event) { @@ -2188,7 +2285,7 @@ pg_event_post(PyObject *self, PyObject *obj) return RAISE(PyExc_TypeError, "argument must be an Event object"); pgEventObject *e = (pgEventObject *)obj; - switch (pg_post_event(e->type, e->dict)) { + switch (pg_post_event(e->type, e->dict)) { // TODO: change "e->dict" to "obj". case 0: Py_RETURN_FALSE; case 1: @@ -2343,6 +2440,12 @@ static PyMethodDef _event_methods[] = { {NULL, NULL, 0, NULL}}; +//!NEW> +static void _event_free(PyObject* mod) { + Py_XDECREF(pg_event_lookup); +} +//!NEW< + MODINIT_DEFINE(event) { PyObject *module, *apiobj; @@ -2356,7 +2459,8 @@ MODINIT_DEFINE(event) NULL, NULL, NULL, - NULL}; + //!NEW + (freefunc)_event_free}; /* imported needed apis; Do this first so if there is an error the module is not loaded. @@ -2414,7 +2518,9 @@ MODINIT_DEFINE(event) } //!NEW> - if (!pg_event_lookup) { + if (pg_event_lookup) + Py_INCREF(pg_event_lookup); + else { pg_event_lookup = PyDict_New(); if (!pg_event_lookup) { @@ -2428,6 +2534,7 @@ MODINIT_DEFINE(event) if (PyModule_AddObject(module, "_event_classes", proxy)) { Py_DECREF(proxy); Py_DECREF(module); + Py_DECREF(pg_event_lookup); } //!NEW< diff --git a/src_c/time.c b/src_c/time.c index 383782d9b3..8eae0e3cac 100644 --- a/src_c/time.c +++ b/src_c/time.c @@ -154,7 +154,7 @@ _pg_timer_free(pgEventTimer *timer) if (is_fully_freed) { PyGILState_STATE gstate = PyGILState_Ensure(); - Py_DECREF(timer->dict_proxy->dict); + Py_DECREF(timer->dict_proxy->obj); PyGILState_Release(gstate); free(timer->dict_proxy); } @@ -213,7 +213,7 @@ _pg_add_event_timer(int ev_type, PyObject *ev_dict, int repeat) PyGILState_STATE gstate = PyGILState_Ensure(); Py_INCREF(ev_dict); PyGILState_Release(gstate); - new->dict_proxy->dict = ev_dict; + new->dict_proxy->obj = ev_dict; new->dict_proxy->lock = 0; new->dict_proxy->num_on_queue = 0; new->dict_proxy->do_free_at_end = 0; From ff504cfc63394f0420b7ec0bb6c32a4103eb3dd9 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Fri, 17 May 2024 15:59:54 +0200 Subject: [PATCH 04/18] Save event objects on the queue. --- src_c/event.c | 248 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 164 insertions(+), 84 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 5386547d7b..904f93d156 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -313,6 +313,8 @@ static PyTypeObject pgEvent_Type; //!NEW #define pgEvent_CheckExact(x) ((x)->ob_type == &pgEvent_Type) #define pgEvent_Check(x) (PyObject_IsInstance(x, (PyObject *)&pgEvent_Type)) +//!NEW +#define pgEvent_IsSubclass(x) (PyObject_IsSubclass(x, (PyObject *)&pgEvent_Type) * (x != (PyObject *)&pgEvent_Type)) #define OFF(x) offsetof(pgEventObject, x) /* The next three functions are used for proxying SDL events to and from @@ -915,7 +917,7 @@ get_joy_device_index(int instance_id) } static PyObject * -dict_from_event(SDL_Event *event) +obj_from_event(SDL_Event *event) { PyObject *dict = NULL, *tuple, *obj; int hx, hy; @@ -1500,7 +1502,12 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) PyObject *type_o = PyObject_GetAttrString(self_t, "type"); if (!type_o) return -1; + type = PyLong_AsLong(type_o); + Py_DECREF(type_o); + + if (PyErr_Occurred()) + return -1; if (!PyArg_ParseTuple(args, "|O!", &PyDict_Type, &dict)) { return -1; @@ -1551,7 +1558,80 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) } //!NEW> -int +static PyType_Slot pg_event_subclass_slots[] = { + {Py_tp_getattro, _pg_EventGetAttr}, + {0, NULL} +}; + +static PyObject * +_pgEvent_CreateSubclass(Uint32 ev_type) +{ + const char* name = _pg_name_from_eventtype(ev_type); + if (strcmp(name, "Unknown") == 0 || strcmp(name, "UserEvent") == 0) { + return (PyObject *)&pgEvent_Type; + } + size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") + char* joined = malloc(name_s); + if (!joined) + return PyErr_NoMemory(); + + PyOS_snprintf(joined, name_s, "pygame.event.%s", name); + PyType_Spec type_spec = {.name=joined, .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; + // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. + PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); + + if (bases == NULL) { + free(joined); + return NULL; + } + + PyObject* cls = PyType_FromSpecWithBases(&type_spec, bases); + + // I'm convinced this is leaking, but for unknown reason to me, + // freeing "joined" it will corrupt the name of returned class. + free(joined); + + PyObject *value = PyLong_FromLong(ev_type); + if (!value) { + return NULL; + } + + if (PyObject_SetAttrString((PyObject *)cls, "type", value) < 0) { + Py_DECREF(value); + return NULL; + } + return cls; +} + +static PyObject * +pgEvent_GetClass(Uint32 e_type) +{ + PyObject *e_typeo, *e_class; + + if (!(e_typeo=PyLong_FromLong(e_type))) + return NULL; + + int e_exists = PyDict_Contains(pg_event_lookup, e_typeo); + if (e_exists < 0) { + Py_DECREF(e_typeo); + return NULL; + } else if (e_exists) { + e_class = PyDict_GetItem(pg_event_lookup, e_typeo); + Py_DECREF(e_typeo); + } else { + e_class = _pgEvent_CreateSubclass(e_type); + if (!e_class || PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) + { + Py_DECREF(e_typeo); + Py_XDECREF(e_class); + return NULL; + } + Py_DECREF(e_typeo); + } + return e_class; +} + +static int _register_user_event_class(PyObject *e_class) { int e_type = -1; @@ -1633,102 +1713,66 @@ static PyTypeObject pgEvent_Type = { }; //!NEW> -static PyType_Slot pg_event_subclass_slots[] = { - {Py_tp_getattro, _pg_EventGetAttr}, - {0, NULL} -}; - -static PyObject * -_pgEvent_CreateSubclass(Uint32 ev_type) -{ - const char* name = _pg_name_from_eventtype(ev_type); - size_t name_s = sizeof name + 13; // 13 = len("pygame.event.") - char* joined = malloc(name_s); - if (!joined) - return PyErr_NoMemory(); - PyOS_snprintf(joined, name_s, "pygame.event.%s", name); - PyType_Spec type_spec = {.name=joined, .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; - // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. - PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); - - if (bases == NULL) { - free(joined); - return NULL; - } - - PyObject* cls = PyType_FromSpecWithBases(&type_spec, bases); - - PyObject *value = PyLong_FromLong(ev_type); - if (!value) - return NULL; - - if (PyObject_SetAttrString((PyObject *)cls, "type", value) < 0) { - Py_DECREF(value); - return NULL; - } - free(joined); - // I'm convinced this is leaking, but for unknown reason to me, - // freeing "joined" it will corrupt the name of returned class. - return cls; -} - static PyObject * pg_event_class(PyObject *self, PyObject *args) { Uint32 e_type; - PyObject *e_typeo; - PyObject *e_class; if (!PyArg_ParseTuple(args, "I", &e_type)) return NULL; - if (!(e_typeo=PyLong_FromLong(e_type))) - return NULL; - - int e_exists = PyDict_Contains(pg_event_lookup, e_typeo); - if (e_exists == -1) { - Py_DECREF(e_typeo); - return NULL; - } else if (e_exists) { - e_class = PyDict_GetItem(pg_event_lookup, e_typeo); - Py_DECREF(e_typeo); - } else { - e_class = _pgEvent_CreateSubclass(e_type); - if (!e_class || PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) - { - Py_DECREF(e_typeo); - Py_XDECREF(e_class); - return NULL; - } - Py_DECREF(e_typeo); - } - - return e_class; + return pgEvent_GetClass(e_type); } -//!NEW< -// TODO + static PyObject * pgEvent_New(SDL_Event *event) { pgEventObject *e; - e = PyObject_New(pgEventObject, &pgEvent_Type); - if (!e) - return PyErr_NoMemory(); + PyObject *e_obj, *e_typeo, *obj; + Uint32 e_type; if (event) { - e->type = _pg_pgevent_deproxify(event->type); - e->dict = dict_from_event(event); + e_type = _pg_pgevent_deproxify(event->type); + obj = obj_from_event(event); } else { - e->type = SDL_NOEVENT; - e->dict = PyDict_New(); + e_type = SDL_NOEVENT; + obj = PyDict_New(); } - if (!e->dict) { - Py_TYPE(e)->tp_free(e); + if (!obj) { + Py_XDECREF(obj); return PyErr_NoMemory(); } - return (PyObject *)e; + if (pgEvent_Check(obj)) + return obj; + + e_typeo = pgEvent_GetClass(e_type); + + if (!e_typeo) { + Py_DECREF(obj); + return NULL; + } + + if (PyObject_HasAttrString(e_typeo, "type")) { + // PyTuple_New(0) returns an immortal object and should always succeed. + e_obj = PyObject_Call(e_typeo, PyTuple_New(0), obj); + Py_DECREF(e_typeo); + Py_DECREF(obj); + if (!e_obj) + return NULL; + return e_obj; + } else { + // Plain event object - for unknown classes. + Py_DECREF(e_typeo); + e = PyObject_New(pgEventObject, &pgEvent_Type); + if (!e) + return PyErr_NoMemory(); + e->type = e_type; + e->dict = obj; + return (PyObject *)e; + } } +//!NEW< /* event module functions */ @@ -2277,15 +2321,11 @@ pg_event_peek(PyObject *self, PyObject *args, PyObject *kwargs) * through our event filter, to do emulation stuff correctly. Then the * event is filtered after that */ +//!NEW> static PyObject * -pg_event_post(PyObject *self, PyObject *obj) +_post_event(Uint32 e_type, PyObject *obj) { - VIDEO_INIT_CHECK(); - if (!pgEvent_Check(obj)) - return RAISE(PyExc_TypeError, "argument must be an Event object"); - - pgEventObject *e = (pgEventObject *)obj; - switch (pg_post_event(e->type, e->dict)) { // TODO: change "e->dict" to "obj". + switch (pg_post_event(e_type, obj)) { case 0: Py_RETURN_FALSE; case 1: @@ -2294,6 +2334,46 @@ pg_event_post(PyObject *self, PyObject *obj) return RAISE(pgExc_SDLError, SDL_GetError()); } } +//!NEW< + +static PyObject * +pg_event_post(PyObject *self, PyObject *obj) +{ + VIDEO_INIT_CHECK(); + //!NEW> + switch (pgEvent_IsSubclass(obj)) { + case -1: PyErr_Clear(); break; + case 1: { + Uint32 e_type; + PyObject *e_typeo = PyObject_GetAttrString(obj, "type"); + PyObject *e_dict, *ret; + + if (!e_typeo) + return NULL; + + e_type = PyLong_AsLong(e_typeo); + Py_DECREF(e_typeo); + + if (PyErr_Occurred()) + return NULL; + + e_dict = PyDict_New(); + if (!e_dict) + return NULL; + + ret = _post_event(e_type, e_dict); + Py_DECREF(e_dict); + return ret; + } + default: break; + } + //!NEW< + if (!pgEvent_Check(obj)) + return RAISE(PyExc_TypeError, "argument must be an Event object"); + + pgEventObject *e = (pgEventObject *)obj; + return _post_event(e->type, obj); +} static PyObject * pg_event_set_allowed(PyObject *self, PyObject *obj) From 5592186e41cb54af7389e436bbf0c3a61f6de9ff Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Fri, 17 May 2024 16:02:19 +0200 Subject: [PATCH 05/18] Scrap posting events with class (if requested, I can revert this). --- src_c/event.c | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 904f93d156..08c1b5a126 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -2340,34 +2340,6 @@ static PyObject * pg_event_post(PyObject *self, PyObject *obj) { VIDEO_INIT_CHECK(); - //!NEW> - switch (pgEvent_IsSubclass(obj)) { - case -1: PyErr_Clear(); break; - case 1: { - Uint32 e_type; - PyObject *e_typeo = PyObject_GetAttrString(obj, "type"); - PyObject *e_dict, *ret; - - if (!e_typeo) - return NULL; - - e_type = PyLong_AsLong(e_typeo); - Py_DECREF(e_typeo); - - if (PyErr_Occurred()) - return NULL; - - e_dict = PyDict_New(); - if (!e_dict) - return NULL; - - ret = _post_event(e_type, e_dict); - Py_DECREF(e_dict); - return ret; - } - default: break; - } - //!NEW< if (!pgEvent_Check(obj)) return RAISE(PyExc_TypeError, "argument must be an Event object"); From 16b3c622921ba4c2b97355e0f5259205dfed5ac4 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Fri, 17 May 2024 23:27:29 +0200 Subject: [PATCH 06/18] Backward-compatible event creation API. --- src_c/event.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 08c1b5a126..354e35a436 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1445,8 +1445,8 @@ pg_event_str(PyObject *self) return PyUnicode_FromFormat("Event(%d)", e->type); } if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("%sEvent(%d, %S)", event_name, e->type, e->dict); - return PyUnicode_FromFormat("%sEvent(%d)", event_name, e->type); + return PyUnicode_FromFormat("%s(%d, %S)", event_name, e->type, e->dict); + return PyUnicode_FromFormat("%s(%d)", event_name, e->type); //!NEW< } @@ -1547,7 +1547,7 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) if (PyDict_GetItemString(dict, "type")) { PyErr_SetString(PyExc_ValueError, - "redundant type field in event dict"); + "'type' field in event dict is not allowed"); Py_DECREF(dict); return -1; } @@ -1568,6 +1568,7 @@ _pgEvent_CreateSubclass(Uint32 ev_type) { const char* name = _pg_name_from_eventtype(ev_type); if (strcmp(name, "Unknown") == 0 || strcmp(name, "UserEvent") == 0) { + Py_INCREF((PyObject *)&pgEvent_Type); return (PyObject *)&pgEvent_Type; } size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") @@ -1684,10 +1685,111 @@ static PyMethodDef eventobj_methods[] = { METH_VARARGS | METH_KEYWORDS | METH_CLASS}, {NULL, NULL, 0, NULL} }; + +/* event metaclass */ + +static PyObject* pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) { + if (type == (PyObject *)&pgEvent_Type) { + Uint32 e_type; + PyObject *e_dict; + + PyObject *t_name = PyUnicode_FromString("type"); + if (!t_name) + return NULL; + + PyObject *dict_name = PyUnicode_FromString("dict"); + if (!dict_name) { + Py_DECREF(t_name); + return NULL; + } + + if (!PyArg_ParseTuple(args, "I|O!", &e_type, &PyDict_Type, &e_dict)) { + Py_DECREF(t_name); + Py_DECREF(dict_name); + return NULL; + } + + if (e_dict && PyDict_Contains(e_dict, t_name)) { + Py_DECREF(t_name); + Py_DECREF(dict_name); + Py_DECREF(e_dict); + PyErr_SetString(PyExc_ValueError, "'type' field in event dict is not allowed"); + } + + if (!e_dict) { + e_dict = PyDict_New(); + if (!e_dict) { + Py_DECREF(t_name); + Py_DECREF(dict_name); + return NULL; + } + } + + if (kwds) { + if (PyDict_Update(e_dict, kwds) < 0) { + Py_DECREF(t_name); + Py_DECREF(dict_name); + Py_DECREF(e_dict); + } + + if (PyDict_Contains(kwds, t_name)) { + PyErr_SetString(PyExc_TypeError, "pygame.event.Event() positional-only argument passed as keyword argument: 'type'"); + Py_DECREF(t_name); + Py_DECREF(dict_name); + Py_DECREF(e_dict); + return NULL; + } + + if (PyDict_Contains(kwds, dict_name)) { + PyErr_SetString(PyExc_TypeError, "pygame.event.Event() positional-only argument passed as keyword argument: 'dict'"); + Py_DECREF(t_name); + Py_DECREF(dict_name); + Py_DECREF(e_dict); + return NULL; + } + + if (PyErr_Occurred()) + return NULL; + } + + Py_DECREF(t_name); + Py_DECREF(dict_name); + + PyObject *e_typeo = pgEvent_GetClass(e_type); + if (!e_typeo) { + Py_DECREF(e_dict); + return NULL; + } + else if (e_typeo == (PyObject *)&pgEvent_Type) { + Py_DECREF(e_typeo); + return PyType_Type.tp_call(type, args, e_dict); + } + + PyObject *ret = PyType_Type.tp_call(e_typeo, PyTuple_New(0), e_dict); + Py_DECREF(e_typeo); + Py_DECREF(e_dict); + return ret; + } + + return PyType_Type.tp_call(type, args, kwds); +} + +static PyTypeObject pgEventMeta_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "pygame.event._EventMeta", + .tp_basicsize = sizeof(PyTypeObject), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_base = &PyType_Type, + .tp_call = pgEventMeta_Call, +}; //!NEW< +/* event type declaration */ + static PyTypeObject pgEvent_Type = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event.Event", + //!NEW + PyVarObject_HEAD_INIT(&pgEventMeta_Type, 0) + .tp_name = "pygame.event.Event", .tp_basicsize = sizeof(pgEventObject), //!NEW .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, @@ -1707,7 +1809,7 @@ static PyTypeObject pgEvent_Type = { .tp_members = pg_event_members, .tp_dictoffset = offsetof(pgEventObject, dict), .tp_init = (initproc)pg_event_init, - .tp_new = PyType_GenericNew, // TODO: hook up pg_event_class + .tp_new = PyType_GenericNew, //!NEW .tp_methods = eventobj_methods, }; @@ -2528,6 +2630,10 @@ MODINIT_DEFINE(event) } /* type preparation */ + if (PyType_Ready(&pgEventMeta_Type) < 0) { + return NULL; + } + if (PyType_Ready(&pgEvent_Type) < 0) { return NULL; } From 01ac3595ba97b93baf19b72676e8197277f4a755 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Sun, 19 May 2024 08:16:03 +0200 Subject: [PATCH 07/18] Direct pygame.event dynamic properties importing. --- src_c/event.c | 489 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 488 insertions(+), 1 deletion(-) diff --git a/src_c/event.c b/src_c/event.c index 354e35a436..8d4bb7d1e1 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -887,6 +887,392 @@ _pg_name_from_eventtype(int type) return "UserEvent"; return "Unknown"; } +//!NEW> +// Madeup function, but it works. +static int +_very_bad_hash(const char *str) +{ + // Rarely works, but for existing names no collisions! + unsigned int sum = 0; + int off = 1; + + for (int i = 0; i < strlen(str); i++) { + sum += str[i]; + sum = sum ^ (sum >> off); + off += 1; + if (off > 10) { + off = 1; + } + } + return sum; +} + +static int +_pg_eventtype_from_name_hash(const char *name) +{ + /* + This was generated with these helper files (rerun if events table modified): + parse.py: + import subprocess as sp + + text = "[Content of the _pg_name_from_eventtype switch]" + + def parse(text): + res = [] + groups = [] + + ev_id = "" + + for line in text.split("\n"): + if line.startswith(" case"): + ev_id = line[len(' case '):-len(':')] + elif line.startswith(" "): + name = line[len(' return "'):-len('";')] + res.append((name, ev_id)) + groups.append((name, ev_id)) + else: + res.append(line) + return res, groups + + + def do_hash(groups): + inp = "\n".join(map(lambda x: x[0], groups)) + p = sp.run(["./a.out"], stdout=sp.PIPE, input=inp, encoding="ascii") + codes = list(map(int, p.stdout.split("\n"))) + ret = [] + for idx in range(len(groups)): + ret.append((*groups[idx], codes[idx])) + return ret + + + def collides(hashes): + hdb = {} + hashes_db = {} + for name, _, h in hashes: + if h in hdb: + print(f"collision: {hdb[h]}, {name}") + exit() + else: + hdb[h] = name + hashes_db[name] = h + return hashes_db + + + def convert(lines, hashes_db): + text = "" + for line in lines: + if isinstance(line, str): + text += line + "\n" + else: + text += f" case {hashes_db[line[0]]}: // {line[0]}\n" \ + f" return {line[1]};\n" + return text + + + if __name__ == "__main__": + lines, groups = parse(text) + hashes = do_hash(groups) + hashes_db = collides(hashes) + print(convert(lines, hashes_db)) + + parse.c: + #include + #include + + unsigned int _very_bad_hash(char *txt) { + unsigned int sum = 0; + int off = 1; + + for (int i = 0; i < strlen(txt); i++) { + sum += txt[i]; + sum = sum ^ (sum >> off); + off += 1; + if (off > 10) { + off = 1; + } + } + return sum; + } + + int main() { + char *line = NULL; + size_t s; + getline(&line, &s, stdin); + line[strcspn(line, "\n")] = 0; + printf("%u", _very_bad_hash(line)); + while (1) { + if (getline(&line, &s, stdin) == -1) { + return 0; + }; + line[strcspn(line, "\n")] = 0; + printf("\n%u", _very_bad_hash(line)); + } + return 0; + } + + And of course, run something like: "gcc parse.c", before running "python parse.py". + + */ + switch (_very_bad_hash(name)) { + case 1784: // ActiveEvent + return SDL_ACTIVEEVENT; + case 1791: // AppTerminating + return SDL_APP_TERMINATING; + case 1757: // AppLowMemory + return SDL_APP_LOWMEMORY; + case 3391: // AppWillEnterBackground + return SDL_APP_WILLENTERBACKGROUND; + case 3729: // AppDidEnterBackground + return SDL_APP_DIDENTERBACKGROUND; + case 2275: // AppWillEnterForeground + return SDL_APP_WILLENTERFOREGROUND; + case 3967: // AppDidEnterForeground + return SDL_APP_DIDENTERFOREGROUND; + case 2377: // ClipboardUpdate + return SDL_CLIPBOARDUPDATE; + case 720: // KeyDown + return SDL_KEYDOWN; + case 570: // KeyUp + return SDL_KEYUP; + case 1917: // KeyMapChanged + return SDL_KEYMAPCHANGED; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case 1940: // LocaleChanged + return SDL_LOCALECHANGED; +#endif + case 1726: // MouseMotion + return SDL_MOUSEMOTION; + case 2417: // MouseButtonDown + return SDL_MOUSEBUTTONDOWN; + case 1967: // MouseButtonUp + return SDL_MOUSEBUTTONUP; + case 1972: // JoyAxisMotion + return SDL_JOYAXISMOTION; + case 1844: // JoyBallMotion + return SDL_JOYBALLMOTION; + case 1765: // JoyHatMotion + return SDL_JOYHATMOTION; + case 1740: // JoyButtonUp + return SDL_JOYBUTTONUP; + case 1907: // JoyButtonDown + return SDL_JOYBUTTONDOWN; + case 405: // Quit + return SDL_QUIT; + case 1098: // SysWMEvent + return SDL_SYSWMEVENT; + case 1751: // VideoResize + return SDL_VIDEORESIZE; + case 1785: // VideoExpose + return SDL_VIDEOEXPOSE; + case 635: // MidiIn + return PGE_MIDIIN; + case 751: // MidiOut + return PGE_MIDIOUT; + case 741: // NoEvent + return SDL_NOEVENT; + case 1821: // FingerMotion + return SDL_FINGERMOTION; + case 1043: // FingerDown + return SDL_FINGERDOWN; + case 826: // FingerUp + return SDL_FINGERUP; + case 1758: // MultiGesture + return SDL_MULTIGESTURE; + case 1089: // MouseWheel + return SDL_MOUSEWHEEL; + case 1010: // TextInput + return SDL_TEXTINPUT; + case 1788: // TextEditing + return SDL_TEXTEDITING; + case 867: // DropFile + return SDL_DROPFILE; + case 953: // DropText + return SDL_DROPTEXT; + case 992: // DropBegin + return SDL_DROPBEGIN; + case 1691: // DropComplete + return SDL_DROPCOMPLETE; + case 2897: // ControllerAxisMotion + return SDL_CONTROLLERAXISMOTION; + case 2914: // ControllerButtonDown + return SDL_CONTROLLERBUTTONDOWN; + case 2734: // ControllerButtonUp + return SDL_CONTROLLERBUTTONUP; + case 3623: // ControllerDeviceAdded + return SDL_CONTROLLERDEVICEADDED; + case 3116: // ControllerDeviceRemoved + return SDL_CONTROLLERDEVICEREMOVED; + case 3380: // ControllerDeviceMapped + return SDL_CONTROLLERDEVICEREMAPPED; + case 2030: // JoyDeviceAdded + return SDL_JOYDEVICEADDED; + case 2302: // JoyDeviceRemoved + return SDL_JOYDEVICEREMOVED; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case 5155: // ControllerTouchpadDown + return SDL_CONTROLLERTOUCHPADDOWN; + case 5915: // ControllerTouchpadMotion + return SDL_CONTROLLERTOUCHPADMOTION; + case 2665: // ControllerTouchpadUp + return SDL_CONTROLLERTOUCHPADUP; + case 3160: // ControllerSensorUpdate + return SDL_CONTROLLERSENSORUPDATE; +#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ + case 2199: // AudioDeviceAdded + return SDL_AUDIODEVICEADDED; + case 2301: // AudioDeviceRemoved + return SDL_AUDIODEVICEREMOVED; + case 2048: // RenderTargetsReset + return SDL_RENDER_TARGETS_RESET; + case 2625: // RenderDeviceReset + return SDL_RENDER_DEVICE_RESET; + case 1706: // WindowShown + return PGE_WINDOWSHOWN; + case 1681: // WindowHidden + return PGE_WINDOWHIDDEN; + case 1963: // WindowExposed + return PGE_WINDOWEXPOSED; + case 1699: // WindowMoved + return PGE_WINDOWMOVED; + case 2003: // WindowResized + return PGE_WINDOWRESIZED; + case 2528: // WindowSizeChanged + return PGE_WINDOWSIZECHANGED; + case 2231: // WindowMinimized + return PGE_WINDOWMINIMIZED; + case 2221: // WindowMaximized + return PGE_WINDOWMAXIMIZED; + case 2188: // WindowRestored + return PGE_WINDOWRESTORED; + case 1710: // WindowEnter + return PGE_WINDOWENTER; + case 1774: // WindowLeave + return PGE_WINDOWLEAVE; + case 2233: // WindowFocusGained + return PGE_WINDOWFOCUSGAINED; + case 2174: // WindowFocusLost + return PGE_WINDOWFOCUSLOST; + case 1770: // WindowClose + return PGE_WINDOWCLOSE; + case 1886: // WindowTakeFocus + return PGE_WINDOWTAKEFOCUS; + case 2026: // WindowHitTest + return PGE_WINDOWHITTEST; + case 2548: // WindowICCProfChanged + return PGE_WINDOWICCPROFCHANGED; + case 2889: // WindowDisplayChanged + return PGE_WINDOWDISPLAYCHANGED; + } + return -1; +} + +static int +_pg_eventtype_from_name(const char *name) +{ + const int guessed_type = _pg_eventtype_from_name_hash(name); + + if (guessed_type == -1) + return -1; + + if (strcmp(name, _pg_name_from_eventtype(guessed_type)) != 0) + return -1; + + return guessed_type; +} + +/* + To generate this, see the python code in _pg_eventtype_from_name_hash, + but replace its '__name__ == "__main__: [...]" with: + if __name__ == "__main__": + lines, groups = parse(text) + print("{") + for line in lines: + if isinstance(line, str): + print(line) + else: + print(f" \"{line[0]}\",") + print(" NULL\n};") +*/ +static const char *_event_names[] = { + "ActiveEvent", + "AppTerminating", + "AppLowMemory", + "AppWillEnterBackground", + "AppDidEnterBackground", + "AppWillEnterForeground", + "AppDidEnterForeground", + "ClipboardUpdate", + "KeyDown", + "KeyUp", + "KeyMapChanged", +#if SDL_VERSION_ATLEAST(2, 0, 14) + "LocaleChanged", +#endif + "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", +#if SDL_VERSION_ATLEAST(2, 0, 14) + "ControllerTouchpadDown", + "ControllerTouchpadMotion", + "ControllerTouchpadUp", + "ControllerSensorUpdate", +#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ + "AudioDeviceAdded", + "AudioDeviceRemoved", + "RenderTargetsReset", + "RenderDeviceReset", + "WindowShown", + "WindowHidden", + "WindowExposed", + "WindowMoved", + "WindowResized", + "WindowSizeChanged", + "WindowMinimized", + "WindowMaximized", + "WindowRestored", + "WindowEnter", + "WindowLeave", + "WindowFocusGained", + "WindowFocusLost", + "WindowClose", + "WindowTakeFocus", + "WindowHitTest", + "WindowICCProfChanged", + "WindowDisplayChanged", + NULL +}; +//!NEW< /* Helper for adding objects to dictionaries. Check for errors with PyErr_Occurred() */ @@ -2558,6 +2944,95 @@ pg_event_custom_type(PyObject *self, PyObject *_null) "pygame.event.custom_type made too many event types."); } +//!NEW> +static PyObject * +pg_event_name_to_id(PyObject *self, PyObject *attr) +{ + if (!PyUnicode_Check(attr)) + return NULL; + + const char *attr_name = PyUnicode_AsUTF8(attr); + + if (PyErr_Occurred()) + return NULL; + + int e_type = _pg_eventtype_from_name(attr_name); + return PyLong_FromLong(e_type); +} + +static PyObject * +pg_event__gettattr__(PyObject *self, PyObject *attr) +{ + if (!PyUnicode_Check(attr)) + return NULL; + + const char *attr_name = PyUnicode_AsUTF8(attr); + + if (PyErr_Occurred()) + return NULL; + + int e_type = _pg_eventtype_from_name(attr_name); + + if (e_type<0) + return PyErr_Format(PyExc_AttributeError, "module 'pygame.event' has no attribute '%s'", attr_name); + return pgEvent_GetClass(e_type); +} +/* To be done. +static PyObject * +pg_event__dir__(PyObject *self, PyObject *args) +{ + size_t length = 0; + + while (_event_names[length] != NULL) { + length++; + } + + PyObject *dict = PyObject_GetAttrString(self, "__dict__"); + if (!dict) { + return NULL; + } + + if (!PyDict_Check(dict)) { + Py_DECREF(dict); + PyErr_Format(PyExc_TypeError, ".__dict__ is not a dictionary"); + return NULL; + } + + PyObject *result = PyDict_Keys(dict); + Py_DECREF(dict); + + if (!result) { + return NULL; + } + + PyObject *dir = PyList_New(PyList_Size(result) + length); + + if (!dir) { + Py_DECREF(result); + return NULL; + } + + for (Py_ssize_t idx = 0; idx < PyList_Size(result); idx++) { + PyObject *item = PyList_GetItem(result, idx); + Py_INCREF(item); + PyList_SetItem(dir, idx, item); + } + Py_DECREF(result); + + for (Py_ssize_t idx = 0; idx < length; idx++) { + PyObject *item = PyUnicode_FromString(_event_names[idx]); + if (!item) { + Py_DECREF(dir); + return NULL; + } + PyList_SetItem(dir, idx + PyList_Size(result), item); + } + + return dir; +} +*/ +//!NEW< + static PyMethodDef _event_methods[] = { {"_internal_mod_init", (PyCFunction)pgEvent_AutoInit, METH_NOARGS, "auto initialize for event module"}, @@ -2591,6 +3066,12 @@ static PyMethodDef _event_methods[] = { DOC_EVENT_CUSTOMTYPE}, //!NEW {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, + //!NEW + {"_name_to_id", (PyCFunction)pg_event_name_to_id, METH_O}, + //!NEW + {"__getattr__", (PyCFunction)pg_event__gettattr__, METH_O}, + //!NEW + // {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS} - To be done. {NULL, NULL, 0, NULL}}; @@ -2610,7 +3091,7 @@ MODINIT_DEFINE(event) DOC_EVENT, -1, _event_methods, - NULL, + NULL, // _event_slots, NULL, NULL, //!NEW @@ -2688,11 +3169,17 @@ MODINIT_DEFINE(event) } PyObject *proxy = PyDictProxy_New(pg_event_lookup); + if (!proxy) { + Py_DECREF(module); + Py_DECREF(pg_event_lookup); + return NULL; + } if (PyModule_AddObject(module, "_event_classes", proxy)) { Py_DECREF(proxy); Py_DECREF(module); Py_DECREF(pg_event_lookup); + return NULL; } //!NEW< From 1cbb21acb7537572daac8764a619d29c67d4dd32 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Tue, 21 May 2024 22:40:08 +0200 Subject: [PATCH 08/18] override pygame.event.Event() call + bugfixes. --- src_c/base.c | 4 + src_c/event.c | 215 +++++++++++++++++++++++++------------------------- 2 files changed, 110 insertions(+), 109 deletions(-) diff --git a/src_c/base.c b/src_c/base.c index 2a609e6601..03f58868e7 100644 --- a/src_c/base.c +++ b/src_c/base.c @@ -296,6 +296,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/event.c b/src_c/event.c index 8d4bb7d1e1..7b21612642 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1735,7 +1735,7 @@ pg_event_dealloc(PyObject *self) } //!NEW> -PyObject * +static PyObject * _pg_EventGetAttr(PyObject *o, PyObject *attr_name) { const char *attr = PyUnicode_AsUTF8(attr_name); @@ -1838,8 +1838,7 @@ pg_event_str(PyObject *self) static PyMemberDef pg_event_members[] = { {"__dict__", T_OBJECT, OFF(dict), READONLY}, - //!NEW - changed type->__type - {"__type", T_INT, OFF(type), READONLY}, + {"type", T_INT, OFF(type), READONLY}, {"dict", T_OBJECT, OFF(dict), READONLY}, {NULL} /* Sentinel */ }; @@ -1945,25 +1944,38 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) //!NEW> static PyType_Slot pg_event_subclass_slots[] = { + {Py_tp_base, &pgEvent_Type}, {Py_tp_getattro, _pg_EventGetAttr}, {0, NULL} }; +/* Return a new reference. */ static PyObject * _pgEvent_CreateSubclass(Uint32 ev_type) { const char* name = _pg_name_from_eventtype(ev_type); + if (strcmp(name, "Unknown") == 0 || strcmp(name, "UserEvent") == 0) { Py_INCREF((PyObject *)&pgEvent_Type); return (PyObject *)&pgEvent_Type; } + size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") char* joined = malloc(name_s); + if (!joined) return PyErr_NoMemory(); PyOS_snprintf(joined, name_s, "pygame.event.%s", name); - PyType_Spec type_spec = {.name=joined, .basicsize = sizeof(pgEventObject), .slots=pg_event_subclass_slots}; + + PyType_Spec type_spec = { + joined, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE, + pg_event_subclass_slots + }; + // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); @@ -1974,9 +1986,13 @@ _pgEvent_CreateSubclass(Uint32 ev_type) PyObject* cls = PyType_FromSpecWithBases(&type_spec, bases); - // I'm convinced this is leaking, but for unknown reason to me, - // freeing "joined" it will corrupt the name of returned class. + // Python <= 3.10 doesn't copy class name, but holds the reference, + // Therefore for these versions, we need to leak "joined". + // This is not an issue, as these classes in theory should be created only once per type. + // Other solution is to generate class names statically. +#if PY_MINOR_VERSION > 10 free(joined); +#endif PyObject *value = PyLong_FromLong(ev_type); if (!value) { @@ -1987,6 +2003,9 @@ _pgEvent_CreateSubclass(Uint32 ev_type) Py_DECREF(value); return NULL; } + + Py_DECREF(value); + return cls; } @@ -1995,26 +2014,34 @@ pgEvent_GetClass(Uint32 e_type) { PyObject *e_typeo, *e_class; - if (!(e_typeo=PyLong_FromLong(e_type))) + e_typeo = PyLong_FromLong(e_type); + + if (!e_typeo) return NULL; - int e_exists = PyDict_Contains(pg_event_lookup, e_typeo); - if (e_exists < 0) { + e_class = PyDict_GetItem(pg_event_lookup, e_typeo); + Py_XINCREF(e_class); // Claiming borrowed reference. + + if (e_class) { Py_DECREF(e_typeo); - return NULL; - } else if (e_exists) { - e_class = PyDict_GetItem(pg_event_lookup, e_typeo); + return e_class; + } + + e_class = _pgEvent_CreateSubclass(e_type); + + if (!e_class) { Py_DECREF(e_typeo); - } else { - e_class = _pgEvent_CreateSubclass(e_type); - if (!e_class || PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) - { - Py_DECREF(e_typeo); - Py_XDECREF(e_class); - return NULL; - } + return NULL; + } + + if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) + { Py_DECREF(e_typeo); + Py_DECREF(e_class); + return NULL; } + + Py_DECREF(e_typeo); return e_class; } @@ -2034,12 +2061,12 @@ _register_user_event_class(PyObject *e_class) return -1; } - if (!(e_typeo)) + if (!e_typeo) return -1; if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) { - Py_XDECREF(e_typeo); + Py_DECREF(e_typeo); return -1; } @@ -2050,6 +2077,11 @@ _register_user_event_class(PyObject *e_class) static PyObject * pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) { + if (!(cls->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + // Only heaptypes. + Py_RETURN_NONE; + } + int e_type = _register_user_event_class((PyObject *)cls); if (e_type < 0) return NULL; @@ -2063,6 +2095,8 @@ pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) return NULL; } + Py_DECREF(value); + Py_RETURN_NONE; } @@ -2073,97 +2107,59 @@ static PyMethodDef eventobj_methods[] = { }; /* event metaclass */ +static PyObject* _pg_event_instantiate_class(Uint32 e_type, PyObject *e_dict) +{ + PyObject *e_typeo = pgEvent_GetClass(e_type); -static PyObject* pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) { - if (type == (PyObject *)&pgEvent_Type) { - Uint32 e_type; - PyObject *e_dict; + if (!e_typeo) { + return NULL; + } else if (e_typeo == (PyObject *)&pgEvent_Type) { + Py_DECREF(e_typeo); + PyObject *ret = PyType_Type.tp_call((PyObject *)&pgEvent_Type, Py_BuildValue("(I)", e_type), e_dict); + return ret; + } - PyObject *t_name = PyUnicode_FromString("type"); - if (!t_name) - return NULL; + PyObject *ret = PyType_Type.tp_call(e_typeo, PyTuple_New(0), e_dict); + Py_DECREF(e_typeo); + return ret; +} - PyObject *dict_name = PyUnicode_FromString("dict"); - if (!dict_name) { - Py_DECREF(t_name); - return NULL; - } +static PyObject* pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) { + if (type == (PyObject *)&pgEvent_Type) { + Uint32 e_type = 0; + PyObject *e_dict = NULL; if (!PyArg_ParseTuple(args, "I|O!", &e_type, &PyDict_Type, &e_dict)) { - Py_DECREF(t_name); - Py_DECREF(dict_name); return NULL; } - if (e_dict && PyDict_Contains(e_dict, t_name)) { - Py_DECREF(t_name); - Py_DECREF(dict_name); - Py_DECREF(e_dict); - PyErr_SetString(PyExc_ValueError, "'type' field in event dict is not allowed"); - } - - if (!e_dict) { + if (kwds && !e_dict) { e_dict = PyDict_New(); - if (!e_dict) { - Py_DECREF(t_name); - Py_DECREF(dict_name); - return NULL; - } - } - - if (kwds) { - if (PyDict_Update(e_dict, kwds) < 0) { - Py_DECREF(t_name); - Py_DECREF(dict_name); - Py_DECREF(e_dict); - } - - if (PyDict_Contains(kwds, t_name)) { - PyErr_SetString(PyExc_TypeError, "pygame.event.Event() positional-only argument passed as keyword argument: 'type'"); - Py_DECREF(t_name); - Py_DECREF(dict_name); - Py_DECREF(e_dict); - return NULL; - } - - if (PyDict_Contains(kwds, dict_name)) { - PyErr_SetString(PyExc_TypeError, "pygame.event.Event() positional-only argument passed as keyword argument: 'dict'"); - Py_DECREF(t_name); - Py_DECREF(dict_name); - Py_DECREF(e_dict); - return NULL; - } - - if (PyErr_Occurred()) + if (!e_dict) return NULL; } - Py_DECREF(t_name); - Py_DECREF(dict_name); - - PyObject *e_typeo = pgEvent_GetClass(e_type); - if (!e_typeo) { + if (e_dict && kwds && PyDict_Update(e_dict, kwds) < 0) { Py_DECREF(e_dict); return NULL; } - else if (e_typeo == (PyObject *)&pgEvent_Type) { - Py_DECREF(e_typeo); - return PyType_Type.tp_call(type, args, e_dict); - } - - PyObject *ret = PyType_Type.tp_call(e_typeo, PyTuple_New(0), e_dict); - Py_DECREF(e_typeo); - Py_DECREF(e_dict); + + PyObject *ret = _pg_event_instantiate_class(e_type, e_dict); + // By trial and error, I discovered that this shouldn't be here. + // But I would like to know why - by reading the code it seems that + // this should be here - does PyArg_ParseTuple increase the refcount? + // Py_XDECREF(e_dict); return ret; } return PyType_Type.tp_call(type, args, kwds); } +// After dropping support for python 3.8, the whole scheme of creating metaclasses to override pygame.event.Event(...) can be dropped in favor of tp_vectorcall. static PyTypeObject pgEventMeta_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pygame.event._EventMeta", - .tp_basicsize = sizeof(PyTypeObject), + .tp_basicsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_base = &PyType_Type, .tp_call = pgEventMeta_Call, @@ -2977,7 +2973,7 @@ pg_event__gettattr__(PyObject *self, PyObject *attr) return PyErr_Format(PyExc_AttributeError, "module 'pygame.event' has no attribute '%s'", attr_name); return pgEvent_GetClass(e_type); } -/* To be done. + static PyObject * pg_event__dir__(PyObject *self, PyObject *args) { @@ -3030,7 +3026,6 @@ pg_event__dir__(PyObject *self, PyObject *args) return dir; } -*/ //!NEW< static PyMethodDef _event_methods[] = { @@ -3071,15 +3066,10 @@ static PyMethodDef _event_methods[] = { //!NEW {"__getattr__", (PyCFunction)pg_event__gettattr__, METH_O}, //!NEW - // {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS} - To be done. + {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS}, {NULL, NULL, 0, NULL}}; -//!NEW> -static void _event_free(PyObject* mod) { - Py_XDECREF(pg_event_lookup); -} -//!NEW< MODINIT_DEFINE(event) { @@ -3094,8 +3084,7 @@ MODINIT_DEFINE(event) NULL, // _event_slots, NULL, NULL, - //!NEW - (freefunc)_event_free}; + NULL}; /* imported needed apis; Do this first so if there is an error the module is not loaded. @@ -3111,9 +3100,11 @@ MODINIT_DEFINE(event) } /* type preparation */ + //!NEW> if (PyType_Ready(&pgEventMeta_Type) < 0) { return NULL; } + //!NEW< if (PyType_Ready(&pgEvent_Type) < 0) { return NULL; @@ -3125,6 +3116,15 @@ MODINIT_DEFINE(event) return NULL; } + //!NEW> + Py_INCREF(&pgEventMeta_Type); + if (PyModule_AddObject(module, "_InternalEventMeta", (PyObject *)&pgEventMeta_Type)) { + Py_DECREF(&pgEventMeta_Type); + Py_DECREF(module); + return NULL; + } + //!NEW< + Py_INCREF(&pgEvent_Type); if (PyModule_AddObject(module, "EventType", (PyObject *)&pgEvent_Type)) { Py_DECREF(&pgEvent_Type); @@ -3157,28 +3157,25 @@ MODINIT_DEFINE(event) } //!NEW> - if (pg_event_lookup) - Py_INCREF(pg_event_lookup); - else { + if (!pg_event_lookup) { pg_event_lookup = PyDict_New(); - if (!pg_event_lookup) - { - Py_DECREF(module); - return NULL; - } + } + + if (!pg_event_lookup) + { + Py_DECREF(module); + return NULL; } PyObject *proxy = PyDictProxy_New(pg_event_lookup); if (!proxy) { Py_DECREF(module); - Py_DECREF(pg_event_lookup); return NULL; } if (PyModule_AddObject(module, "_event_classes", proxy)) { Py_DECREF(proxy); Py_DECREF(module); - Py_DECREF(pg_event_lookup); return NULL; } //!NEW< From f4b65efc3b1344ed546247e3cd1bf6ed3704a3f5 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Tue, 21 May 2024 23:45:52 +0200 Subject: [PATCH 09/18] Run clang-format + useful regex: !NEW>[\S\s]*?!NEW<|!NEW.*\n.* --- src_c/_pygame.h | 14 +- src_c/event.c | 474 +++++++++++++++++++++++------------------------- 2 files changed, 232 insertions(+), 256 deletions(-) diff --git a/src_c/_pygame.h b/src_c/_pygame.h index de98a41e7d..b810000c77 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -196,14 +196,14 @@ PG_SurfaceHasRLE(SDL_Surface *surface); #endif -/* DictProxy is useful for event posting with an arbitrary dict/event object. Maintains - * state of number of events on queue and whether the owner of this struct - * wants this dict/event instance 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 */ +/* DictProxy is useful for event posting with an arbitrary dict/event object. + * Maintains state of number of events on queue and whether the owner of this + * struct wants this dict/event instance 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 *obj; // Either dict or event object. + PyObject *obj; // Either dict or event object. SDL_SpinLock lock; int num_on_queue; Uint8 do_free_at_end; diff --git a/src_c/event.c b/src_c/event.c index 516a3fb09b..b101d6b52b 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -97,7 +97,6 @@ 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}; -//!NEW static PyObject *pg_event_lookup; #ifdef __EMSCRIPTEN__ @@ -312,11 +311,11 @@ _pg_get_event_unicode(SDL_Event *event) return proxify ? PGPOST_##name : PGE_##name static PyTypeObject pgEvent_Type; -//!NEW #define pgEvent_CheckExact(x) ((x)->ob_type == &pgEvent_Type) #define pgEvent_Check(x) (PyObject_IsInstance(x, (PyObject *)&pgEvent_Type)) -//!NEW -#define pgEvent_IsSubclass(x) (PyObject_IsSubclass(x, (PyObject *)&pgEvent_Type) * (x != (PyObject *)&pgEvent_Type)) +#define pgEvent_IsSubclass(x) \ + (PyObject_IsSubclass(x, (PyObject *)&pgEvent_Type) * \ + (x != (PyObject *)&pgEvent_Type)) #define OFF(x) offsetof(pgEventObject, x) /* The next three functions are used for proxying SDL events to and from @@ -708,8 +707,8 @@ pg_post_event_dictproxy(Uint32 type, pgEventDictProxy *dict_proxy) } /* This function posts an SDL "UserEvent" event, can also optionally take a - * python dict/event object. This function does not need GIL to be held if dict is NULL, but - * needs GIL otherwise */ + * python dict/event object. This function does not need GIL to be held if dict + * is NULL, but needs GIL otherwise */ static int pg_post_event(Uint32 type, PyObject *obj) { @@ -897,7 +896,7 @@ _pg_name_from_eventtype(int type) return "UserEvent"; return "Unknown"; } -//!NEW> + // Madeup function, but it works. static int _very_bad_hash(const char *str) @@ -921,7 +920,9 @@ static int _pg_eventtype_from_name_hash(const char *name) { /* - This was generated with these helper files (rerun if events table modified): + This was generated with these helper files (rerun if events table + modified): + parse.py: import subprocess as sp @@ -945,9 +946,10 @@ _pg_eventtype_from_name_hash(const char *name) return res, groups - def do_hash(groups): + def do_hash(groups): inp = "\n".join(map(lambda x: x[0], groups)) - p = sp.run(["./a.out"], stdout=sp.PIPE, input=inp, encoding="ascii") + p = sp.run(["./a.out"], stdout=sp.PIPE, input=inp, + encoding="ascii") codes = list(map(int, p.stdout.split("\n"))) ret = [] for idx in range(len(groups)): @@ -974,9 +976,9 @@ _pg_eventtype_from_name_hash(const char *name) if isinstance(line, str): text += line + "\n" else: - text += f" case {hashes_db[line[0]]}: // {line[0]}\n" \ - f" return {line[1]};\n" - return text + text += f" case {hashes_db[line[0]]}" \ + f": //{line[0]}\n " \ + f"return {line[1]};\n" return text if __name__ == "__main__": @@ -1019,158 +1021,159 @@ _pg_eventtype_from_name_hash(const char *name) } return 0; } - - And of course, run something like: "gcc parse.c", before running "python parse.py". + + And of course, run something like: "gcc parse.c", before running + "python parse.py". */ switch (_very_bad_hash(name)) { - case 1784: // ActiveEvent + case 1784: // ActiveEvent return SDL_ACTIVEEVENT; - case 1791: // AppTerminating + case 1791: // AppTerminating return SDL_APP_TERMINATING; - case 1757: // AppLowMemory + case 1757: // AppLowMemory return SDL_APP_LOWMEMORY; - case 3391: // AppWillEnterBackground + case 3391: // AppWillEnterBackground return SDL_APP_WILLENTERBACKGROUND; - case 3729: // AppDidEnterBackground + case 3729: // AppDidEnterBackground return SDL_APP_DIDENTERBACKGROUND; - case 2275: // AppWillEnterForeground + case 2275: // AppWillEnterForeground return SDL_APP_WILLENTERFOREGROUND; - case 3967: // AppDidEnterForeground + case 3967: // AppDidEnterForeground return SDL_APP_DIDENTERFOREGROUND; - case 2377: // ClipboardUpdate + case 2377: // ClipboardUpdate return SDL_CLIPBOARDUPDATE; - case 720: // KeyDown + case 720: // KeyDown return SDL_KEYDOWN; - case 570: // KeyUp + case 570: // KeyUp return SDL_KEYUP; - case 1917: // KeyMapChanged + case 1917: // KeyMapChanged return SDL_KEYMAPCHANGED; #if SDL_VERSION_ATLEAST(2, 0, 14) - case 1940: // LocaleChanged + case 1940: // LocaleChanged return SDL_LOCALECHANGED; #endif - case 1726: // MouseMotion + case 1726: // MouseMotion return SDL_MOUSEMOTION; - case 2417: // MouseButtonDown + case 2417: // MouseButtonDown return SDL_MOUSEBUTTONDOWN; - case 1967: // MouseButtonUp + case 1967: // MouseButtonUp return SDL_MOUSEBUTTONUP; - case 1972: // JoyAxisMotion + case 1972: // JoyAxisMotion return SDL_JOYAXISMOTION; - case 1844: // JoyBallMotion + case 1844: // JoyBallMotion return SDL_JOYBALLMOTION; - case 1765: // JoyHatMotion + case 1765: // JoyHatMotion return SDL_JOYHATMOTION; - case 1740: // JoyButtonUp + case 1740: // JoyButtonUp return SDL_JOYBUTTONUP; - case 1907: // JoyButtonDown + case 1907: // JoyButtonDown return SDL_JOYBUTTONDOWN; - case 405: // Quit + case 405: // Quit return SDL_QUIT; - case 1098: // SysWMEvent + case 1098: // SysWMEvent return SDL_SYSWMEVENT; - case 1751: // VideoResize + case 1751: // VideoResize return SDL_VIDEORESIZE; - case 1785: // VideoExpose + case 1785: // VideoExpose return SDL_VIDEOEXPOSE; - case 635: // MidiIn + case 635: // MidiIn return PGE_MIDIIN; - case 751: // MidiOut + case 751: // MidiOut return PGE_MIDIOUT; - case 741: // NoEvent + case 741: // NoEvent return SDL_NOEVENT; - case 1821: // FingerMotion + case 1821: // FingerMotion return SDL_FINGERMOTION; - case 1043: // FingerDown + case 1043: // FingerDown return SDL_FINGERDOWN; - case 826: // FingerUp + case 826: // FingerUp return SDL_FINGERUP; - case 1758: // MultiGesture + case 1758: // MultiGesture return SDL_MULTIGESTURE; - case 1089: // MouseWheel + case 1089: // MouseWheel return SDL_MOUSEWHEEL; - case 1010: // TextInput + case 1010: // TextInput return SDL_TEXTINPUT; - case 1788: // TextEditing + case 1788: // TextEditing return SDL_TEXTEDITING; - case 867: // DropFile + case 867: // DropFile return SDL_DROPFILE; - case 953: // DropText + case 953: // DropText return SDL_DROPTEXT; - case 992: // DropBegin + case 992: // DropBegin return SDL_DROPBEGIN; - case 1691: // DropComplete + case 1691: // DropComplete return SDL_DROPCOMPLETE; - case 2897: // ControllerAxisMotion + case 2897: // ControllerAxisMotion return SDL_CONTROLLERAXISMOTION; - case 2914: // ControllerButtonDown + case 2914: // ControllerButtonDown return SDL_CONTROLLERBUTTONDOWN; - case 2734: // ControllerButtonUp + case 2734: // ControllerButtonUp return SDL_CONTROLLERBUTTONUP; - case 3623: // ControllerDeviceAdded + case 3623: // ControllerDeviceAdded return SDL_CONTROLLERDEVICEADDED; - case 3116: // ControllerDeviceRemoved + case 3116: // ControllerDeviceRemoved return SDL_CONTROLLERDEVICEREMOVED; - case 3380: // ControllerDeviceMapped + case 3380: // ControllerDeviceMapped return SDL_CONTROLLERDEVICEREMAPPED; - case 2030: // JoyDeviceAdded + case 2030: // JoyDeviceAdded return SDL_JOYDEVICEADDED; - case 2302: // JoyDeviceRemoved + case 2302: // JoyDeviceRemoved return SDL_JOYDEVICEREMOVED; #if SDL_VERSION_ATLEAST(2, 0, 14) - case 5155: // ControllerTouchpadDown + case 5155: // ControllerTouchpadDown return SDL_CONTROLLERTOUCHPADDOWN; - case 5915: // ControllerTouchpadMotion + case 5915: // ControllerTouchpadMotion return SDL_CONTROLLERTOUCHPADMOTION; - case 2665: // ControllerTouchpadUp + case 2665: // ControllerTouchpadUp return SDL_CONTROLLERTOUCHPADUP; - case 3160: // ControllerSensorUpdate + case 3160: // ControllerSensorUpdate return SDL_CONTROLLERSENSORUPDATE; -#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ - case 2199: // AudioDeviceAdded +#endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ + case 2199: // AudioDeviceAdded return SDL_AUDIODEVICEADDED; - case 2301: // AudioDeviceRemoved + case 2301: // AudioDeviceRemoved return SDL_AUDIODEVICEREMOVED; - case 2048: // RenderTargetsReset + case 2048: // RenderTargetsReset return SDL_RENDER_TARGETS_RESET; - case 2625: // RenderDeviceReset + case 2625: // RenderDeviceReset return SDL_RENDER_DEVICE_RESET; - case 1706: // WindowShown + case 1706: // WindowShown return PGE_WINDOWSHOWN; - case 1681: // WindowHidden + case 1681: // WindowHidden return PGE_WINDOWHIDDEN; - case 1963: // WindowExposed + case 1963: // WindowExposed return PGE_WINDOWEXPOSED; - case 1699: // WindowMoved + case 1699: // WindowMoved return PGE_WINDOWMOVED; - case 2003: // WindowResized + case 2003: // WindowResized return PGE_WINDOWRESIZED; - case 2528: // WindowSizeChanged + case 2528: // WindowSizeChanged return PGE_WINDOWSIZECHANGED; - case 2231: // WindowMinimized + case 2231: // WindowMinimized return PGE_WINDOWMINIMIZED; - case 2221: // WindowMaximized + case 2221: // WindowMaximized return PGE_WINDOWMAXIMIZED; - case 2188: // WindowRestored + case 2188: // WindowRestored return PGE_WINDOWRESTORED; - case 1710: // WindowEnter + case 1710: // WindowEnter return PGE_WINDOWENTER; - case 1774: // WindowLeave + case 1774: // WindowLeave return PGE_WINDOWLEAVE; - case 2233: // WindowFocusGained + case 2233: // WindowFocusGained return PGE_WINDOWFOCUSGAINED; - case 2174: // WindowFocusLost + case 2174: // WindowFocusLost return PGE_WINDOWFOCUSLOST; - case 1770: // WindowClose + case 1770: // WindowClose return PGE_WINDOWCLOSE; - case 1886: // WindowTakeFocus + case 1886: // WindowTakeFocus return PGE_WINDOWTAKEFOCUS; - case 2026: // WindowHitTest + case 2026: // WindowHitTest return PGE_WINDOWHITTEST; - case 2548: // WindowICCProfChanged + case 2548: // WindowICCProfChanged return PGE_WINDOWICCPROFCHANGED; - case 2889: // WindowDisplayChanged + case 2889: // WindowDisplayChanged return PGE_WINDOWDISPLAYCHANGED; } return -1; @@ -1190,7 +1193,7 @@ _pg_eventtype_from_name(const char *name) return guessed_type; } -/* +/* To generate this, see the python code in _pg_eventtype_from_name_hash, but replace its '__name__ == "__main__: [...]" with: if __name__ == "__main__": @@ -1203,86 +1206,83 @@ _pg_eventtype_from_name(const char *name) print(f" \"{line[0]}\",") print(" NULL\n};") */ -static const char *_event_names[] = { - "ActiveEvent", - "AppTerminating", - "AppLowMemory", - "AppWillEnterBackground", - "AppDidEnterBackground", - "AppWillEnterForeground", - "AppDidEnterForeground", - "ClipboardUpdate", - "KeyDown", - "KeyUp", - "KeyMapChanged", +static const char *_event_names[] = {"ActiveEvent", + "AppTerminating", + "AppLowMemory", + "AppWillEnterBackground", + "AppDidEnterBackground", + "AppWillEnterForeground", + "AppDidEnterForeground", + "ClipboardUpdate", + "KeyDown", + "KeyUp", + "KeyMapChanged", #if SDL_VERSION_ATLEAST(2, 0, 14) - "LocaleChanged", + "LocaleChanged", #endif - "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", + "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", #if SDL_VERSION_ATLEAST(2, 0, 14) - "ControllerTouchpadDown", - "ControllerTouchpadMotion", - "ControllerTouchpadUp", - "ControllerSensorUpdate", + "ControllerTouchpadDown", + "ControllerTouchpadMotion", + "ControllerTouchpadUp", + "ControllerSensorUpdate", #endif /*SDL_VERSION_ATLEAST(2, 0, 14)*/ - "AudioDeviceAdded", - "AudioDeviceRemoved", - "RenderTargetsReset", - "RenderDeviceReset", - "WindowShown", - "WindowHidden", - "WindowExposed", - "WindowMoved", - "WindowResized", - "WindowSizeChanged", - "WindowMinimized", - "WindowMaximized", - "WindowRestored", - "WindowEnter", - "WindowLeave", - "WindowFocusGained", - "WindowFocusLost", - "WindowClose", - "WindowTakeFocus", - "WindowHitTest", - "WindowICCProfChanged", - "WindowDisplayChanged", - NULL -}; -//!NEW< + "AudioDeviceAdded", + "AudioDeviceRemoved", + "RenderTargetsReset", + "RenderDeviceReset", + "WindowShown", + "WindowHidden", + "WindowExposed", + "WindowMoved", + "WindowResized", + "WindowSizeChanged", + "WindowMinimized", + "WindowMaximized", + "WindowRestored", + "WindowEnter", + "WindowLeave", + "WindowFocusGained", + "WindowFocusLost", + "WindowClose", + "WindowTakeFocus", + "WindowHitTest", + "WindowICCProfChanged", + "WindowDisplayChanged", + NULL}; /* Helper for adding objects to dictionaries. Check for errors with PyErr_Occurred() */ @@ -1744,7 +1744,6 @@ pg_event_dealloc(PyObject *self) Py_TYPE(self)->tp_free(self); } -//!NEW> static PyObject * _pg_EventGetAttr(PyObject *o, PyObject *attr_name) { @@ -1752,12 +1751,11 @@ _pg_EventGetAttr(PyObject *o, PyObject *attr_name) if (!attr) return NULL; - if (strcmp(attr, "type")==0) { - return PyLong_FromLong(((pgEventObject *) o)->type); + if (strcmp(attr, "type") == 0) { + return PyLong_FromLong(((pgEventObject *)o)->type); } return PyObject_GenericGetAttr(o, attr_name); } -//!NEW< #ifdef PYPY_VERSION /* Because pypy does not work with the __dict__ tp_dictoffset. */ @@ -1767,7 +1765,6 @@ 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) { - //!NEW return _pg_EventGetAttr(o, attr_name); } return result; @@ -1822,28 +1819,30 @@ PyObject * pg_event_str(PyObject *self) { pgEventObject *e = (pgEventObject *)self; - //!NEW> if (!pgEvent_CheckExact(self)) { - PyObject *e_type = (PyObject *) Py_TYPE(self); + PyObject *e_type = (PyObject *)Py_TYPE(self); PyObject *e_name = PyObject_GetAttrString(e_type, "__name__"); PyObject *e_module = PyObject_GetAttrString(e_type, "__module__"); if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("%S.%S(%S)", e_module, e_name, e->dict); + return PyUnicode_FromFormat("%S.%S(%S)", e_module, e_name, + e->dict); return PyUnicode_FromFormat("%S.%S()", e_module, e_name); } - char* event_name = _pg_name_from_eventtype(e->type);; + char *event_name = _pg_name_from_eventtype(e->type); + ; - if (strcmp(event_name, "UserEvent")==0 || strcmp(event_name, "Unknown")==0) { + if (strcmp(event_name, "UserEvent") == 0 || + strcmp(event_name, "Unknown") == 0) { if (PyObject_IsTrue(e->dict)) return PyUnicode_FromFormat("Event(%d, %S)", e->type, e->dict); return PyUnicode_FromFormat("Event(%d)", e->type); } if (PyObject_IsTrue(e->dict)) - return PyUnicode_FromFormat("%s(%d, %S)", event_name, e->type, e->dict); + return PyUnicode_FromFormat("%s(%d, %S)", event_name, e->type, + e->dict); return PyUnicode_FromFormat("%s(%d)", event_name, e->type); - //!NEW< } static PyMemberDef pg_event_members[] = { @@ -1891,7 +1890,6 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) { int type; PyObject *dict = NULL; - //!NEW> PyObject *self_t = (PyObject *)Py_TYPE(self); if (PyObject_HasAttrString(self_t, "type")) { PyObject *type_o = PyObject_GetAttrString(self_t, "type"); @@ -1907,10 +1905,10 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) if (!PyArg_ParseTuple(args, "|O!", &PyDict_Type, &dict)) { return -1; } - } else if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { + } + else if (!PyArg_ParseTuple(args, "i|O!", &type, &PyDict_Type, &dict)) { return -1; } - //!NEW< if (type < 0 || type >= PG_NUMEVENTS) { PyErr_SetString(PyExc_ValueError, "event type out of range"); @@ -1952,54 +1950,49 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) return 0; } -//!NEW> static PyType_Slot pg_event_subclass_slots[] = { {Py_tp_base, &pgEvent_Type}, {Py_tp_getattro, _pg_EventGetAttr}, - {0, NULL} -}; + {0, NULL}}; /* Return a new reference. */ static PyObject * _pgEvent_CreateSubclass(Uint32 ev_type) { - const char* name = _pg_name_from_eventtype(ev_type); + const char *name = _pg_name_from_eventtype(ev_type); if (strcmp(name, "Unknown") == 0 || strcmp(name, "UserEvent") == 0) { Py_INCREF((PyObject *)&pgEvent_Type); return (PyObject *)&pgEvent_Type; } - size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") - char* joined = malloc(name_s); + size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") + char *joined = malloc(name_s); if (!joined) return PyErr_NoMemory(); PyOS_snprintf(joined, name_s, "pygame.event.%s", name); - PyType_Spec type_spec = { - joined, - 0, - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE, - pg_event_subclass_slots - }; + PyType_Spec type_spec = {joined, 0, 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE, + pg_event_subclass_slots}; - // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require a tuple for single base. - PyObject* bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); + // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require + // a tuple for single base. + PyObject *bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); if (bases == NULL) { free(joined); return NULL; } - PyObject* cls = PyType_FromSpecWithBases(&type_spec, bases); + PyObject *cls = PyType_FromSpecWithBases(&type_spec, bases); // Python <= 3.10 doesn't copy class name, but holds the reference, // Therefore for these versions, we need to leak "joined". - // This is not an issue, as these classes in theory should be created only once per type. - // Other solution is to generate class names statically. + // This is not an issue, as these classes in theory should be created only + // once per type. Other solution is to generate class names statically. #if PY_MINOR_VERSION > 10 free(joined); #endif @@ -2030,7 +2023,7 @@ pgEvent_GetClass(Uint32 e_type) return NULL; e_class = PyDict_GetItem(pg_event_lookup, e_typeo); - Py_XINCREF(e_class); // Claiming borrowed reference. + Py_XINCREF(e_class); // Claiming borrowed reference. if (e_class) { Py_DECREF(e_typeo); @@ -2044,8 +2037,7 @@ pgEvent_GetClass(Uint32 e_type) return NULL; } - if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) - { + if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) { Py_DECREF(e_typeo); Py_DECREF(e_class); return NULL; @@ -2067,15 +2059,14 @@ _register_user_event_class(PyObject *e_class) } else { RAISE(pgExc_SDLError, - "Exceeded maximimum number of allowed user-defined types."); + "Exceeded maximimum number of allowed user-defined types."); return -1; } if (!e_typeo) return -1; - if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) - { + if (PyDict_SetItem(pg_event_lookup, e_typeo, e_class) < 0) { Py_DECREF(e_typeo); return -1; } @@ -2112,20 +2103,22 @@ pg_event_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) static PyMethodDef eventobj_methods[] = { {"__init_subclass__", (PyCFunction)(void (*)(void))pg_event_init_subclass, - METH_VARARGS | METH_KEYWORDS | METH_CLASS}, - {NULL, NULL, 0, NULL} -}; + METH_VARARGS | METH_KEYWORDS | METH_CLASS}, + {NULL, NULL, 0, NULL}}; /* event metaclass */ -static PyObject* _pg_event_instantiate_class(Uint32 e_type, PyObject *e_dict) +static PyObject * +_pg_event_instantiate_class(Uint32 e_type, PyObject *e_dict) { PyObject *e_typeo = pgEvent_GetClass(e_type); if (!e_typeo) { return NULL; - } else if (e_typeo == (PyObject *)&pgEvent_Type) { + } + else if (e_typeo == (PyObject *)&pgEvent_Type) { Py_DECREF(e_typeo); - PyObject *ret = PyType_Type.tp_call((PyObject *)&pgEvent_Type, Py_BuildValue("(I)", e_type), e_dict); + PyObject *ret = PyType_Type.tp_call( + (PyObject *)&pgEvent_Type, Py_BuildValue("(I)", e_type), e_dict); return ret; } @@ -2134,7 +2127,9 @@ static PyObject* _pg_event_instantiate_class(Uint32 e_type, PyObject *e_dict) return ret; } -static PyObject* pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) { +static PyObject * +pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) +{ if (type == (PyObject *)&pgEvent_Type) { Uint32 e_type = 0; PyObject *e_dict = NULL; @@ -2165,25 +2160,22 @@ static PyObject* pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds return PyType_Type.tp_call(type, args, kwds); } -// After dropping support for python 3.8, the whole scheme of creating metaclasses to override pygame.event.Event(...) can be dropped in favor of tp_vectorcall. +// After dropping support for python 3.8, the whole scheme of creating +// metaclasses to override pygame.event.Event(...) can be dropped in favor of +// tp_vectorcall. static PyTypeObject pgEventMeta_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "pygame.event._EventMeta", + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event._EventMeta", .tp_basicsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_base = &PyType_Type, .tp_call = pgEventMeta_Call, }; -//!NEW< /* event type declaration */ static PyTypeObject pgEvent_Type = { - //!NEW - PyVarObject_HEAD_INIT(&pgEventMeta_Type, 0) - .tp_name = "pygame.event.Event", + PyVarObject_HEAD_INIT(&pgEventMeta_Type, 0).tp_name = "pygame.event.Event", .tp_basicsize = sizeof(pgEventObject), - //!NEW .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_dealloc = pg_event_dealloc, .tp_repr = pg_event_str, @@ -2192,7 +2184,6 @@ static PyTypeObject pgEvent_Type = { .tp_getattro = pg_EventGetAttr, .tp_setattro = pg_EventSetAttr, #else - //!NEW .tp_getattro = _pg_EventGetAttr, .tp_setattro = PyObject_GenericSetAttr, #endif @@ -2202,11 +2193,9 @@ static PyTypeObject pgEvent_Type = { .tp_dictoffset = offsetof(pgEventObject, dict), .tp_init = (initproc)pg_event_init, .tp_new = PyType_GenericNew, - //!NEW .tp_methods = eventobj_methods, }; -//!NEW> static PyObject * pg_event_class(PyObject *self, PyObject *args) { @@ -2255,7 +2244,8 @@ pgEvent_New(SDL_Event *event) if (!e_obj) return NULL; return e_obj; - } else { + } + else { // Plain event object - for unknown classes. Py_DECREF(e_typeo); e = PyObject_New(pgEventObject, &pgEvent_Type); @@ -2266,7 +2256,6 @@ pgEvent_New(SDL_Event *event) return (PyObject *)e; } } -//!NEW< /* event module functions */ @@ -2829,7 +2818,6 @@ pg_event_peek(PyObject *self, PyObject *args, PyObject *kwargs) * through our event filter, to do emulation stuff correctly. Then the * event is filtered after that */ -//!NEW> static PyObject * _post_event(Uint32 e_type, PyObject *obj) { @@ -2842,7 +2830,6 @@ _post_event(Uint32 e_type, PyObject *obj) return RAISE(pgExc_SDLError, SDL_GetError()); } } -//!NEW< static PyObject * pg_event_post(PyObject *self, PyObject *obj) @@ -2964,7 +2951,6 @@ pg_event_custom_type(PyObject *self, PyObject *_null) "pygame.event.custom_type made too many event types."); } -//!NEW> static PyObject * pg_event_name_to_id(PyObject *self, PyObject *attr) { @@ -2993,8 +2979,10 @@ pg_event__gettattr__(PyObject *self, PyObject *attr) int e_type = _pg_eventtype_from_name(attr_name); - if (e_type<0) - return PyErr_Format(PyExc_AttributeError, "module 'pygame.event' has no attribute '%s'", attr_name); + if (e_type < 0) + return PyErr_Format(PyExc_AttributeError, + "module 'pygame.event' has no attribute '%s'", + attr_name); return pgEvent_GetClass(e_type); } @@ -3050,7 +3038,6 @@ pg_event__dir__(PyObject *self, PyObject *args) return dir; } -//!NEW< static PyMethodDef _event_methods[] = { {"_internal_mod_init", (PyCFunction)pgEvent_AutoInit, METH_NOARGS, @@ -3083,18 +3070,13 @@ static PyMethodDef _event_methods[] = { DOC_EVENT_GETBLOCKED}, {"custom_type", (PyCFunction)pg_event_custom_type, METH_NOARGS, DOC_EVENT_CUSTOMTYPE}, - //!NEW - {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, - //!NEW - {"_name_to_id", (PyCFunction)pg_event_name_to_id, METH_O}, - //!NEW - {"__getattr__", (PyCFunction)pg_event__gettattr__, METH_O}, - //!NEW - {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS}, + {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, + {"_name_to_id", (PyCFunction)pg_event_name_to_id, METH_O}, + {"__getattr__", (PyCFunction)pg_event__gettattr__, METH_O}, + {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS}, {NULL, NULL, 0, NULL}}; - MODINIT_DEFINE(event) { PyObject *module, *apiobj; @@ -3105,7 +3087,7 @@ MODINIT_DEFINE(event) DOC_EVENT, -1, _event_methods, - NULL, // _event_slots, + NULL, NULL, NULL, NULL}; @@ -3124,11 +3106,9 @@ MODINIT_DEFINE(event) } /* type preparation */ - //!NEW> if (PyType_Ready(&pgEventMeta_Type) < 0) { return NULL; } - //!NEW< if (PyType_Ready(&pgEvent_Type) < 0) { return NULL; @@ -3140,14 +3120,13 @@ MODINIT_DEFINE(event) return NULL; } - //!NEW> Py_INCREF(&pgEventMeta_Type); - if (PyModule_AddObject(module, "_InternalEventMeta", (PyObject *)&pgEventMeta_Type)) { + if (PyModule_AddObject(module, "_InternalEventMeta", + (PyObject *)&pgEventMeta_Type)) { Py_DECREF(&pgEventMeta_Type); Py_DECREF(module); return NULL; } - //!NEW< Py_INCREF(&pgEvent_Type); if (PyModule_AddObject(module, "EventType", (PyObject *)&pgEvent_Type)) { @@ -3182,13 +3161,11 @@ MODINIT_DEFINE(event) return NULL; } - //!NEW> if (!pg_event_lookup) { pg_event_lookup = PyDict_New(); } - if (!pg_event_lookup) - { + if (!pg_event_lookup) { Py_DECREF(module); return NULL; } @@ -3204,7 +3181,6 @@ MODINIT_DEFINE(event) Py_DECREF(module); return NULL; } - //!NEW< SDL_RegisterEvents(PG_NUMEVENTS - SDL_USEREVENT); return module; From b0654aa778ddcd50be3f885b4cbcc7a39633c11e Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Tue, 21 May 2024 23:58:07 +0200 Subject: [PATCH 10/18] Fix fallback event init condition. --- src_c/event.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_c/event.c b/src_c/event.c index b101d6b52b..324a4c82dd 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1891,7 +1891,7 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) int type; PyObject *dict = NULL; PyObject *self_t = (PyObject *)Py_TYPE(self); - if (PyObject_HasAttrString(self_t, "type")) { + if (self_t != (PyObject *)&pgEvent_Type) { PyObject *type_o = PyObject_GetAttrString(self_t, "type"); if (!type_o) return -1; From c6822c7f6ca8d250c1135d00613776dce5ed8cce Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Wed, 22 May 2024 09:44:58 +0200 Subject: [PATCH 11/18] Some fixes. --- src_c/event.c | 30 ++++++++++++++++++++---------- test/midi_test.py | 5 +---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 324a4c82dd..2927339713 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -903,9 +903,10 @@ _very_bad_hash(const char *str) { // Rarely works, but for existing names no collisions! unsigned int sum = 0; + int size = (int)strlen(str); int off = 1; - for (int i = 0; i < strlen(str); i++) { + for (int i = 0; i < size; i++) { sum += str[i]; sum = sum ^ (sum >> off); off += 1; @@ -978,7 +979,9 @@ _pg_eventtype_from_name_hash(const char *name) else: text += f" case {hashes_db[line[0]]}" \ f": //{line[0]}\n " \ - f"return {line[1]};\n" return text + f"return {line[1]};\n" + + return text if __name__ == "__main__": @@ -2058,8 +2061,9 @@ _register_user_event_class(PyObject *e_class) e_typeo = PyLong_FromLong(_custom_event++); } else { - RAISE(pgExc_SDLError, - "Exceeded maximimum number of allowed user-defined types."); + PyErr_SetString( + pgExc_SDLError, + "Exceeded maximimum number of allowed user-defined types."); return -1; } @@ -2134,10 +2138,14 @@ pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) Uint32 e_type = 0; PyObject *e_dict = NULL; - if (!PyArg_ParseTuple(args, "I|O!", &e_type, &PyDict_Type, &e_dict)) { + if (!PyArg_ParseTuple(args, "i|O!", &e_type, &PyDict_Type, &e_dict)) { return NULL; } + if (e_type < 0 || e_type >= PG_NUMEVENTS) { + return RAISE(PyExc_ValueError, "event type out of range"); + } + if (kwds && !e_dict) { e_dict = PyDict_New(); if (!e_dict) @@ -2167,7 +2175,6 @@ static PyTypeObject pgEventMeta_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event._EventMeta", .tp_basicsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_base = &PyType_Type, .tp_call = pgEventMeta_Call, }; @@ -3007,33 +3014,34 @@ pg_event__dir__(PyObject *self, PyObject *args) } PyObject *result = PyDict_Keys(dict); + Py_ssize_t res_size = PyList_Size(result); Py_DECREF(dict); if (!result) { return NULL; } - PyObject *dir = PyList_New(PyList_Size(result) + length); + PyObject *dir = PyList_New(res_size + length); if (!dir) { Py_DECREF(result); return NULL; } - for (Py_ssize_t idx = 0; idx < PyList_Size(result); idx++) { + for (Py_ssize_t idx = 0; idx < res_size; idx++) { PyObject *item = PyList_GetItem(result, idx); Py_INCREF(item); PyList_SetItem(dir, idx, item); } Py_DECREF(result); - for (Py_ssize_t idx = 0; idx < length; idx++) { + for (size_t idx = 0; idx < length; idx++) { PyObject *item = PyUnicode_FromString(_event_names[idx]); if (!item) { Py_DECREF(dir); return NULL; } - PyList_SetItem(dir, idx + PyList_Size(result), item); + PyList_SetItem(dir, idx + res_size, item); } return dir; @@ -3106,6 +3114,8 @@ MODINIT_DEFINE(event) } /* type preparation */ + pgEventMeta_Type.tp_base = &PyType_Type; + pgEventMeta_Type.ob_base = *(PyVarObject *)&PyType_Type; if (PyType_Ready(&pgEventMeta_Type) < 0) { return NULL; } diff --git a/test/midi_test.py b/test/midi_test.py index f4189a2351..ab0ea74f81 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) 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]) From 51fd400374c03daa8923133693d6e0691404e76b Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Wed, 22 May 2024 11:28:26 +0200 Subject: [PATCH 12/18] Basic stub. --- buildconfig/stubs/pygame/event.pyi | 242 ++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 4 deletions(-) diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index dc9a1c852f..b3051024af 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -4,25 +4,256 @@ from typing import ( List, Optional, Union, - final, + Literal, + overload, + Type, ) from ._common import Sequence -@final -class Event: +class _EventMeta(type): ... + +class Event(metaclass=_EventMeta): type: int dict: Dict[str, Any] __dict__: Dict[str, Any] __hash__: None # type: ignore + @overload + def __init__( + self, type: int, dict: Dict[str, Any] = ..., /, **kwargs: Any + ) -> None: ... + @overload def __init__( - self, type: int, dict: Dict[str, Any] = ..., **kwargs: Any + 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: ... +# TODO: fill with arguments. + +class ActiveEvent(Event): + type: Literal[32768] = 32768 + +class AppTerminating(Event): + type: Literal[257] = 257 + +class AppLowMemory(Event): + type: Literal[258] = 258 + +class AppWillEnterBackground(Event): + type: Literal[259] = 259 + +class AppDidEnterBackground(Event): + type: Literal[260] = 260 + +class AppWillEnterForeground(Event): + type: Literal[261] = 261 + +class AppDidEnterForeground(Event): + type: Literal[262] = 262 + +class ClipboardUpdate(Event): + type: Literal[2304] = 2304 + +class KeyDown(Event): + type: Literal[768] = 768 + +class KeyUp(Event): + type: Literal[769] = 769 + +class KeyMapChanged(Event): + type: Literal[772] = 772 + +class LocaleChanged(Event): + """Only for SDL 2.0.14+""" + type: Literal[263] = 263 + +class MouseMotion(Event): + type: Literal[1024] = 1024 + +class MouseButtonDown(Event): + type: Literal[1025] = 1025 + +class MouseButtonUp(Event): + type: Literal[1026] = 1026 + +class JoyAxisMotion(Event): + type: Literal[1536] = 1536 + +class JoyBallMotion(Event): + type: Literal[1537] = 1537 + +class JoyHatMotion(Event): + type: Literal[1538] = 1538 + +class JoyButtonUp(Event): + type: Literal[1540] = 1540 + +class JoyButtonDown(Event): + type: Literal[1539] = 1539 + +class Quit(Event): + type: Literal[256] = 256 + +class SysWMEvent(Event): + type: Literal[513] = 513 + +class VideoResize(Event): + type: Literal[32769] = 32769 + +class VideoExpose(Event): + type: Literal[32770] = 32770 + +class MidiIn(Event): + type: Literal[32771] = 32771 + +class MidiOut(Event): + type: Literal[32772] = 32772 + +class NoEvent(Event): + type: Literal[0] = 0 + +class FingerMotion(Event): + type: Literal[1794] = 1794 + +class FingerDown(Event): + type: Literal[1792] = 1792 + +class FingerUp(Event): + type: Literal[1793] = 1793 + +class MultiGesture(Event): + type: Literal[2050] = 2050 + +class MouseWheel(Event): + type: Literal[1027] = 1027 + +class TextInput(Event): + type: Literal[771] = 771 + +class TextEditing(Event): + type: Literal[770] = 770 + +class DropFile(Event): + type: Literal[4096] = 4096 + +class DropText(Event): + type: Literal[4097] = 4097 + +class DropBegin(Event): + type: Literal[4098] = 4098 + +class DropComplete(Event): + type: Literal[4099] = 4099 + +class ControllerAxisMotion(Event): + type: Literal[1616] = 1616 + +class ControllerButtonDown(Event): + type: Literal[1617] = 1617 + +class ControllerButtonUp(Event): + type: Literal[1618] = 1618 + +class ControllerDeviceAdded(Event): + type: Literal[1619] = 1619 + +class ControllerDeviceRemoved(Event): + type: Literal[1620] = 1620 + +class ControllerDeviceMapped(Event): + type: Literal[1621] = 1621 + +class JoyDeviceAdded(Event): + type: Literal[1541] = 1541 + +class JoyDeviceRemoved(Event): + type: Literal[1542] = 1542 + +class ControllerTouchpadDown(Event): + """Only for SDL 2.0.14+""" + type: Literal[1622] = 1622 + +class ControllerTouchpadMotion(Event): + """Only for SDL 2.0.14+""" + type: Literal[1623] = 1623 + +class ControllerTouchpadUp(Event): + """Only for SDL 2.0.14+""" + type: Literal[1624] = 1624 + +class ControllerSensorUpdate(Event): + """Only for SDL 2.0.14+""" + type: Literal[1625] = 1625 + +class AudioDeviceAdded(Event): + type: Literal[4352] = 4352 + +class AudioDeviceRemoved(Event): + type: Literal[4353] = 4353 + +class RenderTargetsReset(Event): + type: Literal[8192] = 8192 + +class RenderDeviceReset(Event): + type: Literal[8193] = 8193 + +class WindowShown(Event): + type: Literal[32774] = 32774 + +class WindowHidden(Event): + type: Literal[32775] = 32775 + +class WindowExposed(Event): + type: Literal[32776] = 32776 + +class WindowMoved(Event): + type: Literal[32777] = 32777 + +class WindowResized(Event): + type: Literal[32778] = 32778 + +class WindowSizeChanged(Event): + type: Literal[32779] = 32779 + +class WindowMinimized(Event): + type: Literal[32780] = 32780 + +class WindowMaximized(Event): + type: Literal[32781] = 32781 + +class WindowRestored(Event): + type: Literal[32782] = 32782 + +class WindowEnter(Event): + type: Literal[32783] = 32783 + +class WindowLeave(Event): + type: Literal[32784] = 32784 + +class WindowFocusGained(Event): + type: Literal[32785] = 32785 + +class WindowFocusLost(Event): + type: Literal[32786] = 32786 + +class WindowClose(Event): + type: Literal[32787] = 32787 + +class WindowTakeFocus(Event): + type: Literal[32788] = 32788 + +class WindowHitTest(Event): + type: Literal[32789] = 32789 + +class WindowICCProfChanged(Event): + type: Literal[32790] = 32790 + +class WindowDisplayChanged(Event): + type: Literal[32791] = 32791 + _EventTypes = Union[int, Sequence[int]] def pump() -> None: ... @@ -43,5 +274,8 @@ def set_grab(grab: bool, /) -> None: ... def get_grab() -> bool: ... def post(event: Event, /) -> bool: ... def custom_type() -> int: ... +def event_class(type: int, /) -> Type[Event]: ... +def __dir__() -> List[str]: ... +def __getattr__(name: str, /) -> object: ... EventType = Event From e9d250d259b44ed2a8420020e8529db208e29d2d Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Wed, 22 May 2024 12:23:13 +0200 Subject: [PATCH 13/18] Stubs finished. --- buildconfig/stubs/pygame/event.pyi | 179 ++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index b3051024af..1dad5f44f9 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -2,6 +2,7 @@ from typing import ( Any, Dict, List, + Tuple, Optional, Union, Literal, @@ -9,7 +10,8 @@ from typing import ( Type, ) -from ._common import Sequence +from pygame._common import Sequence +from pygame.window import Window class _EventMeta(type): ... @@ -24,17 +26,17 @@ class Event(metaclass=_EventMeta): ) -> None: ... @overload def __init__( - self, type: int, dict: Dict[str, Any] = ..., /, **kwargs: Any + self, 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: ... -# TODO: fill with arguments. - class ActiveEvent(Event): type: Literal[32768] = 32768 + gain: int + state: int class AppTerminating(Event): type: Literal[257] = 257 @@ -59,9 +61,19 @@ class ClipboardUpdate(Event): class KeyDown(Event): type: Literal[768] = 768 + unicode: str + key: int + mod: int + scancode: int + window: Optional[Window] class KeyUp(Event): type: Literal[769] = 769 + unicode: str + key: int + mod: int + scancode: int + window: Optional[Window] class KeyMapChanged(Event): type: Literal[772] = 772 @@ -72,36 +84,82 @@ class LocaleChanged(Event): class MouseMotion(Event): type: Literal[1024] = 1024 + pos: Tuple[int, int] + rel: Tuple[int, int] + buttons: tuple[int, int, int] + touch: bool + window: Optional[Window] class MouseButtonDown(Event): type: Literal[1025] = 1025 + pos: Tuple[int, int] + button: int + touch: bool + window: Optional[Window] class MouseButtonUp(Event): type: Literal[1026] = 1026 + pos: Tuple[int, int] + button: int + touch: bool + window: Optional[Window] class JoyAxisMotion(Event): type: Literal[1536] = 1536 + joy: int + instance_id: int + axis: int + value: float class JoyBallMotion(Event): type: Literal[1537] = 1537 + joy: int + instance_id: int + ball: int + rel: Tuple[int, int] class JoyHatMotion(Event): type: Literal[1538] = 1538 + joy: int + instance_id: int + hat: int + value: Tuple[int, int] class JoyButtonUp(Event): type: Literal[1540] = 1540 + joy: int + instance_id: int + button: int class JoyButtonDown(Event): type: Literal[1539] = 1539 + joy: int + instance_id: int + button: int class Quit(Event): type: Literal[256] = 256 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: Literal[513] = 513 + hwnd: int + msg: int + wparam: int + lparam: int + event: bytes class VideoResize(Event): type: Literal[32769] = 32769 + size: Tuple[int, int] + w: int + h: int class VideoExpose(Event): type: Literal[32770] = 32770 @@ -116,73 +174,157 @@ class NoEvent(Event): type: Literal[0] = 0 class FingerMotion(Event): + """Attribute "window" avalible only for SDL 2.0.14+""" type: Literal[1794] = 1794 + 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: Literal[1792] = 1792 + 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: Literal[1793] = 1793 + touch_id: int + finger_id: int + x: float + y: float + dx: float + dy: float + pressure: float + window: Optional[Window] class MultiGesture(Event): type: Literal[2050] = 2050 + touch_id: int + x: float + y: float + rotated: float + pinched: float + num_fingers: int class MouseWheel(Event): type: Literal[1027] = 1027 + flipped: bool + x: int + y: int + precise_x: float + precise_y: float + touch: bool + window: Optional[Window] class TextInput(Event): type: Literal[771] = 771 + text: str + window: Optional[Window] class TextEditing(Event): type: Literal[770] = 770 + text: str + start: int + length: int + window: Optional[Window] class DropFile(Event): type: Literal[4096] = 4096 + file: str + window: Optional[Window] class DropText(Event): type: Literal[4097] = 4097 + text: str + window: Optional[Window] class DropBegin(Event): type: Literal[4098] = 4098 + window: Optional[Window] class DropComplete(Event): type: Literal[4099] = 4099 + window: Optional[Window] class ControllerAxisMotion(Event): type: Literal[1616] = 1616 + instance_id: int + axis: int + value: int class ControllerButtonDown(Event): type: Literal[1617] = 1617 + instance_id: int + button: int class ControllerButtonUp(Event): type: Literal[1618] = 1618 + instance_id: int + button: int class ControllerDeviceAdded(Event): type: Literal[1619] = 1619 + device_index: int + guid: str class ControllerDeviceRemoved(Event): type: Literal[1620] = 1620 + instance_id: int class ControllerDeviceMapped(Event): type: Literal[1621] = 1621 + instance_id: int class JoyDeviceAdded(Event): type: Literal[1541] = 1541 + device_index: int + guid: str class JoyDeviceRemoved(Event): type: Literal[1542] = 1542 + instance_id: int class ControllerTouchpadDown(Event): """Only for SDL 2.0.14+""" type: Literal[1622] = 1622 + 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: Literal[1623] = 1623 + 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: Literal[1624] = 1624 + instance_id: int + touch_id: int + finger_id: int + x: float + y: float + pressure: float class ControllerSensorUpdate(Event): """Only for SDL 2.0.14+""" @@ -190,9 +332,13 @@ class ControllerSensorUpdate(Event): class AudioDeviceAdded(Event): type: Literal[4352] = 4352 + which: int + iscapture: int class AudioDeviceRemoved(Event): type: Literal[4353] = 4353 + which: int + iscapture: int class RenderTargetsReset(Event): type: Literal[8192] = 8192 @@ -202,57 +348,82 @@ class RenderDeviceReset(Event): class WindowShown(Event): type: Literal[32774] = 32774 + window: Optional[Window] class WindowHidden(Event): type: Literal[32775] = 32775 + window: Optional[Window] class WindowExposed(Event): type: Literal[32776] = 32776 + window: Optional[Window] class WindowMoved(Event): type: Literal[32777] = 32777 + x: int + y: int + window: Optional[Window] class WindowResized(Event): type: Literal[32778] = 32778 + x: int + y: int + window: Optional[Window] class WindowSizeChanged(Event): type: Literal[32779] = 32779 + x: int + y: int + window: Optional[Window] class WindowMinimized(Event): type: Literal[32780] = 32780 + window: Optional[Window] class WindowMaximized(Event): type: Literal[32781] = 32781 + window: Optional[Window] class WindowRestored(Event): type: Literal[32782] = 32782 + window: Optional[Window] class WindowEnter(Event): type: Literal[32783] = 32783 + window: Optional[Window] class WindowLeave(Event): type: Literal[32784] = 32784 + window: Optional[Window] class WindowFocusGained(Event): type: Literal[32785] = 32785 + window: Optional[Window] class WindowFocusLost(Event): type: Literal[32786] = 32786 + window: Optional[Window] class WindowClose(Event): type: Literal[32787] = 32787 + window: Optional[Window] class WindowTakeFocus(Event): type: Literal[32788] = 32788 + window: Optional[Window] class WindowHitTest(Event): type: Literal[32789] = 32789 + window: Optional[Window] class WindowICCProfChanged(Event): type: Literal[32790] = 32790 + window: Optional[Window] class WindowDisplayChanged(Event): type: Literal[32791] = 32791 + display_index: int + window: Optional[Window] _EventTypes = Union[int, Sequence[int]] From cb22bbc87bc676bac1b0c3a534eaf670d46cf3b5 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Thu, 23 May 2024 12:51:35 +0200 Subject: [PATCH 14/18] PyType_FromSpecWithBases is too fragile version-wise, changing approach. --- src_c/event.c | 57 ++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 2927339713..a1546bc2c6 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -311,6 +311,7 @@ _pg_get_event_unicode(SDL_Event *event) return proxify ? PGPOST_##name : PGE_##name static PyTypeObject pgEvent_Type; +static PyTypeObject pgEventMeta_Type; #define pgEvent_CheckExact(x) ((x)->ob_type == &pgEvent_Type) #define pgEvent_Check(x) (PyObject_IsInstance(x, (PyObject *)&pgEvent_Type)) #define pgEvent_IsSubclass(x) \ @@ -1953,11 +1954,6 @@ pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) return 0; } -static PyType_Slot pg_event_subclass_slots[] = { - {Py_tp_base, &pgEvent_Type}, - {Py_tp_getattro, _pg_EventGetAttr}, - {0, NULL}}; - /* Return a new reference. */ static PyObject * _pgEvent_CreateSubclass(Uint32 ev_type) @@ -1969,49 +1965,47 @@ _pgEvent_CreateSubclass(Uint32 ev_type) return (PyObject *)&pgEvent_Type; } - size_t name_s = strlen(name) + 14; // 14 = len("pygame.event.\x00") - char *joined = malloc(name_s); - - if (!joined) - return PyErr_NoMemory(); - - PyOS_snprintf(joined, name_s, "pygame.event.%s", name); + PyObject *dict = PyDict_New(); + if (!dict) + return NULL; - PyType_Spec type_spec = {joined, 0, 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE, - pg_event_subclass_slots}; + PyObject *args = + Py_BuildValue("s(O)N", name, (PyObject *)&pgEvent_Type, dict); + if (!args) + return NULL; - // Note: for Python 3.10+ onwards, PyType_FromSpecWithBases doesn't require - // a tuple for single base. - PyObject *bases = PyTuple_Pack(1, (PyObject *)&pgEvent_Type); + PyObject *cls = + PyType_Type.tp_call((PyObject *)&pgEventMeta_Type, args, NULL); + Py_DECREF(args); - if (bases == NULL) { - free(joined); + if (!cls) { return NULL; } - PyObject *cls = PyType_FromSpecWithBases(&type_spec, bases); - - // Python <= 3.10 doesn't copy class name, but holds the reference, - // Therefore for these versions, we need to leak "joined". - // This is not an issue, as these classes in theory should be created only - // once per type. Other solution is to generate class names statically. -#if PY_MINOR_VERSION > 10 - free(joined); -#endif - PyObject *value = PyLong_FromLong(ev_type); if (!value) { return NULL; } - if (PyObject_SetAttrString((PyObject *)cls, "type", value) < 0) { + if (PyObject_SetAttrString(cls, "type", value) < 0) { Py_DECREF(value); return NULL; } Py_DECREF(value); + PyObject *mod_name = PyUnicode_FromString("pygame.event"); + if (!mod_name) { + return NULL; + } + + if (PyObject_SetAttrString(cls, "__module__", mod_name) < 0) { + Py_DECREF(mod_name); + return NULL; + } + + Py_DECREF(mod_name); + return cls; } @@ -3115,7 +3109,6 @@ MODINIT_DEFINE(event) /* type preparation */ pgEventMeta_Type.tp_base = &PyType_Type; - pgEventMeta_Type.ob_base = *(PyVarObject *)&PyType_Type; if (PyType_Ready(&pgEventMeta_Type) < 0) { return NULL; } From 245ed1a082a7f5f849cf17892a1143895387674d Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Fri, 24 May 2024 23:02:56 +0200 Subject: [PATCH 15/18] Tests. --- src_c/event.c | 72 +++++++++++++++++++++++++++++++++------------- test/event_test.py | 46 ++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index a1546bc2c6..5db80f200f 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1771,6 +1771,9 @@ pg_EventGetAttr(PyObject *o, PyObject *attr_name) if (!result) { return _pg_EventGetAttr(o, attr_name); } + else { + Py_INCREF(result); + } return result; } @@ -1892,7 +1895,7 @@ pg_event_richcompare(PyObject *o1, PyObject *o2, int opid) static int pg_event_init(pgEventObject *self, PyObject *args, PyObject *kwargs) { - int type; + int type = 0; PyObject *dict = NULL; PyObject *self_t = (PyObject *)Py_TYPE(self); if (self_t != (PyObject *)&pgEvent_Type) { @@ -2115,12 +2118,26 @@ _pg_event_instantiate_class(Uint32 e_type, PyObject *e_dict) } else if (e_typeo == (PyObject *)&pgEvent_Type) { Py_DECREF(e_typeo); - PyObject *ret = PyType_Type.tp_call( - (PyObject *)&pgEvent_Type, Py_BuildValue("(I)", e_type), e_dict); + PyObject *args = Py_BuildValue("(iO)", e_type, e_dict); + + if (!args) { + Py_DECREF(e_typeo); + return NULL; + } + + PyObject *ret = + PyType_Type.tp_call((PyObject *)&pgEvent_Type, args, NULL); return ret; } - PyObject *ret = PyType_Type.tp_call(e_typeo, PyTuple_New(0), e_dict); + PyObject *args = Py_BuildValue("(O)", e_dict); + + if (!args) { + Py_DECREF(e_typeo); + return NULL; + } + + PyObject *ret = PyType_Type.tp_call(e_typeo, args, NULL); Py_DECREF(e_typeo); return ret; } @@ -2129,7 +2146,7 @@ static PyObject * pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) { if (type == (PyObject *)&pgEvent_Type) { - Uint32 e_type = 0; + int e_type = 0; PyObject *e_dict = NULL; if (!PyArg_ParseTuple(args, "i|O!", &e_type, &PyDict_Type, &e_dict)) { @@ -2140,31 +2157,29 @@ pgEventMeta_Call(PyObject *type, PyObject *args, PyObject *kwds) return RAISE(PyExc_ValueError, "event type out of range"); } - if (kwds && !e_dict) { + if (e_dict) + Py_INCREF(e_dict); + + if (!e_dict) { e_dict = PyDict_New(); if (!e_dict) return NULL; } - if (e_dict && kwds && PyDict_Update(e_dict, kwds) < 0) { + if (kwds && PyDict_Update(e_dict, kwds) < 0) { Py_DECREF(e_dict); return NULL; } PyObject *ret = _pg_event_instantiate_class(e_type, e_dict); - // By trial and error, I discovered that this shouldn't be here. - // But I would like to know why - by reading the code it seems that - // this should be here - does PyArg_ParseTuple increase the refcount? - // Py_XDECREF(e_dict); + Py_XDECREF(e_dict); + return ret; } return PyType_Type.tp_call(type, args, kwds); } -// After dropping support for python 3.8, the whole scheme of creating -// metaclasses to override pygame.event.Event(...) can be dropped in favor of -// tp_vectorcall. static PyTypeObject pgEventMeta_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.event._EventMeta", .tp_basicsize = 0, @@ -2224,7 +2239,6 @@ pgEvent_New(SDL_Event *event) obj = PyDict_New(); } if (!obj) { - Py_XDECREF(obj); return PyErr_NoMemory(); } if (pgEvent_Check(obj)) @@ -2237,7 +2251,7 @@ pgEvent_New(SDL_Event *event) return NULL; } - if (PyObject_HasAttrString(e_typeo, "type")) { + if (e_typeo != (PyObject *)&pgEvent_Type) { // PyTuple_New(0) returns an immortal object and should always succeed. e_obj = PyObject_Call(e_typeo, PyTuple_New(0), obj); Py_DECREF(e_typeo); @@ -2249,12 +2263,30 @@ pgEvent_New(SDL_Event *event) else { // Plain event object - for unknown classes. Py_DECREF(e_typeo); - e = PyObject_New(pgEventObject, &pgEvent_Type); - if (!e) - return PyErr_NoMemory(); + + PyObject *e_type_num = PyLong_FromLong(e_type); + if (!e_type_num) + return NULL; + + PyObject *args = PyTuple_New(1); + if (!args) { + Py_DECREF(e_typeo); + Py_DECREF(e_type_num); + return NULL; + } + + PyTuple_SET_ITEM(args, 0, e_type_num); + e_obj = PyObject_Call(e_typeo, args, obj); + Py_DECREF(args); + + if (!e_obj) { + return NULL; + } + + e = (pgEventObject *)e_obj; e->type = e_type; e->dict = obj; - return (PyObject *)e; + return e_obj; } } diff --git a/test/event_test.py b/test/event_test.py index c3a3d5e1c3..3f27c63308 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -293,6 +293,21 @@ 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.assertIn("type", MyEvent.__dict__) + 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. @@ -484,6 +499,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() @@ -922,6 +948,23 @@ 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: + if ev_type == pygame.USEREVENT: + self.assertIs(pygame.event.event_class(ev_type), pygame.event.Event) + else: + 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) + class EventModuleTestsWithTiming(unittest.TestCase): __tags__ = ["timing"] @@ -965,7 +1008,8 @@ def test_event_wait(self): (70, pygame.NOEVENT, 70), ): start_time = time.perf_counter() - self.assertEqual(pygame.event.wait(wait_time).type, expected_type) + ev = pygame.event.wait(wait_time) + self.assertEqual(ev.type, expected_type) self.assertAlmostEqual( time.perf_counter() - start_time, expected_time / 1000, delta=0.01 ) From c900dcbc5c863adae7081fc7451f8071fcbe6ea0 Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Sat, 25 May 2024 23:29:06 +0200 Subject: [PATCH 16/18] Documentation. --- buildconfig/stubs/pygame/event.pyi | 5 ++ docs/reST/c_api/event.rst | 6 ++ docs/reST/ref/event.rst | 92 ++++++++++++++++++++++++++++++ src_c/_pygame.h | 2 +- src_c/event.c | 7 ++- src_c/include/_pygame.h | 3 + 6 files changed, 111 insertions(+), 4 deletions(-) diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi index 1dad5f44f9..809c8fee2c 100644 --- a/buildconfig/stubs/pygame/event.pyi +++ b/buildconfig/stubs/pygame/event.pyi @@ -105,6 +105,7 @@ class MouseButtonUp(Event): window: Optional[Window] class JoyAxisMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" type: Literal[1536] = 1536 joy: int instance_id: int @@ -112,6 +113,7 @@ class JoyAxisMotion(Event): value: float class JoyBallMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" type: Literal[1537] = 1537 joy: int instance_id: int @@ -119,6 +121,7 @@ class JoyBallMotion(Event): rel: Tuple[int, int] class JoyHatMotion(Event): + """Attribute "joy" is depracated, use "instance_id".""" type: Literal[1538] = 1538 joy: int instance_id: int @@ -126,12 +129,14 @@ class JoyHatMotion(Event): value: Tuple[int, int] class JoyButtonUp(Event): + """Attribute "joy" is depracated, use "instance_id".""" type: Literal[1540] = 1540 joy: int instance_id: int button: int class JoyButtonDown(Event): + """Attribute "joy" is depracated, use "instance_id".""" type: Literal[1539] = 1539 joy: int instance_id: int diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index b46a0e0697..278a43b240 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -79,3 +79,9 @@ Header file: src_c/include/pygame.h 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. + +.. c:function:: PyObject* pgEvent_GetClass(Uint32 type) + + Returns a python class object correlated with the given event type - object is returned + as a new reference. On error returns NULL and sets python exception. + Same as calling ``pygame.event.event_class(type)`` in python. diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index 89562572a9..669e2f4bdf 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -463,6 +463,19 @@ 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, instead of ``pygame.event.Event`` sublclass returned, + ``pygame.event.Event`` itself will be returned, so don't rely on the retuned class having ``type`` attribute equal to a number. + This happens for example, with user event types that weren't created by subclassing ``pygame.event.Event``. + + .. versionadded:: 2.5.0 + .. ## pygame.event.event_class ## + .. class:: Event | :sl:`pygame object for representing events` @@ -477,6 +490,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.5.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, @@ -515,4 +530,81 @@ On Android, the following events can be generated .. ## pygame.event.Event ## +List of Event subclasses available as aliases for event types importable as `pygame.event.{Class name}`. +============================= ============================= ========================== +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`` + .. ## pygame.event ## diff --git a/src_c/_pygame.h b/src_c/_pygame.h index b810000c77..a8762c4249 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -533,7 +533,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 11 #define PYGAMEAPI_WINDOW_NUMSLOTS 1 #define PYGAMEAPI_GEOMETRY_NUMSLOTS 1 diff --git a/src_c/event.c b/src_c/event.c index 5db80f200f..f99fb26bcb 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -2013,11 +2013,11 @@ _pgEvent_CreateSubclass(Uint32 ev_type) } static PyObject * -pgEvent_GetClass(Uint32 e_type) +pgEvent_GetClass(Uint32 type) { PyObject *e_typeo, *e_class; - e_typeo = PyLong_FromLong(e_type); + e_typeo = PyLong_FromLong(type); if (!e_typeo) return NULL; @@ -2030,7 +2030,7 @@ pgEvent_GetClass(Uint32 e_type) return e_class; } - e_class = _pgEvent_CreateSubclass(e_type); + e_class = _pgEvent_CreateSubclass(type); if (!e_class) { Py_DECREF(e_typeo); @@ -3188,6 +3188,7 @@ MODINIT_DEFINE(event) c_api[7] = pgEvent_GetKeyUpInfo; c_api[8] = pgEvent_GetMouseButtonDownInfo; c_api[9] = pgEvent_GetMouseButtonUpInfo; + c_api[10] = pgEvent_GetClass; apiobj = encapsulate_api(c_api, "event"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index 508d89fbd1..7e93f037a9 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -415,6 +415,9 @@ typedef struct pgEventObject pgEventObject; #define pgEvent_GetMouseButtonUpInfo \ (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 9)) +#define pgEvent_GetClass \ + (*(PyObject * (*)(Uint32)) PYGAMEAPI_GET_SLOT(event, 10)) + #define import_pygame_event() IMPORT_PYGAME_MODULE(event) #endif From 09d77ebcee9e6be5507fef5377d5a195ec88ffad Mon Sep 17 00:00:00 2001 From: polastyn <78505251+gresm@users.noreply.github.com> Date: Sun, 26 May 2024 08:41:58 +0200 Subject: [PATCH 17/18] Fix table. --- docs/reST/ref/event.rst | 159 ++++++++++++++++++++-------------------- src_c/doc/event_doc.h | 1 + src_c/event.c | 3 +- 3 files changed, 84 insertions(+), 79 deletions(-) diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index 669e2f4bdf..1b5377308b 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -465,8 +465,8 @@ On Android, the following events can be generated .. function:: event_class - | :sl: `returns related event class to event type` - | :sg: `event_class(type: int, /) -> Type[Event]` + | :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, instead of ``pygame.event.Event`` sublclass returned, @@ -530,81 +530,84 @@ On Android, the following events can be generated .. ## pygame.event.Event ## -List of Event subclasses available as aliases for event types importable as `pygame.event.{Class name}`. -============================= ============================= ========================== -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`` +.. 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`` + ============================= ============================= ========================== .. ## pygame.event ## diff --git a/src_c/doc/event_doc.h b/src_c/doc/event_doc.h index b23e5dce05..ff5290161c 100644 --- a/src_c/doc/event_doc.h +++ b/src_c/doc/event_doc.h @@ -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/event.c b/src_c/event.c index f99fb26bcb..754760a3c1 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -3104,7 +3104,8 @@ static PyMethodDef _event_methods[] = { DOC_EVENT_GETBLOCKED}, {"custom_type", (PyCFunction)pg_event_custom_type, METH_NOARGS, DOC_EVENT_CUSTOMTYPE}, - {"event_class", (PyCFunction)pg_event_class, METH_VARARGS}, + {"event_class", (PyCFunction)pg_event_class, METH_VARARGS, + DOC_EVENT_EVENTCLASS}, {"_name_to_id", (PyCFunction)pg_event_name_to_id, METH_O}, {"__getattr__", (PyCFunction)pg_event__gettattr__, METH_O}, {"__dir__", (PyCFunction)pg_event__dir__, METH_NOARGS}, From c736fe7c8d986c1673dde674510ce9474cddf175 Mon Sep 17 00:00:00 2001 From: gresm <78505251+gresm@users.noreply.github.com> Date: Wed, 29 May 2024 23:22:26 +0200 Subject: [PATCH 18/18] Update docs/reST/ref/event.rst Co-authored-by: Dan Lawrence --- docs/reST/ref/event.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index 1b5377308b..989baa24e1 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -469,7 +469,7 @@ On Android, the following events can be generated | :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, instead of ``pygame.event.Event`` sublclass returned, + but the type is within the range of valid values for the event type, instead of a ``pygame.event.Event`` subclass, ``pygame.event.Event`` itself will be returned, so don't rely on the retuned class having ``type`` attribute equal to a number. This happens for example, with user event types that weren't created by subclassing ``pygame.event.Event``.