use std::time::{Duration, Instant};
use rustc_hash::FxHashMap;
use smithay_client_toolkit::seat::{
Capability, SeatHandler, SeatState,
keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers as WlModifiers, RawModifiers},
pointer::{
AxisScroll, PointerEvent, PointerEventKind, PointerHandler,
cursor_shape::CursorShapeManager,
},
touch::TouchHandler,
};
use smithay_client_toolkit::reexports::calloop::LoopHandle;
use smithay_client_toolkit::reexports::client::{
Connection, Proxy, QueueHandle,
protocol::{wl_keyboard, wl_pointer, wl_seat, wl_surface, wl_touch},
};
use smithay_client_toolkit::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::{
Shape as WpCursorShape, WpCursorShapeDeviceV1,
};
use super::wayland::WaylandState;
use crate::reactive::CursorIcon;
use crate::surface::SurfaceId;
use crate::widgets::{Event, Key, Modifiers, MouseButton, Point, ScrollSource};
const SCROLL_PIXELS_PER_LINE: f32 = 40.0;
fn push_event(events: &mut Vec<(Instant, Event)>, at: Instant, event: Event) {
if matches!(event, Event::MouseMove { .. })
&& let Some(last @ (_, Event::MouseMove { .. })) = events.last_mut()
{
*last = (at, event);
} else {
events.push((at, event));
}
}
fn push_release(
events: &mut Vec<(SurfaceId, Instant, Event)>,
surface: SurfaceId,
at: Instant,
pointer: Point,
) {
events.push((
surface,
at,
Event::MouseUp {
at: Some(pointer),
button: MouseButton::Left,
},
));
events.push((surface, at, Event::MouseLeave));
}
fn point(position: (f64, f64)) -> Point {
Point::new(position.0 as f32, position.1 as f32)
}
fn untimed() -> Instant {
Instant::now()
}
pub(super) struct EventClock {
anchor_ms: u32,
anchor: Instant,
}
impl InputState {
pub(super) fn at(&mut self, ms: u32) -> Instant {
self.event_clock
.get_or_insert_with(|| EventClock::anchored(ms, Instant::now()))
.instant(ms)
}
}
impl EventClock {
fn anchored(ms: u32, now: Instant) -> Self {
Self {
anchor_ms: ms,
anchor: now,
}
}
fn instant(&self, ms: u32) -> Instant {
self.anchor + Duration::from_millis(ms.wrapping_sub(self.anchor_ms) as u64)
}
}
pub struct InputState {
pub(super) pointer: Option<wl_pointer::WlPointer>,
pub(super) pointer_at: Point,
pub(super) pointer_over_surface: bool,
pub(super) pointer_enter_serial: u32,
pub(super) touch: Option<wl_touch::WlTouch>,
pub(super) touch_fingers: FxHashMap<i32, (SurfaceId, f32, f32)>,
pub(super) primary_finger: Option<i32>,
pub(super) event_clock: Option<EventClock>,
pub(super) cursor_shape_manager: Option<CursorShapeManager>,
pub(super) cursor_shape_device: Option<WpCursorShapeDeviceV1>,
pub(super) keyboard: Option<wl_keyboard::WlKeyboard>,
pub(super) modifiers: Modifiers,
pub(super) keyboard_serial: u32,
pub(super) pressed_keys: FxHashMap<u32, Key>,
pub(super) loop_handle: LoopHandle<'static, WaylandState>,
pub(super) latest_input_serial: u32,
}
impl InputState {
pub(super) fn new(
cursor_shape_manager: Option<CursorShapeManager>,
loop_handle: LoopHandle<'static, WaylandState>,
) -> Self {
Self {
pointer: None,
pointer_at: Point::new(0.0, 0.0),
pointer_over_surface: false,
pointer_enter_serial: 0,
touch: None,
touch_fingers: FxHashMap::default(),
primary_finger: None,
event_clock: None,
cursor_shape_manager,
cursor_shape_device: None,
keyboard: None,
modifiers: Modifiers::default(),
keyboard_serial: 0,
pressed_keys: FxHashMap::default(),
loop_handle,
latest_input_serial: 0,
}
}
}
impl WaylandState {
pub fn set_cursor(&mut self, cursor: CursorIcon) {
let (Some(device), Some(pointer)) = (&self.input.cursor_shape_device, &self.input.pointer)
else {
return;
};
let serial = self.input.pointer_enter_serial;
let shape = match cursor {
CursorIcon::Hidden => {
pointer.set_cursor(serial, None, 0, 0);
return;
}
CursorIcon::Default => WpCursorShape::Default,
CursorIcon::Text => WpCursorShape::Text,
CursorIcon::Pointer => WpCursorShape::Pointer,
CursorIcon::Crosshair => WpCursorShape::Crosshair,
CursorIcon::Move => WpCursorShape::Move,
CursorIcon::NotAllowed => WpCursorShape::NotAllowed,
CursorIcon::Grab => WpCursorShape::Grab,
CursorIcon::Grabbing => WpCursorShape::Grabbing,
CursorIcon::ResizeNorth => WpCursorShape::NResize,
CursorIcon::ResizeSouth => WpCursorShape::SResize,
CursorIcon::ResizeEast => WpCursorShape::EResize,
CursorIcon::ResizeWest => WpCursorShape::WResize,
CursorIcon::ResizeNorthEast => WpCursorShape::NeResize,
CursorIcon::ResizeNorthWest => WpCursorShape::NwResize,
CursorIcon::ResizeSouthEast => WpCursorShape::SeResize,
CursorIcon::ResizeSouthWest => WpCursorShape::SwResize,
CursorIcon::ColResize => WpCursorShape::ColResize,
CursorIcon::RowResize => WpCursorShape::RowResize,
CursorIcon::Wait => WpCursorShape::Wait,
CursorIcon::Progress => WpCursorShape::Progress,
};
device.set_shape(serial, shape);
}
}
impl SeatHandler for WaylandState {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Pointer && self.input.pointer.is_none() {
log::info!("Pointer capability available, creating pointer");
match self.seat_state.get_pointer(qh, &seat) {
Ok(pointer) => {
self.input.cursor_shape_device = self
.input
.cursor_shape_manager
.as_ref()
.map(|manager| manager.get_shape_device(&pointer, qh));
self.input.pointer = Some(pointer);
}
Err(e) => {
log::warn!("Failed to get pointer: {e}");
return;
}
}
}
if capability == Capability::Touch && self.input.touch.is_none() {
log::info!("Touch capability available, creating touch");
match self.seat_state.get_touch(qh, &seat) {
Ok(touch) => self.input.touch = Some(touch),
Err(e) => {
log::warn!("Failed to get touch: {e}");
}
}
}
if capability == Capability::Keyboard && self.input.keyboard.is_none() {
log::info!("Keyboard capability available, creating keyboard");
let loop_handle = self.input.loop_handle.clone();
let keyboard = match self.seat_state.get_keyboard_with_repeat(
qh,
&seat,
None,
loop_handle,
Box::new(|state: &mut WaylandState, _kbd, event| state.emit_key_repeat(event)),
) {
Ok(keyboard) => keyboard,
Err(e) => {
log::warn!("Failed to get keyboard: {e}");
return;
}
};
self.input.keyboard = Some(keyboard);
self.selections.attach_devices(qh, &seat);
}
}
fn remove_capability(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Pointer {
log::info!("Pointer capability removed");
if let Some(device) = self.input.cursor_shape_device.take() {
device.destroy();
}
if let Some(pointer) = self.input.pointer.take() {
pointer.release();
}
}
if capability == Capability::Keyboard {
log::info!("Keyboard capability removed");
if let Some(keyboard) = self.input.keyboard.take() {
keyboard.release();
}
}
if capability == Capability::Touch {
log::info!("Touch capability removed");
if let Some(touch) = self.input.touch.take() {
touch.release();
}
self.touch_event(TouchEvent::Cancel { at: untimed() });
}
}
fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {
}
}
enum TouchEvent {
Down {
id: i32,
surface: SurfaceId,
at: Instant,
position: Point,
},
Motion {
id: i32,
at: Instant,
position: Point,
},
Up {
id: i32,
at: Instant,
},
Cancel {
at: Instant,
},
}
fn translate_touch(
fingers: &mut FxHashMap<i32, (SurfaceId, f32, f32)>,
primary: &mut Option<i32>,
event: TouchEvent,
) -> Vec<(SurfaceId, Instant, Event)> {
let mut events = Vec::new();
match event {
TouchEvent::Down {
id,
surface,
at,
position,
} => {
fingers.insert(id, (surface, position.x, position.y));
if primary.is_none() {
*primary = Some(id);
events.push((surface, at, Event::finger_move(position.x, position.y)));
events.push((surface, at, Event::finger_down(position.x, position.y)));
}
}
TouchEvent::Motion { id, at, position } => {
let Some(finger) = fingers.get_mut(&id) else {
return events;
};
finger.1 = position.x;
finger.2 = position.y;
let surface = finger.0;
if *primary == Some(id) {
events.push((surface, at, Event::finger_move(position.x, position.y)));
}
}
TouchEvent::Up { id, at } => {
let Some((surface, x, y)) = fingers.remove(&id) else {
return events;
};
if *primary == Some(id) {
*primary = None;
push_release(&mut events, surface, at, Point::new(x, y));
}
}
TouchEvent::Cancel { at } => {
if let Some(id) = primary.take()
&& let Some((surface, _, _)) = fingers.get(&id).copied()
{
events.push((surface, at, Event::MouseLeave));
}
fingers.clear();
}
}
events
}
impl WaylandState {
fn touch_event(&mut self, event: TouchEvent) {
let translated = translate_touch(
&mut self.input.touch_fingers,
&mut self.input.primary_finger,
event,
);
for (surface_id, at, event) in translated {
if let Some(surface_state) = self.surfaces.get_mut(&surface_id) {
push_event(&mut surface_state.pending_events, at, event);
}
}
}
}
impl TouchHandler for WaylandState {
fn down(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_touch: &wl_touch::WlTouch,
serial: u32,
time: u32,
surface: wl_surface::WlSurface,
id: i32,
position: (f64, f64),
) {
self.input.latest_input_serial = serial;
let Some(surface_id) = self.surface_lookup.get(&surface.id()).copied() else {
return;
};
let at = self.input.at(time);
self.touch_event(TouchEvent::Down {
id,
surface: surface_id,
at,
position: point(position),
});
}
fn up(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_touch: &wl_touch::WlTouch,
_serial: u32,
time: u32,
id: i32,
) {
let at = self.input.at(time);
self.touch_event(TouchEvent::Up { id, at });
}
fn motion(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_touch: &wl_touch::WlTouch,
time: u32,
id: i32,
position: (f64, f64),
) {
let at = self.input.at(time);
self.touch_event(TouchEvent::Motion {
id,
at,
position: point(position),
});
}
fn shape(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_touch: &wl_touch::WlTouch,
_id: i32,
_major: f64,
_minor: f64,
) {
}
fn orientation(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_touch: &wl_touch::WlTouch,
_id: i32,
_orientation: f64,
) {
}
fn cancel(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _touch: &wl_touch::WlTouch) {
self.touch_event(TouchEvent::Cancel { at: untimed() });
}
}
impl PointerHandler for WaylandState {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &wl_pointer::WlPointer,
events: &[PointerEvent],
) {
for event in events {
let surface_id = self.surface_lookup.get(&event.surface.id()).copied();
let at = match event.kind {
PointerEventKind::Motion { time }
| PointerEventKind::Press { time, .. }
| PointerEventKind::Release { time, .. }
| PointerEventKind::Axis { time, .. } => self.input.at(time),
PointerEventKind::Enter { .. } | PointerEventKind::Leave { .. } => untimed(),
};
let target_events: Option<&mut Vec<(Instant, Event)>> = if let Some(id) = surface_id {
self.surfaces.get_mut(&id).map(|s| &mut s.pending_events)
} else if !matches!(event.kind, PointerEventKind::Leave { .. }) {
continue;
} else {
None
};
match event.kind {
PointerEventKind::Enter { serial } => {
self.input.pointer_over_surface = true;
self.input.pointer_enter_serial = serial;
self.input.latest_input_serial = serial;
self.input.pointer_at = point(event.position);
self.current_pointer_surface = surface_id;
if let Some(events) = target_events {
events.push((
at,
Event::MouseEnter {
at: Some(self.input.pointer_at),
},
));
push_event(
events,
at,
Event::mouse_move(self.input.pointer_at.x, self.input.pointer_at.y),
);
}
}
PointerEventKind::Leave { .. } => {
if self.input.pointer_over_surface {
self.input.pointer_over_surface = false;
if let Some(id) = self.current_pointer_surface
&& let Some(surface_state) = self.surfaces.get_mut(&id)
{
surface_state.pending_events.push((at, Event::MouseLeave));
}
self.current_pointer_surface = None;
}
}
PointerEventKind::Motion { .. } => {
self.input.pointer_at = point(event.position);
if let Some(events) = target_events {
push_event(
events,
at,
Event::mouse_move(self.input.pointer_at.x, self.input.pointer_at.y),
);
}
}
PointerEventKind::Press { button, serial, .. } => {
self.input.latest_input_serial = serial;
if let Some(mouse_button) = wayland_button_to_mouse_button(button)
&& let Some(events) = target_events
{
events.push((
at,
Event::mouse_down(
self.input.pointer_at.x,
self.input.pointer_at.y,
mouse_button,
),
));
}
}
PointerEventKind::Release { button, .. } => {
if let Some(mouse_button) = wayland_button_to_mouse_button(button)
&& let Some(events) = target_events
{
events.push((
at,
Event::MouseUp {
at: Some(self.input.pointer_at),
button: mouse_button,
},
));
}
}
PointerEventKind::Axis {
horizontal,
vertical,
source,
..
} => {
if let Some(events) = target_events {
translate_axis(
events,
at,
source,
&horizontal,
&vertical,
self.input.pointer_at,
);
}
}
}
}
}
}
fn translate_axis(
events: &mut Vec<(Instant, Event)>,
at: Instant,
source: Option<wl_pointer::AxisSource>,
horizontal: &AxisScroll,
vertical: &AxisScroll,
pointer: Point,
) {
let scroll_source = match source {
Some(wl_pointer::AxisSource::Wheel) => ScrollSource::Wheel,
Some(wl_pointer::AxisSource::Finger) => ScrollSource::Finger,
Some(wl_pointer::AxisSource::Continuous) => ScrollSource::Continuous,
Some(wl_pointer::AxisSource::WheelTilt) => ScrollSource::Wheel,
_ => ScrollSource::Wheel,
};
let pixels = |scroll: &AxisScroll| {
let steps = if scroll.value120 != 0 {
scroll.value120 as f32 / 120.0
} else {
scroll.discrete as f32
};
if steps != 0.0 {
steps * SCROLL_PIXELS_PER_LINE
} else {
scroll.absolute as f32
}
};
let delta_x = pixels(horizontal);
let delta_y = pixels(vertical);
if delta_x != 0.0 || delta_y != 0.0 {
events.push((
at,
Event::Scroll {
at: Some(pointer),
delta_x,
delta_y,
source: scroll_source,
},
));
}
if horizontal.stop || vertical.stop {
events.push((at, Event::ScrollEnd { at: Some(pointer) }));
}
}
fn wayland_button_to_mouse_button(button: u32) -> Option<MouseButton> {
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
match button {
BTN_LEFT => Some(MouseButton::Left),
BTN_RIGHT => Some(MouseButton::Right),
BTN_MIDDLE => Some(MouseButton::Middle),
_ => None,
}
}
impl KeyboardHandler for WaylandState {
fn enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
surface: &wl_surface::WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[Keysym],
) {
log::debug!("Keyboard focus entered");
let surface_id = self.surface_lookup.get(&surface.id()).copied();
self.current_keyboard_surface = surface_id;
if let Some(id) = surface_id
&& let Some(surface_state) = self.surfaces.get_mut(&id)
{
surface_state
.pending_events
.push((untimed(), Event::FocusIn));
}
}
fn leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
surface: &wl_surface::WlSurface,
_serial: u32,
) {
log::debug!("Keyboard focus left");
let surface_id = self.surface_lookup.get(&surface.id()).copied();
if let Some(id) = surface_id
&& let Some(surface_state) = self.surfaces.get_mut(&id)
{
surface_state
.pending_events
.push((untimed(), Event::FocusOut));
}
self.current_keyboard_surface = None;
}
fn press_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
serial: u32,
event: KeyEvent,
) {
self.input.keyboard_serial = serial;
self.input.latest_input_serial = serial;
if let Some(delivered) = self.input.key_pressed(&event) {
self.deliver_key(delivered);
}
}
fn release_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
let key = self
.input
.pressed_keys
.remove(&event.raw_code)
.or_else(|| keysym_to_key(event.keysym, event.utf8.as_deref(), false));
if let Some(key) = key {
let at = self.input.at(event.time);
let key_event = Event::KeyUp {
key,
modifiers: self.input.modifiers,
};
self.deliver_key((at, key_event));
}
}
fn update_modifiers(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
modifiers: WlModifiers,
_raw_modifiers: RawModifiers,
_layout: u32,
) {
self.input.modifiers = Modifiers {
ctrl: modifiers.ctrl,
alt: modifiers.alt,
shift: modifiers.shift,
logo: modifiers.logo,
caps_lock: modifiers.caps_lock,
};
crate::keyboard::set_keyboard_modifiers(self.input.modifiers);
}
fn repeat_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
self.emit_key_repeat(event);
}
}
impl WaylandState {
fn emit_key_repeat(&mut self, event: KeyEvent) {
if let Some(delivered) = self.input.key_repeated(&event) {
self.deliver_key(delivered);
}
}
fn deliver_key(&mut self, (at, event): (Instant, Event)) {
if let Some(id) = self.current_keyboard_surface
&& let Some(surface_state) = self.surfaces.get_mut(&id)
{
surface_state.pending_events.push((at, event));
}
}
}
impl InputState {
pub(super) fn key_pressed(&mut self, event: &KeyEvent) -> Option<(Instant, Event)> {
let key = keysym_to_key(event.keysym, event.utf8.as_deref(), true)?;
self.pressed_keys.insert(event.raw_code, key);
Some(self.key_down(key, event.time, false))
}
pub(super) fn key_repeated(&mut self, event: &KeyEvent) -> Option<(Instant, Event)> {
let key = keysym_to_key(event.keysym, event.utf8.as_deref(), true)?;
Some(self.key_down(key, event.time, true))
}
fn key_down(&mut self, key: Key, time: u32, repeat: bool) -> (Instant, Event) {
let key_down = Event::KeyDown {
key,
modifiers: self.modifiers,
repeat,
};
(self.at(time), key_down)
}
}
fn keysym_to_key(keysym: Keysym, utf8: Option<&str>, is_press: bool) -> Option<Key> {
match keysym {
Keysym::BackSpace => return Some(Key::Backspace),
Keysym::Delete => return Some(Key::Delete),
Keysym::Return | Keysym::KP_Enter => return Some(Key::Enter),
Keysym::Tab | Keysym::ISO_Left_Tab => return Some(Key::Tab),
Keysym::Escape => return Some(Key::Escape),
Keysym::Left => return Some(Key::Left),
Keysym::Right => return Some(Key::Right),
Keysym::Up => return Some(Key::Up),
Keysym::Down => return Some(Key::Down),
Keysym::Home => return Some(Key::Home),
Keysym::End => return Some(Key::End),
_ => {}
}
if let Some(text) = utf8
&& let Some(c) = text.chars().next()
{
if !c.is_control() || c == '\n' || c == '\r' || c == '\t' {
return Some(Key::Char(c));
}
return keysym_char(keysym);
}
if !is_press {
return keysym_char(keysym);
}
None
}
fn keysym_char(keysym: Keysym) -> Option<Key> {
let raw = keysym.raw();
if (0x20..=0x7e).contains(&raw) {
return Some(Key::Char(char::from_u32(raw)?));
}
if (0xffb0..=0xffb9).contains(&raw) {
return Some(Key::Char(char::from_u32(raw - 0xffb0 + 0x30)?)); }
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widgets::PointerKind;
#[test]
fn a_ctrl_chord_keeps_its_letter() {
let c = Keysym::new(0x63); assert_eq!(
keysym_to_key(c, Some("\u{3}"), true),
Some(Key::Char('c')),
"Ctrl+C must arrive as 'c'"
);
}
#[test]
fn an_open_compose_sequence_inserts_nothing() {
let e = Keysym::new(0x65); assert_eq!(keysym_to_key(e, None, true), None);
}
#[test]
fn a_release_falls_back_to_the_keysym() {
let e = Keysym::new(0x65);
assert_eq!(keysym_to_key(e, None, false), Some(Key::Char('e')));
}
#[test]
fn plain_typing_uses_the_composed_text() {
let e = Keysym::new(0x65); assert_eq!(keysym_to_key(e, Some("é"), true), Some(Key::Char('é')));
}
fn keyboard() -> InputState {
let event_loop = smithay_client_toolkit::reexports::calloop::EventLoop::try_new()
.expect("an event loop needs no compositor");
InputState::new(None, event_loop.handle())
}
fn enter(time: u32) -> KeyEvent {
KeyEvent {
time,
raw_code: 28,
keysym: Keysym::Return,
utf8: Some("\r".into()),
}
}
fn repeat_of(delivered: Option<(Instant, Event)>) -> bool {
match delivered {
Some((_, Event::KeyDown { repeat, .. })) => repeat,
other => panic!("expected a key-down, got {other:?}"),
}
}
#[test]
fn a_press_and_a_held_key_say_which_they_are() {
let mut input = keyboard();
assert!(!repeat_of(input.key_pressed(&enter(0))), "the press");
assert!(
repeat_of(input.key_repeated(&enter(600))),
"the first repeat"
);
assert!(repeat_of(input.key_repeated(&enter(625))), "the next");
assert!(!repeat_of(input.key_pressed(&enter(900))), "pressed again");
}
#[test]
fn a_repeat_carries_its_key_modifiers_and_time() {
let mut input = keyboard();
let (pressed, _) = input.key_pressed(&enter(1_000)).unwrap();
input.modifiers.shift = true;
let (at, event) = input.key_repeated(&enter(1_600)).unwrap();
assert_eq!(at - pressed, Duration::from_millis(600));
let Event::KeyDown { key, modifiers, .. } = event else {
panic!("{event:?}")
};
assert_eq!(key, Key::Enter);
assert!(modifiers.shift);
}
#[test]
fn a_key_with_no_text_yet_delivers_nothing() {
let mut input = keyboard();
let composing = KeyEvent {
time: 0,
raw_code: 18,
keysym: Keysym::new(0x65),
utf8: None,
};
assert!(input.key_pressed(&composing).is_none());
assert!(input.key_repeated(&composing).is_none());
}
#[test]
fn an_events_time_is_read_as_a_distance_from_the_anchor() {
let t0 = Instant::now();
let clock = EventClock::anchored(1_000, t0);
assert_eq!(clock.instant(1_000), t0, "the anchor is where it says");
assert_eq!(
clock.instant(1_250),
t0 + Duration::from_millis(250),
"and a quarter second later is a quarter second later"
);
}
#[test]
fn a_clock_that_has_wrapped_still_moves_forwards() {
let t0 = Instant::now();
let clock = EventClock::anchored(u32::MAX - 4, t0);
assert_eq!(
clock.instant(11),
t0 + Duration::from_millis(16),
"the wrap is sixteen milliseconds forwards, not forty-nine days back"
);
}
const PX: f32 = 10.0;
const PY: f32 = 20.0;
fn at(pointer: Point) -> Event {
Event::mouse_move(pointer.x, pointer.y)
}
#[test]
fn a_queued_move_gives_way_to_the_next_move_and_to_nothing_else() {
let t0 = Instant::now();
let later = t0 + Duration::from_millis(16);
let mut events = Vec::new();
push_event(&mut events, t0, at(Point::new(1.0, 1.0)));
push_event(&mut events, later, at(Point::new(2.0, 2.0)));
let [
(
when,
Event::MouseMove {
at: Some(pointer), ..
},
),
] = events.as_slice()
else {
panic!("two moves are one move, got {events:?}");
};
assert_eq!(*pointer, Point::new(2.0, 2.0), "the newest position wins");
assert_eq!(*when, later, "and brings its instant with it");
push_event(
&mut events,
later,
Event::mouse_down(2.0, 2.0, MouseButton::Left),
);
push_event(&mut events, later, at(Point::new(3.0, 3.0)));
assert!(
matches!(
events.as_slice(),
[
(_, Event::MouseMove { .. }),
(_, Event::MouseDown { .. }),
(_, Event::MouseMove { .. }),
]
),
"the press survives, and the move after it starts a new run, got {events:?}"
);
}
fn axis(
source: Option<wl_pointer::AxisSource>,
horizontal: AxisScroll,
vertical: AxisScroll,
) -> Vec<Event> {
let mut events = Vec::new();
translate_axis(
&mut events,
Instant::now(),
source,
&horizontal,
&vertical,
Point::new(PX, PY),
);
events.into_iter().map(|(_, event)| event).collect()
}
fn nothing() -> AxisScroll {
AxisScroll::default()
}
fn wheel(value120: i32) -> AxisScroll {
AxisScroll {
value120,
..Default::default()
}
}
fn finger(absolute: f64) -> AxisScroll {
AxisScroll {
absolute,
..Default::default()
}
}
fn stopped() -> AxisScroll {
AxisScroll {
stop: true,
..Default::default()
}
}
#[test]
fn a_stop_with_no_delta_is_an_end_and_nothing_else() {
let events = axis(Some(wl_pointer::AxisSource::Finger), nothing(), stopped());
assert_eq!(events.len(), 1, "one event, got {events:?}");
let Event::ScrollEnd { at } = events[0] else {
panic!("a stop is a ScrollEnd, got {:?}", events[0]);
};
assert_eq!(
at,
Some(Point::new(PX, PY)),
"and it carries the pointer, not a delta"
);
let events = axis(Some(wl_pointer::AxisSource::Finger), stopped(), nothing());
assert!(
matches!(events.as_slice(), [Event::ScrollEnd { .. }]),
"the horizontal axis ends a gesture too, got {events:?}"
);
}
#[test]
fn a_wheel_step_is_a_line_and_a_fraction_of_one_is_a_fraction_of_a_line() {
let events = axis(Some(wl_pointer::AxisSource::Wheel), nothing(), wheel(120));
let [
Event::Scroll {
delta_y, source, ..
},
] = events.as_slice()
else {
panic!("a wheel step scrolls, got {events:?}");
};
assert_eq!(*delta_y, SCROLL_PIXELS_PER_LINE);
assert_eq!(*source, ScrollSource::Wheel);
let events = axis(Some(wl_pointer::AxisSource::Wheel), nothing(), wheel(60));
let [Event::Scroll { delta_y, .. }] = events.as_slice() else {
panic!("half a step scrolls, got {events:?}");
};
assert_eq!(*delta_y, SCROLL_PIXELS_PER_LINE / 2.0);
}
#[test]
fn value120_answers_before_discrete() {
let both = AxisScroll {
value120: 60,
discrete: 1,
..Default::default()
};
let events = axis(Some(wl_pointer::AxisSource::Wheel), nothing(), both);
let [Event::Scroll { delta_y, .. }] = events.as_slice() else {
panic!("expected one scroll, got {events:?}");
};
assert_eq!(
*delta_y,
SCROLL_PIXELS_PER_LINE / 2.0,
"the high-resolution field decides"
);
}
#[test]
fn a_touchpad_scrolls_the_pixels_it_reports() {
let events = axis(Some(wl_pointer::AxisSource::Finger), nothing(), finger(7.5));
let [
Event::Scroll {
at,
delta_y,
source,
..
},
] = events.as_slice()
else {
panic!("expected one scroll, got {events:?}");
};
assert_eq!(*delta_y, 7.5, "pixels pass through");
assert_eq!(*source, ScrollSource::Finger);
assert_eq!(*at, Some(Point::new(PX, PY)), "and it carries the pointer");
}
#[test]
fn a_gesture_that_moves_and_stops_says_both_in_that_order() {
let last = AxisScroll {
absolute: 12.0,
stop: true,
..Default::default()
};
let events = axis(Some(wl_pointer::AxisSource::Finger), nothing(), last);
assert!(
matches!(
events.as_slice(),
[Event::Scroll { .. }, Event::ScrollEnd { .. }]
),
"the movement, then the end, got {events:?}"
);
}
#[test]
fn an_axis_message_with_neither_a_delta_nor_a_stop_says_nothing() {
let events = axis(Some(wl_pointer::AxisSource::Wheel), nothing(), nothing());
assert!(events.is_empty(), "got {events:?}");
}
#[test]
fn a_continuous_source_stays_continuous() {
let events = axis(
Some(wl_pointer::AxisSource::Continuous),
nothing(),
finger(5.0),
);
let [
Event::Scroll {
delta_y, source, ..
},
] = events.as_slice()
else {
panic!("expected one scroll, got {events:?}");
};
assert_eq!(*delta_y, 5.0);
assert_eq!(*source, ScrollSource::Continuous);
}
#[test]
fn a_tilted_wheel_is_a_wheel_and_so_is_an_unnamed_source() {
let events = axis(
Some(wl_pointer::AxisSource::WheelTilt),
wheel(120),
nothing(),
);
let [
Event::Scroll {
delta_x, source, ..
},
] = events.as_slice()
else {
panic!("expected one scroll, got {events:?}");
};
assert_eq!(*source, ScrollSource::Wheel);
assert_eq!(
*delta_x, SCROLL_PIXELS_PER_LINE,
"and a tilt steps sideways by a line"
);
let events = axis(None, wheel(120), nothing());
let [Event::Scroll { source, .. }] = events.as_slice() else {
panic!("expected one scroll, got {events:?}");
};
assert_eq!(*source, ScrollSource::Wheel);
}
#[derive(Default)]
struct Touch {
fingers: FxHashMap<i32, (SurfaceId, f32, f32)>,
primary: Option<i32>,
}
impl Touch {
fn send(&mut self, event: TouchEvent) -> Vec<(SurfaceId, Event)> {
translate_touch(&mut self.fingers, &mut self.primary, event)
.into_iter()
.map(|(surface, _, event)| (surface, event))
.collect()
}
}
fn down(id: i32, surface: SurfaceId, x: f32, y: f32) -> TouchEvent {
TouchEvent::Down {
id,
surface,
at: Instant::now(),
position: Point::new(x, y),
}
}
fn motion(id: i32, x: f32, y: f32) -> TouchEvent {
TouchEvent::Motion {
id,
at: Instant::now(),
position: Point::new(x, y),
}
}
fn up(id: i32) -> TouchEvent {
TouchEvent::Up {
id,
at: Instant::now(),
}
}
fn cancel() -> TouchEvent {
TouchEvent::Cancel { at: untimed() }
}
fn press(events: &[(SurfaceId, Event)]) -> (SurfaceId, Option<Point>) {
let [
(
moved_to,
Event::MouseMove {
at: moved,
pointer: moved_by,
},
),
(
pressed_on,
Event::MouseDown {
at: pressed,
button,
pointer: pressed_by,
},
),
] = events
else {
panic!("a landing finger moves then presses, got {events:?}");
};
assert_eq!(*button, MouseButton::Left, "a finger is a left button");
assert_eq!(
(*moved_by, *pressed_by),
(PointerKind::Finger, PointerKind::Finger),
"and a finger, which is what the fold cannot say any other way"
);
assert_eq!(moved_to, pressed_on, "both to one surface");
assert_eq!(moved, pressed, "and both at one place");
(*pressed_on, *pressed)
}
fn release(events: &[(SurfaceId, Event)]) -> (SurfaceId, Option<Point>) {
let [
(released_on, Event::MouseUp { at, button }),
(left, Event::MouseLeave),
] = events
else {
panic!("a leaving finger releases then un-hovers, got {events:?}");
};
assert_eq!(*button, MouseButton::Left);
assert_eq!(released_on, left, "both to one surface");
(*released_on, *at)
}
#[test]
fn a_tap_is_a_move_a_press_and_a_release() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
let landed = touch.send(down(0, surface, PX, PY));
assert_eq!(
press(&landed),
(surface, Some(Point::new(PX, PY))),
"the press lands where the finger did"
);
assert_eq!(touch.primary, Some(0), "and that finger drives the pointer");
let lifted = touch.send(up(0));
assert_eq!(
release(&lifted),
(surface, Some(Point::new(PX, PY))),
"and the release is where it was last seen"
);
assert_eq!(touch.primary, None);
assert!(touch.fingers.is_empty(), "the finger is forgotten");
}
#[test]
fn a_second_finger_on_a_held_first_says_nothing() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, surface, PX, PY));
let second = touch.send(down(1, surface, 100.0, 200.0));
assert!(
second.is_empty(),
"the second finger is silent, got {second:?}"
);
assert_eq!(touch.primary, Some(0), "and the first one still drives");
assert_eq!(
touch.fingers.get(&1).copied(),
Some((surface, 100.0, 200.0)),
"but it is remembered, so its own up can be matched"
);
}
#[test]
fn a_non_primary_fingers_motion_says_nothing_but_is_remembered() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, surface, PX, PY));
touch.send(down(1, surface, 100.0, 200.0));
let moved = touch.send(motion(1, 110.0, 210.0));
assert!(
moved.is_empty(),
"the pointer does not follow, got {moved:?}"
);
assert_eq!(
touch.fingers.get(&1).copied(),
Some((surface, 110.0, 210.0)),
"the stored position still moved"
);
assert_eq!(
touch.fingers.get(&0).copied(),
Some((surface, PX, PY)),
"and the primary finger did not"
);
}
#[test]
fn a_finger_that_slides_off_keeps_delivering_where_it_landed() {
let landed_on = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, landed_on, PX, PY));
let slid = touch.send(motion(0, -40.0, 900.0));
let [(surface, Event::MouseMove { at, pointer })] = slid.as_slice() else {
panic!("the primary finger moves the pointer, got {slid:?}");
};
assert_eq!(*surface, landed_on, "off the surface, still the surface's");
assert_eq!(*at, Some(Point::new(-40.0, 900.0)));
assert_eq!(*pointer, PointerKind::Finger, "and still a finger");
let lifted = touch.send(up(0));
assert_eq!(
release(&lifted),
(landed_on, Some(Point::new(-40.0, 900.0))),
"and so is the release, at the point it slid to"
);
}
#[test]
fn a_cancel_ends_the_press_without_clicking_and_forgets_every_finger() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, surface, PX, PY));
touch.send(down(1, surface, 100.0, 200.0));
let cancelled = touch.send(cancel());
assert!(
matches!(cancelled.as_slice(), [(left, Event::MouseLeave)] if *left == surface),
"a cancel is a leave and nothing else, got {cancelled:?}"
);
assert!(
!cancelled
.iter()
.any(|(_, event)| matches!(event, Event::MouseUp { .. })),
"and never a release, which is what a click is made of"
);
assert_eq!(touch.primary, None);
assert!(
touch.fingers.is_empty(),
"every finger goes, not only the primary"
);
let landed = touch.send(down(0, surface, PX, PY));
assert_eq!(
press(&landed),
(surface, Some(Point::new(PX, PY))),
"the next finger down drives the pointer again"
);
assert_eq!(touch.primary, Some(0));
}
#[test]
fn a_cancel_with_no_primary_finger_says_nothing_and_still_clears() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, surface, PX, PY));
touch.send(down(1, surface, 100.0, 200.0));
touch.send(up(0));
let cancelled = touch.send(cancel());
assert!(
cancelled.is_empty(),
"no press was held, so nothing is released, got {cancelled:?}"
);
assert!(touch.fingers.is_empty(), "and the rest of the hand is gone");
}
#[test]
fn an_up_for_a_finger_that_was_never_down_says_nothing() {
let mut touch = Touch::default();
let stray = touch.send(up(7));
assert!(stray.is_empty(), "got {stray:?}");
assert_eq!(touch.primary, None);
}
#[test]
fn a_lifted_primary_does_not_promote_the_finger_still_down() {
let surface = SurfaceId::next();
let mut touch = Touch::default();
touch.send(down(0, surface, PX, PY));
touch.send(down(1, surface, 100.0, 200.0));
touch.send(up(0));
assert_eq!(touch.primary, None, "nobody drives the pointer now");
assert_eq!(
touch.fingers.get(&1).copied(),
Some((surface, 100.0, 200.0)),
"though a finger is still down"
);
let moved = touch.send(motion(1, 120.0, 220.0));
assert!(
moved.is_empty(),
"and moving it moves nothing, got {moved:?}"
);
}
}