From 919e918c0a3d7de8cbd81ede983979d4a79e12fe Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 03:11:15 +0200 Subject: [PATCH] feat(input-server): expand protocol with pointer, scroll, and gamepad events --- crates/servers/input-server/src/protocol.rs | 187 ++++++++++++++++-- .../input-server/src/tests/protocol.rs | 76 ++++++- 2 files changed, 249 insertions(+), 14 deletions(-) diff --git a/crates/servers/input-server/src/protocol.rs b/crates/servers/input-server/src/protocol.rs index dcdcfd2..13ded6c 100644 --- a/crates/servers/input-server/src/protocol.rs +++ b/crates/servers/input-server/src/protocol.rs @@ -8,13 +8,47 @@ pub enum Key { Right, Enter, Escape, + Space, + Backspace, + Tab, Char(u8), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GamepadButton { + South, + East, + North, + West, + DpadUp, + DpadDown, + DpadLeft, + DpadRight, + Start, + Select, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Axis { + LeftX, + LeftY, + RightX, + RightY, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InputEvent { KeyDown(Key), KeyUp(Key), + + PointerDown { id: u8, x: u16, y: u16 }, + PointerMove { id: u8, x: u16, y: u16 }, + PointerUp { id: u8, x: u16, y: u16 }, + + Scroll { dx: i16, dy: i16 }, + + GamepadButton { button: GamepadButton, pressed: bool }, + GamepadAxis { axis: Axis, value: i16 }, } #[derive(Debug, PartialEq)] @@ -48,6 +82,16 @@ impl Decode for InputRequest { } } +// Event type tags for encoding +const EV_KEY_DOWN: usize = 1; +const EV_KEY_UP: usize = 2; +const EV_PTR_DOWN: usize = 3; +const EV_PTR_MOVE: usize = 4; +const EV_PTR_UP: usize = 5; +const EV_SCROLL: usize = 6; +const EV_GAMEPAD_BTN: usize = 7; +const EV_GAMEPAD_AXIS: usize = 8; + fn encode_key(key: Key) -> (usize, usize) { match key { Key::Up => (0, 0), @@ -56,7 +100,10 @@ fn encode_key(key: Key) -> (usize, usize) { Key::Right => (3, 0), Key::Enter => (4, 0), Key::Escape => (5, 0), - Key::Char(c) => (6, c as usize), + Key::Space => (6, 0), + Key::Backspace => (7, 0), + Key::Tab => (8, 0), + Key::Char(c) => (9, c as usize), } } @@ -68,7 +115,68 @@ fn decode_key(kind: usize, data: usize) -> Option { 3 => Some(Key::Right), 4 => Some(Key::Enter), 5 => Some(Key::Escape), - 6 => Some(Key::Char(data as u8)), + 6 => Some(Key::Space), + 7 => Some(Key::Backspace), + 8 => Some(Key::Tab), + 9 => Some(Key::Char(data as u8)), + _ => None, + } +} + +fn encode_pointer(id: u8, x: u16, y: u16) -> (usize, usize) { + (id as usize, (x as usize) | ((y as usize) << 16)) +} + +fn decode_pointer(w1: usize, w2: usize) -> (u8, u16, u16) { + (w1 as u8, (w2 & 0xFFFF) as u16, ((w2 >> 16) & 0xFFFF) as u16) +} + +fn encode_gamepad_button(button: GamepadButton) -> usize { + match button { + GamepadButton::South => 0, + GamepadButton::East => 1, + GamepadButton::North => 2, + GamepadButton::West => 3, + GamepadButton::DpadUp => 4, + GamepadButton::DpadDown => 5, + GamepadButton::DpadLeft => 6, + GamepadButton::DpadRight => 7, + GamepadButton::Start => 8, + GamepadButton::Select => 9, + } +} + +fn decode_gamepad_button(v: usize) -> Option { + match v { + 0 => Some(GamepadButton::South), + 1 => Some(GamepadButton::East), + 2 => Some(GamepadButton::North), + 3 => Some(GamepadButton::West), + 4 => Some(GamepadButton::DpadUp), + 5 => Some(GamepadButton::DpadDown), + 6 => Some(GamepadButton::DpadLeft), + 7 => Some(GamepadButton::DpadRight), + 8 => Some(GamepadButton::Start), + 9 => Some(GamepadButton::Select), + _ => None, + } +} + +fn encode_axis(axis: Axis) -> usize { + match axis { + Axis::LeftX => 0, + Axis::LeftY => 1, + Axis::RightX => 2, + Axis::RightY => 3, + } +} + +fn decode_axis(v: usize) -> Option { + match v { + 0 => Some(Axis::LeftX), + 1 => Some(Axis::LeftY), + 2 => Some(Axis::RightX), + 3 => Some(Axis::RightY), _ => None, } } @@ -77,14 +185,37 @@ impl Encode for InputResponse { fn encode(&self) -> [usize; 4] { match self { InputResponse::NoEvent => [0, 0, 0, 0], - InputResponse::Event(InputEvent::KeyDown(key)) => { - let (kind, data) = encode_key(*key); - [1, kind, data, 0] - } - InputResponse::Event(InputEvent::KeyUp(key)) => { - let (kind, data) = encode_key(*key); - [2, kind, data, 0] - } + InputResponse::Event(event) => match event { + InputEvent::KeyDown(key) => { + let (k, d) = encode_key(*key); + [EV_KEY_DOWN, k, d, 0] + } + InputEvent::KeyUp(key) => { + let (k, d) = encode_key(*key); + [EV_KEY_UP, k, d, 0] + } + InputEvent::PointerDown { id, x, y } => { + let (w1, w2) = encode_pointer(*id, *x, *y); + [EV_PTR_DOWN, w1, w2, 0] + } + InputEvent::PointerMove { id, x, y } => { + let (w1, w2) = encode_pointer(*id, *x, *y); + [EV_PTR_MOVE, w1, w2, 0] + } + InputEvent::PointerUp { id, x, y } => { + let (w1, w2) = encode_pointer(*id, *x, *y); + [EV_PTR_UP, w1, w2, 0] + } + InputEvent::Scroll { dx, dy } => { + [EV_SCROLL, *dx as usize, *dy as usize, 0] + } + InputEvent::GamepadButton { button, pressed } => { + [EV_GAMEPAD_BTN, encode_gamepad_button(*button), *pressed as usize, 0] + } + InputEvent::GamepadAxis { axis, value } => { + [EV_GAMEPAD_AXIS, encode_axis(*axis), *value as usize, 0] + } + }, } } } @@ -93,14 +224,46 @@ impl Decode for InputResponse { fn decode(words: [usize; 4]) -> Option { match words[0] { 0 => Some(InputResponse::NoEvent), - 1 => { + EV_KEY_DOWN => { let key = decode_key(words[1], words[2])?; Some(InputResponse::Event(InputEvent::KeyDown(key))) } - 2 => { + EV_KEY_UP => { let key = decode_key(words[1], words[2])?; Some(InputResponse::Event(InputEvent::KeyUp(key))) } + EV_PTR_DOWN => { + let (id, x, y) = decode_pointer(words[1], words[2]); + Some(InputResponse::Event(InputEvent::PointerDown { id, x, y })) + } + EV_PTR_MOVE => { + let (id, x, y) = decode_pointer(words[1], words[2]); + Some(InputResponse::Event(InputEvent::PointerMove { id, x, y })) + } + EV_PTR_UP => { + let (id, x, y) = decode_pointer(words[1], words[2]); + Some(InputResponse::Event(InputEvent::PointerUp { id, x, y })) + } + EV_SCROLL => { + Some(InputResponse::Event(InputEvent::Scroll { + dx: words[1] as i16, + dy: words[2] as i16, + })) + } + EV_GAMEPAD_BTN => { + let button = decode_gamepad_button(words[1])?; + Some(InputResponse::Event(InputEvent::GamepadButton { + button, + pressed: words[2] != 0, + })) + } + EV_GAMEPAD_AXIS => { + let axis = decode_axis(words[1])?; + Some(InputResponse::Event(InputEvent::GamepadAxis { + axis, + value: words[2] as i16, + })) + } _ => None, } } diff --git a/crates/servers/input-server/src/tests/protocol.rs b/crates/servers/input-server/src/tests/protocol.rs index 8f7823a..f3c8f40 100644 --- a/crates/servers/input-server/src/tests/protocol.rs +++ b/crates/servers/input-server/src/tests/protocol.rs @@ -14,14 +14,14 @@ fn wait_request_roundtrip() { } #[test] -fn key_down_response_roundtrip() { +fn key_down_roundtrip() { let resp = InputResponse::Event(InputEvent::KeyDown(Key::Up)); let words = resp.encode(); assert_eq!(InputResponse::decode(words), Some(resp)); } #[test] -fn key_up_response_roundtrip() { +fn key_up_roundtrip() { let resp = InputResponse::Event(InputEvent::KeyUp(Key::Left)); let words = resp.encode(); assert_eq!(InputResponse::decode(words), Some(resp)); @@ -34,12 +34,84 @@ fn char_key_roundtrip() { assert_eq!(InputResponse::decode(words), Some(resp)); } +#[test] +fn space_tab_backspace_roundtrip() { + for key in [Key::Space, Key::Tab, Key::Backspace] { + let resp = InputResponse::Event(InputEvent::KeyDown(key)); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); + } +} + #[test] fn no_event_roundtrip() { let words = InputResponse::NoEvent.encode(); assert_eq!(InputResponse::decode(words), Some(InputResponse::NoEvent)); } +#[test] +fn pointer_down_roundtrip() { + let resp = InputResponse::Event(InputEvent::PointerDown { id: 0, x: 100, y: 200 }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn pointer_move_roundtrip() { + let resp = InputResponse::Event(InputEvent::PointerMove { id: 1, x: 300, y: 400 }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn pointer_up_roundtrip() { + let resp = InputResponse::Event(InputEvent::PointerUp { id: 2, x: 50, y: 60 }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn scroll_roundtrip() { + let resp = InputResponse::Event(InputEvent::Scroll { dx: -3, dy: 5 }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn gamepad_button_roundtrip() { + let resp = InputResponse::Event(InputEvent::GamepadButton { + button: GamepadButton::South, + pressed: true, + }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn gamepad_button_released_roundtrip() { + let resp = InputResponse::Event(InputEvent::GamepadButton { + button: GamepadButton::Start, + pressed: false, + }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn gamepad_axis_roundtrip() { + let resp = InputResponse::Event(InputEvent::GamepadAxis { + axis: Axis::LeftX, + value: -128, + }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); +} + +#[test] +fn all_gamepad_buttons_roundtrip() { + let buttons = [ + GamepadButton::South, GamepadButton::East, GamepadButton::North, GamepadButton::West, + GamepadButton::DpadUp, GamepadButton::DpadDown, GamepadButton::DpadLeft, GamepadButton::DpadRight, + GamepadButton::Start, GamepadButton::Select, + ]; + for button in buttons { + let resp = InputResponse::Event(InputEvent::GamepadButton { button, pressed: true }); + assert_eq!(InputResponse::decode(resp.encode()), Some(resp)); + } +} + #[test] fn invalid_request_returns_none() { assert!(InputRequest::decode([99, 0, 0, 0]).is_none());