#[non_exhaustive]pub enum ShellEvent {
Show 20 variants
Resized {
window: WindowId,
size: PhysicalSize,
scale_factor: f64,
},
ScaleFactorChanged {
window: WindowId,
scale_factor: f64,
size: PhysicalSize,
},
CloseRequested {
window: WindowId,
},
WindowDestroyed {
window: WindowId,
},
Focus {
window: WindowId,
focused: bool,
},
Key {
window: WindowId,
device: DeviceId,
time: EventTime,
scancode: Scancode,
key_code: Option<KeyCode>,
keysym: Keysym,
state: ButtonState,
repeat: bool,
modifiers: Modifiers,
},
PointerMotion {
window: WindowId,
device: DeviceId,
time: EventTime,
abs: Option<PhysicalPoint>,
raw_delta: Option<(f64, f64)>,
},
PointerFocus {
window: WindowId,
device: DeviceId,
time: EventTime,
entered: bool,
position: Option<PhysicalPoint>,
},
Button {
window: WindowId,
device: DeviceId,
time: EventTime,
button: PointerButton,
state: ButtonState,
position: Option<PhysicalPoint>,
modifiers: Modifiers,
},
Wheel {
window: WindowId,
device: DeviceId,
time: EventTime,
delta: ScrollDelta,
position: Option<PhysicalPoint>,
modifiers: Modifiers,
},
Touch {
window: WindowId,
device: DeviceId,
time: EventTime,
contact: ContactId,
phase: TouchPhase,
position: PhysicalPoint,
},
TextCommit {
window: WindowId,
time: EventTime,
text: String,
},
TextPreedit {
window: WindowId,
time: EventTime,
text: String,
cursor: Option<usize>,
},
MonitorsChanged,
MonitorRemoved {
monitor: MonitorId,
},
FocusRefused {
window: WindowId,
},
FullScreenApp {
window: WindowId,
active: bool,
},
FrameReady {
window: WindowId,
},
DroppedFile {
window: WindowId,
time: EventTime,
path: PathBuf,
position: Option<PhysicalPoint>,
},
ClipboardData {
window: WindowId,
request: ClipboardRequestId,
mime: ReceivedMime,
content: ClipboardContent,
},
}Expand description
Something the window system reported.
Delivered by Shell::pump. See the module
docs for the timestamp and modifier conventions.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Resized
The client area changed size.
The authoritative size for the swapchain — never the size that was
asked for in WindowDesc. Carries the scale factor
too, because a resize and a scale change can arrive together (dragging a
window between monitors of different DPI does exactly that) and a
consumer that reads size from one event and scale from another will
briefly hold a mismatched pair.
Fields
size: PhysicalSizeNew client area in device pixels.
ScaleFactorChanged
The window’s scale factor changed — it moved to a different monitor, or the user changed the display scale.
Carries the new physical size as well: on Wayland a scale change is a
configure that also restates the size, and on Win32 WM_DPICHANGED
hands over a suggested rectangle. A consumer must treat this as a resize
too. The windowing rules (docs/notes/backends.md) call out “scale-factor changes
mid-session must not leak wrong-size swapchains” as a required test, and
putting both facts in one event is what makes that testable rather than
order-dependent.
Fields
size: PhysicalSizeClient area in device pixels after the change.
CloseRequested
The user asked to close the window — title-bar button, Alt-F4, session logout.
The window is still open. Answer with
reply_close_request; until you
do, the shell holds the request. See
CloseReply.
WindowDestroyed
The window was destroyed and its WindowId is now stale.
Follows a CloseReply::Close, an explicit
destroy_window, or the compositor
taking the window away — every window at once when the connection to
the window system is lost.
Focus
Keyboard focus entered or left the window.
A consumer must treat focus loss as “every key is now up”: no platform delivers releases for keys held when focus leaves, and a game that does not clear its key state walks into a wall until the player taps the key again. The action layer (P2) does this centrally; raw consumers do it themselves.
Key
A key went down or came up.
Three views of the same keystroke — see
crcbl_core::input for why they are all here and what each is for.
A key the input method consumes while composing is not reported as
a Key — neither its press nor its release. The Backspace that
deletes a kana from a TextPreedit and the
Enter that commits the conversion are the input method’s, and a text
field that also saw them as editing keys would delete a character or
submit the form. This is what a Wayland compositor already does by not
sending the key at all; the Win32 backend recognizes the keystrokes its
input method marks as taken. The AppKit backend cannot yet tell such a
keystroke apart, and still reports it.
Fields
time: EventTimeWhen it happened; see the module docs.
key_code: Option<KeyCode>The physical key, layout-independent. None when the scancode maps
to no key this engine names — the key is still bindable through
scancode. Right for game bindings (WASD stays where the fingers
are), wrong for menus and shortcuts: an xkb remap such as
caps:escape or a virtual keyboard’s own keymap does not change it
(wtype’s Down arrives on evdev 1, which is Escape).
keysym: KeysymThe symbol this key produces in the current layout, for menus,
shortcuts, rebind menus and shortcut labels. The keys no layout
moves have named constants, Keysym::ESCAPE, Keysym::DOWN
and the rest.
state: ButtonStateDown or up.
repeat: boolWhether this is an auto-repeat, not a fresh press.
Text fields want repeats; a jump button must not. Filtering them at the source would break the first, keeping them silently breaks the second, so the fact is carried and the consumer decides.
modifiers: ModifiersModifiers held at the time; see the module docs.
PointerMotion
The pointer moved.
Both fields are optional, and which are present is the difference between a UI event and a camera event:
absis where the pointer is, in window pixels.NoneunderPointerMode::Locked, where there is no meaningful position.raw_deltais unaccelerated, unclipped motion.Nonewhen the backend has no relative-motion source — seeShellCaps::RAW_POINTER_MOTION.
A camera must use raw_delta and must not difference successive abs
values: the latter is clamped at the screen edge and has pointer
acceleration already applied, which is why aim in games that get this
wrong stops working when the cursor reaches the edge of the display.
Fields
abs: Option<PhysicalPoint>Position in the window, in device pixels.
PointerFocus
The pointer entered or left the window.
Not in the windowing rules’ event set, and added anyway: every backend
delivers enter/leave natively (wl_pointer.enter, X11
EnterNotify, WM_MOUSELEAVE), the UI layer needs it for hover state,
and the alternative — inferring it from motion events that stop
arriving — cannot distinguish “the pointer left” from “the pointer
stopped moving”.
Fields
position: Option<PhysicalPoint>Where it entered, if the backend said. Always None on leave.
Button
A pointer button went down or came up.
Fields
Which button.
state: ButtonStateDown or up.
position: Option<PhysicalPoint>Where the pointer was, if known. None under
PointerMode::Locked.
Wheel
A scroll wheel or touchpad scrolled.
Fields
delta: ScrollDeltaHow far, in the units the device reported — see
ScrollDelta, which does not collapse detents into pixels.
position: Option<PhysicalPoint>Where the pointer was, if known.
Touch
A finger landed, moved, lifted, or had its gesture taken away.
One event per contact, so two fingers moving at once are two events
and not one averaged pointer. ContactId carries the identity and
TouchPhase the lifecycle; between them a consumer can hold per-finger
state, which is the whole reason this variant exists and the reason a
second finger used to be dropped at the backend.
§A touchscreen also produces pointer events, and that is deliberate
The primary contact — the first finger of a gesture, and the only one
while there is only one — is also reported as
PointerMotion and
Button, exactly as a browser emulates mouse
events for touch and Windows synthesizes them from WM_POINTER. So a
game bound to Binding::MouseButton is playable with one finger without
knowing this variant exists, and a game that wants two fingers reads
Touch and ignores the emulation.
That is an obligation on a backend that sets
ShellCaps::TOUCH, not an accident of one
platform: a backend whose window system does not emulate must do it
itself, or every existing pointer binding stops working the day it
reports its first contact. Contacts that are not primary have no pointer
events at all — a second finger is not the first one teleporting.
§No modifiers, and a position that is never absent
A finger carries no modifiers: no platform reports keyboard state on a
touch event, and a field the backends would have to invent is a field a
consumer would believe. And position is a PhysicalPoint rather than
an Option — a contact is defined by where it touches, including on
the release, and there is no PointerMode::Locked
equivalent that could take the position away.
Fields
device: DeviceIdWhich touchscreen. Every contact on one screen shares this — see
ContactId, which is the one that tells fingers apart.
phase: TouchPhaseWhat it just did.
position: PhysicalPointWhere it is, in device pixels. On
Cancelled this is the last place the
platform knew about rather than a place the user chose.
TextCommit
Text was committed — by typing, by an input method, or by a paste the window system routed as text input.
Separate from Key because the relationship is not
one-to-one: an IME commits several characters from one keystroke, or one
character from five, and a dead key commits nothing at all. A text field
that reconstructs its content from key events is a text field that
cannot type in Japanese.
Fields
TextPreedit
An input method’s composition in progress: the pre-edit a text field draws at its caret, underlined, until it is committed or abandoned.
Each one replaces the last, and an empty text means there is no
pre-edit any more: the composition was committed, cancelled, or deleted
back to nothing. The committed text itself arrives separately, as
TextCommit, and a consumer must not
synthesize it from the last pre-edit: an input method commits the
conversion the user chose, which is usually not the reading they typed.
The two streams are independent — a field inserts each commit at its
caret and draws the latest pre-edit after it — so their relative order
within one pump carries no meaning.
The keystrokes that edit a composition are the input method’s: a key
it consumes while composing is not reported as a
Key.
Sent where ShellCaps::TEXT_IME is set.
A backend without it never sends this.
Shell::set_text_input_area is how a
field tells the input method where to put that window and its
candidate list.
Fields
MonitorsChanged
The set of monitors changed — hotplug, resolution change, or a layout rearrangement.
Carries no payload: Shell::monitors is the
authority, and duplicating the list into the event would let a consumer
hold a stale copy that looks authoritative. A window’s own scale
change arrives separately as
ScaleFactorChanged.
MonitorRemoved
A monitor was disconnected.
Arrives before the MonitorsChanged
after which Shell::monitors no longer lists
it, so a consumer can act on the id while it still knows what the id
named. After that the id names nothing, and it is never reused.
By the time that MonitorsChanged arrives, every
panel created for this monitor has been sent
a CloseRequested: a bar on an unplugged
display has nowhere to be. A window system that closes the panel itself
may send that request before this event. Other windows stay wherever
the window system moves them.
FocusRefused
The window asked for the keyboard and the window system refused it.
A panel that takes the keyboard outright
(PanelKeyboard::Exclusive) asks
when it is shown. Win32 refuses the foreground to a process the user is
not working in, and AppKit can decline to make the panel the key
window; then no Focus follows: keys keep going
to the window the user was in. This is how a program finds
out, without guessing a timeout — to retry, to say so, or to draw the
user’s attention another way.
FullScreenApp
A full-screen window opened on this panel’s monitor, or the last one
closed: active says which.
For a bar that should step aside while a game or a video is full
screen. Raised on Win32, for a panel that
reserves its edge (Reserve::OwnExtent),
from the shell’s ABN_FULLSCREENAPP. Wayland and AppKit do not need
it: their compositors already draw a full-screen window over a bar, or
in a space of its own. X11 does not raise it either, so a bar there
that cares watches _NET_WM_STATE_FULLSCREEN itself.
Fields
FrameReady
A good time to present the window’s next frame: the answer to a
request_frame.
One per request, however many requests were made before it came,
and none for a window destroyed first. Where
ShellCaps::FRAME_CALLBACK is set
the compositor sends it, and may hold it back for as long as nothing of
the window is visible — an output it has turned off, a hidden window —
which is when presenting would block or be wasted. Elsewhere it arrives
at the next pump after the request.
DroppedFile
A file was dropped on the window.
One event per file: a multi-file drop produces several. Only delivered
when the window was created with
WindowDesc::accept_drops and
ShellCaps::DRAG_DROP is set.
Fields
path: PathBufThe dropped path. Not guaranteed to exist or to be readable — it comes from another process, and checking is the consumer’s job.
position: Option<PhysicalPoint>Where in the window it was dropped, if the backend said.
ClipboardData
The answer to a clipboard_request.
Exactly one of these arrives for every accepted request, and it is
the only thing that ever answers one. It may be several frames late: a
backend holds a read until it can answer it rather than reporting an
empty clipboard it has not actually looked at. See
clipboard for why reads are asynchronous and why
there is no “ask again later” outcome.
Fields
request: ClipboardRequestIdWhich request this answers.
mime: ReceivedMimeThe format that was delivered, spelled the way the other application spelled it.
A ReceivedMime rather than a
MimeType because the peer chose this string,
not us: asking for text/plain;charset=utf-8 and being answered
with text/plain is normal and must round-trip verbatim. Compare
with ReceivedMime::matches, never with string equality.
For Empty and
Unavailable there is no peer
spelling to report, so this is the format that was asked for.
content: ClipboardContentWhat the read produced — bytes, an empty clipboard, or a failure.
See ClipboardContent, which exists because the three are not one
case.
Implementations§
Source§impl ShellEvent
impl ShellEvent
Sourcepub const fn window(&self) -> Option<WindowId>
pub const fn window(&self) -> Option<WindowId>
Which window this event concerns, if any.
None only for MonitorsChanged and
MonitorRemoved, which are properties of
the session rather than of a window. A multi-window consumer routes on
this.
Sourcepub const fn time(&self) -> Option<EventTime>
pub const fn time(&self) -> Option<EventTime>
When this event happened, for the variants that are input samples.
None for state transitions; see the module docs for why they
deliberately have no timestamp.
Sourcepub const fn is_input(&self) -> bool
pub const fn is_input(&self) -> bool
Whether this event feeds the input pipeline rather than the window lifecycle.
The P2 action layer subscribes to exactly this subset.
Sourcepub const fn name(&self) -> &'static str
pub const fn name(&self) -> &'static str
A short, stable name for logs and test assertions.
Stable because tests assert on sequences of these: a scripted
HeadlessShell session compares ["Resized", "Focus", "Key", …], which
stays readable in a failure message in a way that a Debug dump of the
full payloads does not.