From ffd17758a238409a472bf13b00549bcc9647dac4 Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Wed, 19 Feb 2020 10:05:09 +0100 Subject: [PATCH 1/9] post keydown events, closes #1580 --- src_c/event.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 4cee4b3b94..718f7468a8 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1746,9 +1746,18 @@ pg_event_post(PyObject *self, PyObject *args) Py_RETURN_NONE; } - if (pgEvent_FillUserEvent(e, &event)) - return NULL; + if (e->type == SDL_KEYDOWN || e->type == SDL_KEYUP){ + event.type = e->type; + event.key.keysym.sym = PyLong_AsLong(PyDict_GetItemString(e->dict, "key")); + if (PyDict_GetItemString(e->dict, "scancode") != NULL) + event.key.keysym.scancode = PyLong_AsLong(PyDict_GetItemString(e->dict, "scancode")); + if (PyDict_GetItemString(e->dict, "mod") != NULL) + event.key.keysym.mod = PyLong_AsLong(PyDict_GetItemString(e->dict, "mod")); + } else { + if (pgEvent_FillUserEvent(e, &event)) + return NULL; + } #if IS_SDLv1 if (SDL_PushEvent(&event) == -1) #else /* IS_SDLv2 */ From 90ff5fffe68a7237cb38672ca8c4b396328d6c6c Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Sun, 23 Feb 2020 13:55:36 +0100 Subject: [PATCH 2/9] error handling --- src_c/event.c | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index 718f7468a8..d1d36ebf03 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1747,13 +1747,43 @@ pg_event_post(PyObject *self, PyObject *args) } if (e->type == SDL_KEYDOWN || e->type == SDL_KEYUP){ + PyObject *event_key = PyDict_GetItemString(e->dict, "key"); + PyObject *event_scancode = PyDict_GetItemString(e->dict, "scancode"); + PyObject *event_mod = PyDict_GetItemString(e->dict, "mod"); + PyObject *event_window_ID= PyDict_GetItemString(e->dict, "window"); event.type = e->type; - event.key.keysym.sym = PyLong_AsLong(PyDict_GetItemString(e->dict, "key")); - if (PyDict_GetItemString(e->dict, "scancode") != NULL) - event.key.keysym.scancode = PyLong_AsLong(PyDict_GetItemString(e->dict, "scancode")); - if (PyDict_GetItemString(e->dict, "mod") != NULL) - event.key.keysym.mod = PyLong_AsLong(PyDict_GetItemString(e->dict, "mod")); + if (event_key == NULL){ + return RAISE(pgExc_SDLError, "key event posted without keycode"); + } + if (!PyInt_Check(event_key)){ + return RAISE(pgExc_SDLError, "posted event keycode must be int"); + } + event.key.keysym.sym = PyLong_AsLong(event_key); + + if (event_scancode != NULL){ + if (!PyInt_Check(event_scancode)){ + return RAISE(pgExc_SDLError, "posted event scancode must be int"); + } + event.key.keysym.scancode = PyLong_AsLong(event_scancode); + } + + if (event_mod != NULL && event_mod != Py_None){ + if (!PyInt_Check(event_scancode)){ + return RAISE(pgExc_SDLError, "posted event modifiers must be int"); + } + if (PyLong_AsLong(event_mod) > 65535 || PyLong_AsLong(event_mod) < 0) { + return RAISE(pgExc_SDLError, "mods must be 16-bit int"); + } + event.key.keysym.mod = (Uint16) PyLong_AsLong(event_mod); + } + + if (event_window_ID != NULL && event_window_ID != Py_None){ + if (!PyInt_Check(event_window_ID)){ + return RAISE(pgExc_SDLError, "posted event window id must be int"); + } + event.key.windowID = PyLong_AsLong(event_window_ID); + } } else { if (pgEvent_FillUserEvent(e, &event)) return NULL; From 7818a0d592bb20e1cfbb51ff3a94c93ad887723a Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Tue, 7 Apr 2020 13:30:34 +0200 Subject: [PATCH 3/9] tests --- src_c/event.c | 7 ++++++- test/event_test.py | 35 ++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src_c/event.c b/src_c/event.c index d1d36ebf03..889467c353 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1784,10 +1784,15 @@ pg_event_post(PyObject *self, PyObject *args) } event.key.windowID = PyLong_AsLong(event_window_ID); } - } else { + } + else if (e->type >= PGE_USEREVENT && e->type < PG_NUMEVENTS) { if (pgEvent_FillUserEvent(e, &event)) return NULL; } + else { + if (pgEvent_FillUserEvent(e, &event)) + return NULL; + } #if IS_SDLv1 if (SDL_PushEvent(&event) == -1) #else /* IS_SDLv2 */ diff --git a/test/event_test.py b/test/event_test.py index af38354b9d..cb990e6bf4 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -1,6 +1,7 @@ import os import sys import unittest +import collections import pygame from pygame.compat import as_unicode @@ -32,6 +33,15 @@ # pygame.NUMEVENTS, ) +EVENT_TEST_PARAMS = collections.defaultdict(dict) +EVENT_TEST_PARAMS.update({ + pygame.KEYDOWN:dict(key=pygame.K_SPACE), + pygame.KEYUP:dict(key=pygame.K_SPACE), + pygame.MOUSEMOTION:dict(), + pygame.MOUSEBUTTONDOWN:dict(button=1), + pygame.MOUSEBUTTONUP:dict(button=1), +}) + NAMES_AND_EVENTS = ( ("NoEvent", pygame.NOEVENT), @@ -221,7 +231,7 @@ def test_set_blocked(self): self.assertTrue(pygame.event.get_blocked(event)) - pygame.event.post(pygame.event.Event(event)) + pygame.event.post(pygame.event.Event(event, **EVENT_TEST_PARAMS[EVENT_TYPES[0]])) ret = pygame.event.get() should_be_blocked = [e for e in ret if e.type == event] @@ -259,7 +269,7 @@ def test_post__and_poll(self): # fuzzing event types for i in range(1, 11): - pygame.event.post(pygame.event.Event(EVENT_TYPES[i])) + pygame.event.post(pygame.event.Event(EVENT_TYPES[i], **EVENT_TEST_PARAMS[EVENT_TYPES[i]])) self.assertEqual( pygame.event.poll().type, EVENT_TYPES[i], race_condition_notification @@ -326,14 +336,14 @@ def test_get__event_sequence(self): # Test when an event type not in the list is in the queue. expected_events = [] pygame.event.clear() - pygame.event.post(pygame.event.Event(other_event_type)) + pygame.event.post(pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type])) retrieved_events = pygame.event.get(event_types) self._assertCountEqual(retrieved_events, expected_events) # Test when 1 event type in the list is in the queue. - expected_events = [pygame.event.Event(event_types[0])] + expected_events = [pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])] pygame.event.clear() pygame.event.post(expected_events[0]) @@ -356,8 +366,7 @@ def test_get__event_sequence(self): def test_clear(self): """Ensure clear() removes all the events on the queue.""" for e in EVENT_TYPES: - pygame.event.post(pygame.event.Event(e)) - + pygame.event.post(pygame.event.Event(e, **EVENT_TEST_PARAMS[e])) poll_event = pygame.event.poll() self.assertNotEqual(poll_event.type, pygame.NOEVENT) @@ -387,10 +396,10 @@ def test_clear__event_sequence(self): # Add the events to the queue. for etype in cleared_event_types: - pygame.event.post(pygame.event.Event(etype)) + pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) for etype in expected_events: - expected_events.append(pygame.event.Event(etype)) + expected_events.append(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) pygame.event.post(expected_events[-1]) # Clear the cleared_events from the queue. @@ -433,7 +442,7 @@ def test_event_name__userevent_boundary(self): def test_wait(self): """Ensure wait() waits for an event on the queue.""" - event = pygame.event.Event(EVENT_TYPES[0]) + event = pygame.event.Event(EVENT_TYPES[0], **EVENT_TEST_PARAMS[EVENT_TYPES[0]]) pygame.event.post(event) wait_event = pygame.event.wait() @@ -444,7 +453,7 @@ def test_peek(self): event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION] for event_type in event_types: - pygame.event.post(pygame.event.Event(event_type)) + pygame.event.post(pygame.event.Event(event_type, **EVENT_TEST_PARAMS[event_type])) # Ensure events can be checked individually. for event_type in event_types: @@ -466,7 +475,7 @@ def test_peek__event_sequence(self): # Test when an event type not in the list is in the queue. pygame.event.clear() - pygame.event.post(pygame.event.Event(other_event_type)) + pygame.event.post(pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type])) peeked = pygame.event.peek(event_types) @@ -474,7 +483,7 @@ def test_peek__event_sequence(self): # Test when 1 event type in the list is in the queue. pygame.event.clear() - pygame.event.post(pygame.event.Event(event_types[0])) + pygame.event.post(pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])) peeked = pygame.event.peek(event_types) @@ -483,7 +492,7 @@ def test_peek__event_sequence(self): # Test all events in the list are in the queue. pygame.event.clear() for etype in event_types: - pygame.event.post(pygame.event.Event(etype)) + pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) peeked = pygame.event.peek(event_types) From 38df803f935b5c30b7403348ea36faca0e323939 Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Tue, 7 Apr 2020 14:41:33 +0200 Subject: [PATCH 4/9] this test should fail but DOES NOT. --- test/event_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/event_test.py b/test/event_test.py index cb990e6bf4..9b0b1dd11b 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -275,6 +275,17 @@ def test_post__and_poll(self): pygame.event.poll().type, EVENT_TYPES[i], race_condition_notification ) + def test_post_and_get_keydown(self): + """Ensure keydown events can be posted to the queue.""" + surf = pygame.display.set_mode((10, 10)) + pygame.event.get() + e1 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a) + pygame.event.post(e1) + posted_event = pygame.event.poll() + self.assertEqual(e1.type, posted_event.type, race_condition_notification) + self.assertEqual(e1.type, pygame.KEYDOWN, race_condition_notification) + self.assertEqual(e1.key, posted_event.key, race_condition_notification) + def test_post_large_user_event(self): pygame.event.post(pygame.event.Event(pygame.USEREVENT, {"a": "a" * 1024})) e = pygame.event.poll() From 59fdb7423aa4e2a6004ed22d6ef33aabf0f17ebc Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Tue, 18 Aug 2020 09:43:47 +0200 Subject: [PATCH 5/9] fix event tests, hopefully --- test/event_test.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/test/event_test.py b/test/event_test.py index 9b0b1dd11b..ab53974d40 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -35,8 +35,8 @@ EVENT_TEST_PARAMS = collections.defaultdict(dict) EVENT_TEST_PARAMS.update({ - pygame.KEYDOWN:dict(key=pygame.K_SPACE), - pygame.KEYUP:dict(key=pygame.K_SPACE), + pygame.KEYDOWN:{'key': pygame.K_SPACE, 'scancode': 2328724320 }, + pygame.KEYUP:{'key': pygame.K_SPACE, 'scancode': 2328724320}, pygame.MOUSEMOTION:dict(), pygame.MOUSEBUTTONDOWN:dict(button=1), pygame.MOUSEBUTTONUP:dict(button=1), @@ -204,6 +204,27 @@ def _assertCountEqual(self, *args, **kwargs): else: self.assertItemsEqual(*args, **kwargs) + def _assertExpectedEvents(self, expected, got): + """Find events like expected events, raise on unexpected or missing, + ignore additional event properties if expected properties are present.""" + + # This does greedy matching, don't encode an NP-hard problem + # into your input data, *please* + items_left=got[:] + for expected_element in expected: + for item in items_left: + for key in expected_element.__dict__: + if item.__dict__[key]!=expected_element.__dict__[key]: + break + else: + #found item! + items_left.remove(item) + break + else: + raise AssertionError("Expected "+str(expected_element)+" among remaining events "+str(items_left)+" out of "+str(got)) + if len(items_left)>0: + raise AssertionError("Unexpected Events: "+str(items_left)) + def setUp(self): pygame.display.init() pygame.event.clear() # flush events @@ -342,7 +363,10 @@ def test_get__event_sequence(self): pygame.event.clear() retrieved_events = pygame.event.get(event_types) - self._assertCountEqual(retrieved_events, expected_events) + # don't use self._assertCountEqual here. This checks for + # expected properties in events, and ignores unexpected ones, for + # forward compatibility with SDL2. + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) # Test when an event type not in the list is in the queue. expected_events = [] @@ -351,7 +375,7 @@ def test_get__event_sequence(self): retrieved_events = pygame.event.get(event_types) - self._assertCountEqual(retrieved_events, expected_events) + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) # Test when 1 event type in the list is in the queue. expected_events = [pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])] @@ -360,19 +384,19 @@ def test_get__event_sequence(self): retrieved_events = pygame.event.get(event_types) - self._assertCountEqual(retrieved_events, expected_events) + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) # Test all events in the list are in the queue. pygame.event.clear() expected_events = [] for etype in event_types: - expected_events.append(pygame.event.Event(etype)) + expected_events.append(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) pygame.event.post(expected_events[-1]) retrieved_events = pygame.event.get(event_types) - self._assertCountEqual(retrieved_events, expected_events) + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) def test_clear(self): """Ensure clear() removes all the events on the queue.""" From 5ffbc2a91624d09ae29670819d28a5a78d26cad0 Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Tue, 18 Aug 2020 11:11:32 +0200 Subject: [PATCH 6/9] omit posted event window ID in SDL1.2 --- src_c/event.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src_c/event.c b/src_c/event.c index 889467c353..761e58ac3f 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1750,7 +1750,11 @@ pg_event_post(PyObject *self, PyObject *args) PyObject *event_key = PyDict_GetItemString(e->dict, "key"); PyObject *event_scancode = PyDict_GetItemString(e->dict, "scancode"); PyObject *event_mod = PyDict_GetItemString(e->dict, "mod"); +#if IS_SDLv1 + PyObject *event_unicode = PyDict_GetItemString(e->dict, "unicode"); +#else /* IS_SDLv2 */ PyObject *event_window_ID= PyDict_GetItemString(e->dict, "window"); +#endif /* IS_SDLv2 */ event.type = e->type; if (event_key == NULL){ @@ -1778,12 +1782,16 @@ pg_event_post(PyObject *self, PyObject *args) event.key.keysym.mod = (Uint16) PyLong_AsLong(event_mod); } +#if IS_SDLv1 + /*ignore unicode property*/ +#else /* IS_SDLv2 */ if (event_window_ID != NULL && event_window_ID != Py_None){ if (!PyInt_Check(event_window_ID)){ return RAISE(pgExc_SDLError, "posted event window id must be int"); } event.key.windowID = PyLong_AsLong(event_window_ID); } +#endif /* IS_SDLv2 */ } else if (e->type >= PGE_USEREVENT && e->type < PG_NUMEVENTS) { if (pgEvent_FillUserEvent(e, &event)) From b7293ea479f214eca0929663255e25ac1df3eb3a Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Tue, 18 Aug 2020 16:29:53 +0200 Subject: [PATCH 7/9] make tests even simpler --- test/event_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/event_test.py b/test/event_test.py index ab53974d40..6281e0978e 100644 --- a/test/event_test.py +++ b/test/event_test.py @@ -35,8 +35,8 @@ EVENT_TEST_PARAMS = collections.defaultdict(dict) EVENT_TEST_PARAMS.update({ - pygame.KEYDOWN:{'key': pygame.K_SPACE, 'scancode': 2328724320 }, - pygame.KEYUP:{'key': pygame.K_SPACE, 'scancode': 2328724320}, + pygame.KEYDOWN:{'key': pygame.K_SPACE}, + pygame.KEYUP:{'key': pygame.K_SPACE}, pygame.MOUSEMOTION:dict(), pygame.MOUSEBUTTONDOWN:dict(button=1), pygame.MOUSEBUTTONUP:dict(button=1), From 50e309be7544eedf00e705df8b1c5018866446a0 Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Mon, 24 Aug 2020 09:30:38 +0200 Subject: [PATCH 8/9] docs for KEYDOWN post behaviour --- docs/reST/ref/event.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reST/ref/event.rst b/docs/reST/ref/event.rst index 1ae83e954a..7fabcc9a6c 100644 --- a/docs/reST/ref/event.rst +++ b/docs/reST/ref/event.rst @@ -346,6 +346,14 @@ Events reserved for :mod:`pygame.midi` use. If the event queue is full a :exc:`pygame.error` is raised. + Caution: In pygame 2.0, calling this function with event types defined by + pygame (such as ``pygame.KEYDOWN``) may put events into the SDL2 event queue. + In this case, an error may be raised if standard attributes of that event + are missing or have incompatible values, and unexpected properties may + be silently omitted. In order to avoid this behaviour, custom event + properties should be used with custom event types. + This behaviour is not guaranteed. + .. ## pygame.event.post ## .. function:: custom_type From e35acd63f8c15946adae57dba54305d341c0ae3f Mon Sep 17 00:00:00 2001 From: Robert Pfeiffer Date: Thu, 3 Sep 2020 16:39:00 +0200 Subject: [PATCH 9/9] commenting --- src_c/event.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src_c/event.c b/src_c/event.c index 761e58ac3f..6cc68ae1fa 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -1798,6 +1798,9 @@ pg_event_post(PyObject *self, PyObject *args) return NULL; } else { + /* HACK: + A non-USEREVENT type is treated like a USEREVENT union in the SDL2 + event queue. This needs to be decoded again. */ if (pgEvent_FillUserEvent(e, &event)) return NULL; }