Skip to content

Commit

Permalink
feat(CompositeDeviceConfig): add udev-based source device matching
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Nov 21, 2024
1 parent 1e3345b commit 4c095a1
Show file tree
Hide file tree
Showing 4 changed files with 571 additions and 158 deletions.
67 changes: 67 additions & 0 deletions rootfs/usr/share/inputplumber/schema/composite_device_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@
"type": "boolean",
"default": false
},
"udev": {
"$ref": "#/definitions/Udev"
},
"evdev": {
"$ref": "#/definitions/Evdev"
},
Expand All @@ -187,6 +190,70 @@
],
"title": "SourceDevice"
},
"Udev": {
"description": "Source device to manage. Properties support globbing patterns.",
"type": "object",
"additionalProperties": false,
"properties": {
"attributes": {
"description": "Device attributes to match. Attributes can be found by running `udevadm info --attribute-walk /path/to/device` and looking at fields that look like: `ATTR{name}==\"value\"`.",
"type": "array",
"items": {
"$ref": "#/definitions/UdevKeyValue"
}
},
"dev_node": {
"description": "Full device node path to match. E.g. '/dev/hidraw3', '/dev/input/event*'",
"type": "string"
},
"dev_path": {
"description": "Full kernel device path to match. The path does not contain the sys mount point, but does start with a `/`. For example, the dev_path for `hidraw3` could be `/devices/pci0000:00/0000:00:08.1/.../hidraw/hidraw3`.",
"type": "string"
},
"driver": {
"description": "Driver being used by the device (or parent devices) to match. E.g. `playstation`, `microsoft`",
"type": "string"
},
"properties": {
"description": "Device properties to match. Properties can be found by running `udevadm info -q property /path/to/device`.",
"type": "array",
"items": {
"$ref": "#/definitions/UdevKeyValue"
}
},
"subsystem": {
"description": "Subsystem to match. E.g. `input`, `hidraw`, `iio`",
"type": "string"
},
"sys_name": {
"description": "Sysname to match. The sysname is typically the last part of the path to the device. E.g. `hidraw3`, `event6`",
"type": "string"
},
"sys_path": {
"description": "Syspath to match. The syspath is an absolute path and includes the sys mount point. For example, the syspath for `hidraw3` could be `/sys/devices/pci0000:00/0000:00:08.1/.../hidraw/hidraw3`, which includes the sys mount point `/sys`.",
"type": "string"
}
},
"required": [],
"title": "Udev"
},
"UdevKeyValue": {
"description": "Udev attribute or property key/value pair"
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"description": "Name of the property or attribute to match. Does NOT support globbing patterns.",
"type": "string"
},
"value": {
"description": "Value of the property or attribute to match. Supports globbing patterns.",
"type": "string"
},
},
"required": ["name"],
"title": "UdevKeyValue"
},
"Evdev": {
"description": "Source device to manage. Properties support globbing patterns.",
"type": "object",
Expand Down
143 changes: 143 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ pub struct SourceDevice {
pub evdev: Option<Evdev>,
pub hidraw: Option<Hidraw>,
pub iio: Option<IIO>,
pub udev: Option<Udev>,
pub unique: Option<bool>,
pub blocked: Option<bool>,
pub ignore: Option<bool>,
Expand All @@ -332,6 +333,26 @@ pub struct Hidraw {
pub name: Option<String>,
}

#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct Udev {
pub attributes: Option<Vec<UdevAttribute>>,
pub dev_node: Option<String>,
pub dev_path: Option<String>,
pub driver: Option<String>,
pub properties: Option<Vec<UdevAttribute>>,
pub subsystem: Option<String>,
pub sys_name: Option<String>,
pub sys_path: Option<String>,
}

#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct UdevAttribute {
pub name: String,
pub value: Option<String>,
}

#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::upper_case_acronyms)]
Expand Down Expand Up @@ -397,6 +418,18 @@ impl CompositeDeviceConfig {

/// Returns a [SourceDevice] if it matches the given [UdevDevice].
pub fn get_matching_device(&self, udevice: &UdevDevice) -> Option<SourceDevice> {
// Check udev matches first
for config in self.source_devices.iter() {
let Some(udev_config) = config.udev.as_ref() else {
continue;
};

if self.has_matching_udev(udevice, udev_config) {
return Some(config.clone());
}
}

// Deprecated method for device matching based on subsystem
let subsystem = udevice.subsystem();
match subsystem.as_str() {
"input" => {
Expand Down Expand Up @@ -431,6 +464,116 @@ impl CompositeDeviceConfig {
None
}

/// Returns true if a given device matches the given udev config
pub fn has_matching_udev(&self, device: &UdevDevice, udev_config: &Udev) -> bool {
log::trace!("Checking udev config '{:?}'", udev_config);

if let Some(attributes) = udev_config.attributes.as_ref() {
let device_attributes = device.get_attributes();

for attribute in attributes {
let Some(device_attr_value) = device_attributes.get(&attribute.name) else {
// If the device does not have this attribute, return false
return false;
};

// If no value was specified in the config, then only match on
// the presence of the attribute and not the value.
let Some(attr_value) = attribute.value.as_ref() else {
continue;
};

// Glob match on the attribute value
log::trace!("Checking attribute: {attr_value} against {device_attr_value}");
if !glob_match(attr_value.as_str(), device_attr_value.as_str()) {
return false;
}
}
}

if let Some(dev_node) = udev_config.dev_node.as_ref() {
let device_dev_node = device.devnode();
log::trace!("Checking dev_node: {dev_node} against {device_dev_node}");
if !glob_match(dev_node.as_str(), device_dev_node.as_str()) {
return false;
}
}

if let Some(dev_path) = udev_config.dev_path.as_ref() {
let device_dev_path = device.devpath();
log::trace!("Checking dev_path: {dev_path} against {device_dev_path}");
if !glob_match(dev_path.as_str(), device_dev_path.as_str()) {
return false;
}
}

if let Some(driver) = udev_config.driver.as_ref() {
let all_drivers = device.drivers();
let mut has_matches = false;

for device_driver in all_drivers {
log::trace!("Checking driver: {driver} against {device_driver}");
if glob_match(driver.as_str(), device_driver.as_str()) {
has_matches = true;
break;
}
}

if !has_matches {
return false;
}
}

if let Some(properties) = udev_config.properties.as_ref() {
let device_properties = device.get_properties();

for property in properties {
let Some(device_prop_value) = device_properties.get(&property.name) else {
// If the device does not have this property, return false
return false;
};

// If no value was specified in the config, then only match on
// the presence of the property and not the value.
let Some(prop_value) = property.value.as_ref() else {
continue;
};

// Glob match on the property value
log::trace!("Checking property: {prop_value} against {device_prop_value}");
if !glob_match(prop_value.as_str(), device_prop_value.as_str()) {
return false;
}
}
}

if let Some(subsystem) = udev_config.subsystem.as_ref() {
let device_subsystem = device.subsystem();
log::trace!("Checking subsystem: {subsystem} against {device_subsystem}");
if !glob_match(subsystem.as_str(), device_subsystem.as_str()) {
return false;
}
}

if let Some(sys_name) = udev_config.sys_name.as_ref() {
let device_sys_name = device.sysname();
log::trace!("Checking sys_name: {sys_name} against {device_sys_name}");
if !glob_match(sys_name.as_str(), device_sys_name.as_str()) {
return false;
}
}

if let Some(sys_path) = udev_config.sys_path.as_ref() {
let device_sys_path = device.syspath();
log::trace!("Checking sys_path: {sys_path} against {device_sys_path}");
if !glob_match(sys_path.as_str(), device_sys_path.as_str()) {
return false;
}
}

true
}

/// Returns true if a given hidraw device is within a list of hidraw configs.
pub fn has_matching_hidraw(&self, device: &UdevDevice, hidraw_config: &Hidraw) -> bool {
log::trace!("Checking hidraw config '{:?}'", hidraw_config,);
Expand Down
Loading

0 comments on commit 4c095a1

Please sign in to comment.