Expand description
ShellEvent — everything the window system tells the engine, in engine
types.
§Timestamps, and the clock they are on
Every input-bearing variant carries an EventTime. This is not
decoration: the input design makes the pattern evaluator a pure function
over timestamped edges (docs/notes/simulation.md) — tap versus hold versus
double-tap are defined by inter-event durations — and topic 26’s client
prediction needs input-to-photon latency, which is a subtraction between an
event timestamp and a present timestamp. Neither works if the timestamp is
the moment the engine got around to draining the queue: a 20 ms frame
quantizes every duration to 20 ms and makes a fast double-tap
indistinguishable from a slow one.
So the timestamp is the window system’s, rebased onto the engine’s
monotonic clock. EventTime’s own documentation states the epoch and
spells out the per-platform rebasing each backend owes; the short version is
that an EventTime and FrameClock readings live
on one timeline, so they can be subtracted.
Non-input variants (Resized,
MonitorsChanged) carry no timestamp. They
are state transitions, not samples; nothing computes a duration between two
resizes, and a field that exists only to be ignored invites someone to use
it for something it is not.
§Modifiers are stamped onto every event, not delivered separately
Wayland sends wl_keyboard.modifiers as its own message, X11 puts a
modifier mask on each event, and Win32 makes you call GetKeyState. The
seam picks the X11 shape: the backend tracks modifier state and stamps the
current value onto each event.
The reason is that the alternative loses information the consumer cannot
recover. If modifiers arrive as a separate event, a consumer that batches a
frame’s events — which every consumer does — sees “Ctrl went down” and “S
went down” as two facts in a list and has to replay them in order to know
whether it was Ctrl+S. That replay is exactly the state machine each
backend has to write anyway, so it is written once, in the backend, where
the ordering is authoritative.
§Non-exhaustive, unlike SurfaceTarget
SurfaceTarget is deliberately exhaustive so a new platform breaks every
HAL backend loudly. ShellEvent is the opposite: a consumer that ignores an
event it has never heard of degrades gracefully (touch input does nothing,
rather than doing something wrong), and
TextPreedit arrived that way after it. Breaking
every match in the engine for each new event is not a useful forcing
function, it is churn. (Gamepads are not window events and do not come
through here: their backends emit crcbl_input::GamepadEvents from a poll
of their own.)
Touch is what that promise looked like when it was
collected: it landed without touching a single match outside the shell,
and a game that never heard of it kept playing with one finger because the
primary contact is reported through the pointer variants as well.
Enums§
- Shell
Event - Something the window system reported.