Skip to content

Commit

Permalink
refactor(TargetDevice): add target device traits
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Jul 26, 2024
1 parent de268e5 commit 8b61bcb
Show file tree
Hide file tree
Showing 8 changed files with 3,360 additions and 70 deletions.
44 changes: 44 additions & 0 deletions src/input/event/native.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::{Duration, Instant};

use evdev::AbsoluteAxisCode;

use crate::input::capability::{Capability, Gamepad, GamepadButton};
Expand Down Expand Up @@ -130,3 +132,45 @@ impl From<EvdevEvent> for NativeEvent {
}
}
}

impl From<ScheduledNativeEvent> for NativeEvent {
fn from(value: ScheduledNativeEvent) -> Self {
value.event
}
}

/// A scheduled event represents an input event that should be sent sometime in
/// the future.
#[derive(Debug, Clone)]
pub struct ScheduledNativeEvent {
event: NativeEvent,
scheduled_time: Instant,
wait_time: Duration,
}

impl ScheduledNativeEvent {
/// Create a new scheduled event with the given time to wait before being
/// emitted.
pub fn new(event: NativeEvent, wait_time: Duration) -> Self {
Self {
event,
scheduled_time: Instant::now(),
wait_time,
}
}

/// Create a new scheduled event with the given timestamp and wait time before
/// being emitted
pub fn new_with_time(event: NativeEvent, timestamp: Instant, wait_time: Duration) -> Self {
Self {
event,
scheduled_time: timestamp,
wait_time,
}
}

/// Returns true when the scheduled event is ready to be emitted
pub fn is_ready(&self) -> bool {
self.scheduled_time.elapsed() > self.wait_time
}
}
11 changes: 4 additions & 7 deletions src/input/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use crate::input::target::touchscreen::TouchscreenDevice;
use crate::input::target::xb360::XBox360Controller;
use crate::input::target::xbox_elite::XboxEliteController;
use crate::input::target::xbox_series::XboxSeriesController;
use crate::input::target::TargetDeviceType;
use crate::input::target::TargetDevice;
use crate::input::target::TargetDeviceTypeId;
use crate::udev;
use crate::udev::device::UdevDevice;
Expand Down Expand Up @@ -356,17 +356,14 @@ impl Manager {
}

/// Create target input device to emulate based on the given device type.
async fn create_target_device(
&mut self,
kind: &str,
) -> Result<TargetDeviceType, Box<dyn Error>> {
async fn create_target_device(&mut self, kind: &str) -> Result<TargetDevice, Box<dyn Error>> {
log::trace!("Creating target device: {kind}");
let Ok(target_id) = TargetDeviceTypeId::try_from(kind) else {
return Err("Invalid target device ID".to_string().into());
};

// Create the target device to emulate based on the kind
let device = TargetDeviceType::from_type_id(target_id, self.dbus.clone());
let device = TargetDevice::from_type_id(target_id, self.dbus.clone());

Ok(device)
}
Expand All @@ -375,7 +372,7 @@ impl Manager {
/// to send events to the given targets.
async fn start_target_devices(
&mut self,
targets: Vec<TargetDeviceType>,
targets: Vec<TargetDevice>,
) -> Result<HashMap<String, TargetDeviceClient>, Box<dyn Error>> {
let mut target_devices = HashMap::new();
for mut target in targets {
Expand Down
287 changes: 287 additions & 0 deletions src/input/target/dbus_new.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
use std::error::Error;

use zbus::blocking::Connection;

use crate::{
dbus::interface::target::dbus::TargetDBusInterface,
input::{
capability::{Capability, Gamepad},
event::{
dbus::{Action, DBusEvent},
native::NativeEvent,
value::InputValue,
},
},
};

use super::{client::TargetDeviceClient, TargetInputDevice, TargetOutputDevice};

/// The threshold for axis inputs to be considered "pressed"
const AXIS_THRESHOLD: f64 = 0.35;

/// The internal emulated device state for tracking analog input
#[derive(Debug, Clone, Default)]
struct State {
pressed_left: bool,
pressed_right: bool,
pressed_up: bool,
pressed_down: bool,
}

/// The [DBusDevice] is a virtual input device that can emit input events. It
/// is primarily used when a [CompositeDevice] is using input interception to
/// divert inputs to an overlay over DBus.
#[derive(Debug)]
pub struct DBusDevice {
state: State,
conn: Connection,
dbus_path: Option<String>,
}

impl DBusDevice {
// Create a new [DBusDevice] instance.
pub fn new(conn: Connection) -> Self {
Self {
state: State::default(),
conn,
dbus_path: None,
}
}

/// Translate the given native event into one or more dbus events
fn translate_event(&mut self, event: NativeEvent) -> Vec<DBusEvent> {
// Check to see if this is an axis event, which requires special
// handling.
let source_cap = event.as_capability();
let is_axis_event = match source_cap {
Capability::Gamepad(gamepad) => matches!(gamepad, Gamepad::Axis(_)),
_ => false,
};

let mut translated = vec![];
let events = DBusEvent::from_native_event(event);
for mut event in events {
if !is_axis_event {
translated.push(event);
continue;
}

// Axis input is a special case, where we need to keep track of the
// current state of the axis, and only emit events whenever the axis
// passes or falls below the defined threshold.
let include_event = match event.action {
Action::Left => {
if self.state.pressed_left && event.as_f64() < AXIS_THRESHOLD {
event.value = InputValue::Float(0.0);
self.state.pressed_left = false;
true
} else if !self.state.pressed_left && event.as_f64() > AXIS_THRESHOLD {
event.value = InputValue::Float(1.0);
self.state.pressed_left = true;
true
} else {
false
}
}
Action::Right => {
if self.state.pressed_right && event.as_f64() < AXIS_THRESHOLD {
event.value = InputValue::Float(0.0);
self.state.pressed_right = false;
true
} else if !self.state.pressed_right && event.as_f64() > AXIS_THRESHOLD {
event.value = InputValue::Float(1.0);
self.state.pressed_right = true;
true
} else {
false
}
}
Action::Up => {
if self.state.pressed_up && event.as_f64() < AXIS_THRESHOLD {
event.value = InputValue::Float(0.0);
self.state.pressed_up = false;
true
} else if !self.state.pressed_up && event.as_f64() > AXIS_THRESHOLD {
event.value = InputValue::Float(1.0);
self.state.pressed_up = true;
true
} else {
false
}
}
Action::Down => {
if self.state.pressed_down && event.as_f64() < AXIS_THRESHOLD {
event.value = InputValue::Float(0.0);
self.state.pressed_down = false;
true
} else if !self.state.pressed_down && event.as_f64() > AXIS_THRESHOLD {
event.value = InputValue::Float(1.0);
self.state.pressed_down = true;
true
} else {
false
}
}
_ => true,
};

if include_event {
translated.push(event);
}
}

translated
}

/// Writes the given event to DBus
fn write_dbus_event(&self, event: DBusEvent) -> Result<(), Box<dyn Error>> {
// Only send valid events
let valid = !matches!(event.action, Action::None);
if !valid {
return Ok(());
}

// DBus events can only be written if there is a DBus path reference.
let Some(path) = self.dbus_path.clone() else {
return Err("No dbus path exists to send events to".into());
};

let conn = self.conn.clone();
// Get the object instance at the given path so we can send DBus signal
// updates
let iface_ref = conn
.object_server()
.interface::<_, TargetDBusInterface>(path.as_str())?;

// Send the input event signal based on the type of value
tokio::task::spawn(async move {
let result = match event.value {
InputValue::Bool(value) => {
let value = match value {
true => 1.0,
false => 0.0,
};
TargetDBusInterface::input_event(
iface_ref.signal_context(),
event.action.as_string(),
value,
)
.await
}
InputValue::Float(value) => {
TargetDBusInterface::input_event(
iface_ref.signal_context(),
event.action.as_string(),
value,
)
.await
}
InputValue::Touch {
index,
is_touching,
pressure,
x,
y,
} => {
// Send the input event signal
TargetDBusInterface::touch_event(
iface_ref.signal_context(),
event.action.as_string(),
index as u32,
is_touching,
pressure.unwrap_or(1.0),
x.unwrap_or(0.0),
y.unwrap_or(0.0),
)
.await
}
_ => Ok(()),
};
if let Err(e) = result {
log::error!("Failed to send event: {e:?}");
}
});

Ok(())
}
}

impl TargetInputDevice for DBusDevice {
fn start_dbus_interface(
&mut self,
dbus: Connection,
path: String,
_client: TargetDeviceClient,
) {
log::debug!("Starting dbus interface: {path}");
let iface = TargetDBusInterface::new();
if let Err(e) = dbus.object_server().at(path.clone(), iface) {
log::debug!("Failed to start dbus interface {path}: {e:?}");
} else {
log::debug!("Started dbus interface on {path}");
self.dbus_path = Some(path);
};
}

fn write_event(
&mut self,
event: crate::input::event::native::NativeEvent,
) -> Result<(), super::InputError> {
log::trace!("Got event to emit: {:?}", event);
let dbus_events = self.translate_event(event);
for dbus_event in dbus_events {
log::trace!("Writing DBus event: {dbus_event:?}");
self.write_dbus_event(dbus_event)?;
}

Ok(())
}

fn get_capabilities(
&self,
) -> Result<Vec<crate::input::capability::Capability>, super::InputError> {
let capabilities = vec![
Capability::DBus(Action::Guide),
Capability::DBus(Action::Quick),
Capability::DBus(Action::Quick2),
Capability::DBus(Action::Context),
Capability::DBus(Action::Option),
Capability::DBus(Action::Select),
Capability::DBus(Action::Accept),
Capability::DBus(Action::Back),
Capability::DBus(Action::ActOn),
Capability::DBus(Action::Left),
Capability::DBus(Action::Right),
Capability::DBus(Action::Up),
Capability::DBus(Action::Down),
Capability::DBus(Action::L1),
Capability::DBus(Action::L2),
Capability::DBus(Action::L3),
Capability::DBus(Action::R1),
Capability::DBus(Action::R2),
Capability::DBus(Action::R3),
Capability::DBus(Action::VolumeUp),
Capability::DBus(Action::VolumeDown),
Capability::DBus(Action::VolumeMute),
Capability::DBus(Action::Keyboard),
Capability::DBus(Action::Screenshot),
Capability::DBus(Action::Touch),
];

Ok(capabilities)
}

fn stop_dbus_interface(&mut self, dbus: Connection, path: String) {
log::debug!("Stopping dbus interface for {path}");
let result = dbus
.object_server()
.remove::<TargetDBusInterface, String>(path.clone());
if let Err(e) = result {
log::error!("Failed to stop dbus interface {path}: {e:?}");
} else {
log::debug!("Stopped dbus interface for {path}");
};
}
}

impl TargetOutputDevice for DBusDevice {}
Loading

0 comments on commit 8b61bcb

Please sign in to comment.