From 9afd4e63c4fed556a6cbaa83ae3dafd292391e7b Mon Sep 17 00:00:00 2001 From: restitux Date: Sun, 10 Aug 2025 01:23:41 -0600 Subject: [PATCH] backend: forward keyboard and mouse input from client --- Cargo.lock | 26 + gamestream-webtransport-proxy/Cargo.toml | 1 + .../src/gamestream/decoder.rs | 1 - .../src/gamestream/mod.rs | 30 +- .../src/proxy/input/input_generated.rs | 1112 +++++++++++++++++ .../src/proxy/input/mod.rs | 1 + .../src/proxy/mod.rs | 16 +- .../src/proxy/packet_parser.rs | 158 +++ moonlight-common-c-sys/build.rs | 12 +- schema/input.fbs | 53 + 10 files changed, 1399 insertions(+), 11 deletions(-) create mode 100644 gamestream-webtransport-proxy/src/proxy/input/input_generated.rs create mode 100644 gamestream-webtransport-proxy/src/proxy/input/mod.rs create mode 100644 gamestream-webtransport-proxy/src/proxy/packet_parser.rs create mode 100644 schema/input.fbs diff --git a/Cargo.lock b/Cargo.lock index 5680d85..0d206dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -537,6 +537,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" +dependencies = [ + "bitflags", + "rustc_version", +] + [[package]] name = "fnv" version = "1.0.7" @@ -668,6 +678,7 @@ version = "0.1.0" dependencies = [ "anyhow", "directories", + "flatbuffers", "getrandom 0.3.3", "hex", "hmac-sha256", @@ -2038,6 +2049,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -2443,6 +2463,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" diff --git a/gamestream-webtransport-proxy/Cargo.toml b/gamestream-webtransport-proxy/Cargo.toml index 08c7252..9fa1cf5 100644 --- a/gamestream-webtransport-proxy/Cargo.toml +++ b/gamestream-webtransport-proxy/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] anyhow = "1.0.98" directories = "6.0.0" +flatbuffers = "25.2.10" getrandom = { version = "0.3.3", features = ["std"] } hex = "0.4.3" hmac-sha256 = "1.1.12" diff --git a/gamestream-webtransport-proxy/src/gamestream/decoder.rs b/gamestream-webtransport-proxy/src/gamestream/decoder.rs index 1a81f99..d156bb1 100644 --- a/gamestream-webtransport-proxy/src/gamestream/decoder.rs +++ b/gamestream-webtransport-proxy/src/gamestream/decoder.rs @@ -256,7 +256,6 @@ extern "C" fn start_cb() { } extern "C" fn submit_decode_unit_cb(decode_unit: PDECODE_UNIT) -> std::os::raw::c_int { - debug!("SUBMIT DECODE UNIT CB"); if decode_unit.is_null() { error!("Decode unit pointer was null"); return -1; diff --git a/gamestream-webtransport-proxy/src/gamestream/mod.rs b/gamestream-webtransport-proxy/src/gamestream/mod.rs index d83af7f..f8f24a7 100644 --- a/gamestream-webtransport-proxy/src/gamestream/mod.rs +++ b/gamestream-webtransport-proxy/src/gamestream/mod.rs @@ -20,7 +20,7 @@ pub fn start_connection( address, stream.server_codec_mode_support, )?; - let mut stream_config = config::stream_config(&stream); + let mut stream_config = config::stream_config(stream); let mut listener_callbacks = config::listener_callbacks(); let (mut decoder_callbacks, decoder_rx) = decoder::decoder_callbacks()?; let ret; @@ -47,3 +47,31 @@ pub fn start_connection( pub fn stop_connection() { unsafe { moonlight_common_c_sys::LiStopConnection() }; } + +pub fn send_keyboard_event(keycode: i16, keyaction: i8, modifiers: u8) -> Result<()> { + let ret = + unsafe { moonlight_common_c_sys::LiSendKeyboardEvent(keycode, keyaction, modifiers as i8) }; + + match ret { + 0 => Ok(()), + _ => Err(anyhow!("Could not send keyboard event: {ret}")), + } +} + +pub fn send_mouse_move_event(movement_x: i16, movement_y: i16) -> Result<()> { + let ret = unsafe { moonlight_common_c_sys::LiSendMouseMoveEvent(movement_x, movement_y) }; + + match ret { + 0 => Ok(()), + _ => Err(anyhow!("Could not send mouse movement event: {ret}")), + } +} + +pub fn send_mouse_input_event(action: i8, button: i32) -> Result<()> { + let ret = unsafe { moonlight_common_c_sys::LiSendMouseButtonEvent(action, button) }; + + match ret { + 0 => Ok(()), + _ => Err(anyhow!("Could not send mouse input event: {ret}")), + } +} diff --git a/gamestream-webtransport-proxy/src/proxy/input/input_generated.rs b/gamestream-webtransport-proxy/src/proxy/input/input_generated.rs new file mode 100644 index 0000000..0566229 --- /dev/null +++ b/gamestream-webtransport-proxy/src/proxy/input/input_generated.rs @@ -0,0 +1,1112 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +// @generated + +use core::mem; +use core::cmp::Ordering; + +extern crate flatbuffers; +use self::flatbuffers::{EndianScalar, Follow}; + +#[allow(unused_imports, dead_code)] +pub mod input_event { + + use core::mem; + use core::cmp::Ordering; + + extern crate flatbuffers; + use self::flatbuffers::{EndianScalar, Follow}; + +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MIN_KEY_ACTION: i8 = 0; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MAX_KEY_ACTION: i8 = 1; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +#[allow(non_camel_case_types)] +pub const ENUM_VALUES_KEY_ACTION: [KeyAction; 2] = [ + KeyAction::DOWN, + KeyAction::UP, +]; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[repr(transparent)] +pub struct KeyAction(pub i8); +#[allow(non_upper_case_globals)] +impl KeyAction { + pub const DOWN: Self = Self(0); + pub const UP: Self = Self(1); + + pub const ENUM_MIN: i8 = 0; + pub const ENUM_MAX: i8 = 1; + pub const ENUM_VALUES: &'static [Self] = &[ + Self::DOWN, + Self::UP, + ]; + /// Returns the variant's name or "" if unknown. + pub fn variant_name(self) -> Option<&'static str> { + match self { + Self::DOWN => Some("DOWN"), + Self::UP => Some("UP"), + _ => None, + } + } +} +impl core::fmt::Debug for KeyAction { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(name) = self.variant_name() { + f.write_str(name) + } else { + f.write_fmt(format_args!("", self.0)) + } + } +} +impl<'a> flatbuffers::Follow<'a> for KeyAction { + type Inner = Self; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + let b = flatbuffers::read_scalar_at::(buf, loc); + Self(b) + } +} + +impl flatbuffers::Push for KeyAction { + type Output = KeyAction; + #[inline] + unsafe fn push(&self, dst: &mut [u8], _written_len: usize) { + flatbuffers::emplace_scalar::(dst, self.0); + } +} + +impl flatbuffers::EndianScalar for KeyAction { + type Scalar = i8; + #[inline] + fn to_little_endian(self) -> i8 { + self.0.to_le() + } + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_little_endian(v: i8) -> Self { + let b = i8::from_le(v); + Self(b) + } +} + +impl<'a> flatbuffers::Verifiable for KeyAction { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + i8::run_verifier(v, pos) + } +} + +impl flatbuffers::SimpleToVerifyInSlice for KeyAction {} +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MIN_MOUSE_BUTTON: i8 = 0; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MAX_MOUSE_BUTTON: i8 = 4; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +#[allow(non_camel_case_types)] +pub const ENUM_VALUES_MOUSE_BUTTON: [MouseButton; 5] = [ + MouseButton::LEFT, + MouseButton::MIDDLE, + MouseButton::RIGHT, + MouseButton::X1, + MouseButton::X2, +]; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[repr(transparent)] +pub struct MouseButton(pub i8); +#[allow(non_upper_case_globals)] +impl MouseButton { + pub const LEFT: Self = Self(0); + pub const MIDDLE: Self = Self(1); + pub const RIGHT: Self = Self(2); + pub const X1: Self = Self(3); + pub const X2: Self = Self(4); + + pub const ENUM_MIN: i8 = 0; + pub const ENUM_MAX: i8 = 4; + pub const ENUM_VALUES: &'static [Self] = &[ + Self::LEFT, + Self::MIDDLE, + Self::RIGHT, + Self::X1, + Self::X2, + ]; + /// Returns the variant's name or "" if unknown. + pub fn variant_name(self) -> Option<&'static str> { + match self { + Self::LEFT => Some("LEFT"), + Self::MIDDLE => Some("MIDDLE"), + Self::RIGHT => Some("RIGHT"), + Self::X1 => Some("X1"), + Self::X2 => Some("X2"), + _ => None, + } + } +} +impl core::fmt::Debug for MouseButton { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(name) = self.variant_name() { + f.write_str(name) + } else { + f.write_fmt(format_args!("", self.0)) + } + } +} +impl<'a> flatbuffers::Follow<'a> for MouseButton { + type Inner = Self; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + let b = flatbuffers::read_scalar_at::(buf, loc); + Self(b) + } +} + +impl flatbuffers::Push for MouseButton { + type Output = MouseButton; + #[inline] + unsafe fn push(&self, dst: &mut [u8], _written_len: usize) { + flatbuffers::emplace_scalar::(dst, self.0); + } +} + +impl flatbuffers::EndianScalar for MouseButton { + type Scalar = i8; + #[inline] + fn to_little_endian(self) -> i8 { + self.0.to_le() + } + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_little_endian(v: i8) -> Self { + let b = i8::from_le(v); + Self(b) + } +} + +impl<'a> flatbuffers::Verifiable for MouseButton { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + i8::run_verifier(v, pos) + } +} + +impl flatbuffers::SimpleToVerifyInSlice for MouseButton {} +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MIN_INPUT: u8 = 0; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MAX_INPUT: u8 = 3; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +#[allow(non_camel_case_types)] +pub const ENUM_VALUES_INPUT: [Input; 4] = [ + Input::NONE, + Input::Keyboard, + Input::MouseMovement, + Input::MouseInput, +]; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[repr(transparent)] +pub struct Input(pub u8); +#[allow(non_upper_case_globals)] +impl Input { + pub const NONE: Self = Self(0); + pub const Keyboard: Self = Self(1); + pub const MouseMovement: Self = Self(2); + pub const MouseInput: Self = Self(3); + + pub const ENUM_MIN: u8 = 0; + pub const ENUM_MAX: u8 = 3; + pub const ENUM_VALUES: &'static [Self] = &[ + Self::NONE, + Self::Keyboard, + Self::MouseMovement, + Self::MouseInput, + ]; + /// Returns the variant's name or "" if unknown. + pub fn variant_name(self) -> Option<&'static str> { + match self { + Self::NONE => Some("NONE"), + Self::Keyboard => Some("Keyboard"), + Self::MouseMovement => Some("MouseMovement"), + Self::MouseInput => Some("MouseInput"), + _ => None, + } + } +} +impl core::fmt::Debug for Input { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(name) = self.variant_name() { + f.write_str(name) + } else { + f.write_fmt(format_args!("", self.0)) + } + } +} +impl<'a> flatbuffers::Follow<'a> for Input { + type Inner = Self; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + let b = flatbuffers::read_scalar_at::(buf, loc); + Self(b) + } +} + +impl flatbuffers::Push for Input { + type Output = Input; + #[inline] + unsafe fn push(&self, dst: &mut [u8], _written_len: usize) { + flatbuffers::emplace_scalar::(dst, self.0); + } +} + +impl flatbuffers::EndianScalar for Input { + type Scalar = u8; + #[inline] + fn to_little_endian(self) -> u8 { + self.0.to_le() + } + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_little_endian(v: u8) -> Self { + let b = u8::from_le(v); + Self(b) + } +} + +impl<'a> flatbuffers::Verifiable for Input { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + u8::run_verifier(v, pos) + } +} + +impl flatbuffers::SimpleToVerifyInSlice for Input {} +pub struct InputUnionTableOffset {} + +// struct ModifierState, aligned to 1 +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq)] +pub struct ModifierState(pub [u8; 4]); +impl Default for ModifierState { + fn default() -> Self { + Self([0; 4]) + } +} +impl core::fmt::Debug for ModifierState { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("ModifierState") + .field("shift", &self.shift()) + .field("ctrl", &self.ctrl()) + .field("alt", &self.alt()) + .field("meta", &self.meta()) + .finish() + } +} + +impl flatbuffers::SimpleToVerifyInSlice for ModifierState {} +impl<'a> flatbuffers::Follow<'a> for ModifierState { + type Inner = &'a ModifierState; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + <&'a ModifierState>::follow(buf, loc) + } +} +impl<'a> flatbuffers::Follow<'a> for &'a ModifierState { + type Inner = &'a ModifierState; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + flatbuffers::follow_cast_ref::(buf, loc) + } +} +impl<'b> flatbuffers::Push for ModifierState { + type Output = ModifierState; + #[inline] + unsafe fn push(&self, dst: &mut [u8], _written_len: usize) { + let src = ::core::slice::from_raw_parts(self as *const ModifierState as *const u8, ::size()); + dst.copy_from_slice(src); + } + #[inline] + fn alignment() -> flatbuffers::PushAlignment { + flatbuffers::PushAlignment::new(1) + } +} + +impl<'a> flatbuffers::Verifiable for ModifierState { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.in_buffer::(pos) + } +} + +impl<'a> ModifierState { + #[allow(clippy::too_many_arguments)] + pub fn new( + shift: bool, + ctrl: bool, + alt: bool, + meta: bool, + ) -> Self { + let mut s = Self([0; 4]); + s.set_shift(shift); + s.set_ctrl(ctrl); + s.set_alt(alt); + s.set_meta(meta); + s + } + + pub fn shift(&self) -> bool { + let mut mem = core::mem::MaybeUninit::<::Scalar>::uninit(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + EndianScalar::from_little_endian(unsafe { + core::ptr::copy_nonoverlapping( + self.0[0..].as_ptr(), + mem.as_mut_ptr() as *mut u8, + core::mem::size_of::<::Scalar>(), + ); + mem.assume_init() + }) + } + + pub fn set_shift(&mut self, x: bool) { + let x_le = x.to_little_endian(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + unsafe { + core::ptr::copy_nonoverlapping( + &x_le as *const _ as *const u8, + self.0[0..].as_mut_ptr(), + core::mem::size_of::<::Scalar>(), + ); + } + } + + pub fn ctrl(&self) -> bool { + let mut mem = core::mem::MaybeUninit::<::Scalar>::uninit(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + EndianScalar::from_little_endian(unsafe { + core::ptr::copy_nonoverlapping( + self.0[1..].as_ptr(), + mem.as_mut_ptr() as *mut u8, + core::mem::size_of::<::Scalar>(), + ); + mem.assume_init() + }) + } + + pub fn set_ctrl(&mut self, x: bool) { + let x_le = x.to_little_endian(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + unsafe { + core::ptr::copy_nonoverlapping( + &x_le as *const _ as *const u8, + self.0[1..].as_mut_ptr(), + core::mem::size_of::<::Scalar>(), + ); + } + } + + pub fn alt(&self) -> bool { + let mut mem = core::mem::MaybeUninit::<::Scalar>::uninit(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + EndianScalar::from_little_endian(unsafe { + core::ptr::copy_nonoverlapping( + self.0[2..].as_ptr(), + mem.as_mut_ptr() as *mut u8, + core::mem::size_of::<::Scalar>(), + ); + mem.assume_init() + }) + } + + pub fn set_alt(&mut self, x: bool) { + let x_le = x.to_little_endian(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + unsafe { + core::ptr::copy_nonoverlapping( + &x_le as *const _ as *const u8, + self.0[2..].as_mut_ptr(), + core::mem::size_of::<::Scalar>(), + ); + } + } + + pub fn meta(&self) -> bool { + let mut mem = core::mem::MaybeUninit::<::Scalar>::uninit(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + EndianScalar::from_little_endian(unsafe { + core::ptr::copy_nonoverlapping( + self.0[3..].as_ptr(), + mem.as_mut_ptr() as *mut u8, + core::mem::size_of::<::Scalar>(), + ); + mem.assume_init() + }) + } + + pub fn set_meta(&mut self, x: bool) { + let x_le = x.to_little_endian(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid value in this slot + unsafe { + core::ptr::copy_nonoverlapping( + &x_le as *const _ as *const u8, + self.0[3..].as_mut_ptr(), + core::mem::size_of::<::Scalar>(), + ); + } + } + +} + +pub enum MouseMovementOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct MouseMovement<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for MouseMovement<'a> { + type Inner = MouseMovement<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> MouseMovement<'a> { + pub const VT_MOVEMENT_X: flatbuffers::VOffsetT = 4; + pub const VT_MOVEMENT_Y: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + MouseMovement { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args MouseMovementArgs + ) -> flatbuffers::WIPOffset> { + let mut builder = MouseMovementBuilder::new(_fbb); + builder.add_movement_y(args.movement_y); + builder.add_movement_x(args.movement_x); + builder.finish() + } + + + #[inline] + pub fn movement_x(&self) -> i16 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(MouseMovement::VT_MOVEMENT_X, Some(0)).unwrap()} + } + #[inline] + pub fn movement_y(&self) -> i16 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(MouseMovement::VT_MOVEMENT_Y, Some(0)).unwrap()} + } +} + +impl flatbuffers::Verifiable for MouseMovement<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("movement_x", Self::VT_MOVEMENT_X, false)? + .visit_field::("movement_y", Self::VT_MOVEMENT_Y, false)? + .finish(); + Ok(()) + } +} +pub struct MouseMovementArgs { + pub movement_x: i16, + pub movement_y: i16, +} +impl<'a> Default for MouseMovementArgs { + #[inline] + fn default() -> Self { + MouseMovementArgs { + movement_x: 0, + movement_y: 0, + } + } +} + +pub struct MouseMovementBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> MouseMovementBuilder<'a, 'b, A> { + #[inline] + pub fn add_movement_x(&mut self, movement_x: i16) { + self.fbb_.push_slot::(MouseMovement::VT_MOVEMENT_X, movement_x, 0); + } + #[inline] + pub fn add_movement_y(&mut self, movement_y: i16) { + self.fbb_.push_slot::(MouseMovement::VT_MOVEMENT_Y, movement_y, 0); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> MouseMovementBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + MouseMovementBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for MouseMovement<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("MouseMovement"); + ds.field("movement_x", &self.movement_x()); + ds.field("movement_y", &self.movement_y()); + ds.finish() + } +} +pub enum MouseInputOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct MouseInput<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for MouseInput<'a> { + type Inner = MouseInput<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> MouseInput<'a> { + pub const VT_BUTTON: flatbuffers::VOffsetT = 4; + pub const VT_BUTTON_ACTION: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + MouseInput { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args MouseInputArgs + ) -> flatbuffers::WIPOffset> { + let mut builder = MouseInputBuilder::new(_fbb); + builder.add_button_action(args.button_action); + builder.add_button(args.button); + builder.finish() + } + + + #[inline] + pub fn button(&self) -> MouseButton { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(MouseInput::VT_BUTTON, Some(MouseButton::LEFT)).unwrap()} + } + #[inline] + pub fn button_action(&self) -> KeyAction { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(MouseInput::VT_BUTTON_ACTION, Some(KeyAction::DOWN)).unwrap()} + } +} + +impl flatbuffers::Verifiable for MouseInput<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("button", Self::VT_BUTTON, false)? + .visit_field::("button_action", Self::VT_BUTTON_ACTION, false)? + .finish(); + Ok(()) + } +} +pub struct MouseInputArgs { + pub button: MouseButton, + pub button_action: KeyAction, +} +impl<'a> Default for MouseInputArgs { + #[inline] + fn default() -> Self { + MouseInputArgs { + button: MouseButton::LEFT, + button_action: KeyAction::DOWN, + } + } +} + +pub struct MouseInputBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> MouseInputBuilder<'a, 'b, A> { + #[inline] + pub fn add_button(&mut self, button: MouseButton) { + self.fbb_.push_slot::(MouseInput::VT_BUTTON, button, MouseButton::LEFT); + } + #[inline] + pub fn add_button_action(&mut self, button_action: KeyAction) { + self.fbb_.push_slot::(MouseInput::VT_BUTTON_ACTION, button_action, KeyAction::DOWN); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> MouseInputBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + MouseInputBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for MouseInput<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("MouseInput"); + ds.field("button", &self.button()); + ds.field("button_action", &self.button_action()); + ds.finish() + } +} +pub enum KeyboardInputOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct KeyboardInput<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for KeyboardInput<'a> { + type Inner = KeyboardInput<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> KeyboardInput<'a> { + pub const VT_KEY_CODE: flatbuffers::VOffsetT = 4; + pub const VT_KEY_ACTION: flatbuffers::VOffsetT = 6; + pub const VT_MODIFIERS: flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + KeyboardInput { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args KeyboardInputArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = KeyboardInputBuilder::new(_fbb); + if let Some(x) = args.modifiers { builder.add_modifiers(x); } + builder.add_key_code(args.key_code); + builder.add_key_action(args.key_action); + builder.finish() + } + + + #[inline] + pub fn key_code(&self) -> i16 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(KeyboardInput::VT_KEY_CODE, Some(0)).unwrap()} + } + #[inline] + pub fn key_action(&self) -> KeyAction { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(KeyboardInput::VT_KEY_ACTION, Some(KeyAction::DOWN)).unwrap()} + } + #[inline] + pub fn modifiers(&self) -> Option<&'a ModifierState> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(KeyboardInput::VT_MODIFIERS, None)} + } +} + +impl flatbuffers::Verifiable for KeyboardInput<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("key_code", Self::VT_KEY_CODE, false)? + .visit_field::("key_action", Self::VT_KEY_ACTION, false)? + .visit_field::("modifiers", Self::VT_MODIFIERS, false)? + .finish(); + Ok(()) + } +} +pub struct KeyboardInputArgs<'a> { + pub key_code: i16, + pub key_action: KeyAction, + pub modifiers: Option<&'a ModifierState>, +} +impl<'a> Default for KeyboardInputArgs<'a> { + #[inline] + fn default() -> Self { + KeyboardInputArgs { + key_code: 0, + key_action: KeyAction::DOWN, + modifiers: None, + } + } +} + +pub struct KeyboardInputBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> KeyboardInputBuilder<'a, 'b, A> { + #[inline] + pub fn add_key_code(&mut self, key_code: i16) { + self.fbb_.push_slot::(KeyboardInput::VT_KEY_CODE, key_code, 0); + } + #[inline] + pub fn add_key_action(&mut self, key_action: KeyAction) { + self.fbb_.push_slot::(KeyboardInput::VT_KEY_ACTION, key_action, KeyAction::DOWN); + } + #[inline] + pub fn add_modifiers(&mut self, modifiers: &ModifierState) { + self.fbb_.push_slot_always::<&ModifierState>(KeyboardInput::VT_MODIFIERS, modifiers); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> KeyboardInputBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + KeyboardInputBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for KeyboardInput<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("KeyboardInput"); + ds.field("key_code", &self.key_code()); + ds.field("key_action", &self.key_action()); + ds.field("modifiers", &self.modifiers()); + ds.finish() + } +} +pub enum InputEventOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct InputEvent<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for InputEvent<'a> { + type Inner = InputEvent<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> InputEvent<'a> { + pub const VT_INPUT_TYPE: flatbuffers::VOffsetT = 4; + pub const VT_INPUT: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + InputEvent { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args InputEventArgs + ) -> flatbuffers::WIPOffset> { + let mut builder = InputEventBuilder::new(_fbb); + if let Some(x) = args.input { builder.add_input(x); } + builder.add_input_type(args.input_type); + builder.finish() + } + + + #[inline] + pub fn input_type(&self) -> Input { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(InputEvent::VT_INPUT_TYPE, Some(Input::NONE)).unwrap()} + } + #[inline] + pub fn input(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>(InputEvent::VT_INPUT, None)} + } + #[inline] + #[allow(non_snake_case)] + pub fn input_as_keyboard(&self) -> Option> { + if self.input_type() == Input::Keyboard { + self.input().map(|t| { + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + unsafe { KeyboardInput::init_from_table(t) } + }) + } else { + None + } + } + + #[inline] + #[allow(non_snake_case)] + pub fn input_as_mouse_movement(&self) -> Option> { + if self.input_type() == Input::MouseMovement { + self.input().map(|t| { + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + unsafe { MouseMovement::init_from_table(t) } + }) + } else { + None + } + } + + #[inline] + #[allow(non_snake_case)] + pub fn input_as_mouse_input(&self) -> Option> { + if self.input_type() == Input::MouseInput { + self.input().map(|t| { + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + unsafe { MouseInput::init_from_table(t) } + }) + } else { + None + } + } + +} + +impl flatbuffers::Verifiable for InputEvent<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_union::("input_type", Self::VT_INPUT_TYPE, "input", Self::VT_INPUT, false, |key, v, pos| { + match key { + Input::Keyboard => v.verify_union_variant::>("Input::Keyboard", pos), + Input::MouseMovement => v.verify_union_variant::>("Input::MouseMovement", pos), + Input::MouseInput => v.verify_union_variant::>("Input::MouseInput", pos), + _ => Ok(()), + } + })? + .finish(); + Ok(()) + } +} +pub struct InputEventArgs { + pub input_type: Input, + pub input: Option>, +} +impl<'a> Default for InputEventArgs { + #[inline] + fn default() -> Self { + InputEventArgs { + input_type: Input::NONE, + input: None, + } + } +} + +pub struct InputEventBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> InputEventBuilder<'a, 'b, A> { + #[inline] + pub fn add_input_type(&mut self, input_type: Input) { + self.fbb_.push_slot::(InputEvent::VT_INPUT_TYPE, input_type, Input::NONE); + } + #[inline] + pub fn add_input(&mut self, input: flatbuffers::WIPOffset) { + self.fbb_.push_slot_always::>(InputEvent::VT_INPUT, input); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> InputEventBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + InputEventBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for InputEvent<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("InputEvent"); + ds.field("input_type", &self.input_type()); + match self.input_type() { + Input::Keyboard => { + if let Some(x) = self.input_as_keyboard() { + ds.field("input", &x) + } else { + ds.field("input", &"InvalidFlatbuffer: Union discriminant does not match value.") + } + }, + Input::MouseMovement => { + if let Some(x) = self.input_as_mouse_movement() { + ds.field("input", &x) + } else { + ds.field("input", &"InvalidFlatbuffer: Union discriminant does not match value.") + } + }, + Input::MouseInput => { + if let Some(x) = self.input_as_mouse_input() { + ds.field("input", &x) + } else { + ds.field("input", &"InvalidFlatbuffer: Union discriminant does not match value.") + } + }, + _ => { + let x: Option<()> = None; + ds.field("input", &x) + }, + }; + ds.finish() + } +} +#[inline] +/// Verifies that a buffer of bytes contains a `InputEvent` +/// and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_input_event_unchecked`. +pub fn root_as_input_event(buf: &[u8]) -> Result { + flatbuffers::root::(buf) +} +#[inline] +/// Verifies that a buffer of bytes contains a size prefixed +/// `InputEvent` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `size_prefixed_root_as_input_event_unchecked`. +pub fn size_prefixed_root_as_input_event(buf: &[u8]) -> Result { + flatbuffers::size_prefixed_root::(buf) +} +#[inline] +/// Verifies, with the given options, that a buffer of bytes +/// contains a `InputEvent` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_input_event_unchecked`. +pub fn root_as_input_event_with_opts<'b, 'o>( + opts: &'o flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, flatbuffers::InvalidFlatbuffer> { + flatbuffers::root_with_opts::>(opts, buf) +} +#[inline] +/// Verifies, with the given verifier options, that a buffer of +/// bytes contains a size prefixed `InputEvent` and returns +/// it. Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_input_event_unchecked`. +pub fn size_prefixed_root_as_input_event_with_opts<'b, 'o>( + opts: &'o flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, flatbuffers::InvalidFlatbuffer> { + flatbuffers::size_prefixed_root_with_opts::>(opts, buf) +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a InputEvent and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid `InputEvent`. +pub unsafe fn root_as_input_event_unchecked(buf: &[u8]) -> InputEvent { + flatbuffers::root_unchecked::(buf) +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a size prefixed InputEvent and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid size prefixed `InputEvent`. +pub unsafe fn size_prefixed_root_as_input_event_unchecked(buf: &[u8]) -> InputEvent { + flatbuffers::size_prefixed_root_unchecked::(buf) +} +#[inline] +pub fn finish_input_event_buffer<'a, 'b, A: flatbuffers::Allocator + 'a>( + fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + root: flatbuffers::WIPOffset>) { + fbb.finish(root, None); +} + +#[inline] +pub fn finish_size_prefixed_input_event_buffer<'a, 'b, A: flatbuffers::Allocator + 'a>(fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, root: flatbuffers::WIPOffset>) { + fbb.finish_size_prefixed(root, None); +} +} // pub mod InputEvent + diff --git a/gamestream-webtransport-proxy/src/proxy/input/mod.rs b/gamestream-webtransport-proxy/src/proxy/input/mod.rs new file mode 100644 index 0000000..30ce262 --- /dev/null +++ b/gamestream-webtransport-proxy/src/proxy/input/mod.rs @@ -0,0 +1 @@ +pub mod input_generated; diff --git a/gamestream-webtransport-proxy/src/proxy/mod.rs b/gamestream-webtransport-proxy/src/proxy/mod.rs index 5e4122e..cf7948b 100644 --- a/gamestream-webtransport-proxy/src/proxy/mod.rs +++ b/gamestream-webtransport-proxy/src/proxy/mod.rs @@ -1,11 +1,12 @@ use anyhow::{Context, Result}; -use salvo::{conn::http1::Connection, hyper::body::Bytes, proto::WebTransportSession}; use tokio::{io::AsyncReadExt, io::AsyncWriteExt, select, sync::RwLock}; use tracing::{debug, error, info}; use crate::{backend, gamestream}; pub mod handler; +mod input; +mod packet_parser; pub struct Proxy { pub cert_hash: [u8; 32], @@ -40,14 +41,16 @@ async fn proxy_main( let mut channels = spawn_gamestream(stream).await?; - let mut buffer = vec![0; 65536].into_boxed_slice(); + let mut packet_buffer = packet_parser::PacketBuffer::new(); + //let mut buffer = vec![0; 65536].into_boxed_slice(); + let mut buffer = [0u8; 65536]; + //let mut buffer = vec![0; 65536].into_boxed_slice(); loop { select! { gamestream_packet = channels.gamestream_channels.decoder_rx.recv() => { match gamestream_packet { Some(frame) => { - info!("Got decoder packet"); let frame_json = serde_json::to_vec(&frame)?; let frame_json_len: u32 = ::try_from(frame_json.len())?; @@ -60,8 +63,11 @@ async fn proxy_main( } } }, - webtransport_packet = wt_recv.read(&mut buffer) => { - info!("Got packet from client"); + ret = wt_recv.read(&mut buffer) => { + let bytes_read = ret?; + + packet_parser::handle_client_packet(&mut packet_buffer, &buffer[..bytes_read])?; + } } } diff --git a/gamestream-webtransport-proxy/src/proxy/packet_parser.rs b/gamestream-webtransport-proxy/src/proxy/packet_parser.rs new file mode 100644 index 0000000..4ad99e3 --- /dev/null +++ b/gamestream-webtransport-proxy/src/proxy/packet_parser.rs @@ -0,0 +1,158 @@ +use anyhow::{Result, anyhow}; +use tracing::{debug, error, info}; + +use super::input::input_generated::input_event; +use crate::gamestream; + +pub struct PacketBuffer { + buffer: Vec, + expected_length: Option, + length_bytes_needed: usize, +} + +impl PacketBuffer { + pub fn new() -> Self { + Self { + buffer: Vec::new(), + expected_length: None, + length_bytes_needed: 4, // Assuming u32 length (4 bytes) + } + } + + /// Process incoming data and return complete packets + pub fn process_data(&mut self, data: &[u8]) -> Vec> { + let mut complete_packets = Vec::new(); + self.buffer.extend_from_slice(data); + + loop { + // If we don't know the expected length yet, try to read it + if self.expected_length.is_none() { + if self.buffer.len() >= self.length_bytes_needed { + // Read length from first 4 bytes (adjust as needed) + let length = u32::from_be_bytes([ + self.buffer[0], + self.buffer[1], + self.buffer[2], + self.buffer[3], + ]) as usize; + + self.expected_length = Some(length); + } else { + // Not enough data to read length yet + break; + } + } + + // We know the expected length, check if we have a complete packet + if let Some(expected_len) = self.expected_length { + let total_packet_size = self.length_bytes_needed + expected_len; + + if self.buffer.len() >= total_packet_size { + // We have a complete packet - extract just the data portion + let packet_data = + self.buffer[self.length_bytes_needed..total_packet_size].to_vec(); + complete_packets.push(packet_data); + + // Remove the processed packet from buffer + self.buffer.drain(0..total_packet_size); + self.expected_length = None; + } else { + // Packet is incomplete + break; + } + } + } + + complete_packets + } +} + +pub fn process_keyboard_event(input_event: input_event::InputEvent) -> Result<()> { + let Some(table) = input_event.input() else { + debug!("Keyboard event table was empty, ignoring"); + return Err(anyhow!("Keyboard event table was empty")); + }; + let keyboard_event = unsafe { input_event::KeyboardInput::init_from_table(table) }; + + let key_action_i8 = match keyboard_event.key_action() { + input_event::KeyAction::DOWN => moonlight_common_c_sys::KEY_ACTION_DOWN, + input_event::KeyAction::UP => moonlight_common_c_sys::KEY_ACTION_UP, + _ => { + debug!("Invalid KeyAction value, ignoring"); + return Err(anyhow!("Invalid KeyAction value")); + } + }; + + gamestream::send_keyboard_event(keyboard_event.key_code(), key_action_i8, 0) +} + +pub fn process_mouse_move_event(input_event: input_event::InputEvent) -> Result<()> { + let Some(table) = input_event.input() else { + debug!("MouseMovement event table was empty, ignoring"); + return Err(anyhow!("MouseMovement event table was empty")); + }; + let mouse_event = unsafe { input_event::MouseMovement::init_from_table(table) }; + + gamestream::send_mouse_move_event(mouse_event.movement_x(), mouse_event.movement_y()) +} + +pub fn process_mouse_input_event(input_event: input_event::InputEvent) -> Result<()> { + let Some(table) = input_event.input() else { + debug!("MouseInput event table was empty, ignoring"); + return Err(anyhow!("MouseInput event table was empty")); + }; + let mouse_event = unsafe { input_event::MouseInput::init_from_table(table) }; + + let button_action_i8 = match mouse_event.button_action() { + input_event::KeyAction::DOWN => moonlight_common_c_sys::BUTTON_ACTION_PRESS, + input_event::KeyAction::UP => moonlight_common_c_sys::BUTTON_ACTION_RELEASE, + _ => { + error!("Invalid KeyAction value, ignoring",); + return Err(anyhow!("Invalid KeyAction value")); + } + }; + + let mouse_button_i32 = match mouse_event.button() { + input_event::MouseButton::LEFT => moonlight_common_c_sys::BUTTON_LEFT, + input_event::MouseButton::MIDDLE => moonlight_common_c_sys::BUTTON_MIDDLE, + input_event::MouseButton::RIGHT => moonlight_common_c_sys::BUTTON_RIGHT, + input_event::MouseButton::X1 => moonlight_common_c_sys::BUTTON_X1, + input_event::MouseButton::X2 => moonlight_common_c_sys::BUTTON_X2, + _ => { + error!("Invalid MouseButton value, ignoring",); + return Err(anyhow!("Invalid MouseButton value")); + } + }; + + gamestream::send_mouse_input_event(button_action_i8, mouse_button_i32) +} + +pub fn handle_client_packet(packet_buffer: &mut PacketBuffer, buffer: &[u8]) -> anyhow::Result<()> { + let complete_packets = packet_buffer.process_data(buffer); + + // TODO: only supports input packets. this should use a union wrapper or something + for packet_data in complete_packets { + let input_event = input_event::root_as_input_event(&packet_data)?; + match input_event.input_type() { + input_event::Input::Keyboard => { + process_keyboard_event(input_event)?; + } + input_event::Input::MouseMovement => { + process_mouse_move_event(input_event)?; + } + input_event::Input::MouseInput => { + process_mouse_input_event(input_event)?; + } + + input_event::Input::NONE => { + debug!("Input event was empty, ignoring.") + } + _ => { + error!("Unknown InputEvent type"); + return Err(anyhow!("Unknown InputEvent type")); + } + } + } + + Ok(()) +} diff --git a/moonlight-common-c-sys/build.rs b/moonlight-common-c-sys/build.rs index 1ecf306..517f0cb 100644 --- a/moonlight-common-c-sys/build.rs +++ b/moonlight-common-c-sys/build.rs @@ -12,9 +12,13 @@ struct CustomCallbacks; impl ParseCallbacks for CustomCallbacks { fn int_macro(&self, name: &str, _value: i64) -> Option { match name { - "STREAM_CFG_LOCAL" => Some(IntKind::I32), - "STREAM_CFG_REMOTE" => Some(IntKind::I32), - "STREAM_CFG_AUTO" => Some(IntKind::I32), + //"STREAM_CFG_LOCAL" => Some(IntKind::I32), + //"STREAM_CFG_REMOTE" => Some(IntKind::I32), + //"STREAM_CFG_AUTO" => Some(IntKind::I32), + "KEY_ACTION_DOWN" => Some(IntKind::I8), + "KEY_ACTION_UP" => Some(IntKind::I8), + "BUTTON_ACTION_PRESS" => Some(IntKind::I8), + "BUTTON_ACTION_RELEASE" => Some(IntKind::I8), _ => None, // Default behavior for all others } } @@ -34,7 +38,7 @@ fn main() { let bindings = bindgen::Builder::default() .header("moonlight-common-c/src/Limelight.h") .clang_arg(format!("-I{}/src", dst.display())) // Include built headers - //.parse_callbacks(Box::new(CustomCallbacks)) + .parse_callbacks(Box::new(CustomCallbacks)) .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) .generate() .expect("Failed to generate bindings"); diff --git a/schema/input.fbs b/schema/input.fbs new file mode 100644 index 0000000..fb56204 --- /dev/null +++ b/schema/input.fbs @@ -0,0 +1,53 @@ +namespace InputEvent; + + +enum KeyAction: byte { + DOWN, + UP, +} + + +table MouseMovement { + movement_x: int16; + movement_y: int16; +} + +enum MouseButton: byte { + LEFT, + MIDDLE, + RIGHT, + X1, + X2 +} + +table MouseInput { + button: MouseButton; + button_action: KeyAction; +} + + +struct ModifierState { + shift: bool; + ctrl: bool; + alt: bool; + meta: bool; +} + +table KeyboardInput { + key_code: int16; + key_action: KeyAction; + modifiers: ModifierState; +} + +union Input { + Keyboard:KeyboardInput, + MouseMovement:MouseMovement, + MouseInput:MouseInput, +} + +table InputEvent { + input: Input; +} + +root_type InputEvent; +