diff --git a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml index 1278124..2094949 100644 --- a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml @@ -23,127 +23,111 @@ matches: # One or more source devices to combine into a single virtual device. The events # from these devices will be watched and translated according to the key map. source_devices: + ## XInput - Connected 0x6182 # Touchpad - group: mouse # Gamepad Mode hidraw: vendor_id: 0x17ef product_id: 0x6182 interface_num: 1 - - group: mouse # DInput Mode + # Gamepad + - group: gamepad hidraw: vendor_id: 0x17ef - product_id: 0x6184 - interface_num: 1 - - group: mouse # Gampead Mode - blocked: true + product_id: 0x6182 + interface_num: 2 + - group: gamepad + unique: true evdev: + name: "{Lenovo Legion Controller for Windows,Generic X-Box pad}" vendor_id: "17ef" product_id: "6182" - name: " Legion Controller for Windows Touchpad" handler: event* - - group: mouse # DInput mode + # Block all evdev devices; mouse, touchpad, keyboard + - group: gamepad blocked: true + unique: false evdev: + name: " Legion Controller for Windows *" vendor_id: "17ef" - product_id: "6184" - name: "Legion-Controller 1-D6 Touchpad" + product_id: "6182" handler: event* - # Mouse - - group: mouse # DInput Mode + ## DInput - Attached 0x6183 + # Touchpad + - group: mouse hidraw: vendor_id: 0x17ef - product_id: 0x6185 + product_id: 0x6183 interface_num: 1 - - group: mouse # Gamepad Mode - blocked: true - evdev: - vendor_id: "17ef" - product_id: "6182" - name: " Legion Controller for Windows Mouse" - handler: event* - - group: mouse # FPS/Dinput Mode - blocked: true - evdev: - vendor_id: "17ef" - product_id: "618[4-5]" - name: "Legion-Controller 1-D6 Mouse" - handler: event* - - group: mouse # FPS/Dinput Mode - blocked: true - unique: false - evdev: - vendor_id: "17ef" - product_id: "618[4-5]" - name: "Legion-Controller 1-D6" - handler: event* - - # Keyboard - - group: mouse # DInput Mode + # Gamepad + - group: gamepad # Dinput report hidraw: vendor_id: 0x17ef - product_id: 0x6185 + product_id: 0x6183 interface_num: 0 - - group: keyboard # Gamepad Mode - blocked: true - evdev: - vendor_id: "17ef" - product_id: "6182" - name: " Legion Controller for Windows Keyboard" - handler: event* - - group: keyboard # FPS/DInput Mode - blocked: true - evdev: - vendor_id: "17ef" - product_id: "618[4-5]" - name: "Legion-Controller 1-D6 Keyboard" - handler: event* - - # Gamepad - - group: gamepad # Gamepad Mode + - group: gamepad # XInput report 40Hz hidraw: vendor_id: 0x17ef - product_id: 0x6182 + product_id: 0x6183 interface_num: 2 - - group: gamepad # Dinput Mode + + ## DInput - Detached 0x6184 + # Touchpad + - group: mouse + hidraw: + vendor_id: 0x17ef + product_id: 0x6184 + interface_num: 1 + # Gamepad + - group: gamepad hidraw: vendor_id: 0x17ef product_id: 0x6184 interface_num: 0 - - group: gamepad # Dinput Mode + - group: gamepad hidraw: vendor_id: 0x17ef product_id: 0x6184 interface_num: 2 - - group: gamepad # FPS Mode + + ## FPS Mode - 0x6185 + # Touchpad + - group: mouse + hidraw: + vendor_id: 0x17ef + product_id: 0x6185 + interface_num: 1 + # Mouse + - group: mouse + hidraw: + vendor_id: 0x17ef + product_id: 0x6185 + interface_num: 1 + # Keyboard + - group: keyboard + hidraw: + vendor_id: 0x17ef + product_id: 0x6185 + interface_num: 0 + # Gamepad + - group: gamepad hidraw: vendor_id: 0x17ef product_id: 0x6185 interface_num: 2 - - group: gamepad # Newer Kernels - evdev: - vendor_id: "17ef" - product_id: "6182" - name: "Lenovo Legion Controller for Windows" - handler: event* - - group: gamepad # Older Kernels - evdev: - vendor_id: "17ef" - product_id: "6182" - name: "Generic X-Box pad" - handler: event* # IMU -# Broken for now --causes IP to hard freeze -# Enabling only gyro_3d allows the tablet gyro to work without needing kernel patch -# TODO: Find out why this is broken and swap tablet gyro to controller gyro as default. -# - group: imu -# iio: -# name: accel_3d -# mount_matrix: -# x: [0, 1, 0] -# y: [0, 0, -1] -# z: [-1, 0, 0] + # Broken for now --causes IP to hard freeze + # Enabling only gyro_3d allows the tablet gyro to work without needing kernel patch + # TODO: Find out why this is broken and swap tablet gyro to controller gyro as default. + # - group: imu + # iio: + # name: accel_3d + # mount_matrix: + # x: [0, 1, 0] + # y: [0, 0, -1] + # z: [-1, 0, 0] - group: imu iio: name: gyro_3d @@ -152,6 +136,15 @@ source_devices: y: [0, 0, -1] z: [-1, 0, 0] + # Block all evdev devices; mouse, touchpad, gamepad, keyboard + - group: gamepad + blocked: true + unique: false + evdev: + vendor_id: "17ef" + product_id: "618[3-5]" + handler: event* + # Optional configuration for the composite device options: # If true, InputPlumber will automatically try to manage the input device. If diff --git a/src/drivers/lego/driver_dinput_combined.rs b/src/drivers/lego/driver_dinput_combined.rs new file mode 100644 index 0000000..0f09953 --- /dev/null +++ b/src/drivers/lego/driver_dinput_combined.rs @@ -0,0 +1,764 @@ +use std::{ + error::Error, + ffi::CString, + time::{Duration, Instant}, +}; + +use hidapi::HidDevice; +use packed_struct::{types::SizedInteger, PackedStruct}; + +use super::{ + event::{ + AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseWheelInput, + StatusEvent, StatusInput, TouchAxisInput, TouchButtonEvent, TriggerEvent, TriggerInput, + }, + hid_report::{DInputDataFullReport, DPadDirection, TouchpadDataReport, XInputDataReport}, +}; + +// Hardware ID's +pub const VID: u16 = 0x17ef; +pub const PID: u16 = 0x6183; + +// Report ID's +pub const KEYBOARD_TOUCH_DATA: u8 = 0x01; +pub const XINPUT_DATA: u8 = 0x04; +pub const DINPUT_DATA: u8 = 0x07; + +// Input report sizes +const DINPUT_PACKET_SIZE: usize = 13; +const XINPUT_PACKET_SIZE: usize = 60; +const TOUCHPAD_PACKET_SIZE: usize = 20; +const HID_TIMEOUT: i32 = 10; + +// Input report axis ranges +pub const PAD_X_MAX: f64 = 1024.0; +pub const PAD_Y_MAX: f64 = 1024.0; +pub const STICK_X_MAX: f64 = 255.0; +pub const STICK_X_MIN: f64 = 0.0; +pub const STICK_Y_MAX: f64 = 255.0; +pub const STICK_Y_MIN: f64 = 0.0; +pub const TRIGG_MAX: f64 = 255.0; + +pub struct Driver { + /// State for the left controller when detached in dinput mode + dinput_state: Option, + /// State for the touchpad device + touchpad_state: Option, + /// State for the internal gamepad controller + xinput_state: Option, + /// HIDRAW device instance + device: HidDevice, + /// Timestamp of the first touch event. Used to detect tap-to-click events + first_touch: Instant, + /// Timestamp of the last touch event. + last_touch: Instant, + /// Whether or not we are detecting a touch event currently. + is_touching: bool, + /// Whether or not we are currently holding a tap-to-click. + is_tapped: bool, +} + +impl Driver { + pub fn new(path: String) -> Result> { + let fmtpath = path.clone(); + let path = CString::new(path)?; + let api = hidapi::HidApi::new()?; + let device = api.open_path(&path)?; + let info = device.get_device_info()?; + if info.vendor_id() != VID || info.product_id() != PID { + return Err(format!("Device '{fmtpath}' is not a Legion Go Controller").into()); + } + + Ok(Self { + device, + dinput_state: None, + first_touch: Instant::now(), + is_tapped: false, + is_touching: false, + last_touch: Instant::now(), + touchpad_state: None, + xinput_state: None, + }) + } + + /// Poll the device and read input reports + pub fn poll(&mut self) -> Result, Box> { + // Read data from the device into a buffer + let mut buf = [0; XINPUT_PACKET_SIZE]; + let bytes_read = self.device.read_timeout(&mut buf[..], HID_TIMEOUT)?; + + let report_id = buf[0]; + let slice = &buf[..bytes_read]; + //log::trace!("Got Report ID: {report_id}"); + //log::trace!("Got Report Size: {bytes_read}"); + + let mut events = match report_id { + DINPUT_DATA => { + if bytes_read != DINPUT_PACKET_SIZE { + return Err("Invalid packet size for Direct Input Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_dinput_report(sized_buf)? + } + + KEYBOARD_TOUCH_DATA => { + if bytes_read != TOUCHPAD_PACKET_SIZE { + return Err("Invalid packet size for Keyboard or Touchpad Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_touchinput_report(sized_buf)? + } + + XINPUT_DATA => { + if bytes_read != XINPUT_PACKET_SIZE { + return Err("Invalid packet size for X-Input Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_xinput_report(sized_buf)? + } + _ => { + //log::trace!("Invalid Report ID."); + let events = vec![]; + events + } + }; + + // There is no release event, so check to see if we are still touching. + if self.is_touching && (self.last_touch.elapsed() > Duration::from_millis(4)) { + let event: Event = self.release_touch(); + events.push(event); + // Check for tap events + if self.first_touch.elapsed() < Duration::from_millis(200) { + // For double clicking, ensure the previous tap is cleared. + if self.is_tapped { + let event: Event = self.release_tap(); + events.push(event); + } + let event: Event = self.start_tap(); + events.push(event); + } + } + + // If we did a click event, see if we shoudl release it. Accounts for click and drag. + if !self.is_touching + && self.is_tapped + && (self.last_touch.elapsed() > Duration::from_millis(100)) + { + let event: Event = self.release_tap(); + events.push(event); + } + + Ok(events) + } + + /// Converts a 0-4096 dinput x axis into a 0-255 xinput axis + fn normalize_x_axis(&self, x_axis_sm: u16, x_axis_lg: u16) -> u8 { + let axis = (x_axis_sm << 8 | x_axis_lg) as i16; + (axis / 16) as u8 + } + + /// Converts a 0-4096 dinput y axis into a 0-255 xinput axis + fn normalize_y_axis(&self, y_axis_sm: u16, y_axis_lg: u16) -> u8 { + let axis = (y_axis_lg << 4 | y_axis_sm) as i16; + (axis / 16) as u8 + } + + /// Unpacks the buffer into a [DInputDataReport] structure and updates + /// the internal dinput_state + fn handle_dinput_report( + &mut self, + buf: [u8; DINPUT_PACKET_SIZE], + ) -> Result, Box> { + let input_report = DInputDataFullReport::unpack(&buf)?; + + // Print input report for debugging + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!("---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_dinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_dinput(old_dinput_state); + + Ok(events) + } + + /// Update dinputl state + fn update_dinput_state( + &mut self, + input_report: DInputDataFullReport, + ) -> Option { + let old_state = self.dinput_state; + self.dinput_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_dinput(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.dinput_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + // Pressing the "legion"" or "settings" buttons in this mode produces an impossible + // axis combination that we can ignore. Both buttons produce the same event to there + // isn't a way to use this. + if state.l_stick_x_sm.to_primitive() == 0 + && state.l_stick_x_lg == 0 + && state.l_stick_y_sm.to_primitive() == 0 + && state.l_stick_y_lg == 0 + && state.r_stick_x_sm.to_primitive() == 0 + && state.r_stick_x_lg == 0 + && state.r_stick_y_sm.to_primitive() == 0 + && state.r_stick_y_lg == 0 + { + //log::debug!("Impossible joystick position. Ignoring event."); + return events; + } + + // Binary Events + if state.a != old_state.a { + events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { + pressed: state.a, + }))); + } + if state.b != old_state.b { + events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { + pressed: state.b, + }))); + } + if state.x != old_state.x { + events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { + pressed: state.x, + }))); + } + if state.y != old_state.y { + events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { + pressed: state.y, + }))); + } + if state.rb != old_state.rb { + events.push(Event::GamepadButton(GamepadButtonEvent::RB(BinaryInput { + pressed: state.rb, + }))); + } + if state.lb != old_state.lb { + events.push(Event::GamepadButton(GamepadButtonEvent::LB(BinaryInput { + pressed: state.lb, + }))); + } + if state.rt != old_state.rt { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerR( + BinaryInput { pressed: state.rt }, + ))); + } + if state.lt != old_state.lt { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerL( + BinaryInput { pressed: state.lt }, + ))); + } + if state.menu != old_state.menu { + events.push(Event::GamepadButton(GamepadButtonEvent::Menu( + BinaryInput { + pressed: state.menu, + }, + ))); + } + if state.view != old_state.view { + events.push(Event::GamepadButton(GamepadButtonEvent::View( + BinaryInput { + pressed: state.view, + }, + ))); + } + if state.rs != old_state.rs { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbR( + BinaryInput { pressed: state.rs }, + ))); + } + if state.ls != old_state.ls { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbL( + BinaryInput { pressed: state.ls }, + ))); + } + if state.dpad_state != old_state.dpad_state { + let new_dpad = state.dpad_state.as_bitflag(); + let old_dpad = old_state.dpad_state.as_bitflag(); + let disabled_fields = old_dpad & !new_dpad; + + if new_dpad & DPadDirection::Up.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + BinaryInput { pressed: true }, + ))); + } + if new_dpad & DPadDirection::Right.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + BinaryInput { pressed: true }, + ))) + } + if new_dpad & DPadDirection::Left.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + BinaryInput { pressed: true }, + ))) + } + if new_dpad & DPadDirection::Down.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + BinaryInput { pressed: true }, + ))) + } + + if disabled_fields & DPadDirection::Up.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + BinaryInput { pressed: false }, + ))); + } + if disabled_fields & DPadDirection::Right.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + BinaryInput { pressed: false }, + ))) + } + if disabled_fields & DPadDirection::Left.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + BinaryInput { pressed: false }, + ))) + } + if disabled_fields & DPadDirection::Down.as_bitflag() != 0 { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + BinaryInput { pressed: false }, + ))) + } + } + + // Axis events + if state.l_stick_x_sm != old_state.l_stick_x_sm + || state.l_stick_y_sm != old_state.l_stick_y_sm + || state.l_stick_x_lg != old_state.l_stick_x_lg + || state.l_stick_y_lg != old_state.l_stick_y_lg + { + events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { + x: self.normalize_x_axis( + state.l_stick_x_sm.to_primitive() as u16, + state.l_stick_x_lg as u16, + ), + y: self.normalize_y_axis( + state.l_stick_y_sm.to_primitive() as u16, + state.l_stick_y_lg as u16, + ), + }))); + } + if state.r_stick_x_sm != old_state.r_stick_x_sm + || state.r_stick_y_sm != old_state.r_stick_y_sm + || state.r_stick_x_lg != old_state.r_stick_x_lg + || state.r_stick_y_lg != old_state.r_stick_y_lg + { + events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { + x: self.normalize_x_axis( + state.r_stick_x_sm.to_primitive() as u16, + state.r_stick_x_lg as u16, + ), + y: self.normalize_y_axis( + state.r_stick_y_sm.to_primitive() as u16, + state.r_stick_y_lg as u16, + ), + }))); + } + } + events + } + + /// Unpacks the buffer into a [TouchpadDataReport] structure and updates + /// the internal touchpad_state + fn handle_touchinput_report( + &mut self, + buf: [u8; TOUCHPAD_PACKET_SIZE], + ) -> Result, Box> { + let input_report = TouchpadDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::trace!("--- Input report ---"); + //log::trace!("{input_report}"); + //log::trace!("---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_touchpad_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_touch(old_dinput_state); + + Ok(events) + } + + /// Update touchinput state + fn update_touchpad_state( + &mut self, + input_report: TouchpadDataReport, + ) -> Option { + let old_state = self.touchpad_state; + self.touchpad_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_touch(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.touchpad_state else { + return events; + }; + + // Translate state changes into events if they have changed + let Some(_) = old_state else { + return events; + }; + + // There is a "hold to tap" event built into the firmware, ignore this event. + if state.touch_x_0 == 314 + && state.touch_y_0 == 512 + && state.touch_x_1 == 682 + && state.touch_y_1 == 512 + { + log::debug!("Detected 'hold to press' event. Ignoring"); + return events; + } + + // Axis events + if !self.is_touching { + self.is_touching = true; + self.first_touch = Instant::now(); + log::trace!("Started TOUCH event"); + } + events.push(Event::Axis(AxisEvent::Touchpad(TouchAxisInput { + index: 0, + is_touching: true, + x: state.touch_x_0, + y: state.touch_y_0, + }))); + + self.last_touch = Instant::now(); + events + } + + /// Unpacks the buffer into a [XinputDataReport] structure and updates + /// the internal xinput_state + fn handle_xinput_report( + &mut self, + buf: [u8; XINPUT_PACKET_SIZE], + ) -> Result, Box> { + let input_report = XInputDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!(" ---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_xinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_xinput(old_dinput_state); + + Ok(events) + } + + /// Update gamepad state + fn update_xinput_state(&mut self, input_report: XInputDataReport) -> Option { + let old_state = self.xinput_state; + self.xinput_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_xinput(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.xinput_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + if state.gamepad_mode != old_state.gamepad_mode { + log::debug!( + "Changed gamepad mode from {} to {}", + old_state.gamepad_mode, + state.gamepad_mode + ); + } + + // Watch for FPS mode, we want to ignore most events in this mode. + // TODO: Add keyboard events for WASD stuff + if state.gamepad_mode == 2 { + //log::debug!("In FPS Mode, rejecting gamepad input."); + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + + return events; + } + + // Binary Events + //if state.a != old_state.a { + // events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { + // pressed: state.a, + // }))); + //} + //if state.b != old_state.b { + // events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { + // pressed: state.b, + // }))); + //} + //if state.x != old_state.x { + // events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { + // pressed: state.x, + // }))); + //} + //if state.y != old_state.y { + // events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { + // pressed: state.y, + // }))); + //} + //if state.menu != old_state.menu { + // events.push(Event::GamepadButton(GamepadButtonEvent::Menu( + // BinaryInput { + // pressed: state.menu, + // }, + // ))); + //} + //if state.view != old_state.view { + // events.push(Event::GamepadButton(GamepadButtonEvent::View( + // BinaryInput { + // pressed: state.view, + // }, + // ))); + //} + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + //if state.down != old_state.down { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + // BinaryInput { + // pressed: state.down, + // }, + // ))); + //} + //if state.up != old_state.up { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + // BinaryInput { pressed: state.up }, + // ))); + //} + //if state.left != old_state.left { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + // BinaryInput { + // pressed: state.left, + // }, + // ))); + //} + //if state.right != old_state.right { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + // BinaryInput { + // pressed: state.right, + // }, + // ))); + //} + //if state.lb != old_state.lb { + // events.push(Event::GamepadButton(GamepadButtonEvent::LB(BinaryInput { + // pressed: state.lb, + // }))); + //} + //if state.rb != old_state.rb { + // events.push(Event::GamepadButton(GamepadButtonEvent::RB(BinaryInput { + // pressed: state.rb, + // }))); + //} + //if state.d_trigger_l != old_state.d_trigger_l { + // events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerL( + // BinaryInput { + // pressed: state.d_trigger_l, + // }, + // ))); + //} + //if state.d_trigger_r != old_state.d_trigger_r { + // events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerR( + // BinaryInput { + // pressed: state.d_trigger_r, + // }, + // ))); + //} + if state.m2 != old_state.m2 { + events.push(Event::GamepadButton(GamepadButtonEvent::M2(BinaryInput { + pressed: state.m2, + }))); + } + if state.m3 != old_state.m3 { + events.push(Event::GamepadButton(GamepadButtonEvent::M3(BinaryInput { + pressed: state.m3, + }))); + } + if state.y1 != old_state.y1 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y1(BinaryInput { + pressed: state.y1, + }))); + } + if state.y2 != old_state.y2 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y2(BinaryInput { + pressed: state.y2, + }))); + } + if state.y3 != old_state.y3 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y3(BinaryInput { + pressed: state.y3, + }))); + } + if state.mouse_click != old_state.mouse_click { + events.push(Event::GamepadButton(GamepadButtonEvent::MouseClick( + BinaryInput { + pressed: state.mouse_click, + }, + ))); + } + //if state.thumb_l != old_state.thumb_l { + // events.push(Event::GamepadButton(GamepadButtonEvent::ThumbL( + // BinaryInput { + // pressed: state.thumb_l, + // }, + // ))); + //} + //if state.thumb_r != old_state.thumb_r { + // events.push(Event::GamepadButton(GamepadButtonEvent::ThumbR( + // BinaryInput { + // pressed: state.thumb_r, + // }, + // ))); + //} + + // Axis events + //if state.l_stick_x != old_state.l_stick_x || state.l_stick_y != old_state.l_stick_y { + // events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { + // x: state.l_stick_x, + // y: state.l_stick_y, + // }))); + //} + //if state.r_stick_x != old_state.r_stick_x || state.r_stick_y != old_state.r_stick_y { + // events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { + // x: state.r_stick_x, + // y: state.r_stick_y, + // }))); + //} + + if state.a_trigger_l != old_state.a_trigger_l { + events.push(Event::Trigger(TriggerEvent::ATriggerL(TriggerInput { + value: state.a_trigger_l, + }))); + } + if state.a_trigger_r != old_state.a_trigger_r { + events.push(Event::Trigger(TriggerEvent::ATriggerR(TriggerInput { + value: state.a_trigger_r, + }))); + } + if state.mouse_z != old_state.mouse_z { + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value: state.mouse_z, + }))); + } + + // Status events + if state.l_controller_battery != old_state.l_controller_battery { + events.push(Event::Status(StatusEvent::LeftControllerBattery( + StatusInput { + value: state.l_controller_battery, + }, + ))); + } + if state.l_controller_mode0 != old_state.l_controller_mode0 { + events.push(Event::Status(StatusEvent::LeftControllerMode0( + StatusInput { + value: state.l_controller_mode0, + }, + ))); + } + if state.l_controller_mode1 != old_state.l_controller_mode1 { + events.push(Event::Status(StatusEvent::LeftControllerMode1( + StatusInput { + value: state.l_controller_mode1, + }, + ))); + } + if state.r_controller_battery != old_state.r_controller_battery { + events.push(Event::Status(StatusEvent::RightControllerBattery( + StatusInput { + value: state.r_controller_battery, + }, + ))); + } + if state.r_controller_mode0 != old_state.r_controller_mode0 { + events.push(Event::Status(StatusEvent::RightControllerMode0( + StatusInput { + value: state.r_controller_mode0, + }, + ))); + } + if state.r_controller_mode1 != old_state.r_controller_mode1 { + events.push(Event::Status(StatusEvent::RightControllerMode1( + StatusInput { + value: state.r_controller_mode1, + }, + ))); + } + }; + + events + } + + fn release_touch(&mut self) -> Event { + log::trace!("Released TOUCH event."); + self.is_touching = false; + Event::Axis(AxisEvent::Touchpad(TouchAxisInput { + index: 0, + is_touching: false, + x: 0, + y: 0, + })) + } + + fn start_tap(&mut self) -> Event { + log::trace!("Started CLICK event."); + self.is_tapped = true; + Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: true })) + } + + fn release_tap(&mut self) -> Event { + log::trace!("Released CLICK event."); + self.is_tapped = false; + Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: false })) + } +} diff --git a/src/drivers/lego/driver.rs b/src/drivers/lego/driver_dinput_split.rs similarity index 68% rename from src/drivers/lego/driver.rs rename to src/drivers/lego/driver_dinput_split.rs index bef503c..cd3ae2a 100644 --- a/src/drivers/lego/driver.rs +++ b/src/drivers/lego/driver_dinput_split.rs @@ -9,37 +9,31 @@ use packed_struct::{types::SizedInteger, PackedStruct}; use super::{ event::{ - AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseAxisInput, - MouseButtonEvent, MouseWheelInput, StatusEvent, StatusInput, TouchAxisInput, - TouchButtonEvent, TriggerEvent, TriggerInput, + AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseWheelInput, + StatusEvent, StatusInput, TouchAxisInput, TouchButtonEvent, TriggerEvent, TriggerInput, }, hid_report::{ - DInputDataLeftReport, DInputDataRightReport, KeyboardDataReport, MouseDataReport, - TouchpadDataReport, XInputDataReport, + DInputDataLeftReport, DInputDataRightReport, TouchpadDataReport, XInputDataReport, }, }; // Hardware ID's pub const VID: u16 = 0x17ef; -pub const PID1: u16 = 0x6182; -pub const PID2: u16 = 0x6184; -pub const PID3: u16 = 0x6185; -pub const PIDS: [u16; 3] = [PID1, PID2, PID3]; -// Hardware limits -pub const DINPUT_LEFT_DATA: u8 = 0x07; -pub const DINPUT_RIGHT_DATA: u8 = 0x08; +pub const PID: u16 = 0x6184; + +// Report ID's pub const KEYBOARD_TOUCH_DATA: u8 = 0x01; -pub const MOUSE_FPS_DATA: u8 = 0x02; pub const XINPUT_DATA: u8 = 0x04; +pub const DINPUT_LEFT_DATA: u8 = 0x07; +pub const DINPUT_RIGHT_DATA: u8 = 0x08; + // Input report sizes const DINPUT_PACKET_SIZE: usize = 13; const XINPUT_PACKET_SIZE: usize = 60; -const KEYBOARD_PACKET_SIZE: usize = 15; -const MOUSE_PACKET_SIZE: usize = 7; const TOUCHPAD_PACKET_SIZE: usize = 20; const HID_TIMEOUT: i32 = 10; + // Input report axis ranges -pub const MOUSE_WHEEL_MAX: f64 = 120.0; pub const PAD_X_MAX: f64 = 1024.0; pub const PAD_Y_MAX: f64 = 1024.0; pub const STICK_X_MAX: f64 = 255.0; @@ -49,14 +43,10 @@ pub const STICK_Y_MIN: f64 = 0.0; pub const TRIGG_MAX: f64 = 255.0; pub struct Driver { - /// State for the left detachable controller when in dinput mode + /// State for the left controller when detached in dinput mode dinputl_state: Option, - /// State for the right detachable controller when in dinput mode + /// State for the right controller when detached in dinput mode dinputr_state: Option, - /// State for the vitrual keyboard device on the left controller in FPS mode - keyboard_state: Option, - /// State for the mouse device - mouse_state: Option, /// State for the touchpad device touchpad_state: Option, /// State for the internal gamepad controller @@ -80,9 +70,7 @@ impl Driver { let api = hidapi::HidApi::new()?; let device = api.open_path(&path)?; let info = device.get_device_info()?; - if info.vendor_id() != VID - || (info.product_id() != PID1 && info.product_id() != PID2) && info.product_id() != PID3 - { + if info.vendor_id() != VID || info.product_id() != PID { return Err(format!("Device '{fmtpath}' is not a Legion Go Controller").into()); } @@ -93,9 +81,7 @@ impl Driver { first_touch: Instant::now(), is_tapped: false, is_touching: false, - keyboard_state: None, last_touch: Instant::now(), - mouse_state: None, touchpad_state: None, xinput_state: None, }) @@ -134,30 +120,13 @@ impl Driver { } KEYBOARD_TOUCH_DATA => { - if bytes_read != KEYBOARD_PACKET_SIZE { - if bytes_read != TOUCHPAD_PACKET_SIZE { - return Err("Invalid packet size for Keyboard or Touchpad Data.".into()); - } - // Handle the incoming input report - let sized_buf = slice.try_into()?; - - self.handle_touchinput_report(sized_buf)? - } else { - // Handle the incoming input report - let sized_buf = slice.try_into()?; - - self.handle_keyboard_report(sized_buf)? - } - } - - MOUSE_FPS_DATA => { - if bytes_read != MOUSE_PACKET_SIZE { - return Err("Invalid packet size for Mouse Data.".into()); + if bytes_read != TOUCHPAD_PACKET_SIZE { + return Err("Invalid packet size for Keyboard or Touchpad Data.".into()); } // Handle the incoming input report let sized_buf = slice.try_into()?; - self.handle_mouseinput_report(sized_buf)? + self.handle_touchinput_report(sized_buf)? } XINPUT_DATA => { @@ -203,8 +172,9 @@ impl Driver { Ok(events) } + /// Unpacks the buffer into a [DInputDataReport] structure and updates - /// the internal dinput_state + /// the internal dinputl_state fn handle_dinputl_report( &mut self, buf: [u8; DINPUT_PACKET_SIZE], @@ -212,9 +182,9 @@ impl Driver { let input_report = DInputDataLeftReport::unpack(&buf)?; // Print input report for debugging - //log::trace!("--- Input report ---"); - //log::trace!("{input_report}"); - //log::trace!("---- End Report ----"); + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!("---- End Report ----"); // Update the state let old_dinput_state = self.update_dinputl_state(input_report); @@ -225,7 +195,7 @@ impl Driver { Ok(events) } - /// Update dinput state + /// Update dinputl state fn update_dinputl_state( &mut self, input_report: DInputDataLeftReport, @@ -330,7 +300,7 @@ impl Driver { } /// Unpacks the buffer into a [DInputDataReport] structure and updates - /// the internal dinput_state + /// the internal dinputr_state fn handle_dinputr_report( &mut self, buf: [u8; DINPUT_PACKET_SIZE], @@ -351,7 +321,7 @@ impl Driver { Ok(events) } - /// Update dinput state + /// Update dinputr state fn update_dinputr_state( &mut self, input_report: DInputDataRightReport, @@ -434,6 +404,7 @@ impl Driver { } events } + /// Converts a 0-4096 dinput x axis into a 0-255 xinput axis fn xify_dinputr_x_axis(&self, x_axis_sm: u16, x_axis_lg: u16) -> u8 { let axis = (x_axis_lg << 4 | x_axis_sm) as i16; @@ -446,134 +417,6 @@ impl Driver { ((axis - 4095).abs() / 16) as u8 } - /// Unpacks the buffer into a [KeyboardDataReport] structure and updates - /// the internal keyboard_state - fn handle_keyboard_report( - &mut self, - buf: [u8; KEYBOARD_PACKET_SIZE], - ) -> Result, Box> { - let input_report = KeyboardDataReport::unpack(&buf)?; - - // Print input report for debugging - // log::trace!("--- Input report ---"); - // log::trace!("{input_report}"); - // log::trace!("---- End Report ----"); - - // Update the state - let old_dinput_state = self.update_keyboard_state(input_report); - - // Translate the state into a stream of input events - let events = self.translate_keyboard(old_dinput_state); - - Ok(events) - } - - /// Update keyboard state - fn update_keyboard_state( - &mut self, - input_report: KeyboardDataReport, - ) -> Option { - let old_state = self.keyboard_state; - self.keyboard_state = Some(input_report); - old_state - } - - /// Translate the state into individual events - fn translate_keyboard(&self, _old_state: Option) -> Vec { - let events = Vec::new(); - let Some(_) = self.keyboard_state else { - return events; - }; - - // Translate state changes into events if they have changed - //if let Some(_) = old_state {} - events - } - - /// Unpacks the buffer into a [MouseDataReport] structure and updates - /// the internal mouse_state - fn handle_mouseinput_report( - &mut self, - buf: [u8; MOUSE_PACKET_SIZE], - ) -> Result, Box> { - let input_report = MouseDataReport::unpack(&buf)?; - - // Print input report for debugging - //log::trace!("--- Input report ---"); - //log::trace!("{input_report}"); - //log::trace!("---- End Report ----"); - - // Update the state - let old_mouse_state = self.update_mouseinput_state(input_report); - - // Translate the state into a stream of input events - let events = self.translate_mouse(old_mouse_state); - - Ok(events) - } - - /// Update mouseinput state - fn update_mouseinput_state( - &mut self, - input_report: MouseDataReport, - ) -> Option { - let old_state = self.mouse_state; - self.mouse_state = Some(input_report); - old_state - } - - /// Translate the state into individual events - fn translate_mouse(&self, old_state: Option) -> Vec { - let mut events = Vec::new(); - let Some(state) = self.mouse_state else { - return events; - }; - - // Translate state changes into events if they have changed - if let Some(old_state) = old_state { - // Binary Events - if state.y3 != old_state.y3 { - events.push(Event::MouseButton(MouseButtonEvent::Y3(BinaryInput { - pressed: state.y3, - }))); - } - if state.m3 != old_state.m3 { - events.push(Event::MouseButton(MouseButtonEvent::M3(BinaryInput { - pressed: state.m3, - }))); - } - if state.mouse_click != old_state.mouse_click { - events.push(Event::MouseButton(MouseButtonEvent::Left(BinaryInput { - pressed: state.mouse_click, - }))); - } - if state.m2 != old_state.m2 { - events.push(Event::MouseButton(MouseButtonEvent::M2(BinaryInput { - pressed: state.m2, - }))); - } - if state.m1 != old_state.m1 { - events.push(Event::MouseButton(MouseButtonEvent::M1(BinaryInput { - pressed: state.m1, - }))); - } - - // Axis events - if state.mouse_x != old_state.mouse_x || state.mouse_y != old_state.mouse_y { - events.push(Event::Axis(AxisEvent::Mouse(MouseAxisInput { - x: state.mouse_x.to_primitive(), - y: state.mouse_y.to_primitive(), - }))); - } - if state.mouse_z != old_state.mouse_z { - events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { - value: state.mouse_z, - }))); - } - } - events - } - /// Unpacks the buffer into a [TouchpadDataReport] structure and updates /// the internal touchpad_state fn handle_touchinput_report( @@ -617,7 +460,18 @@ impl Driver { let Some(_) = old_state else { return events; }; - //// Axis events + + // There is a "hold to tap" event built into the firmware, ignore this event. + if state.touch_x_0 == 314 + && state.touch_y_0 == 512 + && state.touch_x_1 == 682 + && state.touch_y_1 == 512 + { + log::debug!("Detected 'hold to press' event. Ignoring"); + return events; + } + + // Axis events if !self.is_touching { self.is_touching = true; self.first_touch = Instant::now(); @@ -643,9 +497,9 @@ impl Driver { let input_report = XInputDataReport::unpack(&buf)?; // Print input report for debugging - //log::trace!("--- Input report ---"); - //log::trace!("{input_report}"); - //log::trace!(" ---- End Report ----"); + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!(" ---- End Report ----"); // Update the state let old_dinput_state = self.update_xinput_state(input_report); @@ -664,7 +518,7 @@ impl Driver { } /// Translate the state into individual events - fn translate_xinput(&self, old_state: Option) -> Vec { + fn translate_xinput(&mut self, old_state: Option) -> Vec { let mut events = Vec::new(); let Some(state) = self.xinput_state else { return events; @@ -679,6 +533,9 @@ impl Driver { state.gamepad_mode ); } + + // Watch for FPS mode, we want to ignore most events in this mode. + // TODO: Add keyboard events for WASD stuff if state.gamepad_mode == 2 { //log::debug!("In FPS Mode, rejecting gamepad input."); if state.legion != old_state.legion { @@ -698,41 +555,42 @@ impl Driver { return events; } + // Binary Events - if state.a != old_state.a { - events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { - pressed: state.a, - }))); - } - if state.b != old_state.b { - events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { - pressed: state.b, - }))); - } - if state.x != old_state.x { - events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { - pressed: state.x, - }))); - } - if state.y != old_state.y { - events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { - pressed: state.y, - }))); - } - if state.menu != old_state.menu { - events.push(Event::GamepadButton(GamepadButtonEvent::Menu( - BinaryInput { - pressed: state.menu, - }, - ))); - } - if state.view != old_state.view { - events.push(Event::GamepadButton(GamepadButtonEvent::View( - BinaryInput { - pressed: state.view, - }, - ))); - } + //if state.a != old_state.a { + // events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { + // pressed: state.a, + // }))); + //} + //if state.b != old_state.b { + // events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { + // pressed: state.b, + // }))); + //} + //if state.x != old_state.x { + // events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { + // pressed: state.x, + // }))); + //} + //if state.y != old_state.y { + // events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { + // pressed: state.y, + // }))); + //} + //if state.menu != old_state.menu { + // events.push(Event::GamepadButton(GamepadButtonEvent::Menu( + // BinaryInput { + // pressed: state.menu, + // }, + // ))); + //} + //if state.view != old_state.view { + // events.push(Event::GamepadButton(GamepadButtonEvent::View( + // BinaryInput { + // pressed: state.view, + // }, + // ))); + //} if state.legion != old_state.legion { events.push(Event::GamepadButton(GamepadButtonEvent::Legion( BinaryInput { @@ -740,39 +598,39 @@ impl Driver { }, ))); } - if state.quick_access != old_state.quick_access { - events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( - BinaryInput { - pressed: state.quick_access, - }, - ))); - } - if state.down != old_state.down { - events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( - BinaryInput { - pressed: state.down, - }, - ))); - } - if state.up != old_state.up { - events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( - BinaryInput { pressed: state.up }, - ))); - } - if state.left != old_state.left { - events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( - BinaryInput { - pressed: state.left, - }, - ))); - } - if state.right != old_state.right { - events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( - BinaryInput { - pressed: state.right, - }, - ))); - } + //if state.quick_access != old_state.quick_access { + // events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + // BinaryInput { + // pressed: state.quick_access, + // }, + // ))); + //} + //if state.down != old_state.down { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + // BinaryInput { + // pressed: state.down, + // }, + // ))); + //} + //if state.up != old_state.up { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + // BinaryInput { pressed: state.up }, + // ))); + //} + //if state.left != old_state.left { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + // BinaryInput { + // pressed: state.left, + // }, + // ))); + //} + //if state.right != old_state.right { + // events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + // BinaryInput { + // pressed: state.right, + // }, + // ))); + //} if state.lb != old_state.lb { events.push(Event::GamepadButton(GamepadButtonEvent::LB(BinaryInput { pressed: state.lb, @@ -797,31 +655,31 @@ impl Driver { }, ))); } - if state.m2 != old_state.m2 { - events.push(Event::GamepadButton(GamepadButtonEvent::M2(BinaryInput { - pressed: state.m2, - }))); - } - if state.m3 != old_state.m3 { - events.push(Event::GamepadButton(GamepadButtonEvent::M3(BinaryInput { - pressed: state.m3, - }))); - } - if state.y1 != old_state.y1 { - events.push(Event::GamepadButton(GamepadButtonEvent::Y1(BinaryInput { - pressed: state.y1, - }))); - } - if state.y2 != old_state.y2 { - events.push(Event::GamepadButton(GamepadButtonEvent::Y2(BinaryInput { - pressed: state.y2, - }))); - } - if state.y3 != old_state.y3 { - events.push(Event::GamepadButton(GamepadButtonEvent::Y3(BinaryInput { - pressed: state.y3, - }))); - } + //if state.m2 != old_state.m2 { + // events.push(Event::GamepadButton(GamepadButtonEvent::M2(BinaryInput { + // pressed: state.m2, + // }))); + //} + //if state.m3 != old_state.m3 { + // events.push(Event::GamepadButton(GamepadButtonEvent::M3(BinaryInput { + // pressed: state.m3, + // }))); + //} + //if state.y1 != old_state.y1 { + // events.push(Event::GamepadButton(GamepadButtonEvent::Y1(BinaryInput { + // pressed: state.y1, + // }))); + //} + //if state.y2 != old_state.y2 { + // events.push(Event::GamepadButton(GamepadButtonEvent::Y2(BinaryInput { + // pressed: state.y2, + // }))); + //} + //if state.y3 != old_state.y3 { + // events.push(Event::GamepadButton(GamepadButtonEvent::Y3(BinaryInput { + // pressed: state.y3, + // }))); + //} if state.mouse_click != old_state.mouse_click { events.push(Event::GamepadButton(GamepadButtonEvent::MouseClick( BinaryInput { @@ -845,18 +703,18 @@ impl Driver { } // Axis events - if state.l_stick_x != old_state.l_stick_x || state.l_stick_y != old_state.l_stick_y { - events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { - x: state.l_stick_x, - y: state.l_stick_y, - }))); - } - if state.r_stick_x != old_state.r_stick_x || state.r_stick_y != old_state.r_stick_y { - events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { - x: state.r_stick_x, - y: state.r_stick_y, - }))); - } + //if state.l_stick_x != old_state.l_stick_x || state.l_stick_y != old_state.l_stick_y { + // events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { + // x: state.l_stick_x, + // y: state.l_stick_y, + // }))); + //} + //if state.r_stick_x != old_state.r_stick_x || state.r_stick_y != old_state.r_stick_y { + // events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { + // x: state.r_stick_x, + // y: state.r_stick_y, + // }))); + //} if state.a_trigger_l != old_state.a_trigger_l { events.push(Event::Trigger(TriggerEvent::ATriggerL(TriggerInput { diff --git a/src/drivers/lego/driver_fps_mode.rs b/src/drivers/lego/driver_fps_mode.rs new file mode 100644 index 0000000..b5ebb75 --- /dev/null +++ b/src/drivers/lego/driver_fps_mode.rs @@ -0,0 +1,539 @@ +use std::{error::Error, ffi::CString}; + +use hidapi::HidDevice; +use packed_struct::{types::SizedInteger, PackedStruct}; + +use super::{ + event::{ + AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseAxisInput, + MouseButtonEvent, MouseWheelInput, StatusEvent, StatusInput, TriggerEvent, TriggerInput, + }, + hid_report::{KeyboardDataReport, MouseDataReport, XInputDataReport}, +}; + +// Hardware ID's +pub const VID: u16 = 0x17ef; +pub const PID: u16 = 0x6185; + +// Report ID's +pub const KEYBOARD_TOUCH_DATA: u8 = 0x01; +pub const MOUSE_FPS_DATA: u8 = 0x02; +pub const XINPUT_DATA: u8 = 0x04; + +// Input report sizes +const XINPUT_PACKET_SIZE: usize = 60; +const KEYBOARD_PACKET_SIZE: usize = 15; +const MOUSE_PACKET_SIZE: usize = 7; +const HID_TIMEOUT: i32 = 10; + +// Input report axis ranges +pub const MOUSE_WHEEL_MAX: f64 = 120.0; +pub const PAD_X_MAX: f64 = 1024.0; +pub const PAD_Y_MAX: f64 = 1024.0; +pub const STICK_X_MAX: f64 = 255.0; +pub const STICK_X_MIN: f64 = 0.0; +pub const STICK_Y_MAX: f64 = 255.0; +pub const STICK_Y_MIN: f64 = 0.0; +pub const TRIGG_MAX: f64 = 255.0; + +pub struct Driver { + /// State for the vitrual keyboard device on the left controller in FPS mode + keyboard_state: Option, + /// State for the mouse device + mouse_state: Option, + /// State for the internal gamepad controller + xinput_state: Option, + /// HIDRAW device instance + device: HidDevice, +} + +impl Driver { + pub fn new(path: String) -> Result> { + let fmtpath = path.clone(); + let path = CString::new(path)?; + let api = hidapi::HidApi::new()?; + let device = api.open_path(&path)?; + let info = device.get_device_info()?; + if info.vendor_id() != VID || info.product_id() != PID { + return Err(format!("Device '{fmtpath}' is not a Legion Go Controller").into()); + } + + Ok(Self { + device, + keyboard_state: None, + mouse_state: None, + xinput_state: None, + }) + } + + /// Poll the device and read input reports + pub fn poll(&mut self) -> Result, Box> { + // Read data from the device into a buffer + let mut buf = [0; XINPUT_PACKET_SIZE]; + let bytes_read = self.device.read_timeout(&mut buf[..], HID_TIMEOUT)?; + + let report_id = buf[0]; + let slice = &buf[..bytes_read]; + //log::trace!("Got Report ID: {report_id}"); + //log::trace!("Got Report Size: {bytes_read}"); + + let events = match report_id { + KEYBOARD_TOUCH_DATA => { + if bytes_read != KEYBOARD_PACKET_SIZE { + return Err("Invalid packet size for Keyboard Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_keyboard_report(sized_buf)? + } + + MOUSE_FPS_DATA => { + if bytes_read != MOUSE_PACKET_SIZE { + return Err("Invalid packet size for Mouse Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_mouseinput_report(sized_buf)? + } + + XINPUT_DATA => { + if bytes_read != XINPUT_PACKET_SIZE { + return Err("Invalid packet size for X-Input Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_xinput_report(sized_buf)? + } + _ => { + //log::trace!("Invalid Report ID."); + let events = vec![]; + events + } + }; + + Ok(events) + } + + /// Unpacks the buffer into a [KeyboardDataReport] structure and updates + /// the internal keyboard_state + fn handle_keyboard_report( + &mut self, + buf: [u8; KEYBOARD_PACKET_SIZE], + ) -> Result, Box> { + let input_report = KeyboardDataReport::unpack(&buf)?; + + // Print input report for debugging + // log::trace!("--- Input report ---"); + // log::trace!("{input_report}"); + // log::trace!("---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_keyboard_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_keyboard(old_dinput_state); + + Ok(events) + } + + /// Update keyboard state + fn update_keyboard_state( + &mut self, + input_report: KeyboardDataReport, + ) -> Option { + let old_state = self.keyboard_state; + self.keyboard_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_keyboard(&self, _old_state: Option) -> Vec { + let events = Vec::new(); + let Some(_) = self.keyboard_state else { + return events; + }; + + // Translate state changes into events if they have changed + //if let Some(_) = old_state {} + events + } + + /// Unpacks the buffer into a [MouseDataReport] structure and updates + /// the internal mouse_state + fn handle_mouseinput_report( + &mut self, + buf: [u8; MOUSE_PACKET_SIZE], + ) -> Result, Box> { + let input_report = MouseDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::trace!("--- Input report ---"); + //log::trace!("{input_report}"); + //log::trace!("---- End Report ----"); + + // Update the state + let old_mouse_state = self.update_mouseinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_mouse(old_mouse_state); + + Ok(events) + } + + /// Update mouseinput state + fn update_mouseinput_state( + &mut self, + input_report: MouseDataReport, + ) -> Option { + let old_state = self.mouse_state; + self.mouse_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_mouse(&self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.mouse_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + // Binary Events + if state.y3 != old_state.y3 { + events.push(Event::MouseButton(MouseButtonEvent::Y3(BinaryInput { + pressed: state.y3, + }))); + } + if state.m3 != old_state.m3 { + events.push(Event::MouseButton(MouseButtonEvent::M3(BinaryInput { + pressed: state.m3, + }))); + } + if state.mouse_click != old_state.mouse_click { + events.push(Event::MouseButton(MouseButtonEvent::Left(BinaryInput { + pressed: state.mouse_click, + }))); + } + if state.m2 != old_state.m2 { + events.push(Event::MouseButton(MouseButtonEvent::M2(BinaryInput { + pressed: state.m2, + }))); + } + if state.m1 != old_state.m1 { + events.push(Event::MouseButton(MouseButtonEvent::M1(BinaryInput { + pressed: state.m1, + }))); + } + + // Axis events + if state.mouse_x != old_state.mouse_x || state.mouse_y != old_state.mouse_y { + events.push(Event::Axis(AxisEvent::Mouse(MouseAxisInput { + x: state.mouse_x.to_primitive(), + y: state.mouse_y.to_primitive(), + }))); + } + if state.mouse_z != old_state.mouse_z { + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value: state.mouse_z, + }))); + } + } + events + } + + /// Unpacks the buffer into a [XinputDataReport] structure and updates + /// the internal xinput_state + fn handle_xinput_report( + &mut self, + buf: [u8; XINPUT_PACKET_SIZE], + ) -> Result, Box> { + let input_report = XInputDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!(" ---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_xinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_xinput(old_dinput_state); + + Ok(events) + } + + /// Update gamepad state + fn update_xinput_state(&mut self, input_report: XInputDataReport) -> Option { + let old_state = self.xinput_state; + self.xinput_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_xinput(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.xinput_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + if state.gamepad_mode != old_state.gamepad_mode { + log::debug!( + "Changed gamepad mode from {} to {}", + old_state.gamepad_mode, + state.gamepad_mode + ); + } + + // Watch for FPS mode, we want to ignore most events in this mode. + // TODO: Add keyboard events for WASD stuff + if state.gamepad_mode == 2 { + //log::debug!("In FPS Mode, rejecting gamepad input."); + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + + return events; + } + + // Binary Events + if state.a != old_state.a { + events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { + pressed: state.a, + }))); + } + if state.b != old_state.b { + events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { + pressed: state.b, + }))); + } + if state.x != old_state.x { + events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { + pressed: state.x, + }))); + } + if state.y != old_state.y { + events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { + pressed: state.y, + }))); + } + if state.menu != old_state.menu { + events.push(Event::GamepadButton(GamepadButtonEvent::Menu( + BinaryInput { + pressed: state.menu, + }, + ))); + } + if state.view != old_state.view { + events.push(Event::GamepadButton(GamepadButtonEvent::View( + BinaryInput { + pressed: state.view, + }, + ))); + } + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + if state.down != old_state.down { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + BinaryInput { + pressed: state.down, + }, + ))); + } + if state.up != old_state.up { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + BinaryInput { pressed: state.up }, + ))); + } + if state.left != old_state.left { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + BinaryInput { + pressed: state.left, + }, + ))); + } + if state.right != old_state.right { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + BinaryInput { + pressed: state.right, + }, + ))); + } + if state.lb != old_state.lb { + events.push(Event::GamepadButton(GamepadButtonEvent::LB(BinaryInput { + pressed: state.lb, + }))); + } + if state.rb != old_state.rb { + events.push(Event::GamepadButton(GamepadButtonEvent::RB(BinaryInput { + pressed: state.rb, + }))); + } + if state.d_trigger_l != old_state.d_trigger_l { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerL( + BinaryInput { + pressed: state.d_trigger_l, + }, + ))); + } + if state.d_trigger_r != old_state.d_trigger_r { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerR( + BinaryInput { + pressed: state.d_trigger_r, + }, + ))); + } + if state.m2 != old_state.m2 { + events.push(Event::GamepadButton(GamepadButtonEvent::M2(BinaryInput { + pressed: state.m2, + }))); + } + if state.m3 != old_state.m3 { + events.push(Event::GamepadButton(GamepadButtonEvent::M3(BinaryInput { + pressed: state.m3, + }))); + } + if state.y1 != old_state.y1 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y1(BinaryInput { + pressed: state.y1, + }))); + } + if state.y2 != old_state.y2 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y2(BinaryInput { + pressed: state.y2, + }))); + } + if state.y3 != old_state.y3 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y3(BinaryInput { + pressed: state.y3, + }))); + } + if state.mouse_click != old_state.mouse_click { + events.push(Event::GamepadButton(GamepadButtonEvent::MouseClick( + BinaryInput { + pressed: state.mouse_click, + }, + ))); + } + if state.thumb_l != old_state.thumb_l { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbL( + BinaryInput { + pressed: state.thumb_l, + }, + ))); + } + if state.thumb_r != old_state.thumb_r { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbR( + BinaryInput { + pressed: state.thumb_r, + }, + ))); + } + + // Axis events + if state.l_stick_x != old_state.l_stick_x || state.l_stick_y != old_state.l_stick_y { + events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { + x: state.l_stick_x, + y: state.l_stick_y, + }))); + } + if state.r_stick_x != old_state.r_stick_x || state.r_stick_y != old_state.r_stick_y { + events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { + x: state.r_stick_x, + y: state.r_stick_y, + }))); + } + + if state.a_trigger_l != old_state.a_trigger_l { + events.push(Event::Trigger(TriggerEvent::ATriggerL(TriggerInput { + value: state.a_trigger_l, + }))); + } + if state.a_trigger_r != old_state.a_trigger_r { + events.push(Event::Trigger(TriggerEvent::ATriggerR(TriggerInput { + value: state.a_trigger_r, + }))); + } + if state.mouse_z != old_state.mouse_z { + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value: state.mouse_z, + }))); + } + + // Status events + if state.l_controller_battery != old_state.l_controller_battery { + events.push(Event::Status(StatusEvent::LeftControllerBattery( + StatusInput { + value: state.l_controller_battery, + }, + ))); + } + if state.l_controller_mode0 != old_state.l_controller_mode0 { + events.push(Event::Status(StatusEvent::LeftControllerMode0( + StatusInput { + value: state.l_controller_mode0, + }, + ))); + } + if state.l_controller_mode1 != old_state.l_controller_mode1 { + events.push(Event::Status(StatusEvent::LeftControllerMode1( + StatusInput { + value: state.l_controller_mode1, + }, + ))); + } + if state.r_controller_battery != old_state.r_controller_battery { + events.push(Event::Status(StatusEvent::RightControllerBattery( + StatusInput { + value: state.r_controller_battery, + }, + ))); + } + if state.r_controller_mode0 != old_state.r_controller_mode0 { + events.push(Event::Status(StatusEvent::RightControllerMode0( + StatusInput { + value: state.r_controller_mode0, + }, + ))); + } + if state.r_controller_mode1 != old_state.r_controller_mode1 { + events.push(Event::Status(StatusEvent::RightControllerMode1( + StatusInput { + value: state.r_controller_mode1, + }, + ))); + } + }; + + events + } +} diff --git a/src/drivers/lego/driver_xinput.rs b/src/drivers/lego/driver_xinput.rs new file mode 100644 index 0000000..c6f1ccd --- /dev/null +++ b/src/drivers/lego/driver_xinput.rs @@ -0,0 +1,631 @@ +use std::{ + error::Error, + ffi::CString, + time::{Duration, Instant}, +}; + +use hidapi::HidDevice; +use packed_struct::{types::SizedInteger, PackedStruct}; + +use super::{ + event::{ + AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseAxisInput, + MouseButtonEvent, MouseWheelInput, StatusEvent, StatusInput, TouchAxisInput, + TouchButtonEvent, TriggerEvent, TriggerInput, + }, + hid_report::{MouseDataReport, TouchpadDataReport, XInputDataReport}, +}; + +// Hardware ID's +pub const VID: u16 = 0x17ef; +pub const PID: u16 = 0x6182; + +// Report ID's +pub const KEYBOARD_TOUCH_DATA: u8 = 0x01; +pub const MOUSE_FPS_DATA: u8 = 0x02; +pub const XINPUT_DATA: u8 = 0x04; + +// Input report sizes +const XINPUT_PACKET_SIZE: usize = 60; +const MOUSE_PACKET_SIZE: usize = 7; +const TOUCHPAD_PACKET_SIZE: usize = 20; +const HID_TIMEOUT: i32 = 10; + +// Input report axis ranges +pub const MOUSE_WHEEL_MAX: f64 = 120.0; +pub const PAD_X_MAX: f64 = 1024.0; +pub const PAD_Y_MAX: f64 = 1024.0; +pub const STICK_X_MAX: f64 = 255.0; +pub const STICK_X_MIN: f64 = 0.0; +pub const STICK_Y_MAX: f64 = 255.0; +pub const STICK_Y_MIN: f64 = 0.0; +pub const TRIGG_MAX: f64 = 255.0; + +pub struct Driver { + /// State for the mouse device + mouse_state: Option, + /// State for the touchpad device + touchpad_state: Option, + /// State for the internal gamepad controller + xinput_state: Option, + /// HIDRAW device instance + device: HidDevice, + /// Timestamp of the first touch event. Used to detect tap-to-click events + first_touch: Instant, + /// Timestamp of the last touch event. + last_touch: Instant, + /// Whether or not we are detecting a touch event currently. + is_touching: bool, + /// Whether or not we are currently holding a tap-to-click. + is_tapped: bool, +} + +impl Driver { + pub fn new(path: String) -> Result> { + let fmtpath = path.clone(); + let path = CString::new(path)?; + let api = hidapi::HidApi::new()?; + let device = api.open_path(&path)?; + let info = device.get_device_info()?; + if info.vendor_id() != VID || info.product_id() != PID { + return Err(format!("Device '{fmtpath}' is not a Legion Go Controller").into()); + } + + Ok(Self { + device, + first_touch: Instant::now(), + is_tapped: false, + is_touching: false, + last_touch: Instant::now(), + mouse_state: None, + touchpad_state: None, + xinput_state: None, + }) + } + + /// Poll the device and read input reports + pub fn poll(&mut self) -> Result, Box> { + // Read data from the device into a buffer + let mut buf = [0; XINPUT_PACKET_SIZE]; + let bytes_read = self.device.read_timeout(&mut buf[..], HID_TIMEOUT)?; + + let report_id = buf[0]; + let slice = &buf[..bytes_read]; + //log::trace!("Got Report ID: {report_id}"); + //log::trace!("Got Report Size: {bytes_read}"); + + let mut events = match report_id { + KEYBOARD_TOUCH_DATA => { + if bytes_read != TOUCHPAD_PACKET_SIZE { + return Err("Invalid packet size for Keyboard or Touchpad Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_touchinput_report(sized_buf)? + } + + MOUSE_FPS_DATA => { + if bytes_read != MOUSE_PACKET_SIZE { + return Err("Invalid packet size for Mouse Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_mouseinput_report(sized_buf)? + } + + XINPUT_DATA => { + if bytes_read != XINPUT_PACKET_SIZE { + return Err("Invalid packet size for X-Input Data.".into()); + } + // Handle the incoming input report + let sized_buf = slice.try_into()?; + + self.handle_xinput_report(sized_buf)? + } + _ => { + //log::trace!("Invalid Report ID."); + let events = vec![]; + events + } + }; + + // There is no release event, so check to see if we are still touching. + if self.is_touching && (self.last_touch.elapsed() > Duration::from_millis(4)) { + let event: Event = self.release_touch(); + events.push(event); + // Check for tap events + if self.first_touch.elapsed() < Duration::from_millis(200) { + // For double clicking, ensure the previous tap is cleared. + if self.is_tapped { + let event: Event = self.release_tap(); + events.push(event); + } + let event: Event = self.start_tap(); + events.push(event); + } + } + + // If we did a click event, see if we shoudl release it. Accounts for click and drag. + if !self.is_touching + && self.is_tapped + && (self.last_touch.elapsed() > Duration::from_millis(100)) + { + let event: Event = self.release_tap(); + events.push(event); + } + + Ok(events) + } + + /// Unpacks the buffer into a [MouseDataReport] structure and updates + /// the internal mouse_state + fn handle_mouseinput_report( + &mut self, + buf: [u8; MOUSE_PACKET_SIZE], + ) -> Result, Box> { + let input_report = MouseDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::trace!("--- Input report ---"); + //log::trace!("{input_report}"); + //log::trace!("---- End Report ----"); + + // Update the state + let old_mouse_state = self.update_mouseinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_mouse(old_mouse_state); + + Ok(events) + } + + /// Update mouseinput state + fn update_mouseinput_state( + &mut self, + input_report: MouseDataReport, + ) -> Option { + let old_state = self.mouse_state; + self.mouse_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_mouse(&self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.mouse_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + // Binary Events + if state.y3 != old_state.y3 { + events.push(Event::MouseButton(MouseButtonEvent::Y3(BinaryInput { + pressed: state.y3, + }))); + } + if state.m3 != old_state.m3 { + events.push(Event::MouseButton(MouseButtonEvent::M3(BinaryInput { + pressed: state.m3, + }))); + } + if state.mouse_click != old_state.mouse_click { + events.push(Event::MouseButton(MouseButtonEvent::Left(BinaryInput { + pressed: state.mouse_click, + }))); + } + if state.m2 != old_state.m2 { + events.push(Event::MouseButton(MouseButtonEvent::M2(BinaryInput { + pressed: state.m2, + }))); + } + if state.m1 != old_state.m1 { + events.push(Event::MouseButton(MouseButtonEvent::M1(BinaryInput { + pressed: state.m1, + }))); + } + + // Axis events + if state.mouse_x != old_state.mouse_x || state.mouse_y != old_state.mouse_y { + events.push(Event::Axis(AxisEvent::Mouse(MouseAxisInput { + x: state.mouse_x.to_primitive(), + y: state.mouse_y.to_primitive(), + }))); + } + if state.mouse_z != old_state.mouse_z { + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value: state.mouse_z, + }))); + } + } + events + } + + /// Unpacks the buffer into a [TouchpadDataReport] structure and updates + /// the internal touchpad_state + fn handle_touchinput_report( + &mut self, + buf: [u8; TOUCHPAD_PACKET_SIZE], + ) -> Result, Box> { + let input_report = TouchpadDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::trace!("--- Input report ---"); + //log::trace!("{input_report}"); + //log::trace!("---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_touchpad_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_touch(old_dinput_state); + + Ok(events) + } + + /// Update touchinput state + fn update_touchpad_state( + &mut self, + input_report: TouchpadDataReport, + ) -> Option { + let old_state = self.touchpad_state; + self.touchpad_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_touch(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.touchpad_state else { + return events; + }; + + // Translate state changes into events if they have changed + let Some(_) = old_state else { + return events; + }; + + // There is a "hold to tap" event built into the firmware, ignore this event. + if state.touch_x_0 == 314 + && state.touch_y_0 == 512 + && state.touch_x_1 == 682 + && state.touch_y_1 == 512 + { + log::debug!("Detected 'hold to press' event. Ignoring"); + return events; + } + + // Axis events + if !self.is_touching { + self.is_touching = true; + self.first_touch = Instant::now(); + log::trace!("Started TOUCH event"); + } + events.push(Event::Axis(AxisEvent::Touchpad(TouchAxisInput { + index: 0, + is_touching: true, + x: state.touch_x_0, + y: state.touch_y_0, + }))); + + self.last_touch = Instant::now(); + events + } + + /// Unpacks the buffer into a [XinputDataReport] structure and updates + /// the internal xinput_state + fn handle_xinput_report( + &mut self, + buf: [u8; XINPUT_PACKET_SIZE], + ) -> Result, Box> { + let input_report = XInputDataReport::unpack(&buf)?; + + // Print input report for debugging + //log::debug!("--- Input report ---"); + //log::debug!("{input_report}"); + //log::debug!(" ---- End Report ----"); + + // Update the state + let old_dinput_state = self.update_xinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_xinput(old_dinput_state); + + Ok(events) + } + + /// Update gamepad state + fn update_xinput_state(&mut self, input_report: XInputDataReport) -> Option { + let old_state = self.xinput_state; + self.xinput_state = Some(input_report); + old_state + } + + /// Translate the state into individual events + fn translate_xinput(&mut self, old_state: Option) -> Vec { + let mut events = Vec::new(); + let Some(state) = self.xinput_state else { + return events; + }; + + // Translate state changes into events if they have changed + if let Some(old_state) = old_state { + if state.gamepad_mode != old_state.gamepad_mode { + log::debug!( + "Changed gamepad mode from {} to {}", + old_state.gamepad_mode, + state.gamepad_mode + ); + } + + // Watch for FPS mode, we want to ignore most events in this mode. + // TODO: Add keyboard events for WASD stuff + if state.gamepad_mode == 2 { + //log::debug!("In FPS Mode, rejecting gamepad input."); + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + + return events; + } + + // Binary Events + if state.a != old_state.a { + events.push(Event::GamepadButton(GamepadButtonEvent::A(BinaryInput { + pressed: state.a, + }))); + } + if state.b != old_state.b { + events.push(Event::GamepadButton(GamepadButtonEvent::B(BinaryInput { + pressed: state.b, + }))); + } + if state.x != old_state.x { + events.push(Event::GamepadButton(GamepadButtonEvent::X(BinaryInput { + pressed: state.x, + }))); + } + if state.y != old_state.y { + events.push(Event::GamepadButton(GamepadButtonEvent::Y(BinaryInput { + pressed: state.y, + }))); + } + if state.menu != old_state.menu { + events.push(Event::GamepadButton(GamepadButtonEvent::Menu( + BinaryInput { + pressed: state.menu, + }, + ))); + } + if state.view != old_state.view { + events.push(Event::GamepadButton(GamepadButtonEvent::View( + BinaryInput { + pressed: state.view, + }, + ))); + } + if state.legion != old_state.legion { + events.push(Event::GamepadButton(GamepadButtonEvent::Legion( + BinaryInput { + pressed: state.legion, + }, + ))); + } + if state.quick_access != old_state.quick_access { + events.push(Event::GamepadButton(GamepadButtonEvent::QuickAccess( + BinaryInput { + pressed: state.quick_access, + }, + ))); + } + if state.down != old_state.down { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadDown( + BinaryInput { + pressed: state.down, + }, + ))); + } + if state.up != old_state.up { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadUp( + BinaryInput { pressed: state.up }, + ))); + } + if state.left != old_state.left { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadLeft( + BinaryInput { + pressed: state.left, + }, + ))); + } + if state.right != old_state.right { + events.push(Event::GamepadButton(GamepadButtonEvent::DPadRight( + BinaryInput { + pressed: state.right, + }, + ))); + } + if state.lb != old_state.lb { + events.push(Event::GamepadButton(GamepadButtonEvent::LB(BinaryInput { + pressed: state.lb, + }))); + } + if state.rb != old_state.rb { + events.push(Event::GamepadButton(GamepadButtonEvent::RB(BinaryInput { + pressed: state.rb, + }))); + } + if state.d_trigger_l != old_state.d_trigger_l { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerL( + BinaryInput { + pressed: state.d_trigger_l, + }, + ))); + } + if state.d_trigger_r != old_state.d_trigger_r { + events.push(Event::GamepadButton(GamepadButtonEvent::DTriggerR( + BinaryInput { + pressed: state.d_trigger_r, + }, + ))); + } + if state.m2 != old_state.m2 { + events.push(Event::GamepadButton(GamepadButtonEvent::M2(BinaryInput { + pressed: state.m2, + }))); + } + if state.m3 != old_state.m3 { + events.push(Event::GamepadButton(GamepadButtonEvent::M3(BinaryInput { + pressed: state.m3, + }))); + } + if state.y1 != old_state.y1 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y1(BinaryInput { + pressed: state.y1, + }))); + } + if state.y2 != old_state.y2 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y2(BinaryInput { + pressed: state.y2, + }))); + } + if state.y3 != old_state.y3 { + events.push(Event::GamepadButton(GamepadButtonEvent::Y3(BinaryInput { + pressed: state.y3, + }))); + } + if state.mouse_click != old_state.mouse_click { + events.push(Event::GamepadButton(GamepadButtonEvent::MouseClick( + BinaryInput { + pressed: state.mouse_click, + }, + ))); + } + if state.thumb_l != old_state.thumb_l { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbL( + BinaryInput { + pressed: state.thumb_l, + }, + ))); + } + if state.thumb_r != old_state.thumb_r { + events.push(Event::GamepadButton(GamepadButtonEvent::ThumbR( + BinaryInput { + pressed: state.thumb_r, + }, + ))); + } + + // Axis events + if state.l_stick_x != old_state.l_stick_x || state.l_stick_y != old_state.l_stick_y { + events.push(Event::Axis(AxisEvent::LStick(JoyAxisInput { + x: state.l_stick_x, + y: state.l_stick_y, + }))); + } + if state.r_stick_x != old_state.r_stick_x || state.r_stick_y != old_state.r_stick_y { + events.push(Event::Axis(AxisEvent::RStick(JoyAxisInput { + x: state.r_stick_x, + y: state.r_stick_y, + }))); + } + + if state.a_trigger_l != old_state.a_trigger_l { + events.push(Event::Trigger(TriggerEvent::ATriggerL(TriggerInput { + value: state.a_trigger_l, + }))); + } + if state.a_trigger_r != old_state.a_trigger_r { + events.push(Event::Trigger(TriggerEvent::ATriggerR(TriggerInput { + value: state.a_trigger_r, + }))); + } + if state.mouse_z != old_state.mouse_z { + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value: state.mouse_z, + }))); + } + + // Status events + if state.l_controller_battery != old_state.l_controller_battery { + events.push(Event::Status(StatusEvent::LeftControllerBattery( + StatusInput { + value: state.l_controller_battery, + }, + ))); + } + if state.l_controller_mode0 != old_state.l_controller_mode0 { + events.push(Event::Status(StatusEvent::LeftControllerMode0( + StatusInput { + value: state.l_controller_mode0, + }, + ))); + } + if state.l_controller_mode1 != old_state.l_controller_mode1 { + events.push(Event::Status(StatusEvent::LeftControllerMode1( + StatusInput { + value: state.l_controller_mode1, + }, + ))); + } + if state.r_controller_battery != old_state.r_controller_battery { + events.push(Event::Status(StatusEvent::RightControllerBattery( + StatusInput { + value: state.r_controller_battery, + }, + ))); + } + if state.r_controller_mode0 != old_state.r_controller_mode0 { + events.push(Event::Status(StatusEvent::RightControllerMode0( + StatusInput { + value: state.r_controller_mode0, + }, + ))); + } + if state.r_controller_mode1 != old_state.r_controller_mode1 { + events.push(Event::Status(StatusEvent::RightControllerMode1( + StatusInput { + value: state.r_controller_mode1, + }, + ))); + } + }; + + events + } + + fn release_touch(&mut self) -> Event { + log::trace!("Released TOUCH event."); + self.is_touching = false; + Event::Axis(AxisEvent::Touchpad(TouchAxisInput { + index: 0, + is_touching: false, + x: 0, + y: 0, + })) + } + + fn start_tap(&mut self) -> Event { + log::trace!("Started CLICK event."); + self.is_tapped = true; + Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: true })) + } + + fn release_tap(&mut self) -> Event { + log::trace!("Released CLICK event."); + self.is_tapped = false; + Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: false })) + } +} diff --git a/src/drivers/lego/hid_report.rs b/src/drivers/lego/hid_report.rs index 7be5b1d..4d45593 100644 --- a/src/drivers/lego/hid_report.rs +++ b/src/drivers/lego/hid_report.rs @@ -27,6 +27,37 @@ impl ReportType { } } } + +#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug, Default)] +pub enum DPadDirection { + #[default] + Up = 0, + UpRight = 1, + Right = 2, + DownRight = 3, + Down = 4, + DownLeft = 5, + Left = 6, + UpLeft = 7, + None = 8, +} + +impl DPadDirection { + pub fn as_bitflag(&self) -> u8 { + match *self { + Self::Up => 1, // 00000001 + Self::UpRight => 1 | 1 << 1, // 00000011 + Self::Right => 1 << 1, // 00000010 + Self::DownRight => 1 << 2 | 1 << 1, // 00000110 + Self::Down => 1 << 2, // 00000100 + Self::DownLeft => 1 << 2 | 1 << 3, // 00001100 + Self::Left => 1 << 3, // 00001000 + Self::UpLeft => 1 | 1 << 3, // 00001001 + Self::None => 0, // 00000000 + } + } +} + //XInputData // // @@ -225,32 +256,38 @@ pub struct XInputDataReport { #[packed_field(bytes = "1")] pub report_size: u8, - #[packed_field(bytes = "2")] - pub unk_2: u8, - #[packed_field(bytes = "3")] - pub unk_3: u8, - #[packed_field(bytes = "4")] - pub unk_4: u8, - + //#[packed_field(bytes = "2")] + //pub unk_2: u8, + //#[packed_field(bytes = "3")] + //pub unk_3: u8, + //#[packed_field(bytes = "4")] + //pub unk_4: u8, #[packed_field(bytes = "5")] pub l_controller_battery: u8, - #[packed_field(bytes = "6")] + + // BYTE 6 + #[packed_field(bytes = "6")] //48 - 55 pub l_controller_mode0: u8, #[packed_field(bytes = "7")] pub r_controller_battery: u8, + + // BYTE 8 #[packed_field(bytes = "8")] - pub r_controller_mode0: u8, + pub r_controller_mode0: u8, // 64 - 71 #[packed_field(bytes = "9")] pub gamepad_mode: u8, - #[packed_field(bytes = "10")] - pub unk_10: u8, - #[packed_field(bytes = "11")] - pub unk_11: u8, + //#[packed_field(bytes = "10")] + //pub unk_10: u8, + //#[packed_field(bytes = "11")] + //pub unk_11: u8, - #[packed_field(bytes = "12")] + // BYTE 12 + #[packed_field(bytes = "12")] // 96 - 103 pub l_controller_mode1: u8, - #[packed_field(bytes = "13")] + + // BYTE 13 + #[packed_field(bytes = "13")] // 104 - 11 pub r_controller_mode1: u8, #[packed_field(byte = "14", endian = "lsb")] @@ -305,8 +342,8 @@ pub struct XInputDataReport { pub y2: bool, #[packed_field(bits = "162")] pub y3: bool, - #[packed_field(bits = "163")] - pub unk_20_4: bool, + //#[packed_field(bits = "163")] + //pub unk_20_4: bool, #[packed_field(bits = "164")] pub m2: bool, #[packed_field(bits = "165")] @@ -319,21 +356,20 @@ pub struct XInputDataReport { // BYTE 21: Mouse Wheel Click (-128) #[packed_field(bits = "168")] pub mouse_click: bool, - #[packed_field(bits = "169")] - pub unk_21_1: bool, - #[packed_field(bits = "170")] - pub unk_21_2: bool, - #[packed_field(bits = "171")] - pub unk_21_3: bool, - #[packed_field(bits = "172")] - pub unk_21_4: bool, - #[packed_field(bits = "173")] - pub unk_21_5: bool, - #[packed_field(bits = "174")] - pub unk_21_6: bool, - #[packed_field(bits = "175")] - pub unk_21_7: bool, - + //#[packed_field(bits = "169")] + //pub unk_21_1: bool, + //#[packed_field(bits = "170")] + //pub unk_21_2: bool, + //#[packed_field(bits = "171")] + //pub unk_21_3: bool, + //#[packed_field(bits = "172")] + //pub unk_21_4: bool, + //#[packed_field(bits = "173")] + //pub unk_21_5: bool, + //#[packed_field(bits = "174")] + //pub unk_21_6: bool, + //#[packed_field(bits = "175")] + //pub unk_21_7: bool, #[packed_field(bytes = "22")] pub a_trigger_l: u8, #[packed_field(bytes = "23")] @@ -358,59 +394,58 @@ pub struct XInputDataReport { pub right_gyro_x: u8, #[packed_field(bytes = "33")] pub right_gyro_y: u8, - - #[packed_field(bytes = "34")] - pub unk_34: u8, - #[packed_field(bytes = "35")] - pub unk_35: u8, - #[packed_field(bytes = "36")] - pub unk_36: u8, - #[packed_field(bytes = "37")] - pub unk_37: u8, - #[packed_field(bytes = "38")] - pub unk_38: u8, - #[packed_field(bytes = "39")] - pub unk_39: u8, - #[packed_field(bytes = "40")] - pub unk_40: u8, - #[packed_field(bytes = "41")] - pub unk_41: u8, - #[packed_field(bytes = "42")] - pub unk_42: u8, - #[packed_field(bytes = "43")] - pub unk_43: u8, - #[packed_field(bytes = "44")] - pub unk_44: u8, - #[packed_field(bytes = "45")] - pub unk_45: u8, - #[packed_field(bytes = "46")] - pub unk_46: u8, - #[packed_field(bytes = "47")] - pub unk_47: u8, - #[packed_field(bytes = "48")] - pub unk_48: u8, - #[packed_field(bytes = "49")] - pub unk_49: u8, - #[packed_field(bytes = "50")] - pub unk_50: u8, - #[packed_field(bytes = "51")] - pub unk_51: u8, - #[packed_field(bytes = "52")] - pub unk_52: u8, - #[packed_field(bytes = "53")] - pub unk_53: u8, - #[packed_field(bytes = "54")] - pub unk_54: u8, - #[packed_field(bytes = "55")] - pub unk_55: u8, - #[packed_field(bytes = "56")] - pub unk_56: u8, - #[packed_field(bytes = "57")] - pub unk_57: u8, - #[packed_field(bytes = "58")] - pub unk_58: u8, - #[packed_field(bytes = "59")] - pub unk_59: u8, + //#[packed_field(bytes = "34")] + //pub unk_34: u8, + //#[packed_field(bytes = "35")] + //pub unk_35: u8, + //#[packed_field(bytes = "36")] + //pub unk_36: u8, + //#[packed_field(bytes = "37")] + //pub unk_37: u8, + //#[packed_field(bytes = "38")] + //pub unk_38: u8, + //#[packed_field(bytes = "39")] + //pub unk_39: u8, + //#[packed_field(bytes = "40")] + //pub unk_40: u8, + //#[packed_field(bytes = "41")] + //pub unk_41: u8, + //#[packed_field(bytes = "42")] + //pub unk_42: u8, + //#[packed_field(bytes = "43")] + //pub unk_43: u8, + //#[packed_field(bytes = "44")] + //pub unk_44: u8, + //#[packed_field(bytes = "45")] + //pub unk_45: u8, + //#[packed_field(bytes = "46")] + //pub unk_46: u8, + //#[packed_field(bytes = "47")] + //pub unk_47: u8, + //#[packed_field(bytes = "48")] + //pub unk_48: u8, + //#[packed_field(bytes = "49")] + //pub unk_49: u8, + //#[packed_field(bytes = "50")] + //pub unk_50: u8, + //#[packed_field(bytes = "51")] + //pub unk_51: u8, + //#[packed_field(bytes = "52")] + //pub unk_52: u8, + //#[packed_field(bytes = "53")] + //pub unk_53: u8, + //#[packed_field(bytes = "54")] + //pub unk_54: u8, + //#[packed_field(bytes = "55")] + //pub unk_55: u8, + //#[packed_field(bytes = "56")] + //pub unk_56: u8, + //#[packed_field(bytes = "57")] + //pub unk_57: u8, + //#[packed_field(bytes = "58")] + //pub unk_58: u8, + //#[packed_field(bytes = "59")] + //pub unk_59: u8, } // KeyboardData // @@ -483,11 +518,213 @@ pub struct KeyboardDataReport { pub report_size: u8, } -// // DInpuit doesn't have a complete implementation. Triggers and some buttons are not active. Two // identical reports, 7 and 8, indicate if the left or right controller is where the information is -// coming from. +// coming from when detached. Report 7 also has the full controller when attached, but mapped +// differently. + +// Axes +// +// Left Stick Left +// # ReportID: 7 / X: 0 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000007.735942 13 07 00 00 80 00 08 80 08 00 00 00 00 00 // +// Left Stick Right +// # ReportID: 7 / X: 4095 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000003.734726 13 07 ff 0f 80 00 08 80 08 00 00 00 00 00 +// +// Left Stick Up +// # ReportID: 7 / X: 2048 | Y: 0 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000010.387878 13 07 00 08 00 00 08 80 08 00 00 00 00 00 +// +// Left Stick Down +// # ReportID: 7 / X: 2048 | Y: 4095 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000009.355726 13 07 00 f8 ff 00 08 80 08 00 00 00 00 00 +// +// Right Stick Left +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 0 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000004.767933 13 07 00 08 80 00 00 80 08 00 00 00 00 00 +// +// Right Stick Right +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 4095 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000005.617290 13 07 00 08 80 ff 0f 80 08 00 00 00 00 00 +// +// Right Stick Up +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 0 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000006.117911 13 07 00 08 80 00 08 00 08 00 00 00 00 00 +// +// Right Stick Down +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 4095 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000006.732367 13 07 00 08 80 00 f8 ff 08 00 00 00 00 00 +// +// Buttons +// +// DPad Up +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 0 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000806.520835 13 07 00 08 80 00 08 80 00 00 00 00 00 00 +// +// Dpad Right +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 2 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000025.115069 13 07 00 08 80 00 08 80 02 00 00 00 00 00 +// +// DPad Down +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 4 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.826058 13 07 00 08 80 00 08 80 04 00 00 00 00 00 +// +// DPad Left +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 6 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.437427 13 07 00 08 80 00 08 80 06 00 00 00 00 00 +// +// Button A +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000009.013915 13 07 00 08 80 00 08 80 08 01 00 00 00 00 +// +// Button B +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000001.981636 13 07 00 08 80 00 08 80 08 02 00 00 00 00 +// +// Button X +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000010.407126 13 07 00 08 80 00 08 80 08 08 00 00 00 00 +// +// Button Y +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.643979 13 07 00 08 80 00 08 80 08 10 00 00 00 00 +// +// Left Bumper +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000009.157302 13 07 00 08 80 00 08 80 08 40 00 00 00 00 +// +// Right Bumper +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000005.876053 13 07 00 08 80 00 08 80 08 80 00 00 00 00 +// +// Left Trigger +// # ReportID: 7 / X: 2048 | Y: 1984 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.237866 13 07 00 08 7c 00 08 80 08 00 01 00 00 00 +// +// Right Trigger +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.095731 13 07 00 08 80 00 08 80 08 00 02 00 00 00 +// +// View +// # ReportID: 6 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.045963 13 07 00 08 80 00 08 80 08 00 04 00 00 00 +// +// Menu +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000005.027975 13 07 00 08 80 00 08 80 08 00 08 00 00 00 +// +// Left Stick Click +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 | Accelerator: 0 | Brake: 0 | # +// E: 000003.263941 13 07 00 08 80 00 08 80 08 00 20 00 00 00 +// +// Right Stick Click +// # ReportID: 7 / X: 2048 | Y: 2048 | Z: 2088 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 | Accelerator: 0 | Brake: 0 | # +// E: 000002.127843 13 07 00 08 80 28 08 80 08 00 40 00 00 00 + +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] +#[packed_struct(bit_numbering = "msb0", size_bytes = "13")] +pub struct DInputDataFullReport { + // Byte 0 + #[packed_field(bytes = "0")] + pub report_id: u8, + + // Byte 1-3 + #[packed_field(bytes = "1", endian = "lsb")] + pub l_stick_x_lg: u8, + #[packed_field(bits = "16..=19", endian = "lsb")] + pub l_stick_y_sm: Integer>, + #[packed_field(bits = "20..=23", endian = "lsb")] + pub l_stick_x_sm: Integer>, + #[packed_field(bytes = "3", endian = "lsb")] + pub l_stick_y_lg: u8, + + // Byte 4-6 + #[packed_field(bytes = "4", endian = "lsb")] + pub r_stick_x_lg: u8, + #[packed_field(bits = "40..=43", endian = "lsb")] + pub r_stick_y_sm: Integer>, + #[packed_field(bits = "44..=47", endian = "lsb")] + pub r_stick_x_sm: Integer>, + #[packed_field(bytes = "6", endian = "lsb")] + pub r_stick_y_lg: u8, + + // Buttons + // Byte 7 + #[packed_field(bytes = "7", ty = "enum")] + pub dpad_state: DPadDirection, + + // Byte 8 + #[packed_field(bits = "64")] + pub rb: bool, + #[packed_field(bits = "65")] + pub lb: bool, + #[packed_field(bits = "66")] + pub unk_66: bool, + #[packed_field(bits = "67")] + pub y: bool, + #[packed_field(bits = "68")] + pub x: bool, + #[packed_field(bits = "69")] + pub unk_69: bool, + #[packed_field(bits = "70")] + pub b: bool, + #[packed_field(bits = "71")] + pub a: bool, + + // Byte 9 + #[packed_field(bits = "72")] + pub unk_72: bool, + #[packed_field(bits = "73")] + pub rs: bool, + #[packed_field(bits = "74")] + pub ls: bool, + #[packed_field(bits = "75")] + pub unk_75: bool, + #[packed_field(bits = "76")] + pub menu: bool, + #[packed_field(bits = "77")] + pub view: bool, + #[packed_field(bits = "78")] + pub rt: bool, + #[packed_field(bits = "79")] + pub lt: bool, +} + +impl Default for DInputDataFullReport { + fn default() -> Self { + Self { + report_id: 0x11, + l_stick_x_lg: Default::default(), + l_stick_y_sm: Default::default(), + l_stick_x_sm: Default::default(), + l_stick_y_lg: Default::default(), + r_stick_x_lg: Default::default(), + r_stick_y_sm: Default::default(), + r_stick_x_sm: Default::default(), + r_stick_y_lg: Default::default(), + dpad_state: Default::default(), + rb: Default::default(), + lb: Default::default(), + unk_66: Default::default(), + y: Default::default(), + x: Default::default(), + unk_69: Default::default(), + b: Default::default(), + a: Default::default(), + unk_72: Default::default(), + rs: Default::default(), + ls: Default::default(), + unk_75: Default::default(), + menu: Default::default(), + view: Default::default(), + rt: Default::default(), + lt: Default::default(), + } + } +} + // DInputDataLeft // X and Y report backwards here. // No Input @@ -495,7 +732,7 @@ pub struct KeyboardDataReport { // E: 000031.877403 13 08 00 08 80 00 08 80 08 00 00 00 00 00 // // Axes -// +//z // Left Stick Right // # ReportID: 7 / X: 2048 | Y: 0 | Z: 2048 | Rz: 2048 | Hat switch: 8 | # | Button: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | Accelerator: 0 | Brake: 0 | # // E: 000031.876378 13 07 00 08 00 00 08 80 08 00 00 00 00 00 @@ -568,22 +805,22 @@ pub struct DInputDataLeftReport { pub l_stick_x_lg: u8, // Buttons - #[packed_field(bits = "72")] - pub left: bool, - #[packed_field(bits = "73")] - pub down: bool, - #[packed_field(bits = "74")] - pub up: bool, - #[packed_field(bits = "75")] - pub right: bool, - #[packed_field(bits = "76")] - pub y1: bool, - #[packed_field(bits = "77")] - pub y2: bool, - #[packed_field(bits = "78")] - pub view: bool, - #[packed_field(bits = "79")] + #[packed_field(bits = "64")] pub menu: bool, + #[packed_field(bits = "65")] + pub view: bool, + #[packed_field(bits = "66")] + pub y2: bool, + #[packed_field(bits = "67")] + pub y1: bool, + #[packed_field(bits = "68")] + pub right: bool, + #[packed_field(bits = "69")] + pub up: bool, + #[packed_field(bits = "70")] + pub down: bool, + #[packed_field(bits = "71")] + pub left: bool, } // DInputDataRight @@ -661,22 +898,22 @@ pub struct DInputDataRightReport { pub r_stick_x_lg: u8, // Buttons - #[packed_field(bits = "72")] - pub b: bool, - #[packed_field(bits = "73")] - pub y: bool, - #[packed_field(bits = "74")] - pub a: bool, - #[packed_field(bits = "75")] - pub x: bool, - #[packed_field(bits = "76")] - pub y3: bool, - #[packed_field(bits = "77")] - pub m3: bool, - #[packed_field(bits = "78")] - pub m2: bool, - #[packed_field(bits = "79")] + #[packed_field(bits = "64")] pub quick_access: bool, + #[packed_field(bits = "65")] + pub m2: bool, + #[packed_field(bits = "66")] + pub m3: bool, + #[packed_field(bits = "67")] + pub y3: bool, + #[packed_field(bits = "68")] + pub x: bool, + #[packed_field(bits = "69")] + pub a: bool, + #[packed_field(bits = "70")] + pub y: bool, + #[packed_field(bits = "71")] + pub b: bool, } // MouseDataFPS, MouseData diff --git a/src/drivers/lego/mod.rs b/src/drivers/lego/mod.rs index ee1b19b..58cec8a 100644 --- a/src/drivers/lego/mod.rs +++ b/src/drivers/lego/mod.rs @@ -1,3 +1,6 @@ -pub mod driver; +pub mod driver_dinput_combined; +pub mod driver_dinput_split; +pub mod driver_fps_mode; +pub mod driver_xinput; pub mod event; pub mod hid_report; diff --git a/src/input/source/hidraw.rs b/src/input/source/hidraw.rs index d47309b..9998569 100644 --- a/src/input/source/hidraw.rs +++ b/src/input/source/hidraw.rs @@ -1,6 +1,9 @@ pub mod dualsense; pub mod fts3528; -pub mod lego; +pub mod lego_dinput_combined; +pub mod lego_dinput_split; +pub mod lego_fps_mode; +pub mod lego_xinput; pub mod opineo; pub mod rog_ally; pub mod steam_deck; @@ -17,7 +20,9 @@ use crate::{ }; use self::{ - dualsense::DualSenseController, fts3528::Fts3528Touchscreen, lego::LegionController, + dualsense::DualSenseController, fts3528::Fts3528Touchscreen, + lego_dinput_combined::LegionControllerDCombined, lego_dinput_split::LegionControllerDSplit, + lego_fps_mode::LegionControllerFPS, lego_xinput::LegionControllerX, opineo::OrangePiNeoTouchpad, steam_deck::DeckController, }; @@ -28,7 +33,10 @@ enum DriverType { Unknown, DualSense, SteamDeck, - LegionGo, + LegionGoDCombined, + LegionGoDSplit, + LegionGoFPS, + LegionGoX, OrangePiNeo, Fts3528Touchscreen, XpadUhid, @@ -40,7 +48,10 @@ enum DriverType { pub enum HidRawDevice { DualSense(SourceDriver), SteamDeck(SourceDriver), - LegionGo(SourceDriver), + LegionGoDCombined(SourceDriver), + LegionGoDSplit(SourceDriver), + LegionGoFPS(SourceDriver), + LegionGoX(SourceDriver), OrangePiNeo(SourceDriver), Fts3528Touchscreen(SourceDriver), XpadUhid(SourceDriver), @@ -79,10 +90,25 @@ impl HidRawDevice { SourceDriver::new_with_options(composite_device, device, device_info, options); Ok(Self::SteamDeck(source_device)) } - DriverType::LegionGo => { - let device = LegionController::new(device_info.clone())?; + DriverType::LegionGoDCombined => { + let device = LegionControllerDCombined::new(device_info.clone())?; let source_device = SourceDriver::new(composite_device, device, device_info); - Ok(Self::LegionGo(source_device)) + Ok(Self::LegionGoDCombined(source_device)) + } + DriverType::LegionGoDSplit => { + let device = LegionControllerDSplit::new(device_info.clone())?; + let source_device = SourceDriver::new(composite_device, device, device_info); + Ok(Self::LegionGoDSplit(source_device)) + } + DriverType::LegionGoFPS => { + let device = LegionControllerFPS::new(device_info.clone())?; + let source_device = SourceDriver::new(composite_device, device, device_info); + Ok(Self::LegionGoFPS(source_device)) + } + DriverType::LegionGoX => { + let device = LegionControllerX::new(device_info.clone())?; + let source_device = SourceDriver::new(composite_device, device, device_info); + Ok(Self::LegionGoX(source_device)) } DriverType::OrangePiNeo => { let device = OrangePiNeoTouchpad::new(device_info.clone())?; @@ -130,10 +156,33 @@ impl HidRawDevice { return DriverType::SteamDeck; } - // Legion Go - if vid == drivers::lego::driver::VID && drivers::lego::driver::PIDS.contains(&pid) { - log::info!("Detected Legion Go"); - return DriverType::LegionGo; + // Legion Go Dinput Combined + if vid == drivers::lego::driver_dinput_combined::VID + && pid == drivers::lego::driver_dinput_combined::PID + { + log::info!("Detected Legion Go DInput Combined Mode"); + return DriverType::LegionGoDCombined; + } + + // Legion Go Dinput Split + if vid == drivers::lego::driver_dinput_split::VID + && pid == drivers::lego::driver_dinput_split::PID + { + log::info!("Detected Legion Go DInput Split Mode"); + return DriverType::LegionGoDSplit; + } + + // Legion Go FPS Mode + if vid == drivers::lego::driver_fps_mode::VID && pid == drivers::lego::driver_fps_mode::PID + { + log::info!("Detected Legion Go FPS Mode"); + return DriverType::LegionGoFPS; + } + + // Legion Go XInput + if vid == drivers::lego::driver_xinput::VID && pid == drivers::lego::driver_xinput::PID { + log::info!("Detected Legion Go XInput Mode"); + return DriverType::LegionGoX; } // OrangePi NEO diff --git a/src/input/source/hidraw/lego_dinput_combined.rs b/src/input/source/hidraw/lego_dinput_combined.rs new file mode 100644 index 0000000..8a1ee77 --- /dev/null +++ b/src/input/source/hidraw/lego_dinput_combined.rs @@ -0,0 +1,363 @@ +use std::{error::Error, fmt::Debug}; + +use crate::{ + drivers::lego::{ + driver_dinput_combined::{self, Driver}, + event, + }, + input::{ + capability::{ + Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Mouse, MouseButton, + Touch, TouchButton, Touchpad, + }, + event::{native::NativeEvent, value::InputValue}, + source::{InputError, SourceInputDevice, SourceOutputDevice}, + }, + udev::device::UdevDevice, +}; + +/// Legion Go Controller source device implementation +pub struct LegionControllerDCombined { + driver: Driver, +} + +impl LegionControllerDCombined { + /// Create a new Legion controller source device with the given udev + /// device information + pub fn new(device_info: UdevDevice) -> Result> { + let driver = Driver::new(device_info.devnode())?; + Ok(Self { driver }) + } +} + +impl SourceInputDevice for LegionControllerDCombined { + /// Poll the source device for input events + fn poll(&mut self) -> Result, InputError> { + let events = self.driver.poll()?; + let native_events = translate_events(events); + Ok(native_events) + } + + /// Returns the possible input events this device is capable of emitting + fn get_capabilities(&self) -> Result, InputError> { + Ok(CAPABILITIES.into()) + } +} + +impl SourceOutputDevice for LegionControllerDCombined {} + +impl Debug for LegionControllerDCombined { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LegionController").finish() + } +} + +/// Returns a value between -1.0 and 1.0 based on the given value with its +/// minimum and maximum values. +fn normalize_signed_value(raw_value: f64, min: f64, max: f64) -> f64 { + let mid = (max + min) / 2.0; + let event_value = raw_value - mid; + + // Normalize the value + if event_value >= 0.0 { + let maximum = max - mid; + event_value / maximum + } else { + let minimum = min - mid; + let value = event_value / minimum; + -value + } +} + +// Returns a value between 0.0 and 1.0 based on the given value with its +// maximum. +fn normalize_unsigned_value(raw_value: f64, max: f64) -> f64 { + raw_value / max +} + +/// Normalize the value to something between -1.0 and 1.0 based on the Deck's +/// minimum and maximum axis ranges. +fn normalize_axis_value(event: event::AxisEvent) -> InputValue { + match event { + event::AxisEvent::Touchpad(value) => { + let max = driver_dinput_combined::PAD_X_MAX; + let x = normalize_unsigned_value(value.x as f64, max); + + let max = driver_dinput_combined::PAD_Y_MAX; + let y = normalize_unsigned_value(value.y as f64, max); + + // If this is an UP event, don't override the position of X/Y + let (x, y) = if !value.is_touching { + (None, None) + } else { + (Some(x), Some(y)) + }; + + InputValue::Touch { + index: value.index, + is_touching: value.is_touching, + pressure: Some(1.0), + x, + y, + } + } + event::AxisEvent::LStick(value) => { + let min = driver_dinput_combined::STICK_X_MIN; + let max = driver_dinput_combined::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_dinput_combined::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_dinput_combined::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::RStick(value) => { + let min = driver_dinput_combined::STICK_X_MIN; + let max = driver_dinput_combined::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_dinput_combined::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_dinput_combined::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::Mouse(value) => { + let x = value.x as f64; + let x = Some(x); + let y = value.y as f64; + let y = Some(y); + + InputValue::Vector2 { x, y } + } + } +} + +/// Normalize the trigger value to something between 0.0 and 1.0 based on the +/// Legion Go's maximum axis ranges. +fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { + match event { + event::TriggerEvent::ATriggerL(value) => { + let max = driver_dinput_combined::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::ATriggerR(value) => { + let max = driver_dinput_combined::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::MouseWheel(_value) => InputValue::None, + } +} + +/// Translate the given Legion Go events into native events +fn translate_events(events: Vec) -> Vec { + events.into_iter().map(translate_event).collect() +} + +/// Translate the given Legion Go event into a native event +fn translate_event(event: event::Event) -> NativeEvent { + match event { + event::Event::GamepadButton(button) => match button { + event::GamepadButtonEvent::A(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::X(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::B(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Menu(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::View(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Legion(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::QuickAccess(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadDown(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadUp(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadLeft(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadRight(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::LB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y1(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::RB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::MouseClick(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::Axis(axis) => match axis.clone() { + event::AxisEvent::Touchpad(_) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), + normalize_axis_value(axis), + ), + event::AxisEvent::LStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::RStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::Mouse(_) => { + NativeEvent::new(Capability::Mouse(Mouse::Motion), normalize_axis_value(axis)) + } + }, + event::Event::Trigger(trigg) => match trigg.clone() { + event::TriggerEvent::ATriggerL(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::ATriggerR(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::MouseWheel(_) => { + NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)) + } + }, + event::Event::MouseButton(button) => match button { + event::MouseButtonEvent::Y3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M1(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Left)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M2(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Right)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Side)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::Left(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::TouchButton(button) => match button { + event::TouchButtonEvent::Left(value) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + InputValue::Bool(value.pressed), + ), + }, + + _ => NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)), + } +} + +/// List of all capabilities that the Legion Go driver implements +pub const CAPABILITIES: &[Capability] = &[ + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + Capability::Mouse(Mouse::Button(MouseButton::Left)), + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + Capability::Mouse(Mouse::Button(MouseButton::Right)), + Capability::Mouse(Mouse::Button(MouseButton::Side)), + Capability::Mouse(Mouse::Motion), + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), +]; diff --git a/src/input/source/hidraw/lego_dinput_split.rs b/src/input/source/hidraw/lego_dinput_split.rs new file mode 100644 index 0000000..383a804 --- /dev/null +++ b/src/input/source/hidraw/lego_dinput_split.rs @@ -0,0 +1,363 @@ +use std::{error::Error, fmt::Debug}; + +use crate::{ + drivers::lego::{ + driver_dinput_split::{self, Driver}, + event, + }, + input::{ + capability::{ + Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Mouse, MouseButton, + Touch, TouchButton, Touchpad, + }, + event::{native::NativeEvent, value::InputValue}, + source::{InputError, SourceInputDevice, SourceOutputDevice}, + }, + udev::device::UdevDevice, +}; + +/// Legion Go Controller source device implementation +pub struct LegionControllerDSplit { + driver: Driver, +} + +impl LegionControllerDSplit { + /// Create a new Legion controller source device with the given udev + /// device information + pub fn new(device_info: UdevDevice) -> Result> { + let driver = Driver::new(device_info.devnode())?; + Ok(Self { driver }) + } +} + +impl SourceInputDevice for LegionControllerDSplit { + /// Poll the source device for input events + fn poll(&mut self) -> Result, InputError> { + let events = self.driver.poll()?; + let native_events = translate_events(events); + Ok(native_events) + } + + /// Returns the possible input events this device is capable of emitting + fn get_capabilities(&self) -> Result, InputError> { + Ok(CAPABILITIES.into()) + } +} + +impl SourceOutputDevice for LegionControllerDSplit {} + +impl Debug for LegionControllerDSplit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LegionController").finish() + } +} + +/// Returns a value between -1.0 and 1.0 based on the given value with its +/// minimum and maximum values. +fn normalize_signed_value(raw_value: f64, min: f64, max: f64) -> f64 { + let mid = (max + min) / 2.0; + let event_value = raw_value - mid; + + // Normalize the value + if event_value >= 0.0 { + let maximum = max - mid; + event_value / maximum + } else { + let minimum = min - mid; + let value = event_value / minimum; + -value + } +} + +// Returns a value between 0.0 and 1.0 based on the given value with its +// maximum. +fn normalize_unsigned_value(raw_value: f64, max: f64) -> f64 { + raw_value / max +} + +/// Normalize the value to something between -1.0 and 1.0 based on the Deck's +/// minimum and maximum axis ranges. +fn normalize_axis_value(event: event::AxisEvent) -> InputValue { + match event { + event::AxisEvent::Touchpad(value) => { + let max = driver_dinput_split::PAD_X_MAX; + let x = normalize_unsigned_value(value.x as f64, max); + + let max = driver_dinput_split::PAD_Y_MAX; + let y = normalize_unsigned_value(value.y as f64, max); + + // If this is an UP event, don't override the position of X/Y + let (x, y) = if !value.is_touching { + (None, None) + } else { + (Some(x), Some(y)) + }; + + InputValue::Touch { + index: value.index, + is_touching: value.is_touching, + pressure: Some(1.0), + x, + y, + } + } + event::AxisEvent::LStick(value) => { + let min = driver_dinput_split::STICK_X_MIN; + let max = driver_dinput_split::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_dinput_split::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_dinput_split::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::RStick(value) => { + let min = driver_dinput_split::STICK_X_MIN; + let max = driver_dinput_split::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_dinput_split::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_dinput_split::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::Mouse(value) => { + let x = value.x as f64; + let x = Some(x); + let y = value.y as f64; + let y = Some(y); + + InputValue::Vector2 { x, y } + } + } +} + +/// Normalize the trigger value to something between 0.0 and 1.0 based on the +/// Legion Go's maximum axis ranges. +fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { + match event { + event::TriggerEvent::ATriggerL(value) => { + let max = driver_dinput_split::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::ATriggerR(value) => { + let max = driver_dinput_split::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::MouseWheel(_value) => InputValue::None, + } +} + +/// Translate the given Legion Go events into native events +fn translate_events(events: Vec) -> Vec { + events.into_iter().map(translate_event).collect() +} + +/// Translate the given Legion Go event into a native event +fn translate_event(event: event::Event) -> NativeEvent { + match event { + event::Event::GamepadButton(button) => match button { + event::GamepadButtonEvent::A(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::X(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::B(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Menu(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::View(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Legion(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::QuickAccess(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadDown(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadUp(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadLeft(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadRight(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::LB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y1(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::RB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::MouseClick(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::Axis(axis) => match axis.clone() { + event::AxisEvent::Touchpad(_) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), + normalize_axis_value(axis), + ), + event::AxisEvent::LStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::RStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::Mouse(_) => { + NativeEvent::new(Capability::Mouse(Mouse::Motion), normalize_axis_value(axis)) + } + }, + event::Event::Trigger(trigg) => match trigg.clone() { + event::TriggerEvent::ATriggerL(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::ATriggerR(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::MouseWheel(_) => { + NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)) + } + }, + event::Event::MouseButton(button) => match button { + event::MouseButtonEvent::Y3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M1(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Left)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M2(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Right)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Side)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::Left(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::TouchButton(button) => match button { + event::TouchButtonEvent::Left(value) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + InputValue::Bool(value.pressed), + ), + }, + + _ => NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)), + } +} + +/// List of all capabilities that the Legion Go driver implements +pub const CAPABILITIES: &[Capability] = &[ + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + Capability::Mouse(Mouse::Button(MouseButton::Left)), + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + Capability::Mouse(Mouse::Button(MouseButton::Right)), + Capability::Mouse(Mouse::Button(MouseButton::Side)), + Capability::Mouse(Mouse::Motion), + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), +]; diff --git a/src/input/source/hidraw/lego_fps_mode.rs b/src/input/source/hidraw/lego_fps_mode.rs new file mode 100644 index 0000000..47ed4cf --- /dev/null +++ b/src/input/source/hidraw/lego_fps_mode.rs @@ -0,0 +1,366 @@ +use std::{error::Error, fmt::Debug}; + +use crate::{ + drivers::lego::{ + driver_fps_mode::{self, Driver}, + event, + }, + input::{ + capability::{ + Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Mouse, MouseButton, + Touch, TouchButton, Touchpad, + }, + event::{native::NativeEvent, value::InputValue}, + source::{InputError, SourceInputDevice, SourceOutputDevice}, + }, + udev::device::UdevDevice, +}; + +/// Legion Go Controller source device implementation +pub struct LegionControllerFPS { + driver: Driver, +} + +impl LegionControllerFPS { + /// Create a new Legion controller source device with the given udev + /// device information + pub fn new(device_info: UdevDevice) -> Result> { + let driver = Driver::new(device_info.devnode())?; + Ok(Self { driver }) + } +} + +impl SourceInputDevice for LegionControllerFPS { + /// Poll the source device for input events + fn poll(&mut self) -> Result, InputError> { + let events = self.driver.poll()?; + let native_events = translate_events(events); + Ok(native_events) + } + + /// Returns the possible input events this device is capable of emitting + fn get_capabilities(&self) -> Result, InputError> { + Ok(CAPABILITIES.into()) + } +} + +impl SourceOutputDevice for LegionControllerFPS {} + +impl Debug for LegionControllerFPS { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LegionController").finish() + } +} + +/// Returns a value between -1.0 and 1.0 based on the given value with its +/// minimum and maximum values. +fn normalize_signed_value(raw_value: f64, min: f64, max: f64) -> f64 { + let mid = (max + min) / 2.0; + let event_value = raw_value - mid; + + // Normalize the value + if event_value >= 0.0 { + let maximum = max - mid; + event_value / maximum + } else { + let minimum = min - mid; + let value = event_value / minimum; + -value + } +} + +// Returns a value between 0.0 and 1.0 based on the given value with its +// maximum. +fn normalize_unsigned_value(raw_value: f64, max: f64) -> f64 { + raw_value / max +} + +/// Normalize the value to something between -1.0 and 1.0 based on the Deck's +/// minimum and maximum axis ranges. +fn normalize_axis_value(event: event::AxisEvent) -> InputValue { + match event { + event::AxisEvent::Touchpad(value) => { + let max = driver_fps_mode::PAD_X_MAX; + let x = normalize_unsigned_value(value.x as f64, max); + + let max = driver_fps_mode::PAD_Y_MAX; + let y = normalize_unsigned_value(value.y as f64, max); + + // If this is an UP event, don't override the position of X/Y + let (x, y) = if !value.is_touching { + (None, None) + } else { + (Some(x), Some(y)) + }; + + InputValue::Touch { + index: value.index, + is_touching: value.is_touching, + pressure: Some(1.0), + x, + y, + } + } + event::AxisEvent::LStick(value) => { + let min = driver_fps_mode::STICK_X_MIN; + let max = driver_fps_mode::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_fps_mode::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_fps_mode::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::RStick(value) => { + let min = driver_fps_mode::STICK_X_MIN; + let max = driver_fps_mode::STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = driver_fps_mode::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_fps_mode::STICK_Y_MIN; + let y = normalize_signed_value(value.y as f64, min, max); + let y = Some(-y); // Y-Axis is inverted + + InputValue::Vector2 { x, y } + } + event::AxisEvent::Mouse(value) => { + let x = value.x as f64; + let x = Some(x); + let y = value.y as f64; + let y = Some(y); + + InputValue::Vector2 { x, y } + } + } +} + +/// Normalize the trigger value to something between 0.0 and 1.0 based on the +/// Legion Go's maximum axis ranges. +fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { + match event { + event::TriggerEvent::ATriggerL(value) => { + let max = driver_fps_mode::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::ATriggerR(value) => { + let max = driver_fps_mode::TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::MouseWheel(value) => { + let max = driver_fps_mode::MOUSE_WHEEL_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + } +} + +/// Translate the given Legion Go events into native events +fn translate_events(events: Vec) -> Vec { + events.into_iter().map(translate_event).collect() +} + +/// Translate the given Legion Go event into a native event +fn translate_event(event: event::Event) -> NativeEvent { + match event { + event::Event::GamepadButton(button) => match button { + event::GamepadButtonEvent::A(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::X(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::B(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Menu(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::View(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Legion(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::QuickAccess(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadDown(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadUp(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadLeft(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DPadRight(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::LB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbL(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y1(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::RB(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::DTriggerR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ThumbR(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::Y3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M3(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M2(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::MouseClick(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::Axis(axis) => match axis.clone() { + event::AxisEvent::Touchpad(_) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), + normalize_axis_value(axis), + ), + event::AxisEvent::LStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::RStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + normalize_axis_value(axis), + ), + event::AxisEvent::Mouse(_) => { + NativeEvent::new(Capability::Mouse(Mouse::Motion), normalize_axis_value(axis)) + } + }, + event::Event::Trigger(trigg) => match trigg.clone() { + event::TriggerEvent::ATriggerL(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::ATriggerR(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::MouseWheel(_) => { + NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)) + } + }, + event::Event::MouseButton(button) => match button { + event::MouseButtonEvent::Y3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M1(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Left)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M2(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Right)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::M3(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Side)), + InputValue::Bool(value.pressed), + ), + event::MouseButtonEvent::Left(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::TouchButton(button) => match button { + event::TouchButtonEvent::Left(value) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + InputValue::Bool(value.pressed), + ), + }, + + _ => NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)), + } +} + +/// List of all capabilities that the Legion Go driver implements +pub const CAPABILITIES: &[Capability] = &[ + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle3)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + Capability::Mouse(Mouse::Button(MouseButton::Extra)), + Capability::Mouse(Mouse::Button(MouseButton::Left)), + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + Capability::Mouse(Mouse::Button(MouseButton::Right)), + Capability::Mouse(Mouse::Button(MouseButton::Side)), + Capability::Mouse(Mouse::Motion), + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), +]; diff --git a/src/input/source/hidraw/lego.rs b/src/input/source/hidraw/lego_xinput.rs similarity index 94% rename from src/input/source/hidraw/lego.rs rename to src/input/source/hidraw/lego_xinput.rs index 3fe9c56..4426317 100644 --- a/src/input/source/hidraw/lego.rs +++ b/src/input/source/hidraw/lego_xinput.rs @@ -2,7 +2,7 @@ use std::{error::Error, fmt::Debug}; use crate::{ drivers::lego::{ - driver::{self, Driver}, + driver_xinput::{self, Driver}, event, }, input::{ @@ -17,11 +17,11 @@ use crate::{ }; /// Legion Go Controller source device implementation -pub struct LegionController { +pub struct LegionControllerX { driver: Driver, } -impl LegionController { +impl LegionControllerX { /// Create a new Legion controller source device with the given udev /// device information pub fn new(device_info: UdevDevice) -> Result> { @@ -30,7 +30,7 @@ impl LegionController { } } -impl SourceInputDevice for LegionController { +impl SourceInputDevice for LegionControllerX { /// Poll the source device for input events fn poll(&mut self) -> Result, InputError> { let events = self.driver.poll()?; @@ -44,9 +44,9 @@ impl SourceInputDevice for LegionController { } } -impl SourceOutputDevice for LegionController {} +impl SourceOutputDevice for LegionControllerX {} -impl Debug for LegionController { +impl Debug for LegionControllerX { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LegionController").finish() } @@ -80,10 +80,10 @@ fn normalize_unsigned_value(raw_value: f64, max: f64) -> f64 { fn normalize_axis_value(event: event::AxisEvent) -> InputValue { match event { event::AxisEvent::Touchpad(value) => { - let max = driver::PAD_X_MAX; + let max = driver_xinput::PAD_X_MAX; let x = normalize_unsigned_value(value.x as f64, max); - let max = driver::PAD_Y_MAX; + let max = driver_xinput::PAD_Y_MAX; let y = normalize_unsigned_value(value.y as f64, max); // If this is an UP event, don't override the position of X/Y @@ -102,26 +102,26 @@ fn normalize_axis_value(event: event::AxisEvent) -> InputValue { } } event::AxisEvent::LStick(value) => { - let min = driver::STICK_X_MIN; - let max = driver::STICK_X_MAX; + let min = driver_xinput::STICK_X_MIN; + let max = driver_xinput::STICK_X_MAX; let x = normalize_signed_value(value.x as f64, min, max); let x = Some(x); - let min = driver::STICK_Y_MAX; // uses inverted Y-axis - let max = driver::STICK_Y_MIN; + let min = driver_xinput::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_xinput::STICK_Y_MIN; let y = normalize_signed_value(value.y as f64, min, max); let y = Some(-y); // Y-Axis is inverted InputValue::Vector2 { x, y } } event::AxisEvent::RStick(value) => { - let min = driver::STICK_X_MIN; - let max = driver::STICK_X_MAX; + let min = driver_xinput::STICK_X_MIN; + let max = driver_xinput::STICK_X_MAX; let x = normalize_signed_value(value.x as f64, min, max); let x = Some(x); - let min = driver::STICK_Y_MAX; // uses inverted Y-axis - let max = driver::STICK_Y_MIN; + let min = driver_xinput::STICK_Y_MAX; // uses inverted Y-axis + let max = driver_xinput::STICK_Y_MIN; let y = normalize_signed_value(value.y as f64, min, max); let y = Some(-y); // Y-Axis is inverted @@ -143,15 +143,15 @@ fn normalize_axis_value(event: event::AxisEvent) -> InputValue { fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { match event { event::TriggerEvent::ATriggerL(value) => { - let max = driver::TRIGG_MAX; + let max = driver_xinput::TRIGG_MAX; InputValue::Float(normalize_unsigned_value(value.value as f64, max)) } event::TriggerEvent::ATriggerR(value) => { - let max = driver::TRIGG_MAX; + let max = driver_xinput::TRIGG_MAX; InputValue::Float(normalize_unsigned_value(value.value as f64, max)) } event::TriggerEvent::MouseWheel(value) => { - let max = driver::MOUSE_WHEEL_MAX; + let max = driver_xinput::MOUSE_WHEEL_MAX; InputValue::Float(normalize_unsigned_value(value.value as f64, max)) } } diff --git a/src/input/source/mod.rs b/src/input/source/mod.rs index d18a422..08e69a4 100644 --- a/src/input/source/mod.rs +++ b/src/input/source/mod.rs @@ -371,7 +371,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.info(), HidRawDevice::SteamDeck(device) => device.info(), - HidRawDevice::LegionGo(device) => device.info(), + HidRawDevice::LegionGoDCombined(device) => device.info(), + HidRawDevice::LegionGoDSplit(device) => device.info(), + HidRawDevice::LegionGoFPS(device) => device.info(), + HidRawDevice::LegionGoX(device) => device.info(), HidRawDevice::OrangePiNeo(device) => device.info(), HidRawDevice::Fts3528Touchscreen(device) => device.info(), HidRawDevice::XpadUhid(device) => device.info(), @@ -394,7 +397,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.info_ref(), HidRawDevice::SteamDeck(device) => device.info_ref(), - HidRawDevice::LegionGo(device) => device.info_ref(), + HidRawDevice::LegionGoDCombined(device) => device.info_ref(), + HidRawDevice::LegionGoDSplit(device) => device.info_ref(), + HidRawDevice::LegionGoFPS(device) => device.info_ref(), + HidRawDevice::LegionGoX(device) => device.info_ref(), HidRawDevice::OrangePiNeo(device) => device.info_ref(), HidRawDevice::Fts3528Touchscreen(device) => device.info_ref(), HidRawDevice::XpadUhid(device) => device.info_ref(), @@ -417,7 +423,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.get_id(), HidRawDevice::SteamDeck(device) => device.get_id(), - HidRawDevice::LegionGo(device) => device.get_id(), + HidRawDevice::LegionGoDCombined(device) => device.get_id(), + HidRawDevice::LegionGoDSplit(device) => device.get_id(), + HidRawDevice::LegionGoFPS(device) => device.get_id(), + HidRawDevice::LegionGoX(device) => device.get_id(), HidRawDevice::OrangePiNeo(device) => device.get_id(), HidRawDevice::Fts3528Touchscreen(device) => device.get_id(), HidRawDevice::XpadUhid(device) => device.get_id(), @@ -440,7 +449,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.client(), HidRawDevice::SteamDeck(device) => device.client(), - HidRawDevice::LegionGo(device) => device.client(), + HidRawDevice::LegionGoDCombined(device) => device.client(), + HidRawDevice::LegionGoDSplit(device) => device.client(), + HidRawDevice::LegionGoFPS(device) => device.client(), + HidRawDevice::LegionGoX(device) => device.client(), HidRawDevice::OrangePiNeo(device) => device.client(), HidRawDevice::Fts3528Touchscreen(device) => device.client(), HidRawDevice::XpadUhid(device) => device.client(), @@ -463,7 +475,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.run().await, HidRawDevice::SteamDeck(device) => device.run().await, - HidRawDevice::LegionGo(device) => device.run().await, + HidRawDevice::LegionGoDCombined(device) => device.run().await, + HidRawDevice::LegionGoDSplit(device) => device.run().await, + HidRawDevice::LegionGoFPS(device) => device.run().await, + HidRawDevice::LegionGoX(device) => device.run().await, HidRawDevice::OrangePiNeo(device) => device.run().await, HidRawDevice::Fts3528Touchscreen(device) => device.run().await, HidRawDevice::XpadUhid(device) => device.run().await, @@ -486,7 +501,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.get_capabilities(), HidRawDevice::SteamDeck(device) => device.get_capabilities(), - HidRawDevice::LegionGo(device) => device.get_capabilities(), + HidRawDevice::LegionGoDCombined(device) => device.get_capabilities(), + HidRawDevice::LegionGoDSplit(device) => device.get_capabilities(), + HidRawDevice::LegionGoFPS(device) => device.get_capabilities(), + HidRawDevice::LegionGoX(device) => device.get_capabilities(), HidRawDevice::OrangePiNeo(device) => device.get_capabilities(), HidRawDevice::Fts3528Touchscreen(device) => device.get_capabilities(), HidRawDevice::XpadUhid(device) => device.get_capabilities(), @@ -509,7 +527,10 @@ impl SourceDevice { SourceDevice::HidRaw(device) => match device { HidRawDevice::DualSense(device) => device.get_device_path(), HidRawDevice::SteamDeck(device) => device.get_device_path(), - HidRawDevice::LegionGo(device) => device.get_device_path(), + HidRawDevice::LegionGoDCombined(device) => device.get_device_path(), + HidRawDevice::LegionGoDSplit(device) => device.get_device_path(), + HidRawDevice::LegionGoFPS(device) => device.get_device_path(), + HidRawDevice::LegionGoX(device) => device.get_device_path(), HidRawDevice::OrangePiNeo(device) => device.get_device_path(), HidRawDevice::Fts3528Touchscreen(device) => device.get_device_path(), HidRawDevice::XpadUhid(device) => device.get_device_path(),