LVRS Document Viewer
architecture/event-pipeline.svx
Path: architecture/event-pipeline.svx
Event Pipeline
This document describes the end-to-end event path from OS/Qt events to high-level QML behavior in LVRS.
Pipeline Stages
- Capture Stage:
RuntimeEvents
- Installs event filter and records keyboard/pointer/context/touch/tablet/gesture/UI lifecycle events.
- Maintains counters, recent-event ring buffer, and input snapshot (
inputState()). - Emits
eventRecorded(eventData)as canonical runtime stream.
- Hook Stage:
Backend
hookUserEvents()subscribes toRuntimeEvents::eventRecorded.- Mirrors events into bounded backend cache (
hookedUserEvents). - Maintains per-type counters and last input snapshot for backend-first reads.
- Consumption Stage:
EventListener
- Converts trigger names to concrete source subscriptions.
- Builds incident-first payloads (coordinates/button/modifier core).
- Adds
input/uienrichment only when explicitly enabled. - Supports dedup windows for global press/context sequences.
- Dispatch Stage:
ApplicationWindow
- Hosts always-on global listeners for app-level pressed/context signals.
- Re-emits normalized payloads as
globalPressedEventandglobalContextEvent.
- Feature Stage
ContextMenu: outside-dismiss and action dispatch.- Editors/hierarchy: nested wheel isolation via
WheelScrollGuard. - Runtime console/debug tools: event stream visualization.
Canonical Payload Shape
Global pointer/context flows always carry:
- Position:
x,y,globalX,globalY - Input masks:
buttons,modifiers
Optional enrichments:
input: normalized input state snapshot (includeInputState=true)ui: hit-test metadata (includeUiHit=true)backend: optional backend summary when requested
This shape is intentionally shared so feature components can consume one schema.
Why Backend-First Exists (Opt-In)
Directly reading runtime singleton state from many QML handlers can cause temporal skew under bursty input. Backend-first mode reduces skew by reading from a stable mirrored cache, but it is intentionally opt-in to avoid hot-path overhead.
Context Dismiss Flow (Reference)
- Global press/context event arrives with global coordinates.
- Target component maps global coordinates into overlay-local space.
- If point is outside popup/dialog bounds, close component.
- Dedup windows suppress duplicate context events generated by overlapping source paths.
Operational Checks
When validating event behavior, verify:
RuntimeEvents.running == trueBackend.userEventHooked == trueonly for listeners that opt into backend/input enrichment- expected trigger fires exactly once within dedup window
- payload carries expected optional
ui/inputfields only when enabled
Extended Example: Global Context Menu Dispatch
A reliable context-menu dispatch flow in a complex page typically uses:
EventListener(trigger: "globalContextRequested")- payload UI hit-test inspection (
eventData.ui.path) - menu model selection by target path/class
- menu open at
eventData.globalX/globalY
This flow avoids dependency on local event boundaries.
Observability Probes
During troubleshooting, log at least these probes:
- runtime sequence (
RuntimeEvents.eventSequence) - backend mirror count (
Backend.hookedEventCount) - dedup timestamps in listener payload handling
- menu/dialog outside-dismiss geometry checks
Failure Analysis Playbook
If global interactions feel inconsistent:
- check runtime daemon running state,
- check backend hook state when backend-first listeners are enabled,
- validate dedup thresholds are not overly aggressive,
- verify coordinate mapping against overlay parent.