eventsys
Cross-platform input events with PyGame/SDL2-style types.
eventsys is the poller and device mux
(Runtime, HostEventsDevice, …). Event type
constants and namedtuples live in events; SDL key codes
live in keys.
For board wiring and application-owned coordinator setup, see Runtime.
Quick start — poll loop
import events
import eventsys
runtime = eventsys.Runtime()
keypad = eventsys.KeypadDevice(read=lambda: pressed_keys) # set of key codes
runtime.register(keypad)
while True:
for event in runtime.poll(): # always a list — safe to iterate
if event.type == events.KEYDOWN:
print("down", event.key)
elif event.type == events.QUIT:
breakQuick start — subscribe
import events
import eventsys
runtime = eventsys.Runtime()
def on_key(event):
print(event)
runtime.on(events.KEYDOWN, on_key)
runtime.on([events.KEYDOWN, events.KEYUP], on_key)
runtime.on_device(eventsys.KEYPAD, on_key)Quick start — async
Pair eventsys with multimer on asyncio-native hosts:
import eventsys
import multimer
runtime = eventsys.Runtime()
async def main():
while True:
for event in runtime.poll():
handle(event)
await multimer.sleep_ms(0)
runtime.run_async(main) # Jupyter / PyScript; or asyncio.run(main()) on desktopOr subscribe and let the runtime auto-service drive the app:
runtime.on(runtime.events.MOUSEBUTTONDOWN, handle)
runtime.run_forever()Poll vs subscribe
| Pattern | When to use |
|---|---|
| Poll | Main loop owns flow; inspect every event each frame. |
runtime.on() |
React to specific event types without a big if
chain. |
runtime.on_device() |
Handle all events from touch, keypad, joystick, etc. |
runtime.poll() always returns a list
(possibly empty). It never returns None.
Built-in devices
| Device | Input contract |
|---|---|
HostEventsDevice |
read() returns ready-made events (desktop SDL/PyGame
bridge). |
TouchDevice |
read() returns (x, y, pressed); maps to
mouse events. Device type eventsys.POINTER (LVGL
INDEV_TYPE.POINTER). |
KeypadDevice |
read() returns a set of pressed key
codes. |
EncoderDevice |
read() returns scroll delta / button state. |
JoystickDevice |
joystick_driver with PyGame-style
get_axis, get_button, get_hat,
… |
Register devices with runtime.register(dev) or the
constructor helpers (Runtime(..., touch_read=...),
runtime.add_keypad(read=...), etc.).
Joystick
import eventsys
class MyDriver(eventsys.JoystickDriver):
def get_instance_id(self):
return 0
# implement get_numaxes, get_axis, get_numbuttons, get_button, …
joy = eventsys.JoystickDevice(
joystick_driver=MyDriver(),
emulate_digital=[(0, 1)], # optional: analog axes → hat motion
)
runtime.register(joy)
runtime.on_device(eventsys.JOYSTICK, lambda e: print(e))Quit handling
When constructed with display=, the runtime handles quit
implicitly: on events.QUIT it runs before_quit
(if set), then display.quit(), then stops the shared timer.
Set runtime.before_quit for application-specific teardown
before the display is released. LVGL uses its own coordinator.
runtime.before_quit = _lvgl_shutdownUse runtime.quit_requested in
output-only loops that do not dispatch events (the auto-service still
handles host QUIT when you call poll or run
run_forever):
from board_config import display_drv
from app_runtime import runtime
while not runtime.quit_requested:
draw_frame()
# Prefer runtime.run_forever() for interactive apps; poll only when you
# own a custom frame loop and need to drain events yourself.Canonical interactive apps subscribe callbacks and stay alive with:
runtime.on(runtime.events.MOUSEBUTTONDOWN, handle)
runtime.run_forever()display_drv.quit() only releases resources (REPL-safe);
your loop must still exit when runtime.quit_requested
becomes true or you handle events.QUIT.
Custom events and devices
import events
import eventsys
events.register_event(types={"MINE": None}, classes={"Mine": "type a b"})
eventsys.register_device("MYPAD", [events.KEYDOWN, events.KEYUP])Use eventsys.capabilities() to inspect the dialect and
built-in device list.
FAQ
No events arrive — call runtime.poll()
frequently in your main loop.
Touch coordinates wrong — set
TouchDevice.rotation_table for your panel rotation.
Joystick hats from analog sticks — pass
emulate_digital=[(axis_x, axis_y), …].
pydevices-examples integration
pydevices-examples's app_runtime explicitly constructs
eventsys.Runtime.from_board_config(board_config) for
non-LVGL examples. Board configs expose neutral hardware capabilities
and never instantiate a runtime. Display-only apps may omit eventsys;
LVGL uses display_driver. See Runtime, Architecture, and Displays.
Next
- multimer — timers and async main loops
- Displays — how backends feed the runtime
- App starter