Skip to content

Commit

Permalink
Merge pull request #1584 from robertpfeiffer/postkeydownevents
Browse files Browse the repository at this point in the history
post keydown events, closes #1580
  • Loading branch information
illume committed Sep 5, 2020
2 parents 1e55a94 + e35acd6 commit e40d00d
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 20 deletions.
8 changes: 8 additions & 0 deletions docs/reST/ref/event.rst
Expand Up @@ -368,6 +368,14 @@ and the ``joy`` attribute was deprecated.

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
Expand Down
59 changes: 57 additions & 2 deletions src_c/event.c
Expand Up @@ -1832,9 +1832,64 @@ 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){
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){
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 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))
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;
}
#if IS_SDLv1
if (SDL_PushEvent(&event) == -1)
#else /* IS_SDLv2 */
Expand Down
80 changes: 62 additions & 18 deletions test/event_test.py
@@ -1,6 +1,7 @@
import os
import sys
import unittest
import collections

import pygame
from pygame.compat import as_unicode
Expand Down Expand Up @@ -32,6 +33,15 @@
# pygame.NUMEVENTS,
)

EVENT_TEST_PARAMS = collections.defaultdict(dict)
EVENT_TEST_PARAMS.update({
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),
})


NAMES_AND_EVENTS = (
("NoEvent", pygame.NOEVENT),
Expand Down Expand Up @@ -194,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
Expand Down Expand Up @@ -221,7 +252,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]

Expand Down Expand Up @@ -259,12 +290,23 @@ 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
)

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()
Expand Down Expand Up @@ -321,43 +363,45 @@ 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 = []
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)
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])]
expected_events = [pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])]
pygame.event.clear()
pygame.event.post(expected_events[0])

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."""
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)
Expand Down Expand Up @@ -387,10 +431,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.
Expand Down Expand Up @@ -433,7 +477,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()

Expand All @@ -444,7 +488,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:
Expand All @@ -466,15 +510,15 @@ 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)

self.assertFalse(peeked)

# 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)

Expand All @@ -483,7 +527,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)

Expand Down

0 comments on commit e40d00d

Please sign in to comment.