diff --git a/src/drivers/xpad_uhid/driver.rs b/src/drivers/xpad_uhid/driver.rs index ac5069d4..ed3aae99 100644 --- a/src/drivers/xpad_uhid/driver.rs +++ b/src/drivers/xpad_uhid/driver.rs @@ -4,7 +4,9 @@ use hidapi::HidDevice; use packed_struct::PackedStruct; use crate::{ - drivers::xpad_uhid::hid_report::{DPadDirection, XBoxSeriesInputDataReport}, + drivers::xpad_uhid::hid_report::{ + DPadDirection, XBoxOneBtInputDataReport, XBoxSeriesInputDataReport, + }, udev::device::UdevDevice, }; @@ -17,8 +19,10 @@ use super::{ pub const DATA: u8 = 0x01; pub const GUIDE: u8 = 0x02; -// Input report size +// Input report size for Xbox Series controllers const PACKET_SIZE: usize = 17; +// Input report size for Xbox One Bluetooth controllers +const XBOX_ONE_BT_PACKET_SIZE: usize = 16; // HID buffer read timeout const HID_TIMEOUT: i32 = 10; @@ -99,20 +103,28 @@ impl Driver { let bytes_read = self.device.read_timeout(&mut buf[..], HID_TIMEOUT)?; let report_id = buf[0]; - let slice = &buf[..bytes_read]; //log::debug!("Got Report ID: {report_id}"); //log::debug!("Got Report Size: {bytes_read}"); let events = match report_id { DATA => { log::trace!("Got input data."); - if bytes_read != PACKET_SIZE { - return Err("Invalid packet size for input data.".into()); - } - // Handle the incoming input report - let sized_buf = slice.try_into()?; - - self.handle_input_report(sized_buf)? + let input_report = match bytes_read { + // Xbox Series controller format (17 bytes) + PACKET_SIZE => XBoxSeriesInputDataReport::unpack(&buf)?, + // Xbox One Bluetooth format (16 bytes) + XBOX_ONE_BT_PACKET_SIZE => XBoxOneBtInputDataReport::unpack( + buf[..XBOX_ONE_BT_PACKET_SIZE].try_into()?, + )? + .to_series_report(), + _ => { + log::warn!( + "Unexpected packet size for input data: {bytes_read} (expected {PACKET_SIZE} or {XBOX_ONE_BT_PACKET_SIZE})" + ); + return Ok(vec![]); + } + }; + self.handle_input_report(input_report) } // XBox One gamepads have a separate report for guide button presses // for some reason. @@ -136,14 +148,9 @@ impl Driver { Ok(events) } - /// Unpacks the buffer into a [DataReport] structure and updates - /// the internal state - fn handle_input_report( - &mut self, - buf: [u8; PACKET_SIZE], - ) -> Result, Box> { - let input_report = XBoxSeriesInputDataReport::unpack(&buf)?; - + /// Updates the internal state with the given input report and translates + /// state changes into a stream of input events + fn handle_input_report(&mut self, input_report: XBoxSeriesInputDataReport) -> Vec { // Print input report for debugging log::trace!("--- Input report ---"); log::trace!("{input_report}"); @@ -153,9 +160,7 @@ impl Driver { let old_dinput_state = self.update_state(input_report); // Translate the state into a stream of input events - let events = self.translate_events(old_dinput_state); - - Ok(events) + self.translate_events(old_dinput_state) } /// Update touchinput state @@ -308,7 +313,7 @@ impl Driver { value: state.trigger_l, }))); } - if state.trigger_l != old_state.trigger_r { + if state.trigger_r != old_state.trigger_r { events.push(Event::Trigger(TriggerEvent::TriggerR(TriggerInput { value: state.trigger_r, }))); diff --git a/src/drivers/xpad_uhid/hid_report.rs b/src/drivers/xpad_uhid/hid_report.rs index 65f899de..6edc037b 100644 --- a/src/drivers/xpad_uhid/hid_report.rs +++ b/src/drivers/xpad_uhid/hid_report.rs @@ -277,6 +277,109 @@ pub enum DPadDirection { UpLeft = 8, } +/// Button state for Xbox One Bluetooth controllers. +/// The Xbox One S/X BT HID report packs 10 buttons into 2 bytes (10 bits + 6 padding), +/// with a different bit layout than the Xbox Series controller. +/// Button order from the HID descriptor (buttons 1-10): +/// A, B, X, Y, LB, RB, View, Menu, LStick, RStick +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq, Default)] +#[packed_struct(bit_numbering = "msb0", size_bytes = "2")] +pub struct XBoxOneBtButtonState { + // byte 0 (byte 14 of report) - msb0: bit 0 = MSB (0x80), bit 7 = LSB (0x01) + #[packed_field(bits = "0")] + pub menu: bool, // 0x80 + #[packed_field(bits = "1")] + pub view: bool, // 0x40 + #[packed_field(bits = "2")] + pub rb: bool, // 0x20 + #[packed_field(bits = "3")] + pub lb: bool, // 0x10 + #[packed_field(bits = "4")] + pub y: bool, // 0x08 + #[packed_field(bits = "5")] + pub x: bool, // 0x04 + #[packed_field(bits = "6")] + pub b: bool, // 0x02 + #[packed_field(bits = "7")] + pub a: bool, // 0x01 + + // byte 1 (byte 15 of report) + #[packed_field(bits = "14")] + pub thumb_r: bool, // 0x02 + #[packed_field(bits = "15")] + pub thumb_l: bool, // 0x01 +} + +/// Xbox One Bluetooth input data report (16 bytes). +/// This is the HID report format used by Xbox One S/X controllers over Bluetooth +/// (e.g., product ID 0x02E0). It uses Report ID 0x01 but has a different layout +/// from the Xbox Series controller: 10-bit triggers, 4-bit hat switch, and +/// 10 buttons in 2 bytes instead of 3. +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] +#[packed_struct(bit_numbering = "msb0", size_bytes = "16")] +pub struct XBoxOneBtInputDataReport { + // BYTE 0 + #[packed_field(bytes = "0")] + pub report_id: u8, + + // Axes + // BYTES 1-2 + #[packed_field(bytes = "1..=2", endian = "lsb")] + pub l_stick_x: u16, + // BYTES 3-4 + #[packed_field(bytes = "3..=4", endian = "lsb")] + pub l_stick_y: u16, + // BYTES 5-6 + #[packed_field(bytes = "5..=6", endian = "lsb")] + pub r_stick_x: u16, + // BYTES 7-8 + #[packed_field(bytes = "7..=8", endian = "lsb")] + pub r_stick_y: u16, + // BYTES 9-10: left trigger (10-bit value + 6-bit padding, as u16 LE) + #[packed_field(bytes = "9..=10", endian = "lsb")] + pub trigger_l: u16, + // BYTES 11-12: right trigger (10-bit value + 6-bit padding, as u16 LE) + #[packed_field(bytes = "11..=12", endian = "lsb")] + pub trigger_r: u16, + + // BYTE 13: hat switch (lower 4 bits) + padding (upper 4 bits) + #[packed_field(bytes = "13", ty = "enum")] + pub dpad_state: DPadDirection, + + // BYTES 14-15: 10 buttons + 6-bit padding + #[packed_field(bytes = "14..=15")] + pub button_state: XBoxOneBtButtonState, +} + +impl XBoxOneBtInputDataReport { + /// Convert to an XBoxSeriesInputDataReport for unified event processing + pub fn to_series_report(&self) -> XBoxSeriesInputDataReport { + XBoxSeriesInputDataReport { + report_id: self.report_id, + l_stick_x: self.l_stick_x, + l_stick_y: self.l_stick_y, + r_stick_x: self.r_stick_x, + r_stick_y: self.r_stick_y, + trigger_l: self.trigger_l, + trigger_r: self.trigger_r, + dpad_state: self.dpad_state, + button_state: ButtonState { + a: self.button_state.a, + b: self.button_state.b, + x: self.button_state.x, + y: self.button_state.y, + lb: self.button_state.lb, + rb: self.button_state.rb, + view: self.button_state.view, + menu: self.button_state.menu, + thumb_l: self.button_state.thumb_l, + thumb_r: self.button_state.thumb_r, + ..Default::default() + }, + } + } +} + #[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] #[packed_struct(bit_numbering = "msb0", size_bytes = "17")] pub struct XBoxSeriesInputDataReport { diff --git a/src/input/event/evdev.rs b/src/input/event/evdev.rs index 318ed7dc..2e99b372 100644 --- a/src/input/event/evdev.rs +++ b/src/input/event/evdev.rs @@ -345,7 +345,7 @@ impl EvdevEvent { KeyCode::KEY_MACRO => Capability::NotImplemented, KeyCode::KEY_MAIL => Capability::NotImplemented, KeyCode::KEY_MEDIA => Capability::NotImplemented, - KeyCode::KEY_MENU => Capability::NotImplemented, + KeyCode::KEY_MENU => Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), KeyCode::KEY_MICMUTE => Capability::NotImplemented, KeyCode::KEY_MINUS => Capability::Keyboard(Keyboard::KeyMinus), KeyCode::KEY_MOVE => Capability::NotImplemented,