viewer — Host-side Wayland framebuffer viewer #
viewer is the host-side Wayland client that renders the active PocketBook task's framebuffer and forwards pointer + keyboard input back to the guest firmware via /hwevent.
Build #
# From repo root (builds inside container, outputs to host path):
./pbemu build [firmware]
Output: src/viewer/build-pc/viewer
Main loop overview #
viewer_app_run() drives a poll() loop over five file descriptors:
| Slot | FD | Event |
|---|---|---|
POLL_FD_WL |
Wayland socket | Compositor events (configure, frame, pointer) |
POLL_FD_TIMER |
timerfd |
Periodic SHM poll (default 33 ms ≈ 30 fps) |
POLL_FD_SIGNAL |
signalfd |
SIGINT/SIGTERM for clean shutdown |
POLL_FD_SETTLE |
timerfd |
One-shot geometry-settle timer (default 150 ms) |
POLL_FD_KEY_REPEAT |
timerfd |
Compositor-configured keyboard repeat synthesis |
On each timer tick the viewer:
- Copies one informer snapshot using the shared odd/even
sequenceprotocol. - Updates its window title, touch matrix, and active framebuffer SHM attachment from that snapshot.
- Commits any pending framebuffer changes to Wayland, paced by frame callbacks.
When a frame is committed, further timer-driven repaints are suppressed until the compositor's frame callback fires.
Source map #
src/main.c — entry point #
Creates a viewer_app, calls viewer_app_run(), destroys it, exits. No logic here.
src/viewer_app.c — main orchestration loop #
Owns the viewer_app struct, wires all subsystems together, and runs the poll() event loop.
Key functions:
viewer_app_create()— allocates and initialises all subsystemsviewer_app_run()—poll()loop; handles Wayland, timer, settle-timer, and signal eventsviewer_app_destroy()— tears down SHM mapping, Wayland, timerstry_repaint()— reads informer state, decodes framebuffer geometry, and schedules/commits repaintsupdate_active_task()— attaches/detaches task SHM when the informer-selected task changes; refreshes title + touch matrixattach_shm()/detach_shm()— SysV SHM lifecycle managementcommit_frame()— copies SHM pixels to a freebuffer_poolslot, converts 8bpp→XRGB if needed, commits to Waylandon_pointer_event()— translates Wayland pointer press/move/release to/hweventmessages viainput_bridgeon_key_event()— mapsxkbcommon-translated Wayland key events to PocketBookEVT_KEY*/EVT_EXT_KBmessages
The viewer_app struct tracks:
current_task_id,shm_key,shm_id,shm_addr,shm_size— active task SHM statecurrent_info/pending_info— framebuffer geometry (pending has a settle timer to ignore transient changes; settles after 150 ms of stability)touch_matrix[8]— loaded from informer state; falls back to the default matrix until informer publishes onefb_y_offset— wrapped scanout origin from theiv_fbinfoheader: SHM rowris presented at window row(r + fb_y_offset) % fb_h, replicating the device display pipeline (the guest's panel painter draws the system bar into the bottom SHM rows; the wrap shows it at the top). A change forces a full repaint. Pointer input is sent in physical window coordinates — the guest subtractsfb_y_offsetitselftitle[256]— Wayland window title assembled from informer task/subtask namesrepaint_state/last_repaint_seq— read-only attachment to thepb_repaint_stateSHM segment written by the shim; used to limit blits to dirty rectangles or skip unchanged frames entirely
src/buffer_pool.c — double-buffered wl_shm pool #
Manages two memfd-backed XRGB8888 Wayland buffers. Only one buffer is submitted to the compositor at a time; the other is free for writing. Buffer release is signalled by the compositor via wl_buffer.release.
Key functions:
buffer_pool_init()— sets up the pool struct (no allocation yet)buffer_pool_resize()— destroys and re-creates all slots at the newwidth×heightbuffer_pool_get_free()— returns a slot not currently held by the compositorbuffer_pool_all_free()— true when both slots are free (safe to resize)buffer_pool_destroy()— unmaps and closes all slots
Each slot (buffer_pool_slot): fd (memfd), data (mmap'd XRGB8888 pixels), data_size, wl_buf, busy flag.
src/wayland_client.c — Wayland protocol objects and callbacks #
Manages the Wayland connection, surfaces, input seats, and xkbcommon keyboard state.
Keyboard specifics:
- binds
wl_keyboardwhen the seat advertises keyboard capability - loads the compositor keymap into
xkbcommon - applies locale-aware compose rules for dead keys / accented input
- uses a
timerfdto generate repeat callbacks at the compositor's configured rate/delay - emits high-level callbacks with translated
keysym+ UTF-32 codepoint data
Current key mapping strategy in viewer_app:
- navigation / device-like keys (
Esc, arrows,PageUp,PageDown,Home,Menu,Tab, keypad +/-) become PocketBookEVT_KEYDOWN/EVT_KEYUP/EVT_KEYREPEATwithIV_KEY_*codes Backspace,Delete, and other text-edit control characters travel throughEVT_EXT_KBEnterstill emitsIV_KEY_OK, and on keydown it also mirrorsU+000DthroughEVT_EXT_KBso PocketBook text widgets can submit- printable Unicode text becomes PocketBook
EVT_EXT_KBcarrying the translated UTF-32 character
Key objects managed: wl_display, wl_registry, wl_compositor, wl_shm, xdg_wm_base, xdg_surface, xdg_toplevel, wp_viewporter, wp_viewport, xdg_decoration_manager, wl_seat, wl_pointer.
Key functions:
wl_state_connect()— connects to Wayland, runs registry roundtrip, creates surface and xdg_toplevelwl_state_set_title()— updates the xdg_toplevel title (called with task label)wl_state_dispatch()— wrapswl_display_dispatch()wl_state_repeat_fd()/wl_state_dispatch_key_repeat()— expose timerfd-based key repeat to the outer poll loopwl_state_disconnect()— tears down all Wayland objects
Pointer event callbacks: pointer_enter, pointer_leave, pointer_motion, pointer_button, pointer_axis — delegate to wl_state.callbacks.on_pointer_*.
Touch matrix convention: 8 int32 values. Default {0,0,10000,0,10000,0,0,0} maps hw_x=logical_y, hw_y=logical_x (rotated-landscape panel). Forward transform: [x*10000; y*10000] = M * [hw_x; hw_y] + [m[0]; m[3]] where M = [m[1],m[2]; m[4],m[5]].
The following modules are compiled into libpbcore (see src/pbcore/README.md) and linked by both the viewer and the host probe tools.
src/pbcore/src/frame_source.c — informer snapshot helpers #
Attaches the informer SysV SHM segment, validates its header, and returns consistent snapshot copies to the rest of the viewer. Also provides helpers for decoding framebuffer geometry, task titles, and touch-matrix fallback.
Key functions:
read_informer_state()— attaches the informer segment if needed, then copies a consistent snapshot using the odd/evensequenceprotocolload_touch_matrix_from_informer()— returns informer touch-matrix data when present, otherwise the default{0,0,10000,0,10000,0,0,0}decode_informer_framebuffer_info()— validates the informer framebuffer fields and converts them toframebuffer_infoformat_informer_title()— formatsPocketBook – <task> — <subtask>from informer names when availablefnv1a_64()— FNV-1a 64-bit hash for frame change detection
src/pbcore/src/geometry_map.c — surface-to-framebuffer coordinate mapping #
Pure computation: maps a Wayland surface pointer coordinate to a framebuffer pixel coordinate, accounting for the wp_viewporter scaling ratio.
Key function:
map_surface_to_framebuffer(map, surf_x, surf_y, fb_x_out, fb_y_out)— scales(surf_x, surf_y)byfb_w/win_w × fb_h/win_h; without viewporter falls back to 1:1; clamps to framebuffer bounds
geometry_map fields: fb_w, fb_h (framebuffer dims), win_w, win_h (compositor window dims, 0 until first configure), viewporter_enabled.
src/pbcore/src/input_bridge.c — /hwevent POSIX message queue output #
Encodes Wayland pointer and keyboard events into PocketBook /hwevent 10×int32 messages and sends them via POSIX mq.
Key functions:
pb_input_open()— opens/hweventmqO_WRONLY|O_NONBLOCKpb_input_close()— closes the mq descriptorcompute_hw_coords()— inverts the 8-element affine touch matrix via Cramer's rule to map(logical_x, logical_y)→(hw_x, hw_y); addsfb_y_offsettological_ybefore inversion (modular wrap by framebuffer height); falls back to(hw_x=y, hw_y=x)when determinant is zeropb_input_build_event()— fills a pointer-shapedhwevent_msgstruct (pressure/devtype for POINTERDOWN/MOVE/UP; MTSYNC zeroes x/y and usesDEV_UNKNOWN; timestamps viagettimeofday)pb_input_build_common_event()— fills the generic{mod, par1, par2}payload used by keyboard/button eventspb_input_send()/pb_input_send_common()— build and send onehwevent_msgviamq_send()
Informer snapshot (pb_informer_state, src/common/pb_informer.h) #
The viewer's control plane comes from the informer snapshot rather than shim-owned sidecar files or monitor SHM offsets.
| Field | Meaning |
|---|---|
magic, version, struct_size |
Protocol validation for cross-arch compatibility |
sequence |
Odd/even seqlock counter used to copy a stable snapshot |
flags |
Validity bits for task, framebuffer, touch matrix, and names |
active_task_id, active_subtask_id |
Current monitor foreground task and subtask |
fbshmkey, fbshmsize |
SysV SHM key and size for the active task framebuffer |
width, height, depth, stride |
Normalised framebuffer geometry published by the informer |
pixel_data_offset, fb_y_offset |
Byte offset to pixels within the task SHM and wrapped scanout origin |
touch_matrix[8] |
Touch-panel affine transform read through official hwconfig APIs |
task_name, subtask_name |
Human-facing window-title strings |
The task SHM segment itself still begins with an iv_fbinfo header on the ARM side, but that header is decoded by the informer before the viewer sees it.
Repaint hint (pb_repaint_state, src/common/pb_repaint.h) #
A second SysV SHM segment (PB_REPAINT_SHM_KEY) carries a seqlocked dirty-region hint written by the shim's update_hooks.c and read by the viewer's try_repaint(). The viewer attaches it read-only on first successful shmget().
| Field | Meaning |
|---|---|
sequence |
Seqlock counter; odd while writer is active, even when a stable snapshot is available |
flags |
PB_REPAINT_FULL — full repaint requested; PB_REPAINT_RECT — dirty rectangle in x/y/w/h |
x, y, w, h |
Dirty rectangle coordinates (valid when PB_REPAINT_RECT is set) |