← pydevices docs

Displays

The displaydev product package in pydevices provides several display driver classes. All expose a drawing surface compatible with MicroPython's framebuf API; pydevices-examples demonstrates them.

See Architecture for how drivers connect to board_config.py.

Pick a driver

Your target Driver class Board config example
MicroPython MCU (SPI/I80) BusDisplay board_configs/busdisplay/spi/...
CPython / MicroPython Unix desktop SDLDisplay board_configs/sdldisplay/
Windows CPython (native Win32) WinDisplay board_configs/windisplay/
Windows / Chromebook (PyGame easier) PGDisplay board_configs/pgdisplay/
CircuitPython RGB / USB video FBDisplay varies
Jupyter notebook JNDisplay board_configs/jndisplay/
PyScript browser PSDisplay board_configs/psdisplay/

Install the matching board config — it constructs the driver for you.

Display classes

BusDisplay

For microcontrollers on MicroPython and CircuitPython.

SDLDisplay

SDL2 desktop backend (CPython, MicroPython Unix, CircuitPython Unix). Uses an SDL texture as GRAM. It is the default on MicroPython Unix and available on CPython via board_configs/sdldisplay/.

SDL2 bindings for SDLDisplay: import usdl2 from pydevices-desktop (TestPyPI) or the MIP desktop board package in pydevices (drivers/usdl2.py). A native usdl2 module is used when already present in the firmware or environment. See MicroPython — Desktop SDL.

WinDisplay

Native Win32 HWND backend for CPython on Windows (uwin32). Logical RGB565 GRAM, presented with StretchDIBits. displaydev.auto.AutoDisplay tries it first on win32 before pygame/SDL. Explicit config: board_configs/windisplay/.

PGDisplay

PyGame desktop backend. displaydev.auto.AutoDisplay (used by board_configs/desktop/) selects it after WinDisplay on Windows, and first on other CPython desktops; if PyGame is not installed it falls back to SDLDisplay. Explicit config: board_configs/pgdisplay/.

FBDisplay

Works with CircuitPython framebufferio.FramebufferDisplay — dotclock (RGB), USB Video, RGB Matrix.

USB Video lets a board stream the framebuffer as a USB webcam (RP2040; host support varies).

JNDisplay

Jupyter Notebook output via an interactive ipywidgets image. Input (mouse, wheel, keyboard) is captured by JNDevices (ipyevents) and delivered as events. Config: board_configs/jndisplay/.

PSDisplay

PyScript browser canvas. Input (pointer/touch/pen, wheel, keyboard, gamepad) is captured by PSDevices and delivered as events. Config: board_configs/psdisplay/. See PyScript.

Display backends expose input without choosing an application coordinator. Applications using the optional eventsys runtime can drain the backend's events records through a HostEventsDevice. The input source depends on what each platform exposes:

Backends Input source Wired via
SDLDisplay, PGDisplay, WinDisplay System-wide OS queue drain (module get_events, also on display_drv.get_events) Runtime(..., host_read=display_drv.get_events)
JNDisplay, PSDisplay Per-surface PSDevices / JNDevices, exposed as display_drv.get_events Runtime(..., host_read=display_drv.get_events)

With eventsys, handlers see the same events objects, so application code does not need to know which backend is active. LVGL instead connects these neutral backend capabilities through its own display_driver coordinator. Desktop board configs also use timer_async=env_bool("PYDEVICES_TIMER_ASYNC", display_drv.requires_async_timer) (requires_async_timer is True only on PS/JN). eventsys.Runtime raises if timer_async=False while any attached display has requires_async_timer.

Desktop (SDL2, PyGame)

SDL2 and PyGame provide a real OS event queue. The driver module drains it and converts each event to an events object:

from displaydev.sdldisplay import SDLDisplay
import eventsys

display_drv = SDLDisplay(...)
runtime = eventsys.Runtime(
    displays=[display_drv],
    host_read=display_drv.get_events,
)

Use poll_event() only for optional manual single-event checks — not as the host_read= callback (it returns one event, not a list).

Desktop hosts (SDLDisplay, PGDisplay, WinDisplay) set display_drv.quit_chord to CTRL+Q (keys.K_q + keys.KMOD_CTRL). HostEventsDevice matches that chord with keys.chord_matches and emits events.QUIT. Window-close still emits events.QUIT from SDL/PyGame. MCU drivers leave quit_chord as None.

Pointer coordinates use display_drv.touch_scale (see capabilities() per backend); HostEventsDevice divides mouse events by that scale.

This captures mouse motion/buttons, the wheel, the keyboard, the window-close (QUIT) event, and joysticks/gamepads (JOYAXISMOTION, JOYBALLMOTION, JOYHATMOTION, JOYBUTTONDOWN, JOYBUTTONUP). Connect controllers before launching — hot-plugging after startup is not handled.

Browser / notebook (PyScript, Jupyter)

PSDevices (PyScript) and JNDevices (Jupyter) capture all available input on the canvas/widget and turn it into the same events objects. The display owns that drain as get_events:

from displaydev.psdisplay import PSDisplay
import eventsys

display_drv = PSDisplay("display_canvas", width, height)
runtime = eventsys.Runtime(
    displays=[display_drv],
    host_read=display_drv.get_events,
    timer_async=display_drv.requires_async_timer,
)

Each captures:

import keys

display_drv.quit_chord = (keys.K_c, keys.KMOD_CTRL)  # e.g. CTRL+C on Jupyter

Caveat: key events require the canvas/widget to be focused (click it first), and the notebook/browser front end may consume some keys (arrows, space, Ctrl/Cmd shortcuts) before they reach the helper. This makes keyboard input on these backends less reliable than on the desktop SDL/PyGame backends.

Rotation on these backends only reshapes the surface (e.g. 320×480 ↔︎ 480×320); it does not physically rotate, so pointer coordinates need no rotation remapping.

Canvases

Anything you can draw on implements the framebuf API:

Timing

pydevices-examples does not include a task scheduler. Options:

Vertical scrolling

Many drivers expose ILI9341-style vertical scroll: a top fixed band (TFA), a scrollable middle (VSA), and a bottom fixed band (BFA). You define regions with set_vscroll(tfa, bfa) or vscrdef, then move content with the vscroll property (wrapper around vscsad).

The pydevices_demo guide explains this model with diagrams, covers drawing at vscroll = 0 during redraw, and shows auto-scroll with multimer.

Related examples: scroll_touch_test.py (touch Up/Down), eventsys_encoder_test.py (encoder).

Rotation

BusDisplay uses CircuitPython-style rotation degrees (0, 90, 180, 270).

Known issues: Unix SDL rotation clears the screen; scrolling while rotated has edge cases on desktop and MCU — track work on GitHub Issues.

Next

API reference

displaydev source and product docs.