-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(TargetDevice): add target device traits
- Loading branch information
1 parent
de268e5
commit b81ac3b
Showing
9 changed files
with
3,778 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
Oops, something went wrong.