Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,223 changes: 1,157 additions & 66 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ libiio = "*"
libevdev = "*"

[dependencies]
cec-rs = "11.0.2"
clap = { version = "4.5.27", features = ["derive"] }
clap_complete = "4.5.42"
env_logger = "0.11.3"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ target/release/$(NAME): $(ALL_RS) Cargo.lock
all: build debug ## Build release and debug builds

.PHONY: run
run: setup debug ## Build and run
run: debug ## Build and run
sudo LOG_LEVEL=$(LOG_LEVEL) ./target/debug/$(NAME)

.PHONY: clean
Expand Down
45 changes: 45 additions & 0 deletions rootfs/usr/share/inputplumber/devices/60-cec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/ShadowBlip/InputPlumber/main/rootfs/usr/share/inputplumber/schema/composite_device_v1.json
# Schema version number
version: 1

# The type of configuration schema
kind: CompositeDevice

# Name of the composite device mapping
name: CEC

# Maximum number of source devices per CompositeDevice.
maximum_sources: 2

# Only use this profile if *any* of the given matches matches. If this list is
# empty, then the source devices will *always* be checked.
# /sys/class/dmi/id/product_name
matches: []

# One or more source devices to combine into a single virtual device. The events
# from these devices will be watched and translated according to the key map.
source_devices:
- group: gamepad
udev:
attributes:
- name: protocols
value: '\[cec\]'
sys_name: "event*"
subsystem: input
- group: gamepad
udev:
sys_name: "cec*"
subsystem: "cec"

# Optional configuration for the composite device
options:
# If true, InputPlumber will automatically try to manage the input device. If
# this is false, InputPlumber will not try to manage the device unless an
# external service enables management of the device. Defaults to 'false'
auto_manage: false

# The target input device(s) to emulate by default
target_devices:
- xbox-elite
- mouse
- keyboard
6 changes: 6 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub enum Commands {
#[command(subcommand)]
cmd: TargetsCommand,
},
/// Query a given device
Query { udev: Option<bool> },
}

pub async fn main_cli(args: Args) -> Result<(), Box<dyn Error>> {
Expand All @@ -65,6 +67,10 @@ pub async fn main_cli(args: Args) -> Result<(), Box<dyn Error>> {
Commands::Device { id: number, cmd } => handle_device(connection, cmd, number).await?,
Commands::Devices { cmd } => handle_devices(connection, cmd).await?,
Commands::Targets { cmd } => handle_targets(connection, cmd).await?,
Commands::Query { udev } => {
println!("Got udev: {udev:?}");
//
}
}

Ok(())
Expand Down
9 changes: 8 additions & 1 deletion src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ use crate::{
Event,
},
output_event::UinputOutputEvent,
source::{evdev::EventDevice, hidraw::HidRawDevice, iio::IioDevice, SourceDevice},
source::{
cec::CecDevice, evdev::EventDevice, hidraw::HidRawDevice, iio::IioDevice, SourceDevice,
},
},
udev::{device::UdevDevice, hide_device, unhide_device},
};
Expand Down Expand Up @@ -1454,6 +1456,11 @@ impl CompositeDevice {
let device = IioDevice::new(device, self.client(), source_config.clone())?;
SourceDevice::Iio(device)
}
"cec" => {
log::debug!("Adding source device: {:?}", device.name());
let device = CecDevice::new(device, self.client(), source_config.clone())?;
SourceDevice::Cec(device)
}
_ => {
return Err(format!(
"Unspported subsystem: {subsystem}, unable to add source device {}",
Expand Down
66 changes: 63 additions & 3 deletions src/input/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,56 @@ impl Manager {
log::debug!("Finished adding event device {id}");
}

"cec" => {
if device.devnode().is_empty() {
log::warn!("cec device discarded for missing devnode: {dev_name} ({dev_sysname}) at {dev_path}");
return Ok(());
}

log::debug!("cec device added: {} ({})", device.name(), device.sysname());

// Create a DBus interface for the event device
let conn = self.dbus.clone();
let path = iio::get_dbus_path(sys_name.clone());

log::debug!("Attempting to listen on dbus for device {dev_name} ({dev_sysname}) | {dev_path}");
let dbus_path = path.clone();
task::spawn(async move {
let result = SourceUdevDeviceInterface::listen_on_dbus(
conn.clone(),
dbus_path.as_str(),
sysname.as_str(),
dev.clone(),
)
.await;
if let Err(e) = result {
log::error!("Error creating source udev dbus interface: {e:?}");
}

//let result = SourceCecInterface::listen_on_dbus(conn, sysname, dev).await;
//if let Err(e) = result {
// log::error!("Error creating source evdev dbus interface: {e:?}");
//}
log::debug!("Finished adding source device on dbus");
});

// Add the device as a source device
self.source_device_dbus_paths.insert(id.clone(), path);

// Check to see if the device is virtual
if device.is_virtual() {
log::debug!("{dev_name} ({dev_sysname}) is virtual, skipping consideration for {dev_path}");
return Ok(());
} else {
log::trace!("Device {dev_name} ({dev_sysname}) is real - {dev_path}");
}

// Signal that a source device was added
log::debug!("Spawing task to add source device: {id}");
self.on_source_device_added(id.clone(), device).await?;
log::debug!("Finished adding event device {id}");
}

_ => {
return Err(format!("Device subsystem not supported: {subsystem:?}").into());
}
Expand Down Expand Up @@ -1428,6 +1478,11 @@ impl Manager {
.remove::<SourceIioImuInterface, ObjectPath>(path.clone())
.await
}
//"cec" => {
// conn.object_server()
// .remove::<SourceCecInterface, ObjectPath>(path.clone())
// .await
//}
_ => Err(zbus::Error::Failure(format!(
"Invalid subsystem: '{subsystem}'"
))),
Expand Down Expand Up @@ -1542,10 +1597,12 @@ impl Manager {
let subsystem = {
match base_path.as_str() {
"/dev" => {
if !name.starts_with("hidraw") {
None
} else {
if name.starts_with("hidraw") {
Some("hidraw")
} else if name.starts_with("cec") {
Some("cec")
} else {
None
}
}
"/dev/input" => Some("input"),
Expand Down Expand Up @@ -1647,6 +1704,9 @@ impl Manager {
let iio_devices = udev::discover_devices("iio")?;
let iio_devices = iio_devices.into_iter().map(|dev| dev.into()).collect();
Manager::discover_devices(cmd_tx, iio_devices).await?;
let cec_devices = udev::discover_devices("cec")?;
let cec_devices = cec_devices.into_iter().map(|dev| dev.into()).collect();
Manager::discover_devices(cmd_tx, cec_devices).await?;

Ok(())
}
Expand Down
69 changes: 69 additions & 0 deletions src/input/source/cec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::error::Error;

use device::CecRawDevice;

use crate::{
config,
input::{capability::Capability, composite_device::client::CompositeDeviceClient},
udev::device::UdevDevice,
};

use super::{client::SourceDeviceClient, SourceDeviceCompatible, SourceDriver};

pub mod device;

/// [CecDevice] represents an input device using CEC
#[derive(Debug)]
pub enum CecDevice {
Device(SourceDriver<CecRawDevice>),
}

impl SourceDeviceCompatible for CecDevice {
fn get_device_ref(&self) -> &UdevDevice {
match self {
Self::Device(source_driver) => source_driver.info_ref(),
}
}

fn get_id(&self) -> String {
match self {
Self::Device(source_driver) => source_driver.get_id(),
}
}

fn client(&self) -> SourceDeviceClient {
match self {
Self::Device(source_driver) => source_driver.client(),
}
}

async fn run(self) -> Result<(), Box<dyn Error>> {
match self {
Self::Device(source_driver) => source_driver.run().await,
}
}

fn get_capabilities(&self) -> Result<Vec<Capability>, super::InputError> {
match self {
Self::Device(source_driver) => source_driver.get_capabilities(),
}
}

fn get_device_path(&self) -> String {
match self {
Self::Device(source_driver) => source_driver.get_device_path(),
}
}
}

impl CecDevice {
pub fn new(
device_info: UdevDevice,
composite_device: CompositeDeviceClient,
conf: Option<config::SourceDevice>,
) -> Result<Self, Box<dyn Error + Send + Sync>> {
let device = CecRawDevice::new();
let source_device = SourceDriver::new(composite_device, device, device_info, conf);
Ok(Self::Device(source_device))
}
}
36 changes: 36 additions & 0 deletions src/input/source/cec/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use cec_rs::{CecConnectionCfgBuilder, CecDeviceType, CecDeviceTypeVec};

use crate::input::{
capability::Capability,
source::{InputError, SourceInputDevice, SourceOutputDevice},
};

#[derive(Debug)]
pub struct CecRawDevice {}

impl CecRawDevice {
pub fn new() -> Self {
let cfg = CecConnectionCfgBuilder::default()
.device_types(CecDeviceTypeVec::new(CecDeviceType::AudioSystem))
.build();
Self {}
}
}

impl Default for CecRawDevice {
fn default() -> Self {
Self::new()
}
}

impl SourceInputDevice for CecRawDevice {
fn poll(&mut self) -> Result<Vec<crate::input::event::native::NativeEvent>, InputError> {
todo!()
}

fn get_capabilities(&self) -> Result<Vec<Capability>, InputError> {
todo!()
}
}

impl SourceOutputDevice for CecRawDevice {}
Loading
Loading