diff --git a/src/drivers/opineo/driver.rs b/src/drivers/opineo/driver.rs index 29c944ef..9af924a5 100644 --- a/src/drivers/opineo/driver.rs +++ b/src/drivers/opineo/driver.rs @@ -7,7 +7,10 @@ use std::{ use hidapi::HidDevice; use packed_struct::{types::SizedInteger, PackedStruct}; -use crate::udev::device::UdevDevice; +use crate::{ + drivers::opineo::event::{TriggerEvent, TriggerInput}, + udev::device::UdevDevice, +}; use super::{ event::{BinaryInput, Event, TouchAxisEvent, TouchButtonEvent}, @@ -32,18 +35,24 @@ const HID_TIMEOUT: i32 = 10; // Input report axis ranges pub const PAD_X_MAX: f64 = 512.0; pub const PAD_Y_MAX: f64 = 512.0; +pub const PAD_FORCE_MAX: f64 = 127.0; +pub const PAD_FORCE_NORMAL: u8 = 32; /* Simulated average */ + +const CLICK_DELAY: Duration = Duration::from_millis(75); pub struct Driver { /// HIDRAW device instance device: HidDevice, + /// 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, - /// Whether or not we are currently holding a tap-to-click. - is_tapped: bool, - /// Timestamp of the last touch event. Used to track if the touch has ended. + /// Timestamp of the last touch event. last_touch: Instant, - /// Timestamp of the first touch event. Used to detect tap-to-click events - first_touch: Instant, + /// Whether or not a touch event was started that hasn't been cleared. + touch_started: bool, /// State for the touchpad device touchpad_state: Option, } @@ -62,9 +71,10 @@ impl Driver { Ok(Self { device, first_touch: Instant::now(), - is_tapped: false, + is_clicked: false, is_touching: false, last_touch: Instant::now(), + touch_started: false, touchpad_state: None, }) } @@ -77,8 +87,6 @@ impl Driver { 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 { TOUCH_DATA => { @@ -92,35 +100,43 @@ impl Driver { self.handle_touchinput_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 self.is_touching { + if self.last_touch.elapsed() >= Duration::from_millis(4) { + self.is_touching = false; + } + return Ok(events); + } + + // Check for quick click conditions + if self.touch_started && self.first_touch.elapsed() <= CLICK_DELAY * 2 { + // For double clicking, ensure the previous click is cleared. + if self.is_clicked { + let mut new_events = self.release_click(); + events.append(&mut new_events); } + + let mut new_events = self.start_click(); + events.append(&mut new_events); + + return Ok(events); } - // 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(); + // Check for release conditions + if self.touch_started && self.last_touch.elapsed() > CLICK_DELAY / 2 { + let event: Event = self.release_touch(); events.push(event); + + // If we did a click event, see if we should release it. Accounts for click and drag. + if self.is_clicked { + let mut new_events = self.release_click(); + events.append(&mut new_events); + return Ok(events); + } } Ok(events) @@ -172,8 +188,16 @@ impl Driver { //// Axis events if !self.is_touching { self.is_touching = true; - self.first_touch = Instant::now(); - log::trace!("Started TOUCH event"); + // Check for click events + if self.touch_started && self.last_touch.elapsed() <= CLICK_DELAY / 3 { + let mut new_events = self.start_click(); + events.append(&mut new_events); + } + if !self.touch_started { + self.touch_started = true; + self.first_touch = Instant::now(); + log::trace!("Started TOUCH event"); + } } events.push(Event::TouchAxis(TouchAxisEvent { index: 0, @@ -188,7 +212,7 @@ impl Driver { fn release_touch(&mut self) -> Event { log::trace!("Released TOUCH event."); - self.is_touching = false; + self.touch_started = false; Event::TouchAxis(TouchAxisEvent { index: 0, is_touching: false, @@ -197,15 +221,38 @@ impl Driver { }) } - fn start_tap(&mut self) -> Event { + fn start_click(&mut self) -> Vec { log::trace!("Started CLICK event."); - self.is_tapped = true; - Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: true })) + 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::PadForce(TriggerInput { + value: PAD_FORCE_NORMAL, + })); + events.push(event); + events } - fn release_tap(&mut self) -> Event { + fn release_click(&mut self) -> Vec { log::trace!("Released CLICK event."); - self.is_tapped = false; - Event::TouchButton(TouchButtonEvent::Left(BinaryInput { pressed: false })) + log::trace!("First touch elapsed: {:?}", self.first_touch.elapsed()); + log::trace!("Last touch elapsed: {:?}", self.last_touch.elapsed()); + self.is_clicked = 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::PadForce(TriggerInput { value: 0 })); + events.push(event); + events } } diff --git a/src/drivers/opineo/event.rs b/src/drivers/opineo/event.rs index 6fbb179c..d8761349 100644 --- a/src/drivers/opineo/event.rs +++ b/src/drivers/opineo/event.rs @@ -3,6 +3,7 @@ pub enum Event { TouchAxis(TouchAxisEvent), TouchButton(TouchButtonEvent), + Trigger(TriggerEvent), } /// Axis input contain (x, y) coordinates @@ -20,9 +21,21 @@ pub struct BinaryInput { pub pressed: bool, } +/// Trigger input contains non-negative integers +#[derive(Clone, Debug)] +pub struct TriggerInput { + pub value: u8, +} + /// Button events represend binary inputs #[derive(Clone, Debug)] pub enum TouchButtonEvent { /// Tap to click button Left(BinaryInput), } + +/// Trigger events contain values indicating how far a trigger is pulled +#[derive(Clone, Debug)] +pub enum TriggerEvent { + PadForce(TriggerInput), +} diff --git a/src/input/source/hidraw/opineo.rs b/src/input/source/hidraw/opineo.rs index 714752f3..bf98255d 100644 --- a/src/input/source/hidraw/opineo.rs +++ b/src/input/source/hidraw/opineo.rs @@ -2,13 +2,13 @@ use std::{error::Error, fmt::Debug}; use crate::{ drivers::opineo::{ - driver::{self, Driver, LPAD_NAMES, RPAD_NAMES}, + driver::{self, Driver, LPAD_NAMES, PAD_FORCE_MAX, RPAD_NAMES}, event, }, input::{ - capability::{Capability, Touch, TouchButton, Touchpad}, + capability::{Capability, Gamepad, GamepadTrigger, Touch, TouchButton, Touchpad}, event::{native::NativeEvent, value::InputValue}, - output_capability::{OutputCapability, LED}, + output_capability::OutputCapability, source::{InputError, OutputError, SourceInputDevice, SourceOutputDevice}, }, udev::device::UdevDevice, @@ -120,6 +120,16 @@ fn normalize_axis_value(event: event::TouchAxisEvent) -> InputValue { } } +/// Normalize the trigger value to something between 0.0 and 1.0 based on the +/// Orange Pi's maximum axis ranges. +fn normalize_trigger_value(event: event::TriggerEvent) -> InputValue { + match event { + event::TriggerEvent::PadForce(value) => { + InputValue::Float(normalize_unsigned_value(value.value as f64, PAD_FORCE_MAX)) + } + } +} + /// Translate the given OrangePi NEO events into native events fn translate_events(events: Vec, touchpad_side: TouchpadSide) -> Vec { let mut translated = Vec::with_capacity(events.len()); @@ -145,8 +155,6 @@ fn translate_event(event: event::Event, touchpad_side: TouchpadSide) -> NativeEv normalize_axis_value(axis), ), }, - // TODO: Consider making a [TouchButton::Tap] event so we can do more events with touchpads - // that have physical buttons (e.g. Steam Deck). event::Event::TouchButton(button) => match button { event::TouchButtonEvent::Left(value) => match touchpad_side { TouchpadSide::Unknown => { @@ -162,18 +170,33 @@ fn translate_event(event: event::Event, touchpad_side: TouchpadSide) -> NativeEv ), }, }, + event::Event::Trigger(trigg) => match trigg.clone() { + event::TriggerEvent::PadForce(_) => match touchpad_side { + TouchpadSide::Unknown => { + NativeEvent::new(Capability::NotImplemented, InputValue::Bool(false)) + } + TouchpadSide::Left => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTouchpadForce)), + normalize_trigger_value(trigg), + ), + TouchpadSide::Right => NativeEvent::new( + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTouchpadForce)), + normalize_trigger_value(trigg), + ), + }, + }, } } -/// List of all capabilities that the OrangePi NEO driver implements +/// List of all input capabilities that the OrangePi NEO driver implements pub const CAPABILITIES: &[Capability] = &[ + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTouchpadForce)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTouchpadForce)), Capability::Touchpad(Touchpad::LeftPad(Touch::Button(TouchButton::Press))), Capability::Touchpad(Touchpad::LeftPad(Touch::Motion)), Capability::Touchpad(Touchpad::RightPad(Touch::Button(TouchButton::Press))), Capability::Touchpad(Touchpad::RightPad(Touch::Motion)), ]; -pub const OUTPUT_CAPABILITIES: &[OutputCapability] = &[ - OutputCapability::ForceFeedback, - OutputCapability::LED(LED::Color), -]; +/// List of all output capabilities that the OrangePi NEO supports +pub const OUTPUT_CAPABILITIES: &[OutputCapability] = &[OutputCapability::ForceFeedback];