From 773e4c4c4bc9787e4d4833e44930598d5165e14c Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Wed, 17 Jun 2026 14:17:49 -0700 Subject: [PATCH] fix(Hardware Support): Fix Legion Go Regressions Fixes multiple regressions on the Legion Go 1 with original firmware by doing the following: - Split original Go 1 firmware and new Go 2 firmware into separate drivers. - Remove unsupported events (IMU events, M1, Alt-Tab, and Show Desktop buttons), from the Go 1 driver. - Remove default event filter from the Go 1 driver. - Remove Screen based IMU from the Go 2 configuration. --- .../inputplumber/devices/50-legion_go_2.yaml | 9 - src/drivers/lego/go1_driver.rs | 472 ++++++++++++++++++ src/drivers/lego/{driver.rs => go2_driver.rs} | 40 +- src/drivers/lego/mod.rs | 24 +- src/input/composite_device/mod.rs | 1 + src/input/source/hidraw.rs | 93 ++-- src/input/source/hidraw/legion_go.rs | 75 +-- src/input/source/hidraw/legion_go2.rs | 404 +++++++++++++++ 8 files changed, 971 insertions(+), 147 deletions(-) create mode 100644 src/drivers/lego/go1_driver.rs rename src/drivers/lego/{driver.rs => go2_driver.rs} (96%) create mode 100644 src/input/source/hidraw/legion_go2.rs diff --git a/rootfs/usr/share/inputplumber/devices/50-legion_go_2.yaml b/rootfs/usr/share/inputplumber/devices/50-legion_go_2.yaml index 5a23bb2d..4d6caabf 100644 --- a/rootfs/usr/share/inputplumber/devices/50-legion_go_2.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-legion_go_2.yaml @@ -114,15 +114,6 @@ source_devices: product_id: 0x61ee interface_num: 2 - # IMU - - group: imu - iio: - name: gyro_3d - mount_matrix: - x: [1, 0, 0] - y: [0, -1, 0] - z: [0, 0, 1] - # 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/go1_driver.rs b/src/drivers/lego/go1_driver.rs new file mode 100644 index 00000000..9bd9bd69 --- /dev/null +++ b/src/drivers/lego/go1_driver.rs @@ -0,0 +1,472 @@ +use std::collections::HashSet; +use std::{error::Error, ffi::CString}; + +use hidapi::HidDevice; +use packed_struct::PackedStruct; +use tokio::time::Instant; + +use crate::input::capability::Capability; +use crate::udev::device::UdevDevice; + +use super::{ + event::{ + AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseWheelInput, + TouchAxisInput, TouchButtonEvent, TriggerEvent, TriggerInput, + }, + hid_report::{GamepadMode, XInputDataReport}, + CLICK_DELAY, GO1_PIDS, GP_IID, HID_TIMEOUT, PAD_FORCE_NORMAL, RELEASE_DELAY, VID, + XINPUT_COMMAND_ID, XINPUT_DATA, XINPUT_PACKET_SIZE, +}; + +pub struct Driver { + /// HIDRAW device instance + hid_device: HidDevice, + /// List of events that should not be generated + filtered_events: HashSet, + /// Timestamp of the first touch event. + first_touch: Instant, + /// Whether or not we are currently holding a click-to-click. + is_clicked: bool, + /// Whether or not we are detecting a touch event currently. + is_touching: bool, + /// Timestamp of the last touch event. + last_touch: Instant, + /// Whether or not a touch event was started that hasn't been cleared. + touch_started: bool, + /// State for the internal gamepad controller + state: Option, +} + +impl Driver { + pub fn new(udev_device: UdevDevice) -> Result> { + let fmtpath = udev_device.devnode().clone(); + let path = CString::new(fmtpath.clone())?; + let api = hidapi::HidApi::new()?; + let hid_device = api.open_path(&path)?; + let info = hid_device.get_device_info()?; + + if info.vendor_id() != VID + || !GO1_PIDS.contains(&info.product_id()) + || info.interface_number() != GP_IID + { + return Err(format!("Device '{fmtpath}' is not a Legion Go S Controller").into()); + } + + Ok(Self { + hid_device, + filtered_events: Default::default(), + first_touch: Instant::now(), + is_clicked: false, + is_touching: false, + last_touch: Instant::now(), + touch_started: false, + state: None, + }) + } + + //TODO: Using InputPlumber Capability enum prevents this driver from having the ability to be + //a standalone crate. When this driver is eventually separated, refactor the Event type to + //follow the pattern DeviceEvent(Event, Value) and create a match table for + //Capability->Event/Event->Capability in the SourceDriver implementation. + pub fn update_filtered_events(&mut self, events: HashSet) { + self.filtered_events = events; + } + + /// 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.hid_device.read_timeout(&mut buf[..], HID_TIMEOUT)?; + + if bytes_read > XINPUT_PACKET_SIZE { + return Err("Invalid packet size for X-Input Data.".into()); + } + + let report_id = buf[0]; + let command_id = buf[2]; + + // Configuration event responses happen on the same endpoint. If this data packet isn't + // specifically xinput data it can crash the driver, so block it. + if command_id != XINPUT_COMMAND_ID { + //log::trace!("Got event that isn't xinput data, skipping"); + return Ok(vec![]); + } + let slice = &buf[..bytes_read]; + //log::trace!("Got Report ID: {report_id}"); + //log::trace!("Got Report Size: {bytes_read}"); + //log::trace!("Raw Data: {:02x?}", buf); + + let events = match report_id { + 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 [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_state = self.update_xinput_state(input_report); + + // Translate the state into a stream of input events + let events = self.translate_xinput(old_state); + + Ok(events) + } + + /// Update gamepad state + fn update_xinput_state(&mut self, input_report: XInputDataReport) -> Option { + let old_state = self.state; + self.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.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!( + "Gamepad mode changed from {} to {}", + old_state.gamepad_mode, + state.gamepad_mode + ); + } + + if state.gamepad_mode == GamepadMode::Fps { + //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 { + log::debug!("Raw mouse value: {:b}, {}", state.mouse_z, !state.mouse_z); + let value = state.mouse_z.wrapping_sub(128) as i8; + let value = match value { + 64..=127 => value - 127 - 1, + v => v, + }; + + log::debug!("Normalized mouse value: {value}"); + events.push(Event::Trigger(TriggerEvent::MouseWheel(MouseWheelInput { + value, + }))); + } + + // IMU Events + if state.l_con_state != old_state.l_con_state + || state.r_con_state != old_state.r_con_state + { + log::trace!("Left controller connected state: {:?}", state.l_con_state); + log::trace!("Right controller connected state: {:?}", state.r_con_state); + } + + // Touchpad events + // Detect if we are touching or not, x, y will always be 0, 0 when the pad is not + // touched. + self.is_touching = state.touch_x != 0 && state.touch_y != 0; + + // Handle touching + if self.is_touching { + self.last_touch = Instant::now(); + + // If this is the first event of a new touch, log the time. + if !self.touch_started { + log::trace!("START Touch"); + log::trace!("Last touch elapsed: {:?}", self.last_touch.elapsed()); + + self.touch_started = true; + self.first_touch = Instant::now(); + } + // Handle tap to click + } else if !self.is_touching + && self.touch_started + && self.first_touch.elapsed() < CLICK_DELAY + { + // Handle double click + if self.is_clicked && self.first_touch.elapsed() < RELEASE_DELAY { + log::trace!("Double Click"); + let mut new_events = self.release_click(); + events.append(&mut new_events); + } + let mut click_events = self.start_click(); + events.append(&mut click_events); + // Handle release events + } else if !self.is_touching && self.last_touch.elapsed() > RELEASE_DELAY { + // Unclick if we we clicking and are no longer touching. + if self.is_clicked { + let mut new_events = self.release_click(); + events.append(&mut new_events); + } + + // Clear this touch sequence + if self.touch_started { + self.touch_started = false; + log::trace!("END Touch"); + } + } + + if state.touch_x != old_state.touch_x || state.touch_y != old_state.touch_y { + events.push(Event::Axis(AxisEvent::Touchpad(TouchAxisInput { + index: 0, + is_touching: self.is_touching, + x: state.touch_x, + y: state.touch_y, + }))); + } + } + events + } + + fn start_click(&mut self) -> Vec { + if self.is_clicked { + log::trace!("Rejecting extra click"); + return vec![]; + } + log::trace!("Started CLICK event."); + log::trace!("First touch elapsed: {:?}", self.first_touch.elapsed()); + log::trace!("Last touch elapsed: {:?}", self.last_touch.elapsed()); + self.is_clicked = true; + let mut events = Vec::new(); + + let event = Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: true })); + events.push(event); + // The touchpad doesn't have a force sensor. The deck target wont produce a "click" + // event in desktop or lizard mode without a force value. Simulate a 1/4 press to work + // around this. + let event = Event::Trigger(TriggerEvent::RpadForce(TriggerInput { + value: PAD_FORCE_NORMAL, + })); + events.push(event); + events + } + + fn release_click(&mut self) -> Vec { + log::trace!("Released CLICK event."); + log::trace!("First touch elapsed: {:?}", self.first_touch.elapsed()); + log::trace!("Last touch elapsed: {:?}", self.last_touch.elapsed()); + self.is_clicked = false; + self.touch_started = false; + let mut events = Vec::new(); + let event = Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: false })); + events.push(event); + // The touchpad doesn't have a force sensor. The deck target wont produce a "click" + // event in desktop or lizard mode without a force value. Simulate a 1/4 press to work + // around this. + let event = Event::Trigger(TriggerEvent::RpadForce(TriggerInput { value: 0 })); + events.push(event); + events + } +} diff --git a/src/drivers/lego/driver.rs b/src/drivers/lego/go2_driver.rs similarity index 96% rename from src/drivers/lego/driver.rs rename to src/drivers/lego/go2_driver.rs index 081295e9..fddb1a75 100644 --- a/src/drivers/lego/driver.rs +++ b/src/drivers/lego/go2_driver.rs @@ -5,25 +5,17 @@ use hidapi::HidDevice; use packed_struct::PackedStruct; use tokio::time::Instant; -use crate::drivers::lego::DEFAULT_EVENT_FILTER; -use crate::input::capability::Source; +use crate::input::capability::{Capability, Source}; use crate::udev::device::UdevDevice; -use crate::{ - drivers::lego::{ - event::{ImuAxisInput, TouchAxisInput, TouchButtonEvent}, - hid_report::GamepadMode, - CLICK_DELAY, PAD_FORCE_NORMAL, RELEASE_DELAY, XINPUT_COMMAND_ID, - }, - input::capability::Capability, -}; use super::{ event::{ - AxisEvent, BinaryInput, Event, GamepadButtonEvent, JoyAxisInput, MouseWheelInput, - TriggerEvent, TriggerInput, + AxisEvent, BinaryInput, Event, GamepadButtonEvent, ImuAxisInput, JoyAxisInput, + MouseWheelInput, TouchAxisInput, TouchButtonEvent, TriggerEvent, TriggerInput, }, - hid_report::XInputDataReport, - GP_IID, HID_TIMEOUT, PIDS, VID, XINPUT_DATA, XINPUT_PACKET_SIZE, + hid_report::{GamepadMode, XInputDataReport}, + CLICK_DELAY, DEFAULT_EVENT_FILTER, GO2_PIDS, GP_IID, HID_TIMEOUT, PAD_FORCE_NORMAL, + RELEASE_DELAY, VID, XINPUT_COMMAND_ID, XINPUT_DATA, XINPUT_PACKET_SIZE, }; pub struct Driver { @@ -44,7 +36,7 @@ pub struct Driver { /// Whether or not a touch event was started that hasn't been cleared. touch_started: bool, /// State for the internal gamepad controller - xinput_state: Option, + state: Option, } impl Driver { @@ -56,7 +48,7 @@ impl Driver { let info = hid_device.get_device_info()?; if info.vendor_id() != VID - || !PIDS.contains(&info.product_id()) + || !GO2_PIDS.contains(&info.product_id()) || info.interface_number() != GP_IID { return Err(format!("Device '{fmtpath}' is not a Legion Go S Controller").into()); @@ -71,7 +63,7 @@ impl Driver { is_touching: false, last_touch: Instant::now(), touch_started: false, - xinput_state: None, + state: None, }) } @@ -119,6 +111,10 @@ impl Driver { let mut buf = [0; XINPUT_PACKET_SIZE]; let bytes_read = self.hid_device.read_timeout(&mut buf[..], HID_TIMEOUT)?; + if bytes_read > XINPUT_PACKET_SIZE { + return Err("Invalid packet size for X-Input Data.".into()); + } + let report_id = buf[0]; let command_id = buf[2]; @@ -167,25 +163,25 @@ impl Driver { //log::debug!(" ---- End Report ----"); // Update the state - let old_dinput_state = self.update_xinput_state(input_report); + let old_state = self.update_xinput_state(input_report); // Translate the state into a stream of input events - let events = self.translate_xinput(old_dinput_state); + let events = self.translate_xinput(old_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); + let old_state = self.state; + self.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 { + let Some(state) = self.state else { return events; }; diff --git a/src/drivers/lego/mod.rs b/src/drivers/lego/mod.rs index ef79c6b8..4151b073 100644 --- a/src/drivers/lego/mod.rs +++ b/src/drivers/lego/mod.rs @@ -1,5 +1,6 @@ -pub mod driver; pub mod event; +pub mod go1_driver; +pub mod go2_driver; pub mod hid_report; use std::time::Duration; @@ -7,27 +8,34 @@ use std::time::Duration; use crate::input::capability::{Capability, Source}; // Hardware ID's +pub const VID: u16 = 0x17ef; +pub const GP_IID: i32 = 0x02; + +// Go 1 const LEGO_1_XINPUT_PID: u16 = 0x6182; const LEGO_1_DINPUT_ATTACHED_PID: u16 = 0x6183; const LEGO_1_DINPUT_DETACHED_PID: u16 = 0x6184; const LEGO_1_FPS_PID: u16 = 0x6185; -const LEGO_2_XINPUT_PID: u16 = 0x61eb; -const LEGO_2_DINPUT_ATTACHED_PID: u16 = 0x61ec; -const LEGO_2_DINPUT_DETATCHED_PID: u16 = 0x61ed; -const LEGO_2_FPS_PID: u16 = 0x61ee; -pub const PIDS: [u16; 8] = [ +pub const GO1_PIDS: [u16; 4] = [ LEGO_1_XINPUT_PID, LEGO_1_DINPUT_ATTACHED_PID, LEGO_1_DINPUT_DETACHED_PID, LEGO_1_FPS_PID, +]; + +// Go 2 +const LEGO_2_XINPUT_PID: u16 = 0x61eb; +const LEGO_2_DINPUT_ATTACHED_PID: u16 = 0x61ec; +const LEGO_2_DINPUT_DETATCHED_PID: u16 = 0x61ed; +const LEGO_2_FPS_PID: u16 = 0x61ee; + +pub const GO2_PIDS: [u16; 4] = [ LEGO_2_XINPUT_PID, LEGO_2_DINPUT_ATTACHED_PID, LEGO_2_DINPUT_DETATCHED_PID, LEGO_2_FPS_PID, ]; -pub const VID: u16 = 0x17ef; -pub const GP_IID: i32 = 0x02; const CLICK_DELAY: Duration = Duration::from_millis(150); const RELEASE_DELAY: Duration = Duration::from_millis(30); diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index c8316c4a..fd2fdd78 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -1570,6 +1570,7 @@ impl CompositeDevice { /// Executed whenever a source device is added to this [CompositeDevice]. async fn on_source_device_added(&mut self, device: DeviceInfo) -> Result<(), Box> { if let Err(e) = self.add_source_device(device) { + log::error!("add_source_device failed: {:?}", e); return Err(e.to_string().into()); } self.run_source_devices().await?; diff --git a/src/input/source/hidraw.rs b/src/input/source/hidraw.rs index 7c438b87..a45e4bf0 100644 --- a/src/input/source/hidraw.rs +++ b/src/input/source/hidraw.rs @@ -6,6 +6,7 @@ pub mod gpd_win_mini_macro_keyboard; pub mod gpd_win_mini_touchpad; pub mod horipad_steam; pub mod legion_go; +pub mod legion_go2; pub mod legos_imu; pub mod legos_touchpad; pub mod legos_xinput; @@ -18,18 +19,6 @@ pub mod zotac_zone; use std::{error::Error, time::Duration}; -use blocked::BlockedHidrawDevice; -use flydigi_vader_4_pro::Vader4Pro; -use gpd_win_mini_macro_keyboard::GpdWinMiniMacroKeyboard; -use gpd_win_mini_touchpad::GpdWinMiniTouchpad; -use horipad_steam::HoripadSteam; -use legos_imu::LegionSImuController; -use legos_touchpad::LegionSTouchpadController; -use oxp_hid::OxpHid; -use rog_ally::RogAlly; -use xpad_uhid::XpadUhid; -use zotac_zone::ZotacZone; - use crate::{ config, constants::BUS_SOURCES_PREFIX, @@ -42,10 +31,14 @@ use crate::{ }; use self::{ - dualsense::DualSenseController, fts3528::Fts3528Touchscreen, legion_go::LegionGoController, - legos_xinput::LegionSXInputController, opineo::OrangePiNeoTouchpad, steam_deck::DeckController, + blocked::BlockedHidrawDevice, dualsense::DualSenseController, flydigi_vader_4_pro::Vader4Pro, + fts3528::Fts3528Touchscreen, gpd_win_mini_macro_keyboard::GpdWinMiniMacroKeyboard, + gpd_win_mini_touchpad::GpdWinMiniTouchpad, horipad_steam::HoripadSteam, + legion_go::LegionGoController, legion_go2::LegionGo2Controller, + legos_imu::LegionSImuController, legos_touchpad::LegionSTouchpadController, + legos_xinput::LegionSXInputController, opineo::OrangePiNeoTouchpad, oxp_hid::OxpHid, + rog_ally::RogAlly, steam_deck::DeckController, xpad_uhid::XpadUhid, zotac_zone::ZotacZone, }; - use super::{InputError, OutputError, SourceDeviceCompatible, SourceDriver, SourceDriverOptions}; /// List of available drivers @@ -53,13 +46,14 @@ enum DriverType { Blocked, DualSense, Fts3528Touchscreen, - GpdWinMiniTouchpad, GpdWinMiniMacroKeyboard, + GpdWinMiniTouchpad, HoripadSteam, + LegionGo, + LegionGo2, LegionGoSImu, LegionGoSTouchpad, LegionGoSXInput, - LegionGoXInput, OrangePiNeo, OxpHid, RogAlly, @@ -76,13 +70,14 @@ pub enum HidRawDevice { Blocked(SourceDriver), DualSense(SourceDriver), Fts3528Touchscreen(SourceDriver), - GpdWinMiniTouchpad(SourceDriver), GpdWinMiniMacroKeyboard(SourceDriver), + GpdWinMiniTouchpad(SourceDriver), HoripadSteam(SourceDriver), + LegionGo(SourceDriver), + LegionGo2(SourceDriver), LegionGoSImu(SourceDriver), LegionGoSTouchpad(SourceDriver), LegionGoSXInput(SourceDriver), - LegionGo(SourceDriver), OrangePiNeo(SourceDriver), OxpHid(SourceDriver), RogAlly(SourceDriver), @@ -98,13 +93,14 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.info_ref(), HidRawDevice::DualSense(source_driver) => source_driver.info_ref(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.info_ref(), - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.info_ref(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.info_ref(), + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.info_ref(), HidRawDevice::HoripadSteam(source_driver) => source_driver.info_ref(), + HidRawDevice::LegionGo(source_driver) => source_driver.info_ref(), + HidRawDevice::LegionGo2(source_driver) => source_driver.info_ref(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.info_ref(), HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.info_ref(), HidRawDevice::LegionGoSXInput(source_driver) => source_driver.info_ref(), - HidRawDevice::LegionGo(source_driver) => source_driver.info_ref(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.info_ref(), HidRawDevice::OxpHid(source_driver) => source_driver.info_ref(), HidRawDevice::RogAlly(source_driver) => source_driver.info_ref(), @@ -120,13 +116,14 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_id(), HidRawDevice::DualSense(source_driver) => source_driver.get_id(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_id(), - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_id(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.get_id(), + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_id(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_id(), + HidRawDevice::LegionGo(source_driver) => source_driver.get_id(), + HidRawDevice::LegionGo2(source_driver) => source_driver.get_id(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.get_id(), HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.get_id(), HidRawDevice::LegionGoSXInput(source_driver) => source_driver.get_id(), - HidRawDevice::LegionGo(source_driver) => source_driver.get_id(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.get_id(), HidRawDevice::OxpHid(source_driver) => source_driver.get_id(), HidRawDevice::RogAlly(source_driver) => source_driver.get_id(), @@ -142,13 +139,14 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.client(), HidRawDevice::DualSense(source_driver) => source_driver.client(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.client(), - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.client(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.client(), + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.client(), HidRawDevice::HoripadSteam(source_driver) => source_driver.client(), + HidRawDevice::LegionGo(source_driver) => source_driver.client(), + HidRawDevice::LegionGo2(source_driver) => source_driver.client(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.client(), HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.client(), HidRawDevice::LegionGoSXInput(source_driver) => source_driver.client(), - HidRawDevice::LegionGo(source_driver) => source_driver.client(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.client(), HidRawDevice::OxpHid(source_driver) => source_driver.client(), HidRawDevice::RogAlly(source_driver) => source_driver.client(), @@ -164,13 +162,14 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.run().await, HidRawDevice::DualSense(source_driver) => source_driver.run().await, HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.run().await, - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.run().await, HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.run().await, + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.run().await, HidRawDevice::HoripadSteam(source_driver) => source_driver.run().await, + HidRawDevice::LegionGo(source_driver) => source_driver.run().await, + HidRawDevice::LegionGo2(source_driver) => source_driver.run().await, HidRawDevice::LegionGoSImu(source_driver) => source_driver.run().await, HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.run().await, HidRawDevice::LegionGoSXInput(source_driver) => source_driver.run().await, - HidRawDevice::LegionGo(source_driver) => source_driver.run().await, HidRawDevice::OrangePiNeo(source_driver) => source_driver.run().await, HidRawDevice::OxpHid(source_driver) => source_driver.run().await, HidRawDevice::RogAlly(source_driver) => source_driver.run().await, @@ -186,15 +185,16 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_capabilities(), HidRawDevice::DualSense(source_driver) => source_driver.get_capabilities(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_capabilities(), - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_capabilities(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => { source_driver.get_capabilities() } + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_capabilities(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_capabilities(), + HidRawDevice::LegionGo(source_driver) => source_driver.get_capabilities(), + HidRawDevice::LegionGo2(source_driver) => source_driver.get_capabilities(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.get_capabilities(), HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.get_capabilities(), HidRawDevice::LegionGoSXInput(source_driver) => source_driver.get_capabilities(), - HidRawDevice::LegionGo(source_driver) => source_driver.get_capabilities(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.get_capabilities(), HidRawDevice::OxpHid(source_driver) => source_driver.get_capabilities(), HidRawDevice::RogAlly(source_driver) => source_driver.get_capabilities(), @@ -212,19 +212,20 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Fts3528Touchscreen(source_driver) => { source_driver.get_output_capabilities() } - HidRawDevice::GpdWinMiniTouchpad(source_driver) => { + HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => { source_driver.get_output_capabilities() } - HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => { + HidRawDevice::GpdWinMiniTouchpad(source_driver) => { source_driver.get_output_capabilities() } HidRawDevice::HoripadSteam(source_driver) => source_driver.get_output_capabilities(), + HidRawDevice::LegionGo(source_driver) => source_driver.get_output_capabilities(), + HidRawDevice::LegionGo2(source_driver) => source_driver.get_output_capabilities(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.get_output_capabilities(), HidRawDevice::LegionGoSTouchpad(source_driver) => { source_driver.get_output_capabilities() } HidRawDevice::LegionGoSXInput(source_driver) => source_driver.get_output_capabilities(), - HidRawDevice::LegionGo(source_driver) => source_driver.get_output_capabilities(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.get_output_capabilities(), HidRawDevice::OxpHid(source_driver) => source_driver.get_output_capabilities(), HidRawDevice::RogAlly(source_driver) => source_driver.get_output_capabilities(), @@ -240,13 +241,14 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_device_path(), HidRawDevice::DualSense(source_driver) => source_driver.get_device_path(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_device_path(), - HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_device_path(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.get_device_path(), + HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_device_path(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_device_path(), + HidRawDevice::LegionGo(source_driver) => source_driver.get_device_path(), + HidRawDevice::LegionGo2(source_driver) => source_driver.get_device_path(), HidRawDevice::LegionGoSImu(source_driver) => source_driver.get_device_path(), HidRawDevice::LegionGoSTouchpad(source_driver) => source_driver.get_device_path(), HidRawDevice::LegionGoSXInput(source_driver) => source_driver.get_device_path(), - HidRawDevice::LegionGo(source_driver) => source_driver.get_device_path(), HidRawDevice::OrangePiNeo(source_driver) => source_driver.get_device_path(), HidRawDevice::OxpHid(source_driver) => source_driver.get_device_path(), HidRawDevice::RogAlly(source_driver) => source_driver.get_device_path(), @@ -317,12 +319,18 @@ impl HidRawDevice { ); Ok(Self::SteamDeck(source_device)) } - DriverType::LegionGoXInput => { + DriverType::LegionGo => { let device = LegionGoController::new(device_info.clone())?; let source_device = SourceDriver::new(composite_device, device, device_info.into(), conf); Ok(Self::LegionGo(source_device)) } + DriverType::LegionGo2 => { + let device = LegionGo2Controller::new(device_info.clone())?; + let source_device = + SourceDriver::new(composite_device, device, device_info.into(), conf); + Ok(Self::LegionGo2(source_device)) + } DriverType::LegionGoSImu => { let options = SourceDriverOptions { poll_rate: Duration::from_millis(4), @@ -482,13 +490,22 @@ impl HidRawDevice { return DriverType::SteamDeck; } - // Legion Go XInput + // Legion Go if vid == drivers::lego::VID - && drivers::lego::PIDS.contains(&pid) + && drivers::lego::GO1_PIDS.contains(&pid) && iid == drivers::lego::GP_IID { log::info!("Detected Legion Go Controller"); - return DriverType::LegionGoXInput; + return DriverType::LegionGo; + } + + // Legion Go 2 + if vid == drivers::lego::VID + && drivers::lego::GO2_PIDS.contains(&pid) + && iid == drivers::lego::GP_IID + { + log::info!("Detected Legion Go 2 Controller"); + return DriverType::LegionGo2; } // Legion Go S IMU diff --git a/src/input/source/hidraw/legion_go.rs b/src/input/source/hidraw/legion_go.rs index dee8ce80..d5e933a1 100644 --- a/src/input/source/hidraw/legion_go.rs +++ b/src/input/source/hidraw/legion_go.rs @@ -3,15 +3,15 @@ use std::{error::Error, fmt::Debug}; use crate::{ drivers::lego::{ - driver::Driver, event::{self, AxisEvent}, + go1_driver::Driver, PAD_FORCE_MAX, PAD_X_MAX, PAD_Y_MAX, STICK_X_MAX, STICK_X_MIN, STICK_Y_MAX, STICK_Y_MIN, TRIGG_MAX, }, input::{ capability::{ Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Mouse, MouseButton, - Source, Touch, TouchButton, Touchpad, + Touch, TouchButton, Touchpad, }, event::{ native::NativeEvent, @@ -142,10 +142,6 @@ impl LegionGoController { InputValue::Bool(value.pressed), ), event::GamepadButtonEvent::M2(value) => NativeEvent::new( - Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStickTouch)), - InputValue::Bool(value.pressed), - ), - event::GamepadButtonEvent::M1(value) => NativeEvent::new( Capability::Gamepad(Gamepad::Button(GamepadButton::RightStickTouch)), InputValue::Bool(value.pressed), ), @@ -153,14 +149,7 @@ impl LegionGoController { Capability::Mouse(Mouse::Button(MouseButton::Middle)), InputValue::Bool(value.pressed), ), - event::GamepadButtonEvent::ShowDesktop(value) => NativeEvent::new( - Capability::Gamepad(Gamepad::Button(GamepadButton::Keyboard)), - InputValue::Bool(value.pressed), - ), - event::GamepadButtonEvent::AltTab(value) => NativeEvent::new( - Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess2)), - InputValue::Bool(value.pressed), - ), + _ => NativeEvent::new(Capability::NotImplemented, InputValue::None), }, event::Event::Axis(axis) => match axis.clone() { AxisEvent::LStick(_) => NativeEvent::new( @@ -175,30 +164,7 @@ impl LegionGoController { Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), normalize_axis_value(axis), ), - AxisEvent::LeftAccel(_) => NativeEvent::new( - Capability::Accelerometer(Source::Left), - normalize_axis_value(axis), - ), - AxisEvent::LeftGyro(_) => NativeEvent::new( - Capability::Gyroscope(Source::Left), - normalize_axis_value(axis), - ), - AxisEvent::RightAccel(_) => NativeEvent::new( - Capability::Accelerometer(Source::Right), - normalize_axis_value(axis), - ), - AxisEvent::RightGyro(_) => NativeEvent::new( - Capability::Gyroscope(Source::Right), - normalize_axis_value(axis), - ), - AxisEvent::MultiAccel(_) => NativeEvent::new( - Capability::Accelerometer(Source::Center), - normalize_axis_value(axis), - ), - AxisEvent::MultiGyro(_) => NativeEvent::new( - Capability::Gyroscope(Source::Center), - normalize_axis_value(axis), - ), + _ => NativeEvent::new(Capability::NotImplemented, InputValue::None), }, event::Event::Trigger(trigg) => match trigg.clone() { event::TriggerEvent::ATriggerL(_) => NativeEvent::new( @@ -246,17 +212,6 @@ impl SourceInputDevice for LegionGoController { self.driver.update_filtered_events(events); Ok(()) } - - fn get_default_event_filter(&self) -> Result, InputError> { - let filtered_events = self.driver.get_default_event_filter(); - let filtered_events = match filtered_events { - Ok(events) => events, - Err(e) => { - return Err(format!("Failed to get default event filter: {:?}", e).into()); - } - }; - Ok(filtered_events) - } } impl SourceOutputDevice for LegionGoController {} @@ -297,20 +252,6 @@ fn normalize_axis_value(event: AxisEvent) -> InputValue { InputValue::Vector2 { x, y } } - AxisEvent::LeftAccel(value) - | AxisEvent::RightAccel(value) - | AxisEvent::MultiAccel(value) => InputValue::Vector3 { - x: Some(value.pitch as f64), - y: Some(value.roll as f64), - z: Some(value.yaw as f64), - }, - AxisEvent::LeftGyro(value) | AxisEvent::RightGyro(value) | AxisEvent::MultiGyro(value) => { - InputValue::Vector3 { - x: Some(value.pitch as f64), - y: Some(value.roll as f64), - z: Some(value.yaw as f64), - } - } AxisEvent::Touchpad(value) => { let max = PAD_X_MAX; let x = normalize_unsigned_value(value.x as f64, max); @@ -333,6 +274,7 @@ fn normalize_axis_value(event: AxisEvent) -> InputValue { y, } } + _ => InputValue::None, } } @@ -360,9 +302,6 @@ fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { } /// List of all capabilities that the Legion Go driver implements pub const CAPABILITIES: &[Capability] = &[ - Capability::Accelerometer(Source::Center), - Capability::Accelerometer(Source::Left), - Capability::Accelerometer(Source::Right), Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), @@ -376,7 +315,6 @@ pub const CAPABILITIES: &[Capability] = &[ Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), - Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStickTouch)), Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), Capability::Gamepad(Gamepad::Button(GamepadButton::North)), Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), @@ -395,9 +333,6 @@ pub const CAPABILITIES: &[Capability] = &[ Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTouchpadForce)), Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), - Capability::Gyroscope(Source::Center), - Capability::Gyroscope(Source::Left), - Capability::Gyroscope(Source::Right), Capability::Mouse(Mouse::Wheel), Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), diff --git a/src/input/source/hidraw/legion_go2.rs b/src/input/source/hidraw/legion_go2.rs new file mode 100644 index 00000000..c9ba70ed --- /dev/null +++ b/src/input/source/hidraw/legion_go2.rs @@ -0,0 +1,404 @@ +use std::collections::HashSet; +use std::{error::Error, fmt::Debug}; + +use crate::{ + drivers::lego::{ + event::{self, AxisEvent}, + go2_driver::Driver, + PAD_FORCE_MAX, PAD_X_MAX, PAD_Y_MAX, STICK_X_MAX, STICK_X_MIN, STICK_Y_MAX, STICK_Y_MIN, + TRIGG_MAX, + }, + input::{ + capability::{ + Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Mouse, MouseButton, + Source, Touch, TouchButton, Touchpad, + }, + event::{ + native::NativeEvent, + value::InputValue, + value::{normalize_signed_value, normalize_unsigned_value}, + }, + source::{InputError, SourceInputDevice, SourceOutputDevice}, + }, + udev::device::UdevDevice, +}; + +/// Legion Go Controller source device implementation +pub struct LegionGo2Controller { + driver: Driver, +} + +impl LegionGo2Controller { + /// 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)?; + Ok(Self { driver }) + } + + /// Translate the given Legion Go events into native events + fn translate_events(&mut self, events: Vec) -> Vec { + //events.into_iter().map(translate_event).collect() + let mut new_events = Vec::new(); + + for event in events { + let new_event = self.translate_event(event); + new_events.push(new_event); + } + + new_events + } + + /// Translate the given Legion Go event into a native event + fn translate_event(&mut self, 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::RightStickTouch)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::M1(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStickTouch)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::MouseClick(value) => NativeEvent::new( + Capability::Mouse(Mouse::Button(MouseButton::Middle)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::ShowDesktop(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::Keyboard)), + InputValue::Bool(value.pressed), + ), + event::GamepadButtonEvent::AltTab(value) => NativeEvent::new( + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess2)), + InputValue::Bool(value.pressed), + ), + }, + event::Event::Axis(axis) => match axis.clone() { + AxisEvent::LStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + normalize_axis_value(axis), + ), + AxisEvent::RStick(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + normalize_axis_value(axis), + ), + AxisEvent::Touchpad(_) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), + normalize_axis_value(axis), + ), + AxisEvent::LeftAccel(_) => NativeEvent::new( + Capability::Accelerometer(Source::Left), + normalize_axis_value(axis), + ), + AxisEvent::LeftGyro(_) => NativeEvent::new( + Capability::Gyroscope(Source::Left), + normalize_axis_value(axis), + ), + AxisEvent::RightAccel(_) => NativeEvent::new( + Capability::Accelerometer(Source::Right), + normalize_axis_value(axis), + ), + AxisEvent::RightGyro(_) => NativeEvent::new( + Capability::Gyroscope(Source::Right), + normalize_axis_value(axis), + ), + AxisEvent::MultiAccel(_) => NativeEvent::new( + Capability::Accelerometer(Source::Center), + normalize_axis_value(axis), + ), + AxisEvent::MultiGyro(_) => NativeEvent::new( + Capability::Gyroscope(Source::Center), + 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::Mouse(Mouse::Wheel), + normalize_trigger_value(trigg), + ), + event::TriggerEvent::RpadForce(_) => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTouchpadForce)), + normalize_trigger_value(trigg), + ), + }, + event::Event::TouchButton(button) => match button { + event::TouchButtonEvent::Left(value) => NativeEvent::new( + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + InputValue::Bool(value.pressed), + ), + }, + } + } +} + +impl SourceInputDevice for LegionGo2Controller { + /// Poll the source device for input events + fn poll(&mut self) -> Result, InputError> { + let events = self.driver.poll()?; + let native_events = self.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()) + } + + fn update_event_filter(&mut self, events: HashSet) -> Result<(), InputError> { + self.driver.update_filtered_events(events); + Ok(()) + } + + fn get_default_event_filter(&self) -> Result, InputError> { + let filtered_events = self.driver.get_default_event_filter(); + let filtered_events = match filtered_events { + Ok(events) => events, + Err(e) => { + return Err(format!("Failed to get default event filter: {:?}", e).into()); + } + }; + Ok(filtered_events) + } +} + +impl SourceOutputDevice for LegionGo2Controller {} + +impl Debug for LegionGo2Controller { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LegionController").finish() + } +} + +/// Normalize the value to something between -1.0 and 1.0 based on the +/// minimum and maximum axis ranges. +fn normalize_axis_value(event: AxisEvent) -> InputValue { + match event { + AxisEvent::LStick(value) => { + let min = STICK_X_MIN; + let max = STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = STICK_Y_MAX; // uses inverted Y-axis + let max = 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 } + } + AxisEvent::RStick(value) => { + let min = STICK_X_MIN; + let max = STICK_X_MAX; + let x = normalize_signed_value(value.x as f64, min, max); + let x = Some(x); + + let min = STICK_Y_MAX; // uses inverted Y-axis + let max = 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 } + } + AxisEvent::LeftAccel(value) + | AxisEvent::RightAccel(value) + | AxisEvent::MultiAccel(value) => InputValue::Vector3 { + x: Some(value.pitch as f64), + y: Some(value.roll as f64), + z: Some(value.yaw as f64), + }, + AxisEvent::LeftGyro(value) | AxisEvent::RightGyro(value) | AxisEvent::MultiGyro(value) => { + InputValue::Vector3 { + x: Some(value.pitch as f64), + y: Some(value.roll as f64), + z: Some(value.yaw as f64), + } + } + AxisEvent::Touchpad(value) => { + let max = PAD_X_MAX; + let x = normalize_unsigned_value(value.x as f64, max); + + let max = 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, + } + } + } +} + +/// 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 = TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::ATriggerR(value) => { + let max = TRIGG_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + event::TriggerEvent::MouseWheel(value) => InputValue::Vector2 { + x: None, + y: Some(value.value as f64), + }, + event::TriggerEvent::RpadForce(value) => { + let max = PAD_FORCE_MAX; + InputValue::Float(normalize_unsigned_value(value.value as f64, max)) + } + } +} +/// List of all capabilities that the Legion Go driver implements +pub const CAPABILITIES: &[Capability] = &[ + Capability::Accelerometer(Source::Center), + Capability::Accelerometer(Source::Left), + Capability::Accelerometer(Source::Right), + 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::Keyboard)), + 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::LeftStickTouch)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess)), + Capability::Gamepad(Gamepad::Button(GamepadButton::QuickAccess2)), + 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::RightStickTouch)), + 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::RightTouchpadForce)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + Capability::Gyroscope(Source::Center), + Capability::Gyroscope(Source::Left), + Capability::Gyroscope(Source::Right), + Capability::Mouse(Mouse::Wheel), + Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), + Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), +];