diff --git a/Cargo.toml b/Cargo.toml
index 44ae3f59..68a033ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -119,6 +119,7 @@ hidapi = "2.6.4"
industrial-io = "0.6.1"
#evdev = { version = "0.12.1", features = ["tokio"] }
inotify = "0.11.0"
+libloading = "0.9.0"
# Omit trace logging for release builds
log = { version = "0.4.29", features = [
"max_level_trace",
diff --git a/bindings/dbus-xml/org.shadowblip.Input.Source.FastRPCDevice.xml b/bindings/dbus-xml/org.shadowblip.Input.Source.FastRPCDevice.xml
new file mode 100644
index 00000000..fb5546ac
--- /dev/null
+++ b/bindings/dbus-xml/org.shadowblip.Input.Source.FastRPCDevice.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json
index 1e66fe7a..0c70be4a 100644
--- a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json
+++ b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json
@@ -195,6 +195,9 @@
"evdev": {
"$ref": "#/definitions/Evdev"
},
+ "fastrpc": {
+ "$ref": "#/definitions/FastRPC"
+ },
"hidraw": {
"$ref": "#/definitions/Hidraw"
},
@@ -380,6 +383,20 @@
"required": [],
"title": "Evdev"
},
+ "FastRPC": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "description": "Name of the FastRPC device: /dev/fastrpc*",
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "title": "FastRPC"
+ },
"Hidraw": {
"type": "object",
"additionalProperties": false,
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 91af0b2d..c2be7278 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -231,6 +231,9 @@ pub struct SourceDevice {
/// Devices that match the given evdev properties will be captured by InputPlumber
#[serde(skip_serializing_if = "Option::is_none")]
pub evdev: Option,
+ /// Devices that match the given fastrpc properties will be captured by InputPlumber
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub fastrpc: Option,
/// Devices that match the given hidraw properties will be captured by InputPlumber
#[serde(skip_serializing_if = "Option::is_none")]
pub hidraw: Option,
@@ -403,6 +406,15 @@ pub struct IIO {
pub mount_matrix: Option,
}
+#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub struct FastRpc {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub id: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
+}
+
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::upper_case_acronyms)]
@@ -440,6 +452,16 @@ pub struct MountMatrix {
pub z: [f64; 3],
}
+impl Default for MountMatrix {
+ fn default() -> Self {
+ MountMatrix {
+ x: [1.0, 0.0, 0.0],
+ y: [0.0, 1.0, 0.0],
+ z: [0.0, 0.0, 1.0],
+ }
+ }
+}
+
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct EventsConfig {
@@ -579,6 +601,12 @@ impl CompositeDeviceConfig {
return Some(config.clone());
}
}
+ "fastrpc" => {
+ let fastrpc_config = config.fastrpc.as_ref()?;
+ if self.has_matching_fastrpc(udevice, fastrpc_config) {
+ return Some(config.clone());
+ }
+ }
_ => (),
}
@@ -841,6 +869,35 @@ impl CompositeDeviceConfig {
true
}
+ /// Returns true if a given FastRPC device is within a list of fastrpc configs.
+ pub fn has_matching_fastrpc(&self, device: &UdevDevice, fastrpc_config: &FastRpc) -> bool {
+ log::trace!(
+ "Checking FastRPC config: {:?} against {:?}",
+ fastrpc_config,
+ device
+ );
+
+ if let Some(id) = fastrpc_config.id.as_ref() {
+ let dsyspath = device.syspath();
+ log::trace!("Checking id: {id} against {dsyspath}");
+ if !glob_match(id.as_str(), dsyspath.as_str()) {
+ return false;
+ }
+ }
+
+ if let Some(name) = fastrpc_config.name.as_ref() {
+ // FastRPC devices don't seem to have a name (or it's empty)
+ // The sysname works fine
+ let dname = device.sysname();
+ log::trace!("Checking sysname: {name} against {dname}");
+ if !glob_match(name.as_str(), dname.as_str()) {
+ return false;
+ }
+ }
+
+ true
+ }
+
/// Returns true if a given evdev device is within a list of evdev configs.
pub fn has_matching_evdev(&self, device: &UdevDevice, evdev_config: &Evdev) -> bool {
//TODO: Check if the evdev has no proterties defined, that would always match.
diff --git a/src/dbus/interface/source/fastrpc.rs b/src/dbus/interface/source/fastrpc.rs
new file mode 100644
index 00000000..de1df5f2
--- /dev/null
+++ b/src/dbus/interface/source/fastrpc.rs
@@ -0,0 +1,33 @@
+use crate::{
+ dbus::{interface::Unregisterable, polkit::check_polkit},
+ udev::device::UdevDevice,
+};
+use zbus::{fdo, message::Header, Connection};
+use zbus_macros::interface;
+
+/// DBusInterface exposing information about a FastRPC device
+pub struct SourceFastRpcInterface {
+ device: UdevDevice,
+}
+
+impl SourceFastRpcInterface {
+ pub fn new(device: UdevDevice) -> SourceFastRpcInterface {
+ SourceFastRpcInterface { device }
+ }
+}
+
+#[interface(name = "org.shadowblip.Input.Source.FastRPCDevice")]
+impl SourceFastRpcInterface {
+ /// Returns the human readable name of the device (e.g. XBox 360 Pad)
+ #[zbus(property)]
+ async fn id(
+ &self,
+ #[zbus(connection)] conn: &Connection,
+ #[zbus(header)] hdr: Option>,
+ ) -> fdo::Result {
+ check_polkit(conn, hdr, "org.shadowblip.Input.Source.FastRPCDevice.Id").await?;
+ Ok(self.device.sysname())
+ }
+}
+
+impl Unregisterable for SourceFastRpcInterface {}
diff --git a/src/dbus/interface/source/mod.rs b/src/dbus/interface/source/mod.rs
index e384dbdb..952cb615 100644
--- a/src/dbus/interface/source/mod.rs
+++ b/src/dbus/interface/source/mod.rs
@@ -1,4 +1,5 @@
pub mod evdev;
+pub mod fastrpc;
pub mod hidraw;
pub mod iio_imu;
pub mod led;
diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs
index 4f058b3d..14e9b20f 100644
--- a/src/drivers/mod.rs
+++ b/src/drivers/mod.rs
@@ -10,6 +10,7 @@ pub mod opineo;
pub mod oxp_hid;
pub mod oxp_tty;
pub mod rog_ally;
+pub mod ssc;
pub mod steam_deck;
pub mod ultimate2_wireless;
pub mod unified_gamepad;
diff --git a/src/drivers/ssc/bindings.rs b/src/drivers/ssc/bindings.rs
new file mode 100644
index 00000000..ff935137
--- /dev/null
+++ b/src/drivers/ssc/bindings.rs
@@ -0,0 +1,210 @@
+use libloading::Library;
+use std::error::Error;
+
+#[allow(non_camel_case_types)]
+pub type gboolean = std::ffi::c_int;
+pub const GFALSE: std::ffi::c_int = 0;
+
+#[allow(non_camel_case_types)]
+pub type gpointer = *mut std::ffi::c_void;
+
+pub type GObject = std::ffi::c_void;
+
+/// Opaque data type representing a set of sources to be handled in a main loop.
+/// https://docs.gtk.org/glib/struct.MainContext.html
+pub type GMainContext = std::ffi::c_void;
+
+/// Connection flags used to specify the behaviour of a signal’s connection.
+/// https://docs.gtk.org/gobject/flags.ConnectFlags.html
+pub type GConnectFlags = std::ffi::c_uint;
+
+/// Allows operations to be cancelled.
+/// https://docs.gtk.org/gio/class.Cancellable.html
+pub type GCancellable = std::ffi::c_void;
+
+/// Non-zero integer which uniquely identifies a particular string.
+/// https://docs.gtk.org/glib/alias.Quark.html
+pub type GQuark = std::ffi::c_uint;
+
+/// Contains information about an error that has occurred.
+/// https://docs.gtk.org/glib/struct.Error.html
+#[repr(C)]
+pub struct GError {
+ pub domain: GQuark,
+ pub code: i32,
+ pub message: *mut std::ffi::c_char,
+}
+
+/// Represents a callback supplied by the programmer.
+/// https://docs.gtk.org/gobject/struct.Closure.html
+#[repr(C)]
+pub struct GClosure {
+ pub ref_count: u32,
+ _truncated_record_marker: std::ffi::c_void,
+}
+
+/// The type used for the various notification callbacks which can be registered on closures.
+/// https://docs.gtk.org/gobject/callback.ClosureNotify.html
+pub type GClosureNotify = Option;
+
+/// The type used for callback functions in structure definitions and function signatures.
+/// https://docs.gtk.org/gobject/callback.Callback.html
+pub type GCallback = Option;
+
+/* GLib functions */
+/// g_main_context_iteration:
+/// Runs a single iteration for the given main loop.
+/// https://docs.gtk.org/glib/method.MainContext.iteration.html
+pub type FnGMainContextIteration =
+ unsafe extern "C" fn(context: *mut GMainContext, may_block: gboolean) -> gboolean;
+
+/// g_quark_to_string:
+/// Gets the string associated with the given GQuark.
+/// https://docs.gtk.org/glib/func.quark_to_string.html
+pub type FnGQuarkToString = unsafe extern "C" fn(quark: GQuark) -> *const std::ffi::c_char;
+
+/* GObject functions */
+/// g_object_unref:
+/// Decreases the reference count of the provided object.
+/// https://docs.gtk.org/gobject/method.Object.unref.html
+pub type FnGObjectUnref = unsafe extern "C" fn(object: *mut GObject);
+
+/// g_signal_connect_data:
+/// Connects a GCallback function to a signal for a particular object.
+/// This function cannot fail. If the given signal name doesn’t exist, a critical warning is emitted.
+/// https://docs.gtk.org/gobject/func.signal_connect_data.html
+pub type FnGSignalConnectData = unsafe extern "C" fn(
+ instance: *mut GObject,
+ // A string of the form “signal-name::detail”
+ detailed_signal: *const std::ffi::c_char,
+ c_handler: GCallback,
+
+ // Data to pass to c_handler calls.
+ data: gpointer,
+ destroy_data: GClosureNotify,
+ connect_flags: GConnectFlags,
+);
+
+/* Gio functions */
+/// g_cancellable_new:
+/// Creates a new GCancellable object.
+/// Applications that want to start one or more operations that should be cancellable should create a GCancellable and pass it to the operations.
+/// https://docs.gtk.org/gio/ctor.Cancellable.new.html
+pub type FnGCancellableNew = unsafe extern "C" fn() -> *mut GCancellable;
+
+/// g_cancellable_cancel:
+/// Will set cancellable to cancelled, and will emit the GCancellable::cancelled signal. Thread safe.
+/// https://docs.gtk.org/gio/method.Cancellable.cancel.html
+pub type FnGCancellableCancel = unsafe extern "C" fn(cancellable: *mut GCancellable);
+
+/* SSC functions */
+/// ssc_sensor_*_new_sync:
+/// This constructs a sensor object and returns a pointer to it.
+/// As the signature is the same between ssc_sensor_gyroscope_new_sync & ssc_sensor_accelerometer_new_sync,
+/// we reuse this for both.
+pub type FnSscSensorNewSync = unsafe extern "C" fn(
+ cancellable: *mut GCancellable,
+ error: *mut *mut GError,
+) -> *mut std::ffi::c_void;
+
+/// ssc_sensor_*_open_sync:
+/// Takes a sensor object and opens / starts communications with it.
+/// This is a blocking operation.
+pub type FnSscSensorOpenSync = unsafe extern "C" fn(
+ sensor: *mut GObject,
+ cancellable: *mut GCancellable,
+ error: *mut *mut GError,
+) -> u32;
+
+/// Holds handles to the symbols in libssc and its dependencies.
+/// This should stay thin (just bindings), keep the helper stuff elsewhere (like runtime.rs)
+/// In the future this file could be replaced using bindgen's dylib support.
+pub struct SscDylibs {
+ _glib: Library,
+ _gio: Library,
+ _gobject: Library,
+ _ssc: Library,
+
+ /* GLib */
+ pub g_main_context_iteration: FnGMainContextIteration,
+ pub g_quark_to_string: FnGQuarkToString,
+
+ /* Gio */
+ pub g_cancellable_new: FnGCancellableNew,
+ pub g_cancellable_cancel: FnGCancellableCancel,
+
+ /* GObject */
+ pub g_object_unref: FnGObjectUnref,
+ pub g_signal_connect_data: FnGSignalConnectData,
+
+ /* SSC */
+ pub ssc_sensor_gyroscope_new_sync: FnSscSensorNewSync,
+ pub ssc_sensor_gyroscope_open_sync: FnSscSensorOpenSync,
+ pub ssc_sensor_accelerometer_new_sync: FnSscSensorNewSync,
+ pub ssc_sensor_accelerometer_open_sync: FnSscSensorOpenSync,
+}
+
+/// Helper function for Library::new to return a nice error
+#[macro_export]
+macro_rules! load_library {
+ ( $library_name:expr ) => {
+ Library::new($library_name)
+ .map_err(|e| format!("Library {} not found: {}", $library_name, e))
+ };
+}
+
+/// Helper function for Library.get to return a nice error
+#[macro_export]
+macro_rules! load_symbol {
+ ( $library:expr, $symbol_name:expr ) => {
+ $library
+ .get($symbol_name)
+ .map_err(|e| format!("Symbol {} not found: {}", $symbol_name, e))
+ };
+}
+
+impl SscDylibs {
+ /// Load symbols dynamically from libssc and its dependencies
+ pub unsafe fn load() -> Result> {
+ // SAFETY: Library::new (from libloading) is "safe" to use as it returns an error, but dynamic library
+ // loading can be unsafe in general. We handle possible errors here but we can't do much else.
+ let glib = unsafe { load_library!("libglib-2.0.so.0")? };
+ let gio = unsafe { load_library!("libgio-2.0.so.0")? };
+ let gobject = unsafe { load_library!("libgobject-2.0.so.0")? };
+ let ssc = unsafe { load_library!("libssc.so")? };
+
+ // SAFETY: Handles possible errors when a symbol doesn't exist, so this is safe as long as our function &
+ // type definitions are correct. The symbols should always be valid as we also keep references to the library handles.
+ unsafe {
+ Ok(Self {
+ g_main_context_iteration: *load_symbol!(glib, "g_main_context_iteration")?,
+ g_quark_to_string: *load_symbol!(glib, "g_quark_to_string")?,
+
+ g_cancellable_new: *load_symbol!(gio, "g_cancellable_new")?,
+ g_cancellable_cancel: *load_symbol!(gio, "g_cancellable_cancel")?,
+
+ g_object_unref: *load_symbol!(gobject, "g_object_unref")?,
+ g_signal_connect_data: *load_symbol!(gobject, "g_signal_connect_data")?,
+
+ ssc_sensor_gyroscope_new_sync: *load_symbol!(ssc, "ssc_sensor_gyroscope_new_sync")?,
+ ssc_sensor_gyroscope_open_sync: *load_symbol!(
+ ssc,
+ "ssc_sensor_gyroscope_open_sync"
+ )?,
+ ssc_sensor_accelerometer_new_sync: *load_symbol!(
+ ssc,
+ "ssc_sensor_accelerometer_new_sync"
+ )?,
+ ssc_sensor_accelerometer_open_sync: *load_symbol!(
+ ssc,
+ "ssc_sensor_accelerometer_open_sync"
+ )?,
+
+ _glib: glib,
+ _gio: gio,
+ _gobject: gobject,
+ _ssc: ssc,
+ })
+ }
+ }
+}
diff --git a/src/drivers/ssc/driver.rs b/src/drivers/ssc/driver.rs
new file mode 100644
index 00000000..49bacada
--- /dev/null
+++ b/src/drivers/ssc/driver.rs
@@ -0,0 +1,138 @@
+use std::collections::HashSet;
+use std::error::Error;
+use std::sync::Arc;
+use tokio::{sync::mpsc, sync::mpsc::error::TryRecvError};
+
+use crate::config::MountMatrix;
+use crate::input::capability::{Capability, Source};
+
+use super::event::{AxisData, Event};
+use super::runtime::{SscObject, SscRuntime};
+
+pub struct Driver {
+ runtime: Arc,
+ _gyroscope: SscObject,
+ _accelerometer: SscObject,
+
+ // Event handling / polling
+ rx: mpsc::Receiver,
+ filtered_events: HashSet,
+
+ mount_matrix: MountMatrix,
+}
+
+impl Driver {
+ pub fn new(mount_matrix: Option) -> Result> {
+ let runtime = SscRuntime::load()?;
+ let (tx, rx) = mpsc::channel::(1024);
+
+ let gyroscope = runtime.clone().create_gyroscope()?;
+ let gyroscope_tx = tx.clone();
+ gyroscope.set_measurement_handler(move |x, y, z| {
+ _ = gyroscope_tx.try_send(Event::Gyro(AxisData {
+ roll: x as f64,
+ pitch: y as f64,
+ yaw: z as f64,
+ }));
+ });
+
+ let accelerometer = runtime.clone().create_accelerometer()?;
+ let accelerometer_tx = tx.clone();
+ accelerometer.set_measurement_handler(move |x, y, z| {
+ _ = accelerometer_tx.try_send(Event::Accelerometer(AxisData {
+ roll: x as f64,
+ pitch: y as f64,
+ yaw: z as f64,
+ }));
+ });
+
+ Ok(Self {
+ runtime,
+ _gyroscope: gyroscope,
+ _accelerometer: accelerometer,
+ rx,
+ filtered_events: Default::default(),
+ mount_matrix: mount_matrix.unwrap_or_default(),
+ })
+ }
+
+ pub fn update_filtered_events(&mut self, events: HashSet) {
+ self.filtered_events = events;
+ }
+
+ pub fn get_default_event_filter(
+ &self,
+ ) -> Result, Box> {
+ Ok(HashSet::new())
+ }
+
+ /// Poll the device for data
+ pub fn poll(&mut self) -> Result, Box> {
+ // libssc uses GLib and relies on the GLib main loop
+ // Instead of creating a main loop and main context then managing it alongside InputPlumber's loop etc,
+ // we can just perform an iteration of the GLib context every poll
+ self.runtime.iterate_glib_main_loop();
+
+ let mut events: Vec = vec![];
+
+ loop {
+ match self.rx.try_recv() {
+ Ok(Event::Gyro(mut event)) => {
+ if self
+ .filtered_events
+ .contains(&Capability::Gyroscope(Source::Center))
+ {
+ continue;
+ }
+
+ self.rotate_value(&mut event);
+
+ events.push(Event::Gyro(event));
+ }
+
+ Ok(Event::Accelerometer(mut event)) => {
+ if self
+ .filtered_events
+ .contains(&Capability::Accelerometer(Source::Center))
+ {
+ continue;
+ }
+
+ self.rotate_value(&mut event);
+
+ events.push(Event::Accelerometer(event));
+ }
+
+ Err(TryRecvError::Empty) => break,
+ Err(error) => return Err(format!("Error when handling SSC events: {error}").into()),
+ }
+ }
+
+ Ok(events)
+ }
+
+ /// Rotate the given axis data according to the mount matrix. This is used
+ /// to calculate the final value according to the sensor oritentation.
+ /// This is taken from iio_imu/driver.rs
+ // Values are intended to be multiplied as:
+ // x' = mxx * x + myx * y + mzx * z
+ // y' = mxy * x + myy * y + mzy * z
+ // z' = mxz * x + myz * y + mzz * z
+ fn rotate_value(&self, value: &mut AxisData) {
+ let x = value.roll;
+ let y = value.pitch;
+ let z = value.yaw;
+ let mxx = self.mount_matrix.x[0];
+ let myx = self.mount_matrix.x[1];
+ let mzx = self.mount_matrix.x[2];
+ let mxy = self.mount_matrix.y[0];
+ let myy = self.mount_matrix.y[1];
+ let mzy = self.mount_matrix.y[2];
+ let mxz = self.mount_matrix.z[0];
+ let myz = self.mount_matrix.z[1];
+ let mzz = self.mount_matrix.z[2];
+ value.roll = mxx * x + myx * y + mzx * z;
+ value.pitch = mxy * x + myy * y + mzy * z;
+ value.yaw = mxz * x + myz * y + mzz * z;
+ }
+}
diff --git a/src/drivers/ssc/event.rs b/src/drivers/ssc/event.rs
new file mode 100644
index 00000000..52b1f3bd
--- /dev/null
+++ b/src/drivers/ssc/event.rs
@@ -0,0 +1,18 @@
+/// Events that can be emitted by the SSC's "measurement" sensors
+#[derive(Clone, Debug)]
+pub enum Event {
+ /// Accelerometer events measure the acceleration in a particular direction
+ /// in units of meters per second. It is generally used to determine which
+ /// direction is "down" due to the accelerating force of gravity.
+ Accelerometer(AxisData),
+ /// Gyro events measure the angular velocity in rads per second.
+ Gyro(AxisData),
+}
+
+/// AxisData represents the state of the accelerometer or gyro (x, y, z) values
+#[derive(Clone, Debug, Default)]
+pub struct AxisData {
+ pub roll: f64,
+ pub pitch: f64,
+ pub yaw: f64,
+}
diff --git a/src/drivers/ssc/mod.rs b/src/drivers/ssc/mod.rs
new file mode 100644
index 00000000..e173c8d2
--- /dev/null
+++ b/src/drivers/ssc/mod.rs
@@ -0,0 +1,4 @@
+mod bindings;
+pub mod driver;
+pub mod event;
+mod runtime;
diff --git a/src/drivers/ssc/runtime.rs b/src/drivers/ssc/runtime.rs
new file mode 100644
index 00000000..5ccff219
--- /dev/null
+++ b/src/drivers/ssc/runtime.rs
@@ -0,0 +1,214 @@
+use std::{error::Error, ptr::null_mut, sync::Arc, time::Duration};
+
+use crate::drivers::ssc::bindings::{
+ gpointer, FnSscSensorNewSync, FnSscSensorOpenSync, GCallback, GCancellable, GClosure, GError,
+ GObject, SscDylibs, GFALSE,
+};
+
+pub type MeasurementHandlerCb = Box;
+
+/// "Trampoline" for the sensor measurement callback. Bounces to the function stored in the data ptr.
+pub unsafe extern "C" fn measurement_handler(
+ _obj: *mut GObject,
+ x: f32,
+ y: f32,
+ z: f32,
+ data: gpointer,
+) {
+ let cb: &mut Box =
+ unsafe { &mut *(data as *mut MeasurementHandlerCb) };
+ cb(x, y, z);
+}
+
+/// GClosureNotify that drops the boxed callback when the signal is destroyed.
+pub unsafe extern "C" fn closure_destroy_handler(data: gpointer, _: *mut GClosure) {
+ drop(unsafe { Box::from_raw(data as *mut Box) });
+}
+
+/// Wrapper for libssc sensor objects (mostly just a wrapper for GObject)
+pub struct SscObject {
+ _ptr: *mut std::ffi::c_void,
+ _runtime: Arc,
+}
+
+// SAFETY: The GLib functions we're using this with are thread safe.
+unsafe impl Send for SscObject {}
+
+impl Drop for SscObject {
+ fn drop(&mut self) {
+ // SAFETY: _ptr is always non-null and the library handle is kept alive by the _runtime reference.
+ unsafe {
+ (self._runtime.dylibs.g_object_unref)(self.ptr() as *mut _);
+ }
+ }
+}
+
+impl SscObject {
+ /// Create a SscObject instance. The provided pointer is required to be non-null.
+ fn new(ptr: *mut std::ffi::c_void, runtime: Arc) -> Result {
+ if ptr.is_null() {
+ Err(())
+ } else {
+ Ok(Self {
+ _ptr: ptr,
+ _runtime: runtime,
+ })
+ }
+ }
+
+ pub unsafe fn ptr(&self) -> *mut std::ffi::c_void {
+ self._ptr
+ }
+
+ pub fn set_measurement_handler(&self, callback: F) {
+ let boxed: Box = Box::new(Box::new(callback));
+ let user_data = Box::into_raw(boxed) as gpointer;
+
+ // SAFETY: The GObject pointer is guaranteed non-null and g_signal_connect_data is kept valid by the _runtime reference.
+ unsafe {
+ (self._runtime.dylibs.g_signal_connect_data)(
+ self._ptr as *mut _,
+ c"measurement".as_ptr(),
+ std::mem::transmute::<*const (), GCallback>(measurement_handler as *const ()),
+ user_data,
+ Some(closure_destroy_handler),
+ 0,
+ );
+ }
+ }
+}
+
+/// Wrapper for GCancellable
+struct Cancellable {
+ _ptr: *mut GCancellable,
+}
+
+impl Cancellable {
+ fn new(ptr: *mut GCancellable) -> Self {
+ Self { _ptr: ptr }
+ }
+
+ pub fn ptr(&self) -> *mut GCancellable {
+ self._ptr
+ }
+
+ pub fn cancel_after(timeout: Duration, runtime: Arc) -> Self {
+ // SAFETY: g_cancellable_new is valid as long as runtime is valid.
+ let this = unsafe { Self::new((runtime.dylibs.g_cancellable_new)()) };
+ let this_ptr_u64 = this.ptr() as std::ffi::c_ulong;
+
+ std::thread::spawn(move || {
+ std::thread::sleep(timeout);
+ // SAFETY: These handles are valid, we keep the runtime instance alive for this thread's lifetime
+ unsafe {
+ (runtime.dylibs.g_cancellable_cancel)(this_ptr_u64 as *mut GCancellable);
+ (runtime.dylibs.g_object_unref)(this_ptr_u64 as *mut _);
+ }
+ });
+
+ this
+ }
+}
+
+/// Contains the dynamic libraries and all method pointers needed for libssc to work.
+/// This currently consists of: glib-2.0, gobject-2.0, gio-2.0, libssc
+pub struct SscRuntime {
+ pub(crate) dylibs: SscDylibs,
+}
+
+impl SscRuntime {
+ pub fn load() -> Result, Box> {
+ Ok(Arc::new(Self {
+ // SAFETY: Gives access to a bunch of function handles. This is safe as long as we use them as intended.
+ // The handles are valid as long as this SscRuntime is.
+ dylibs: unsafe { SscDylibs::load()? },
+ }))
+ }
+
+ /// Converts a GLib error ptr to a basic string (or None if the error is null)
+ pub fn convert_glib_error(&self, error: *mut GError) -> Option {
+ if error.is_null() {
+ None
+ } else {
+ // SAFETY: The handle for g_quark_to_string is sure to be alive here
+ let domain_str = unsafe {
+ let domain_cstr =
+ std::ffi::CStr::from_ptr((self.dylibs.g_quark_to_string)((*error).domain));
+ domain_cstr.to_string_lossy().into_owned()
+ };
+
+ let domain = unsafe { (*error).domain };
+ let code = unsafe { (*error).code };
+
+ Some(format!(
+ "GLib error: domain = {} / {}, code = {}",
+ domain, domain_str, code
+ ))
+ }
+ }
+
+ /// Safe wrapper for GLib's g_main_context_iteration
+ pub fn iterate_glib_main_loop(&self) {
+ // SAFETY: This function may have side effects if other parts of InputPlumber start using the main context.
+ // This function handle is always non-null and we don't have to pass anything to it that isn't NULL / 0.
+ unsafe {
+ (self.dylibs.g_main_context_iteration)(std::ptr::null_mut(), GFALSE);
+ }
+ }
+
+ /// The signatures for gyroscope_(open/new)_sync and accelerometer_(open/new)_sync are the same, so we can reuse some code
+ unsafe fn create_measurement_sensor(
+ self: Arc,
+ new_fn: FnSscSensorNewSync,
+ open_fn: FnSscSensorOpenSync,
+ ) -> Result> {
+ let mut err: *mut GError = null_mut();
+ let ptr = unsafe {
+ let cancellable = Cancellable::cancel_after(Duration::from_secs(1), self.clone());
+ (new_fn)(cancellable.ptr(), &mut err)
+ };
+
+ // Instantiate the sensor and get our GObject ptr from it
+ if let Some(v) = self.convert_glib_error(err) {
+ return Err(format!("Failed to instantiate SSC sensor: {v}").into());
+ }
+
+ if ptr.is_null() {
+ return Err("Failed to instantiate SSC sensor: (got a null pointer)".into());
+ }
+
+ // "Open" the sensor (make it start doing stuff)
+ // note: We set the data callback later using set_measurement_handler, so this is just new sensor -> open sensor
+ unsafe {
+ err = std::ptr::null_mut();
+ let cancellable = Cancellable::cancel_after(Duration::from_secs(4), self.clone());
+ (open_fn)(ptr, cancellable.ptr(), &mut err)
+ };
+
+ if let Some(v) = self.convert_glib_error(err) {
+ return Err(format!("Failed to open SSC sensor: {v}").into());
+ }
+
+ SscObject::new(ptr, self).map_err(|_| "Failed to create SSC measurement sensor".into())
+ }
+
+ pub fn create_gyroscope(self: Arc) -> Result> {
+ // SAFETY: These handles will stay available as long as this SscRuntime does.
+ unsafe {
+ let new_fn = self.dylibs.ssc_sensor_gyroscope_new_sync;
+ let open_fn = self.dylibs.ssc_sensor_gyroscope_open_sync;
+ self.create_measurement_sensor(new_fn, open_fn)
+ }
+ }
+
+ pub fn create_accelerometer(
+ self: Arc,
+ ) -> Result> {
+ // SAFETY: These handles will stay available as long as this SscRuntime does.
+ unsafe {
+ let new_fn = self.dylibs.ssc_sensor_accelerometer_new_sync;
+ let open_fn = self.dylibs.ssc_sensor_accelerometer_open_sync;
+ self.create_measurement_sensor(new_fn, open_fn)
+ }
+ }
+}
diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs
index c8316c4a..038d126c 100644
--- a/src/input/composite_device/mod.rs
+++ b/src/input/composite_device/mod.rs
@@ -18,29 +18,24 @@ use zbus::{object_server::Interface, Connection};
use crate::{
config::{
- capability_map::CapabilityMapConfig, path::get_profiles_path, CompositeDeviceConfig,
- DeviceProfile, LoadError, ProfileMapping,
+ CompositeDeviceConfig, DeviceProfile, LoadError, ProfileMapping, capability_map::CapabilityMapConfig, path::get_profiles_path
},
dbus::interface::{
- composite_device::CompositeDeviceInterface, force_feedback::ForceFeedbackInterface,
- DBusInterfaceManager,
+ DBusInterfaceManager, composite_device::CompositeDeviceInterface, force_feedback::ForceFeedbackInterface
},
input::{
capability::{Capability, Gamepad, GamepadButton, Mouse},
event::{
- native::NativeEvent,
- value::{InputValue, TranslationError},
- Event,
+ Event, native::NativeEvent, value::{InputValue, TranslationError}
},
output_capability::OutputCapability,
output_event::UinputOutputEvent,
source::{
- evdev::EventDevice, hidraw::HidRawDevice, iio::IioDevice, led::LedDevice,
- tty::TtyDevice, SourceDevice,
+ SourceDevice, evdev::EventDevice, fastrpc::FastRpcDevice, hidraw::HidRawDevice, iio::IioDevice, led::LedDevice, tty::TtyDevice
},
target::TargetDeviceTypeId,
},
- udev::{hide_device, unhide_device, HideFlag},
+ udev::{HideFlag, hide_device, unhide_device},
};
use self::{client::CompositeDeviceClient, command::CompositeCommand};
@@ -1764,6 +1759,11 @@ impl CompositeDevice {
let device = TtyDevice::new(device, self.client(), source_config.clone())?;
SourceDevice::Tty(device)
}
+ "fastrpc" => {
+ log::debug!("Adding FastRPC source device: {:?}", device.sysname());
+ let device = FastRpcDevice::new(device, self.client(), source_config.clone())?;
+ SourceDevice::FastRpc(device)
+ }
_ => {
return Err(format!(
"Unspported subsystem: {subsystem}, unable to add source device {}",
diff --git a/src/input/manager.rs b/src/input/manager.rs
index fe31b041..3d0674f0 100644
--- a/src/input/manager.rs
+++ b/src/input/manager.rs
@@ -27,6 +27,7 @@ use crate::constants::BUS_SOURCES_PREFIX;
use crate::constants::BUS_TARGETS_PREFIX;
use crate::dbus::interface::manager::ManagerInterface;
use crate::dbus::interface::source::evdev::SourceEventDeviceInterface;
+use crate::dbus::interface::source::fastrpc::SourceFastRpcInterface;
use crate::dbus::interface::source::hidraw::SourceHIDRawInterface;
use crate::dbus::interface::source::iio_imu::SourceIioImuInterface;
use crate::dbus::interface::source::led::SourceLedInterface;
@@ -38,6 +39,7 @@ use crate::dmi::get_cpu_info;
use crate::dmi::get_dmi_data;
use crate::input::composite_device::CompositeDevice;
use crate::input::source::evdev;
+use crate::input::source::fastrpc;
use crate::input::source::hidraw;
use crate::input::source::iio;
use crate::input::source::led;
@@ -1176,6 +1178,7 @@ impl Manager {
"iio" => iio::get_dbus_path(sys_name),
"leds" => led::get_dbus_path(sys_name),
"tty" => tty::get_dbus_path(sys_name),
+ "fastrpc" => fastrpc::get_dbus_path(sys_name),
_ => return Err(format!("Device subsystem not supported: {subsystem:?}").into()),
};
let conn = self.dbus.connection().clone();
@@ -1203,6 +1206,10 @@ impl Manager {
let tty_iface = SourceTtyInterface::new(dev);
dbus.register(tty_iface);
}
+ "fastrpc" => {
+ let fastrpc_iface = SourceFastRpcInterface::new(dev);
+ dbus.register(fastrpc_iface);
+ }
_ => (),
}
@@ -1383,6 +1390,24 @@ impl Manager {
}
}
+ "fastrpc" => {
+ log::debug!(
+ "FastRPC device added: {} ({})",
+ device.name(),
+ device.sysname()
+ );
+
+ // Check to see if the device is virtual
+ if device.is_virtual() {
+ // note: FastRPC devices seem to always be virtual, allow it
+ log::trace!(
+ "{dev_name} ({dev_sysname}) is virtual, using it anyways (fastrpc)"
+ );
+ } else {
+ log::trace!("Device {dev_name} ({dev_sysname}) is real - {dev_path}");
+ }
+ }
+
_ => {
return Err(format!("Device subsystem not supported: {subsystem:?}").into());
}
@@ -1510,13 +1535,11 @@ impl Manager {
WatchEvent::Create { name, base_path } => {
let subsystem = {
match base_path.as_str() {
- "/dev" => {
- if !name.starts_with("hidraw") {
- None
- } else {
- Some("hidraw")
- }
- }
+ "/dev" => match &name {
+ x if x.starts_with("hidraw") => Some("hidraw"),
+ x if x.starts_with("fastrpc") => Some("fastrpc"),
+ _ => None,
+ },
"/dev/input" => Some("input"),
_ => None,
@@ -1623,6 +1646,15 @@ impl Manager {
let tty_devices = tty_devices.into_iter().map(|dev| dev.into()).collect();
Manager::discover_devices(cmd_tx, tty_devices).await?;
+ // FastRPC devices are part of the "misc" subsystem (but always have the "fastrpc" prefix)
+ let fastrpc_devices = udev::discover_devices("misc")?;
+ let fastrpc_devices = fastrpc_devices
+ .into_iter()
+ .filter(|x| x.sysname().to_string_lossy().starts_with("fastrpc"))
+ .map(|dev| dev.into())
+ .collect();
+ Manager::discover_devices(cmd_tx, fastrpc_devices).await?;
+
Ok(())
}
diff --git a/src/input/source/fastrpc.rs b/src/input/source/fastrpc.rs
new file mode 100644
index 00000000..4a88e0b9
--- /dev/null
+++ b/src/input/source/fastrpc.rs
@@ -0,0 +1,112 @@
+pub mod ssc_imu;
+
+use std::error::Error;
+
+use crate::{
+ config,
+ constants::BUS_SOURCES_PREFIX,
+ input::{
+ capability::Capability, composite_device::client::CompositeDeviceClient,
+ info::DeviceInfoRef, output_capability::OutputCapability, source::fastrpc::ssc_imu::SscImu,
+ },
+ udev::device::UdevDevice,
+};
+
+use super::{InputError, OutputError, SourceDeviceCompatible, SourceDriver};
+
+enum DriverType {
+ // Unknown,
+ SscImu,
+}
+
+/// [FastRpcDevice] represents a device (likely the Sensor Core) using the Qualcomm FastRPC subsystem.
+#[derive(Debug)]
+pub enum FastRpcDevice {
+ SscImu(SourceDriver),
+}
+
+impl SourceDeviceCompatible for FastRpcDevice {
+ fn get_device_ref(&self) -> DeviceInfoRef<'_> {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.info_ref(),
+ }
+ }
+
+ fn get_id(&self) -> String {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.get_id(),
+ }
+ }
+
+ fn client(&self) -> super::client::SourceDeviceClient {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.client(),
+ }
+ }
+
+ async fn run(self) -> Result<(), Box> {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.run().await,
+ }
+ }
+
+ fn get_capabilities(&self) -> Result, InputError> {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.get_capabilities(),
+ }
+ }
+
+ fn get_output_capabilities(&self) -> Result, OutputError> {
+ Ok(vec![])
+ }
+
+ fn get_device_path(&self) -> String {
+ match self {
+ FastRpcDevice::SscImu(source_driver) => source_driver.get_device_path(),
+ }
+ }
+}
+
+impl FastRpcDevice {
+ /// Create a new [FastRpcDevice] associated with the given device and
+ /// composite device. The appropriate driver will be selected based on
+ /// the provided device.
+ pub fn new(
+ device_info: UdevDevice,
+ composite_device: CompositeDeviceClient,
+ conf: Option,
+ ) -> Result> {
+ let driver_type = FastRpcDevice::get_driver_type(&device_info);
+
+ let imu_config = conf
+ .as_ref()
+ .and_then(|c| c.config.clone())
+ .and_then(|c| c.imu);
+
+ match driver_type {
+ // DriverType::Unknown => Err("No driver for FastRPC interface found".into()),
+ DriverType::SscImu => {
+ let device = SscImu::new(device_info.clone(), imu_config)?;
+ let source_device =
+ SourceDriver::new(composite_device, device, device_info.into(), conf);
+ Ok(Self::SscImu(source_device))
+ }
+ }
+ }
+
+ /// Return the driver type for the given device info
+ fn get_driver_type(device: &UdevDevice) -> DriverType {
+ let device_name = device.name();
+ let name = device_name.as_str();
+ log::debug!("Finding driver for FastRPC interface: {name}");
+
+ // TODO(gio)
+ DriverType::SscImu
+ }
+}
+
+/// Returns the DBus path for an [FastRpcDevice] from a device id (E.g. fastrpc:device0)
+pub fn get_dbus_path(id: String) -> String {
+ let name = id.replace([':', '-', '.'], "_");
+ format!("{}/{}", BUS_SOURCES_PREFIX, name)
+}
diff --git a/src/input/source/fastrpc/ssc_imu.rs b/src/input/source/fastrpc/ssc_imu.rs
new file mode 100644
index 00000000..15c5f780
--- /dev/null
+++ b/src/input/source/fastrpc/ssc_imu.rs
@@ -0,0 +1,108 @@
+use std::{collections::HashSet, error::Error, fmt::Debug};
+
+use crate::{
+ config::ImuConfig,
+ drivers::ssc::{self, driver::Driver},
+ input::{
+ capability::{Capability, Source},
+ event::{native::NativeEvent, value::InputValue},
+ source::{InputError, SourceInputDevice, SourceOutputDevice},
+ },
+ udev::device::UdevDevice,
+};
+
+pub struct SscImu {
+ driver: Driver,
+}
+
+impl SscImu {
+ pub fn new(
+ _device_info: UdevDevice,
+ imu_config: Option,
+ ) -> Result> {
+ let mount_matrix = imu_config.as_ref().and_then(|c| c.mount_matrix.clone());
+
+ let driver = Driver::new(mount_matrix)?;
+
+ Ok(Self { driver })
+ }
+}
+
+impl SourceInputDevice for SscImu {
+ /// Poll the given input 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())
+ }
+
+ 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 SscImu {}
+
+impl Debug for SscImu {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("SscImu").finish()
+ }
+}
+
+// SAFETY: Mark this struct as thread-safe as it will only ever be called from
+// a single thread.
+unsafe impl Send for SscImu {}
+
+/// Translate the given driver events into native events
+fn translate_events(events: Vec) -> Vec {
+ events.into_iter().map(translate_event).collect()
+}
+
+/// Translate the given driver event into a native event
+fn translate_event(event: ssc::event::Event) -> NativeEvent {
+ match event {
+ ssc::event::Event::Accelerometer(data) => {
+ let cap = Capability::Accelerometer(Source::Center);
+ let value = InputValue::Vector3 {
+ x: Some(data.roll),
+ y: Some(data.pitch),
+ z: Some(data.yaw),
+ };
+ NativeEvent::new(cap, value)
+ }
+ ssc::event::Event::Gyro(data) => {
+ let cap = Capability::Gyroscope(Source::Center);
+ let value = InputValue::Vector3 {
+ // Similar to bmi_imu, these values needs to be scaled a bunch
+ // This seems close to correct, needs some more testing
+ x: Some(data.roll.to_degrees() * 16.0),
+ y: Some(data.pitch.to_degrees() * 16.0),
+ z: Some(data.yaw.to_degrees() * 16.0),
+ };
+ NativeEvent::new(cap, value)
+ }
+ }
+}
+
+/// List of all capabilities that the driver implements
+pub const CAPABILITIES: &[Capability] = &[
+ Capability::Accelerometer(Source::Center),
+ Capability::Gyroscope(Source::Center),
+];
diff --git a/src/input/source/iio/accel_gyro_3d.rs b/src/input/source/iio/accel_gyro_3d.rs
index 737dacaa..a03e49f6 100644
--- a/src/input/source/iio/accel_gyro_3d.rs
+++ b/src/input/source/iio/accel_gyro_3d.rs
@@ -94,7 +94,7 @@ impl Debug for AccelGyro3dImu {
}
}
-// NOTE: Mark this struct as thread-safe as it will only ever be called from
+// SAFETY: Mark this struct as thread-safe as it will only ever be called from
// a single thread.
unsafe impl Send for AccelGyro3dImu {}
diff --git a/src/input/source/iio/bmi_imu.rs b/src/input/source/iio/bmi_imu.rs
index 329c675f..a1de387b 100644
--- a/src/input/source/iio/bmi_imu.rs
+++ b/src/input/source/iio/bmi_imu.rs
@@ -87,7 +87,7 @@ impl Debug for BmiImu {
}
}
-// NOTE: Mark this struct as thread-safe as it will only ever be called from
+// SAFETY: Mark this struct as thread-safe as it will only ever be called from
// a single thread.
unsafe impl Send for BmiImu {}
diff --git a/src/input/source/mod.rs b/src/input/source/mod.rs
index ab8f3c2e..b9f505c1 100644
--- a/src/input/source/mod.rs
+++ b/src/input/source/mod.rs
@@ -16,8 +16,8 @@ use tokio::sync::mpsc::{self, error::TryRecvError};
use crate::config;
use self::{
- client::SourceDeviceClient, command::SourceCommand, evdev::EventDevice, hidraw::HidRawDevice,
- iio::IioDevice, tty::TtyDevice,
+ client::SourceDeviceClient, command::SourceCommand, evdev::EventDevice, fastrpc::FastRpcDevice,
+ hidraw::HidRawDevice, iio::IioDevice, tty::TtyDevice,
};
use super::{
@@ -32,6 +32,7 @@ use super::{
pub mod client;
pub mod command;
pub mod evdev;
+pub mod fastrpc;
pub mod hidraw;
pub mod iio;
pub mod led;
@@ -620,6 +621,7 @@ pub(crate) trait SourceDeviceCompatible {
#[derive(Debug)]
pub enum SourceDevice {
Event(EventDevice),
+ FastRpc(FastRpcDevice),
HidRaw(HidRawDevice),
Iio(IioDevice),
Led(LedDevice),
@@ -631,6 +633,7 @@ impl SourceDevice {
pub fn get_device_ref(&self) -> DeviceInfoRef<'_> {
match self {
SourceDevice::Event(device) => device.get_device_ref(),
+ SourceDevice::FastRpc(device) => device.get_device_ref(),
SourceDevice::HidRaw(device) => device.get_device_ref(),
SourceDevice::Iio(device) => device.get_device_ref(),
SourceDevice::Led(device) => device.get_device_ref(),
@@ -642,6 +645,7 @@ impl SourceDevice {
pub fn get_id(&self) -> String {
match self {
SourceDevice::Event(device) => device.get_id(),
+ SourceDevice::FastRpc(device) => device.get_id(),
SourceDevice::HidRaw(device) => device.get_id(),
SourceDevice::Iio(device) => device.get_id(),
SourceDevice::Led(device) => device.get_id(),
@@ -653,6 +657,7 @@ impl SourceDevice {
pub fn get_persistent_id(&self) -> Option {
match self {
SourceDevice::Event(device) => device.get_serial(),
+ SourceDevice::FastRpc(_) => None,
SourceDevice::HidRaw(device) => device.get_serial(),
SourceDevice::Iio(_) => None,
SourceDevice::Led(_) => None,
@@ -664,6 +669,7 @@ impl SourceDevice {
pub fn client(&self) -> SourceDeviceClient {
match self {
SourceDevice::Event(device) => device.client(),
+ SourceDevice::FastRpc(device) => device.client(),
SourceDevice::HidRaw(device) => device.client(),
SourceDevice::Iio(device) => device.client(),
SourceDevice::Led(device) => device.client(),
@@ -675,6 +681,7 @@ impl SourceDevice {
pub async fn run(self) -> Result<(), Box> {
match self {
SourceDevice::Event(device) => device.run().await,
+ SourceDevice::FastRpc(device) => device.run().await,
SourceDevice::HidRaw(device) => device.run().await,
SourceDevice::Iio(device) => device.run().await,
SourceDevice::Led(device) => device.run().await,
@@ -686,6 +693,7 @@ impl SourceDevice {
pub fn get_capabilities(&self) -> Result, InputError> {
match self {
SourceDevice::Event(device) => device.get_capabilities(),
+ SourceDevice::FastRpc(device) => device.get_capabilities(),
SourceDevice::HidRaw(device) => device.get_capabilities(),
SourceDevice::Iio(device) => device.get_capabilities(),
SourceDevice::Led(device) => device.get_capabilities(),
@@ -697,6 +705,7 @@ impl SourceDevice {
pub fn get_output_capabilities(&self) -> Result, OutputError> {
match self {
SourceDevice::Event(device) => device.get_output_capabilities(),
+ SourceDevice::FastRpc(device) => device.get_output_capabilities(),
SourceDevice::HidRaw(device) => device.get_output_capabilities(),
SourceDevice::Iio(device) => device.get_output_capabilities(),
SourceDevice::Led(device) => device.get_output_capabilities(),
@@ -708,6 +717,7 @@ impl SourceDevice {
pub fn get_device_path(&self) -> String {
match self {
SourceDevice::Event(device) => device.get_device_path(),
+ SourceDevice::FastRpc(device) => device.get_device_path(),
SourceDevice::HidRaw(device) => device.get_device_path(),
SourceDevice::Iio(device) => device.get_device_path(),
SourceDevice::Led(device) => device.get_device_path(),
diff --git a/src/udev/device.rs b/src/udev/device.rs
index 667725b2..b0b4d1a5 100644
--- a/src/udev/device.rs
+++ b/src/udev/device.rs
@@ -1,7 +1,6 @@
use std::{
collections::HashMap,
error::Error,
- ffi::OsStr,
fs::{self, read_link},
path::{Path, PathBuf},
};
@@ -369,15 +368,12 @@ impl UdevDevice {
let devnode = format!("{base_path}/{name}");
let subsystem = {
match base_path {
- "/dev" => {
- if name.starts_with("hidraw") {
- Some("hidraw")
- } else if name.starts_with("iio:") {
- Some("iio")
- } else {
- None
- }
- }
+ "/dev" => match &name {
+ x if x.starts_with("hidraw") => Some("hidraw"),
+ x if x.starts_with("iio:") => Some("iio"),
+ x if x.starts_with("fastrpc") => Some("fastrpc"),
+ _ => None,
+ },
"/dev/input" => Some("input"),
_ => None,
@@ -436,15 +432,21 @@ impl UdevDevice {
Err(_) => return Default::default(),
};
+ let subsystem = match device.subsystem().map(|s| s.to_string_lossy().to_string()) {
+ // FastRPC has special behaviour, check the comment in "From<::udev::Device> for UdevDevice"
+ Some(x) if x == "misc" && device.sysname().to_string_lossy().starts_with("fastrpc") => {
+ "fastrpc".to_string()
+ }
+ Some(x) => x,
+ None => String::default(),
+ };
+
Self {
devnode: device
.devnode()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default(),
- subsystem: device
- .subsystem()
- .map(|s| s.to_string_lossy().to_string())
- .unwrap_or_default(),
+ subsystem,
syspath: device.syspath().to_string_lossy().to_string(),
sysname: device.sysname().to_string_lossy().to_string(),
name: Some(device.name().to_string()),
@@ -624,6 +626,9 @@ impl UdevDevice {
"leds" => {
format!("leds://{}", self.sysname)
}
+ "fastrpc" => {
+ format!("fastrpc://{}", self.sysname)
+ }
_ => "".to_string(),
}
}
@@ -695,11 +700,17 @@ impl From<::udev::Device> for UdevDevice {
.unwrap_or(Path::new(""))
.to_string_lossy()
.to_string();
- let subsystem = device
- .subsystem()
- .unwrap_or(OsStr::new(""))
- .to_string_lossy()
- .to_string();
+
+ let subsystem = match device.subsystem().map(|s| s.to_string_lossy().to_string()) {
+ // FastRPC devices are part of the "misc" subsystem
+ // Instead of handling that case everywhere, just check if the device is a fastrpc device and fake the subsystem
+ Some(x) if x == "misc" && device.sysname().to_string_lossy().starts_with("fastrpc") => {
+ "fastrpc".to_string()
+ }
+ Some(x) => x,
+ None => String::default(),
+ };
+
let sysname = device.sysname().to_string_lossy().to_string();
let syspath = device.syspath().to_string_lossy().to_string();
let properties = device.get_properties();