iisacc logo

LVRS Document Viewer

architecture/event-pipeline.svx

Path: architecture/event-pipeline.svx

Last modified:

Event Pipeline

This document describes the end-to-end event path from OS/Qt events to high-level QML behavior in LVRS.

Pipeline Stages

  1. 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.
  1. Hook Stage: Backend
  • hookUserEvents() subscribes to RuntimeEvents::eventRecorded.
  • Mirrors events into bounded backend cache (hookedUserEvents).
  • Maintains per-type counters and last input snapshot for backend-first reads.
  1. Consumption Stage: EventListener
  • Converts trigger names to concrete source subscriptions.
  • Builds incident-first payloads (coordinates/button/modifier core).
  • Adds input/ui enrichment only when explicitly enabled.
  • Supports dedup windows for global press/context sequences.
  1. Dispatch Stage: ApplicationWindow
  • Hosts always-on global listeners for app-level pressed/context signals.
  • Re-emits normalized payloads as globalPressedEvent and globalContextEvent.
  1. 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)

  1. Global press/context event arrives with global coordinates.
  2. Target component maps global coordinates into overlay-local space.
  3. If point is outside popup/dialog bounds, close component.
  4. Dedup windows suppress duplicate context events generated by overlapping source paths.

Operational Checks

When validating event behavior, verify:

  • RuntimeEvents.running == true
  • Backend.userEventHooked == true only for listeners that opt into backend/input enrichment
  • expected trigger fires exactly once within dedup window
  • payload carries expected optional ui/input fields only when enabled

Extended Example: Global Context Menu Dispatch

A reliable context-menu dispatch flow in a complex page typically uses:

  1. EventListener(trigger: "globalContextRequested")
  2. payload UI hit-test inspection (eventData.ui.path)
  3. menu model selection by target path/class
  4. 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:

  1. check runtime daemon running state,
  2. check backend hook state when backend-first listeners are enabled,
  3. validate dedup thresholds are not overly aggressive,
  4. verify coordinate mapping against overlay parent.