feat: add libinput support

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-09-30 00:23:36 +00:00
parent cb4c459175
commit 5700a89954
17 changed files with 675 additions and 201 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
[workspace] [workspace]
members = ["cli", "daemon", "libcompat/udev", "libs/ipc", "libs/kobject-uevent", "libs/rule"] members = ["cli", "daemon", "libcompat/udev", "libs/db", "libs/ipc", "libs/kobject-uevent", "libs/rule"]
resolver = "3" resolver = "3"
+1
View File
@@ -15,3 +15,4 @@ serde_json = "1"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "fs", "signal", "sync"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "fs", "signal", "sync"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
db = { path = "../libs/db" }
+39 -16
View File
@@ -1,3 +1,4 @@
use db::{Db, DbSection};
use kobject_uevent::KobjectUevent; use kobject_uevent::KobjectUevent;
use rule::{engine::Engine, parser::When}; use rule::{engine::Engine, parser::When};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@@ -7,52 +8,57 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, LazyLock, RwLock}, sync::{Arc, LazyLock, RwLock},
}; };
use tokio::sync::Mutex; use tokio::sync::{Mutex, broadcast};
static DEVICES: LazyLock<RwLock<FxHashMap<String, ArcDevice>>> = static DEVICES: LazyLock<RwLock<FxHashMap<String, ArcDevice>>> =
LazyLock::new(|| Default::default()); LazyLock::new(|| Default::default());
static DB: LazyLock<Arc<Db>> = LazyLock::new(|| Db::open(db::PERSIST_PATH));
static NOTIFY: LazyLock<broadcast::Sender<String>> = LazyLock::new(|| broadcast::channel(128).0);
pub type ArcDevice = Arc<Device>; pub type ArcDevice = Arc<Device>;
#[derive(Debug)] #[derive(Debug)]
pub struct Device { pub struct Device {
lock: Mutex<()>, lock: Mutex<()>,
properties: DbSection,
engine: Engine, engine: Engine,
} }
impl Device { impl Device {
pub fn new() -> Self { pub fn new(name: &str) -> Self {
let engine = Engine::new(); let engine = Engine::with_properties(DB.section(name));
engine.set_property("DEVROOT".into(), dev_root().to_string_lossy().into()); engine
.properties
.insert("DEVROOT", dev_root().to_string_lossy().into());
Self { Self {
lock: Mutex::new(()), lock: Mutex::new(()),
properties: DB.section(name),
engine, engine,
} }
} }
fn apply_uevent(&self, ev: &KobjectUevent) { fn apply_uevent(&self, ev: &KobjectUevent) {
for (k, v) in ev.inner().iter() { for (k, v) in ev.inner().iter() {
self.engine.set_property(k.into(), v.into()); self.engine.properties.insert(k, v.into());
} }
self.update_derived_properties(); self.update_derived_properties();
} }
fn update_derived_properties(&self) { fn update_derived_properties(&self) {
if let Some(devname) = self.engine.get_property("DEVNAME") { if let Some(devname) = self.engine.properties.get("DEVNAME") {
self.engine.set_property( self.engine
"DEVNODE".into(), .properties
format!("{}/{devname}", dev_root().display()), .insert("DEVNODE", format!("{}/{devname}", dev_root().display()));
);
} }
} }
async fn update_devnode_creds(&self) { async fn update_devnode_creds(&self) {
let Some(devnode) = self.engine.get_property("DEVNODE") else { let Some(devnode) = self.engine.properties.get("DEVNODE") else {
return; return;
}; };
let uid = crate::util::uid_by_string(self.engine.get_property("OWNER").as_deref()); let uid = crate::util::uid_by_string(self.engine.properties.get("OWNER").as_deref());
let gid = crate::util::gid_by_string(self.engine.get_property("GROUP").as_deref()); let gid = crate::util::gid_by_string(self.engine.properties.get("GROUP").as_deref());
let mode = crate::util::parse_mode(self.engine.get_property("MODE").as_deref()); let mode = crate::util::parse_mode(self.engine.properties.get("MODE").as_deref());
if let Err(err) = crate::util::set_ownership(&devnode, uid, gid).await { if let Err(err) = crate::util::set_ownership(&devnode, uid, gid).await {
tracing::warn!("{devnode}: failed to set ownership: {err}"); tracing::warn!("{devnode}: failed to set ownership: {err}");
@@ -67,7 +73,7 @@ impl Device {
} }
async fn update_symlinks(&self) { async fn update_symlinks(&self) {
let Some(devnode) = self.engine.get_property("DEVNODE") else { let Some(devnode) = self.engine.properties.get("DEVNODE") else {
return; return;
}; };
for i in self.engine.get_list("SYMLINKS") { for i in self.engine.get_list("SYMLINKS") {
@@ -104,6 +110,9 @@ pub async fn handle_uevent(ev: &KobjectUevent) {
} }
None => (), None => (),
} }
if let Some(devpath) = ev.devpath() {
_ = NOTIFY.send(devpath.into());
}
} }
async fn add(ev: &KobjectUevent) { async fn add(ev: &KobjectUevent) {
@@ -112,7 +121,7 @@ async fn add(ev: &KobjectUevent) {
let Some(devpath) = ev.devpath() else { let Some(devpath) = ev.devpath() else {
return; return;
}; };
let device = Device::new(); let device = Device::new(devpath);
device.apply_uevent(&ev); device.apply_uevent(&ev);
device.engine.exec(&crate::rules::get()).await; device.engine.exec(&crate::rules::get()).await;
@@ -140,6 +149,7 @@ async fn remove(ev: &KobjectUevent) {
device.remove_symlinks().await; device.remove_symlinks().await;
device.properties.clear();
DEVICES.write().unwrap().remove(devpath); DEVICES.write().unwrap().remove(devpath);
} }
@@ -221,6 +231,19 @@ pub async fn wait_for_idle() {
} }
} }
pub async fn wait_for_event() -> Option<String> {
let mut rx = NOTIFY.subscribe();
rx.recv().await.ok()
}
pub async fn get_device(name: &str) -> DbSection {
DB.section(name)
}
pub fn sync_db() {
DB.save();
}
fn dev_root() -> PathBuf { fn dev_root() -> PathBuf {
std::env::var("LXDEVICED_DEV_ROOT") std::env::var("LXDEVICED_DEV_ROOT")
.as_deref() .as_deref()
+13
View File
@@ -3,6 +3,7 @@ use ipc::{
server::{Connection, Listener}, server::{Connection, Listener},
}; };
use rule::parser::When; use rule::parser::When;
use rustc_hash::FxHashMap;
use serde::Serialize; use serde::Serialize;
use std::pin::Pin; use std::pin::Pin;
@@ -35,6 +36,8 @@ async fn handle_connection(mut connection: Connection) -> std::io::Result<()> {
Request::ReloadRules => handler(reload_rules()), Request::ReloadRules => handler(reload_rules()),
Request::ListDevices(when) => handler(list_devices(when)), Request::ListDevices(when) => handler(list_devices(when)),
Request::WaitForIdle => handler(wait_for_idle()), Request::WaitForIdle => handler(wait_for_idle()),
Request::WaitForEvent => handler(wait_for_event()),
Request::GetDevice(name) => handler(get_device(name)),
}; };
let reply = handler.await; let reply = handler.await;
connection.send(reply).await?; connection.send(reply).await?;
@@ -63,6 +66,16 @@ async fn wait_for_idle() -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn wait_for_event() -> Result<String, Error> {
crate::device::wait_for_event()
.await
.ok_or(Error::PermissionDenied)
}
async fn get_device(name: String) -> Result<FxHashMap<String, String>, Error> {
Ok(crate::device::get_device(&name).await.dump())
}
fn handler<R: Serialize>( fn handler<R: Serialize>(
fut: impl Future<Output = Result<R, Error>> + Send + 'static, fut: impl Future<Output = Result<R, Error>> + Send + 'static,
) -> Pin<Box<dyn Future<Output = Result<serde_json::Value, Error>> + Send>> { ) -> Pin<Box<dyn Future<Output = Result<serde_json::Value, Error>> + Send>> {
+1
View File
@@ -15,6 +15,7 @@ pub fn start_monitor() -> std::io::Result<()> {
crate::device::update_devnode(&ev).await; crate::device::update_devnode(&ev).await;
tokio::spawn(async move { tokio::spawn(async move {
crate::device::handle_uevent(&ev).await; crate::device::handle_uevent(&ev).await;
crate::device::sync_db();
}); });
} }
}); });
+2
View File
@@ -10,4 +10,6 @@ crate-type = ["cdylib"]
ipc = { path = "../../libs/ipc" } ipc = { path = "../../libs/ipc" }
libc = "0.2" libc = "0.2"
rustc-hash = "2" rustc-hash = "2"
db = { path = "../../libs/db" }
fnmatch-regex = "0.3"
rule = { path = "../../libs/rule" } rule = { path = "../../libs/rule" }
+219 -126
View File
@@ -1,26 +1,28 @@
mod util;
use crate::util::FfiStringPool;
use db::Db;
use ipc::client::Client; use ipc::client::Client;
use rule::parser::{Str, When}; use rule::{
engine::Engine,
parser::{Str, When},
};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{ use std::{
ffi::{CStr, CString}, ffi::CStr,
str::FromStr, os::fd::AsRawFd,
sync::atomic::{self, AtomicUsize}, sync::atomic::{self, AtomicUsize},
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct Udev { pub struct Udev {
client: Client,
userdata: *mut u8, userdata: *mut u8,
refcount: AtomicUsize, refcount: AtomicUsize,
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_new() -> *mut Udev { pub unsafe extern "C" fn udev_new() -> *mut Udev {
let Ok(client) = Client::connect() else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(Udev { Box::into_raw(Box::new(Udev {
client,
userdata: std::ptr::null_mut(), userdata: std::ptr::null_mut(),
refcount: AtomicUsize::new(1), refcount: AtomicUsize::new(1),
})) }))
@@ -61,8 +63,8 @@ pub unsafe extern "C" fn udev_set_userdata(udev: *mut Udev, userdata: *mut u8) {
#[derive(Debug)] #[derive(Debug)]
pub struct UdevListEntry { pub struct UdevListEntry {
key: CString, key: *const libc::c_char,
value: CString, value: *const libc::c_char,
next: Option<Box<UdevListEntry>>, next: Option<Box<UdevListEntry>>,
} }
@@ -87,7 +89,7 @@ pub unsafe extern "C" fn udev_list_entry_get_by_name(
unsafe { unsafe {
let name = CStr::from_ptr(name); let name = CStr::from_ptr(name);
while !entry.is_null() { while !entry.is_null() {
if (*entry).key == name { if CStr::from_ptr((*entry).key) == name {
return entry; return entry;
} }
entry = udev_list_entry_get_next(entry); entry = udev_list_entry_get_next(entry);
@@ -100,14 +102,14 @@ pub unsafe extern "C" fn udev_list_entry_get_by_name(
pub unsafe extern "C" fn udev_list_entry_get_name( pub unsafe extern "C" fn udev_list_entry_get_name(
entry: *const UdevListEntry, entry: *const UdevListEntry,
) -> *const libc::c_char { ) -> *const libc::c_char {
unsafe { (*entry).key.as_ptr() } unsafe { (*entry).key }
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_list_entry_get_value( pub unsafe extern "C" fn udev_list_entry_get_value(
entry: *const UdevListEntry, entry: *const UdevListEntry,
) -> *const libc::c_char { ) -> *const libc::c_char {
unsafe { (*entry).value.as_ptr() } unsafe { (*entry).value }
} }
#[derive(Debug)] #[derive(Debug)]
@@ -115,40 +117,33 @@ pub struct UdevDevice {
refcount: AtomicUsize, refcount: AtomicUsize,
udev: *mut Udev, udev: *mut Udev,
properties: UdevListEntry, properties: UdevListEntry,
syspath: Option<CString>, syspath: Option<*const libc::c_char>,
devlinks: UdevListEntry, devlinks: UdevListEntry,
tags: UdevListEntry, tags: UdevListEntry,
sysattr: UdevListEntry, sysattr: UdevListEntry,
action: Option<CString>,
seqnum: libc::c_ulonglong, seqnum: libc::c_ulonglong,
sysattr_cache: FxHashMap<CString, CString>, strings: FfiStringPool,
} }
impl UdevDevice { impl UdevDevice {
fn new(udev: *mut Udev, properties: FxHashMap<String, String>) -> Self { fn new(udev: *mut Udev, properties: FxHashMap<String, String>) -> Self {
let syspath_str = properties.get("DEVPATH").map(|x| format!("/sys/{x}")); let mut strings = FfiStringPool::new();
let syspath = syspath_str let syspath = properties.get("DEVPATH").map(|x| format!("/sys/{x}"));
.as_deref() let devlinks = util::parse_zero_separated_list(
.map(|x| CString::from_str(x).unwrap_or_default()); properties
let devlinks = properties
.get("SYMLINKS") .get("SYMLINKS")
.map(|x| x.as_str()) .map(|x| x.as_str())
.unwrap_or_default() .unwrap_or_default(),
.split('\0') );
.filter(|x| !x.is_empty()) let devlinks = vec_to_list(&mut strings, &devlinks);
.map(|x| x.to_string()) let tags = util::parse_zero_separated_list(
.collect(); properties
let devlinks = vec_to_list(&devlinks);
let tags = properties
.get("TAGS") .get("TAGS")
.map(|x| x.as_str()) .map(|x| x.as_str())
.unwrap_or_default() .unwrap_or_default(),
.split('\0') );
.filter(|x| !x.is_empty()) let tags = vec_to_list(&mut strings, &tags);
.map(|x| x.to_string())
.collect();
let tags = vec_to_list(&tags);
let mut sysattr = Vec::new(); let mut sysattr = Vec::new();
if let Some(syspath_str) = &syspath_str if let Some(syspath_str) = &syspath
&& let Ok(read_dir) = std::fs::read_dir(syspath_str) && let Ok(read_dir) = std::fs::read_dir(syspath_str)
{ {
for i in read_dir { for i in read_dir {
@@ -158,18 +153,18 @@ impl UdevDevice {
sysattr.push(i.file_name().to_string_lossy().to_string()); sysattr.push(i.file_name().to_string_lossy().to_string());
} }
} }
let sysattr = vec_to_list(&sysattr); let sysattr = vec_to_list(&mut strings, &sysattr);
let properties = *hashmap_to_list(&mut strings, &properties);
Self { Self {
refcount: AtomicUsize::new(1), refcount: AtomicUsize::new(1),
udev, udev: unsafe { udev_ref(udev) },
properties: *hashmap_to_list(&properties), properties,
syspath, syspath: syspath.map(|x| strings.insert_rust(x).as_ptr()),
devlinks: *devlinks, devlinks: *devlinks,
tags: *tags, tags: *tags,
sysattr: *sysattr, sysattr: *sysattr,
action: None,
seqnum: 0, seqnum: 0,
sysattr_cache: Default::default(), strings,
} }
} }
} }
@@ -183,19 +178,58 @@ impl Drop for UdevDevice {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_device_new_from_syspath( pub unsafe extern "C" fn udev_device_new_from_syspath(
_udev: *mut Udev, udev: *mut Udev,
_syspath: *const libc::c_char, syspath: *const libc::c_char,
) -> *mut UdevDevice { ) -> *mut UdevDevice {
std::ptr::null_mut() unsafe {
let Ok(syspath) = CStr::from_ptr(syspath).to_str() else {
return std::ptr::null_mut();
};
let Some(devpath) = syspath.strip_prefix("/sys") else {
return std::ptr::null_mut();
};
udev_device_new_from_lxdeviced_name(udev, devpath.into())
}
}
fn udev_device_new_from_lxdeviced_name(udev: *mut Udev, name: String) -> *mut UdevDevice {
let Ok(mut client) = Client::connect() else {
return std::ptr::null_mut();
};
let Ok(properties) = client.get_device(name) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(UdevDevice::new(udev, properties)))
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_device_new_from_devnum( pub unsafe extern "C" fn udev_device_new_from_devnum(
_udev: *mut Udev, udev: *mut Udev,
_type: libc::c_char, ty: libc::c_char,
_devnum: libc::dev_t, devnum: libc::dev_t,
) -> *mut UdevDevice { ) -> *mut UdevDevice {
std::ptr::null_mut() let block = When::Match(Str::Direct("SUBSYSTEM".into()), Str::Direct("block".into()));
let ty = match ty {
b'b' => block,
b'c' => When::Not(Box::new(block)),
_ => return std::ptr::null_mut(),
};
let major = libc::major(devnum);
let minor = libc::minor(devnum);
let major = When::Match(Str::Direct("MAJOR".into()), Str::Direct(major.to_string()));
let minor = When::Match(Str::Direct("MINOR".into()), Str::Direct(minor.to_string()));
let devnum = When::All(vec![major, minor]);
let when = When::All(vec![ty, devnum]);
let Ok(mut client) = Client::connect() else {
return std::ptr::null_mut();
};
let Ok(result) = client.list_devices(when) else {
return std::ptr::null_mut();
};
let Some(devname) = result.first() else {
return std::ptr::null_mut();
};
udev_device_new_from_lxdeviced_name(udev, devname.into())
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -277,13 +311,7 @@ pub unsafe extern "C" fn udev_device_get_devtype(dev: *const UdevDevice) -> *con
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_device_get_syspath(dev: *const UdevDevice) -> *const libc::c_char { pub unsafe extern "C" fn udev_device_get_syspath(dev: *const UdevDevice) -> *const libc::c_char {
unsafe { unsafe { (*dev).syspath.as_ref().copied().unwrap_or_default() }
(*dev)
.syspath
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or_default()
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -292,12 +320,15 @@ pub unsafe extern "C" fn udev_device_get_sysname(dev: *const UdevDevice) -> *con
if dev.is_null() { if dev.is_null() {
return std::ptr::null(); return std::ptr::null();
} }
let Some(syspath) = (*dev).syspath.as_ref() else { let Some(syspath) = (*dev).syspath else {
return std::ptr::null(); return std::ptr::null();
}; };
let split_pos = syspath.as_bytes().iter().rposition(|x| *x == b'/'); let split_pos = CStr::from_ptr(syspath)
.to_bytes()
.iter()
.rposition(|x| *x == b'/');
let offset = split_pos.map(|x| x + 1).unwrap_or_default(); let offset = split_pos.map(|x| x + 1).unwrap_or_default();
syspath.as_ptr().add(offset) syspath.add(offset)
} }
} }
@@ -372,7 +403,13 @@ pub unsafe extern "C" fn udev_device_get_property_value(
dev: *const UdevDevice, dev: *const UdevDevice,
key: *const libc::c_char, key: *const libc::c_char,
) -> *const libc::c_char { ) -> *const libc::c_char {
unsafe { udev_list_entry_get_value(udev_list_entry_get_by_name(&(*dev).properties, key)) } unsafe {
let entry = udev_list_entry_get_by_name(&(*dev).properties, key);
if entry.is_null() {
return std::ptr::null_mut();
}
udev_list_entry_get_value(entry)
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -398,13 +435,7 @@ pub unsafe extern "C" fn udev_device_get_devnum(dev: *const UdevDevice) -> libc:
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_device_get_action(dev: *const UdevDevice) -> *const libc::c_char { pub unsafe extern "C" fn udev_device_get_action(dev: *const UdevDevice) -> *const libc::c_char {
unsafe { unsafe { udev_device_get_property_value(dev, c"ACTION".as_ptr()) }
(*dev)
.action
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or_default()
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -429,26 +460,21 @@ pub unsafe extern "C" fn udev_device_get_sysattr_value(
let Ok(sysattr_str) = sysattr.to_str() else { let Ok(sysattr_str) = sysattr.to_str() else {
return std::ptr::null(); return std::ptr::null();
}; };
if let Some(val) = (*dev).sysattr_cache.get(sysattr) {
return val.as_ptr();
}
let Some(syspath) = (*dev).syspath.as_ref() else { let Some(syspath) = (*dev).syspath.as_ref() else {
return std::ptr::null(); return std::ptr::null();
}; };
let Ok(mut path) = String::from_utf8(syspath.as_bytes().to_vec()) else { let Ok(mut path) = String::from_utf8(CStr::from_ptr(*syspath).to_bytes().to_vec()) else {
return std::ptr::null(); return std::ptr::null();
}; };
path.push('/'); path.push('/');
path.push_str(sysattr_str); path.push_str(sysattr_str);
let Ok(data) = std::fs::read_to_string(path) else { let Ok(mut data) = std::fs::read_to_string(path) else {
return std::ptr::null(); return std::ptr::null();
}; };
let Ok(data) = CString::from_str(&data) else { if data.ends_with("\n") {
return std::ptr::null(); data.pop();
}; }
let ptr = data.as_ptr(); (*dev).strings.insert_rust(data).as_ptr()
(*dev).sysattr_cache.insert(CString::from(sysattr), data);
ptr
} }
} }
@@ -472,21 +498,12 @@ pub unsafe extern "C" fn udev_device_set_sysattr_value(
Some(p) => p, Some(p) => p,
None => return -libc::ENOENT, None => return -libc::ENOENT,
}; };
let path = match syspath.to_str() { let path = match CStr::from_ptr(*syspath).to_str() {
Ok(p) => format!("{}/{}", p, sysattr), Ok(p) => format!("{}/{}", p, sysattr),
Err(_) => return -libc::ENOENT, Err(_) => return -libc::ENOENT,
}; };
match std::fs::write(&path, value) { match std::fs::write(&path, value) {
Ok(_) => { Ok(_) => 0,
if let Ok(cstr) = CString::new(value) {
let key = match CString::new(sysattr) {
Ok(k) => k,
Err(_) => return 0,
};
dev.sysattr_cache.insert(key, cstr);
}
0
}
Err(e) => match e.raw_os_error() { Err(e) => match e.raw_os_error() {
Some(code) => -code, Some(code) => -code,
None => -libc::EIO, None => -libc::EIO,
@@ -519,6 +536,8 @@ pub unsafe extern "C" fn udev_device_has_current_tag(
pub struct UdevMonitor { pub struct UdevMonitor {
udev: *mut Udev, udev: *mut Udev,
refcount: AtomicUsize, refcount: AtomicUsize,
client: Client,
conditions: Vec<When>,
} }
impl Drop for UdevMonitor { impl Drop for UdevMonitor {
fn drop(&mut self) { fn drop(&mut self) {
@@ -560,20 +579,30 @@ pub unsafe extern "C" fn udev_monitor_new_from_netlink(
name: *const libc::c_char, name: *const libc::c_char,
) -> *mut UdevMonitor { ) -> *mut UdevMonitor {
unsafe { unsafe {
let name = CStr::from_ptr(name); if CStr::from_ptr(name) != c"udev" {
if name != c"udev" {
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
let Ok(client) = Client::connect() else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(UdevMonitor { Box::into_raw(Box::new(UdevMonitor {
udev, udev: udev_ref(udev),
refcount: AtomicUsize::new(1), refcount: AtomicUsize::new(1),
client,
conditions: Vec::new(),
})) }))
} }
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_enable_receiving(_: *const UdevMonitor) -> libc::c_int { pub unsafe extern "C" fn udev_monitor_enable_receiving(monitor: *mut UdevMonitor) -> libc::c_int {
-libc::EPERM unsafe {
let monitor = &mut *monitor;
match monitor.client.send(&ipc::repr::Request::WaitForEvent) {
Ok(()) => 0,
Err(err) => -err.raw_os_error().unwrap_or(libc::EPERM),
}
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -585,40 +614,94 @@ pub unsafe extern "C" fn udev_monitor_set_receive_buffer_size(
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_get_fd(_: *const UdevMonitor) -> libc::c_int { pub unsafe extern "C" fn udev_monitor_get_fd(monitor: *const UdevMonitor) -> libc::c_int {
-1 unsafe { (*monitor).client.inner().as_raw_fd() }
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_receive_device(_: *const UdevMonitor) -> *mut UdevDevice { pub unsafe extern "C" fn udev_monitor_receive_device(monitor: *mut UdevMonitor) -> *mut UdevDevice {
std::ptr::null_mut() unsafe {
let monitor = &mut *monitor;
let db = Db::open_in_memory();
loop {
let result = monitor.client.recv::<String>();
udev_monitor_enable_receiving(monitor);
let Ok(Ok(result)) = result else {
return std::ptr::null_mut();
};
let Ok(device) = monitor.client.get_device(result) else {
return std::ptr::null_mut();
};
let section = db.section("default");
section.restore(device.clone());
let engine = Engine::with_properties(section);
if !engine
.exec_condition(&When::All(monitor.conditions.clone()))
.unwrap_or_default()
{
return std::ptr::null_mut();
}
return Box::into_raw(Box::new(UdevDevice::new(monitor.udev, device)));
}
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_filter_add_match_subsystem_devtype( pub unsafe extern "C" fn udev_monitor_filter_add_match_subsystem_devtype(
_mon: *mut UdevMonitor, mon: *mut UdevMonitor,
_subsystem: *const libc::c_char, subsystem: *const libc::c_char,
_devtype: *const libc::c_char, devtype: *const libc::c_char,
) -> libc::c_int { ) -> libc::c_int {
-libc::EPERM unsafe {
udev_monitor_match(mon, "SUBSYSTEM", subsystem, false);
udev_monitor_match(mon, "DEVTYPE", devtype, false);
0
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_filter_add_match_tag( pub unsafe extern "C" fn udev_monitor_filter_add_match_tag(
_mon: *mut UdevMonitor, mon: *mut UdevMonitor,
_tag: *const libc::c_char, tag: *const libc::c_char,
) -> libc::c_int { ) -> libc::c_int {
-libc::EPERM unsafe { udev_monitor_match(mon, "TAGS", tag, false) }
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_filter_update(_mon: *mut UdevMonitor) -> libc::c_int { pub unsafe extern "C" fn udev_monitor_filter_update(_mon: *mut UdevMonitor) -> libc::c_int {
-libc::EPERM 0
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_monitor_filter_remove(_mon: *mut UdevMonitor) -> libc::c_int { pub unsafe extern "C" fn udev_monitor_filter_remove(mon: *mut UdevMonitor) -> libc::c_int {
-libc::EPERM unsafe {
(*mon).conditions.clear();
0
}
}
unsafe fn udev_monitor_match(
this: *mut UdevMonitor,
key: &str,
data: *const libc::c_char,
not: bool,
) -> libc::c_int {
unsafe {
if data.is_null() {
return 0;
}
let Ok(data) = CStr::from_ptr(data).to_str() else {
return -libc::EINVAL;
};
let mut when = When::Match(Str::Direct(key.into()), Str::Direct(data.into()));
if not {
when = When::Not(Box::new(when));
}
(*this).conditions.push(when);
0
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -627,6 +710,7 @@ pub struct UdevEnumerate {
refcount: AtomicUsize, refcount: AtomicUsize,
filters: Vec<When>, filters: Vec<When>,
result: Option<UdevListEntry>, result: Option<UdevListEntry>,
strings: FfiStringPool,
} }
impl Drop for UdevEnumerate { impl Drop for UdevEnumerate {
fn drop(&mut self) { fn drop(&mut self) {
@@ -664,12 +748,15 @@ pub unsafe extern "C" fn udev_enumerate_get_udev(p: *mut UdevEnumerate) -> *mut
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn udev_enumerate_new(udev: *mut Udev) -> *mut UdevEnumerate { pub unsafe extern "C" fn udev_enumerate_new(udev: *mut Udev) -> *mut UdevEnumerate {
unsafe {
Box::into_raw(Box::new(UdevEnumerate { Box::into_raw(Box::new(UdevEnumerate {
udev, udev: udev_ref(udev),
refcount: AtomicUsize::new(1), refcount: AtomicUsize::new(1),
filters: Vec::with_capacity(16), filters: Vec::with_capacity(16),
result: None, result: None,
strings: FfiStringPool::new(),
})) }))
}
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -735,7 +822,7 @@ pub unsafe extern "C" fn udev_enumerate_add_match_sysname(
udev_enumerate: *mut UdevEnumerate, udev_enumerate: *mut UdevEnumerate,
sysname: *const libc::c_char, sysname: *const libc::c_char,
) -> libc::c_int { ) -> libc::c_int {
unsafe { udev_enumerate_match(udev_enumerate, "SYSNAME", sysname, false) } unsafe { udev_enumerate_match(udev_enumerate, "DEVNAME", sysname, false) }
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@@ -774,13 +861,18 @@ pub unsafe extern "C" fn udev_enumerate_scan_devices(
udev_enumerate: *mut UdevEnumerate, udev_enumerate: *mut UdevEnumerate,
) -> libc::c_int { ) -> libc::c_int {
unsafe { unsafe {
let udev = (*udev_enumerate).udev;
let when = When::All((*udev_enumerate).filters.clone()); let when = When::All((*udev_enumerate).filters.clone());
let result = match (*udev).client.list_devices(when) { let Ok(mut client) = Client::connect() else {
return -libc::EPERM;
};
let mut result = match client.list_devices(when) {
Ok(x) => x, Ok(x) => x,
Err(_) => return -libc::EPERM, Err(_) => return -libc::EPERM,
}; };
(*udev_enumerate).result = Some(*vec_to_list(&result)); for i in &mut result {
i.insert_str(0, "/sys");
}
(*udev_enumerate).result = Some(*vec_to_list(&mut (*udev_enumerate).strings, &result));
0 0
} }
} }
@@ -815,7 +907,8 @@ unsafe fn udev_enumerate_match(
let Ok(data) = CStr::from_ptr(data).to_str() else { let Ok(data) = CStr::from_ptr(data).to_str() else {
return -libc::EINVAL; return -libc::EINVAL;
}; };
let mut when = When::Match(Str::Direct(key.into()), Str::Direct(data.into())); let regex = fnmatch_regex::glob_to_regex_pattern(data).unwrap_or_default();
let mut when = When::PartMatch(Str::Direct(key.into()), Str::Direct((&regex[1..]).into()));
if not { if not {
when = When::Not(Box::new(when)); when = When::Not(Box::new(when));
} }
@@ -906,16 +999,17 @@ pub unsafe extern "C" fn udev_util_encode_string(
len as _ len as _
} }
fn hashmap_to_list(hashmap: &FxHashMap<String, String>) -> Box<UdevListEntry> { fn hashmap_to_list(
strings: &mut FfiStringPool,
hashmap: &FxHashMap<String, String>,
) -> Box<UdevListEntry> {
let mut head = None; let mut head = None;
let mut tail = &mut head; let mut tail = &mut head;
for (key, value) in hashmap { for (key, value) in hashmap {
let key_cstr = CString::new(key.as_str()).unwrap();
let value_cstr = CString::new(value.as_str()).unwrap();
let entry = Box::new(UdevListEntry { let entry = Box::new(UdevListEntry {
key: key_cstr, key: strings.insert_rust(key.clone()).as_ptr(),
value: value_cstr, value: strings.insert_rust(value.clone()).as_ptr(),
next: None, next: None,
}); });
*tail = Some(entry); *tail = Some(entry);
@@ -923,21 +1017,20 @@ fn hashmap_to_list(hashmap: &FxHashMap<String, String>) -> Box<UdevListEntry> {
} }
head.unwrap_or(Box::new(UdevListEntry { head.unwrap_or(Box::new(UdevListEntry {
key: CString::new("").unwrap(), key: std::ptr::null(),
value: CString::new("").unwrap(), value: std::ptr::null(),
next: None, next: None,
})) }))
} }
fn vec_to_list(vec: &Vec<String>) -> Box<UdevListEntry> { fn vec_to_list(strings: &mut FfiStringPool, vec: &Vec<String>) -> Box<UdevListEntry> {
let mut head = None; let mut head = None;
let mut tail = &mut head; let mut tail = &mut head;
for s in vec { for s in vec {
let cstr = CString::new(s.as_str()).unwrap();
let entry = Box::new(UdevListEntry { let entry = Box::new(UdevListEntry {
key: cstr, key: strings.insert_rust(s.clone()).as_ptr(),
value: CString::new("").unwrap(), value: strings.insert_rust("".into()).as_ptr(),
next: None, next: None,
}); });
*tail = Some(entry); *tail = Some(entry);
@@ -946,8 +1039,8 @@ fn vec_to_list(vec: &Vec<String>) -> Box<UdevListEntry> {
head.unwrap_or_else(|| { head.unwrap_or_else(|| {
Box::new(UdevListEntry { Box::new(UdevListEntry {
key: CString::new("").unwrap(), key: std::ptr::null(),
value: CString::new("").unwrap(), value: std::ptr::null(),
next: None, next: None,
}) })
}) })
+28
View File
@@ -0,0 +1,28 @@
use rustc_hash::FxHashMap;
use std::ffi::{CStr, CString};
#[derive(Debug)]
pub struct FfiStringPool {
strings: FxHashMap<String, CString>,
}
impl FfiStringPool {
pub fn new() -> Self {
Self {
strings: FxHashMap::default(),
}
}
pub fn insert_rust(&mut self, s: String) -> &CStr {
let s = s.replace("\0", "");
let entry = self.strings.entry(s);
let value = entry.or_insert_with_key(|key| CString::new(key.clone()).unwrap());
unsafe { &*(value.as_c_str() as *const CStr) }
}
}
pub fn parse_zero_separated_list(s: &str) -> Vec<String> {
s.split('\0')
.filter(|x| !x.is_empty())
.map(|x| x.to_string())
.collect()
}
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "db"
version = "0.1.0"
edition = "2024"
[dependencies]
rustc-hash = "2"
serde_json = "1"
+115
View File
@@ -0,0 +1,115 @@
use rustc_hash::FxHashMap;
use std::{
path::PathBuf,
sync::{Arc, RwLock},
};
pub const PERSIST_PATH: &str = "/run/lxdeviced.json";
#[derive(Debug)]
pub struct Db {
path: Option<PathBuf>,
map: RwLock<FxHashMap<String, String>>,
}
impl Db {
pub fn open_in_memory() -> Arc<Self> {
Arc::new(Self {
path: None,
map: Default::default(),
})
}
pub fn open(path: impl Into<PathBuf>) -> Arc<Self> {
let path = path.into();
let text = std::fs::read_to_string(&path).unwrap_or_default();
let map = serde_json::from_str(&text).unwrap_or_default();
Arc::new(Self {
path: Some(path),
map: RwLock::new(map),
})
}
pub fn section(self: &Arc<Self>, name: impl Into<String>) -> DbSection {
DbSection {
name: name.into(),
db: self.clone(),
}
}
pub fn save(&self) {
if let Some(path) = &self.path {
let text = serde_json::to_string(&*self.map.read().unwrap()).unwrap();
_ = std::fs::write(&path, text);
}
}
}
#[derive(Debug)]
pub struct DbSection {
name: String,
db: Arc<Db>,
}
impl DbSection {
pub fn db(&self) -> &Arc<Db> {
&self.db
}
pub fn clear(&self) {
let mut map = self.db.map.write().unwrap();
let mut keys = Vec::new();
let prefix = self.real_key_name("");
for key in map.keys() {
if key.starts_with(&prefix) {
keys.push(key.clone());
}
}
for key in keys {
map.remove(&key);
}
}
pub fn insert(&self, key: &str, val: String) -> Option<String> {
let mut map = self.db.map.write().unwrap();
map.insert(self.real_key_name(key), val)
}
pub fn get(&self, key: &str) -> Option<String> {
self.db
.map
.read()
.unwrap()
.get(&self.real_key_name(key))
.cloned()
}
pub fn remove(&self, key: &str) -> Option<String> {
self.db
.map
.write()
.unwrap()
.remove(&self.real_key_name(key))
}
pub fn restore(&self, dump: FxHashMap<String, String>) {
self.clear();
for (k, v) in dump {
self.insert(&k, v);
}
}
pub fn dump(&self) -> FxHashMap<String, String> {
let mut result = FxHashMap::default();
let prefix = self.real_key_name("");
let total = self.db.map.read().unwrap();
for (key, val) in total.iter() {
if let Some(realkey) = key.strip_prefix(&prefix) {
result.insert(realkey.into(), val.into());
}
}
result
}
fn real_key_name(&self, name: &str) -> String {
format!("{}\0{}", self.name, name)
}
}
+1
View File
@@ -9,3 +9,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
thiserror = "2" thiserror = "2"
tokio = { version = "1", features = ["net", "io-util"] } tokio = { version = "1", features = ["net", "io-util"] }
rustc-hash = "2"
+9
View File
@@ -3,6 +3,7 @@ use crate::{
repr::{Error, Request, de_reply}, repr::{Error, Request, de_reply},
}; };
use rule::parser::When; use rule::parser::When;
use rustc_hash::FxHashMap;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::{ use std::{
io::{Read, Write}, io::{Read, Write},
@@ -26,6 +27,10 @@ impl Client {
}) })
} }
pub fn inner(&self) -> &UnixStream {
&self.stream
}
pub fn send(&mut self, req: &Request) -> std::io::Result<()> { pub fn send(&mut self, req: &Request) -> std::io::Result<()> {
self.buf.clear(); self.buf.clear();
serde_json::to_writer(&mut self.buf, req).unwrap(); serde_json::to_writer(&mut self.buf, req).unwrap();
@@ -61,6 +66,10 @@ impl Client {
self.invoke(&Request::ListDevices(when)) self.invoke(&Request::ListDevices(when))
} }
pub fn get_device(&mut self, name: String) -> Result<FxHashMap<String, String>, Error> {
self.invoke(&Request::GetDevice(name))
}
pub fn wait_for_idle(&mut self) -> Result<(), Error> { pub fn wait_for_idle(&mut self) -> Result<(), Error> {
self.invoke(&Request::WaitForIdle) self.invoke(&Request::WaitForIdle)
} }
+6
View File
@@ -15,6 +15,12 @@ pub enum Request {
#[serde(rename = "/org.semilabs.os/LxDeviced/WaitForIdle@LXDEVICED_0.1.0")] #[serde(rename = "/org.semilabs.os/LxDeviced/WaitForIdle@LXDEVICED_0.1.0")]
WaitForIdle, WaitForIdle,
#[serde(rename = "/org.semilabs.os/LxDeviced/WaitForEvent@LXDEVICED_0.1.0")]
WaitForEvent,
#[serde(rename = "/org.semilabs.os/LxDeviced/GetDevice@LXDEVICED_0.1.0")]
GetDevice(String),
} }
pub fn ser_reply<T: Serialize>(result: Result<T, Error>) -> String { pub fn ser_reply<T: Serialize>(result: Result<T, Error>) -> String {
+1
View File
@@ -12,3 +12,4 @@ serde = { version = "1", features = ["derive"] }
thiserror = "2" thiserror = "2"
tokio = { version = "1", features = ["process", "time"] } tokio = { version = "1", features = ["process", "time"] }
tracing = "0.1" tracing = "0.1"
db = { path = "../db" }
+32 -49
View File
@@ -2,12 +2,12 @@ use crate::{
Error, Error,
parser::{Action, OnError, Rule, RuleFile, Str, When}, parser::{Action, OnError, Rule, RuleFile, Str, When},
}; };
use db::DbSection;
use regex::Regex; use regex::Regex;
use rustc_hash::FxHashMap;
use std::{ use std::{
path::Path, path::Path,
process::{ExitStatus, Stdio}, process::{ExitStatus, Stdio},
sync::{Arc, RwLock}, sync::Arc,
time::Duration, time::Duration,
}; };
use tokio::{io::AsyncReadExt, process::Child}; use tokio::{io::AsyncReadExt, process::Child};
@@ -29,29 +29,25 @@ impl RuleSet {
); );
continue; continue;
}; };
for file in read_dir { let mut files: Vec<_> = read_dir.filter_map(|x| x.ok()).map(|x| x.path()).collect();
let Ok(file) = file else { files.sort_unstable();
tracing::warn!( for file in files {
"Error while reading directory \"{}\" for rule files.", if file
dir.as_ref().display() .file_name()
); .unwrap()
break; .as_encoded_bytes()
}; .starts_with(b".")
if file.file_name().as_encoded_bytes().starts_with(b".") { {
continue; continue;
} }
let rule_file = match RuleFile::parse(file.path()) { let rule_file = match RuleFile::parse(&file) {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {
tracing::warn!( tracing::warn!("Failed to parse rule file \"{}\": {}", file.display(), e,);
"Failed to parse rule file \"{}\": {}",
file.path().display(),
e,
);
continue; continue;
} }
}; };
tracing::debug!("Activating rule file \"{}\"", file.path().display()); tracing::debug!("Activating rule file \"{}\"", file.display());
rule_files.push(rule_file); rule_files.push(rule_file);
} }
} }
@@ -61,31 +57,20 @@ impl RuleSet {
#[derive(Debug)] #[derive(Debug)]
pub struct Engine { pub struct Engine {
properties: RwLock<FxHashMap<String, String>>, pub properties: DbSection,
command_policy: CommandPolicy, command_policy: CommandPolicy,
} }
impl Engine { impl Engine {
pub fn new() -> Self { pub fn with_properties(properties: DbSection) -> Self {
Self { Self {
properties: Default::default(), properties,
command_policy: CommandPolicy::default(), command_policy: CommandPolicy::default(),
} }
} }
pub fn get_property(&self, key: &str) -> Option<String> {
self.properties.read().unwrap().get(key).cloned()
}
pub fn set_property(&self, key: String, val: String) {
self.properties.write().unwrap().insert(key, val);
}
pub fn remove_property(&self, key: &str) {
self.properties.write().unwrap().remove(key);
}
pub fn get_list(&self, key: &str) -> Vec<String> { pub fn get_list(&self, key: &str) -> Vec<String> {
self.get_property(key) self.properties
.get(key)
.unwrap_or_default() .unwrap_or_default()
.split('\0') .split('\0')
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
@@ -93,13 +78,15 @@ impl Engine {
.collect() .collect()
} }
pub fn list_add(&self, key: String, val: &str) { pub fn list_add(&self, key: &str, val: &str) {
let orig = self.get_property(&key).unwrap_or_default(); let orig = self.properties.get(&key).unwrap_or_default();
self.set_property(key, format!("{orig}{val}\0")); self.properties.insert(&key, format!("{orig}{val}\0"));
} }
pub async fn exec(&self, rule_set: &RuleSet) { pub async fn exec(&self, rule_set: &RuleSet) {
for rule_file in rule_set.0.iter() { for rule_file in rule_set.0.iter() {
self.properties
.insert("RULEFILE", rule_file.path.display().to_string());
for rule in rule_file.rules.iter() { for rule in rule_file.rules.iter() {
if let Err(err) = self.exec_rule(rule).await { if let Err(err) = self.exec_rule(rule).await {
if !matches!(rule.on_error, OnError::Continue) { if !matches!(rule.on_error, OnError::Continue) {
@@ -149,7 +136,7 @@ impl Engine {
let regex = self.bake_str(regex)?; let regex = self.bake_str(regex)?;
let regex = format!("^{regex}$"); let regex = format!("^{regex}$");
let regex = Regex::new(&regex).map_err(Error::Regex)?; let regex = Regex::new(&regex).map_err(Error::Regex)?;
let Some(val) = self.get_property(&key) else { let Some(val) = self.properties.get(&key) else {
return Ok(false); return Ok(false);
}; };
Ok(regex.is_match(&val)) Ok(regex.is_match(&val))
@@ -158,7 +145,7 @@ impl Engine {
let key = self.bake_str(key)?; let key = self.bake_str(key)?;
let regex = self.bake_str(regex)?; let regex = self.bake_str(regex)?;
let regex = Regex::new(&regex).map_err(Error::Regex)?; let regex = Regex::new(&regex).map_err(Error::Regex)?;
let Some(val) = self.get_property(&key) else { let Some(val) = self.properties.get(&key) else {
return Ok(false); return Ok(false);
}; };
Ok(regex.is_match(&val)) Ok(regex.is_match(&val))
@@ -171,15 +158,15 @@ impl Engine {
Action::Set(key, val) => { Action::Set(key, val) => {
let key = self.bake_str(key)?; let key = self.bake_str(key)?;
let val = self.bake_str(val)?; let val = self.bake_str(val)?;
self.set_property(key, val); self.properties.insert(&key, val);
} }
Action::Add(key, val) => { Action::Add(key, val) => {
let key = self.bake_str(key)?; let key = self.bake_str(key)?;
let val = self.bake_str(val)?; let val = self.bake_str(val)?;
self.list_add(key, &val); self.list_add(&key, &val);
} }
Action::Unset(key) => { Action::Unset(key) => {
self.remove_property(&self.bake_str(key)?); self.properties.remove(&self.bake_str(key)?);
} }
Action::Command(cmd) => { Action::Command(cmd) => {
let cmd = self.bake_str(cmd)?; let cmd = self.bake_str(cmd)?;
@@ -189,7 +176,7 @@ impl Engine {
let key = self.bake_str(key)?; let key = self.bake_str(key)?;
let cmd = self.bake_str(cmd)?; let cmd = self.bake_str(cmd)?;
let val = self.command_policy.run_provider(&cmd).await?; let val = self.command_policy.run_provider(&cmd).await?;
self.set_property(key, val); self.properties.insert(&key, val);
} }
} }
Ok(()) Ok(())
@@ -216,7 +203,8 @@ impl Engine {
current_property = None; current_property = None;
} else if ch == '}' { } else if ch == '}' {
let value = self let value = self
.get_property(property) .properties
.get(property)
.ok_or_else(|| Error::PropertyNotFound(property.clone()))?; .ok_or_else(|| Error::PropertyNotFound(property.clone()))?;
result.push_str(&value); result.push_str(&value);
current_property = None; current_property = None;
@@ -239,11 +227,6 @@ impl Engine {
} }
} }
} }
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CommandPolicy { pub struct CommandPolicy {
+65
View File
@@ -0,0 +1,65 @@
[
(
when: All([
Match("SUBSYSTEM", "input"),
]),
do: [
UseProvider("SCRIPT_INPUT_ID", f("echo $(dirname {RULEFILE})/../scripts/input_id")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT_KEYBOARD", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT_KEYBOARD DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT_MOUSE", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT_MOUSE DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT_TOUCHPAD", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT_TOUCHPAD DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT_TOUCHSCREEN", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT_TOUCHSCREEN DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
(
when: All([
Match("SCRIPT_INPUT_ID", ".*"),
]),
do: [
UseProvider("ID_INPUT_JOYSTICK", f("{SCRIPT_INPUT_ID} MODE=ID_INPUT_JOYSTICK DEVPATH={DEVPATH}")),
],
on_error: Continue,
),
]
+125
View File
@@ -0,0 +1,125 @@
#!/bin/mksh
# Kernel constant definitions.
EV_KEY=1
EV_REL=2
EV_ABS=3
EV_FF=21
BTN_MOUSE=272
ASCII_KEYS="30 48 46 32 18 33 34 35 23 36 37 38 50 49 24 25 16 19 31 20 22 47 17 45 21 44"
INPUT_PROP_POINTER=0
INPUT_PROP_DIRECT=1
# Helper functions
bitmap_contains() {
hexstr="$1"
bitnum="$2"
char_from_right=$(( bitnum / 4 ))
bit_in_char=$(( bitnum % 4 ))
chars_per_group=16
group_index=$(( char_from_right / chars_per_group ))
char_in_group=$(( char_from_right % chars_per_group ))
set -A groups $hexstr
n=${#groups[@]}
idx=$(( n - 1 - group_index ))
if [ "$idx" -lt 0 ] || [ "$idx" -ge "$n" ]; then
return 1
fi
group=${groups[idx]}
while [ ${#group} -lt $chars_per_group ]; do
group="0$group"
done
pos=$(( chars_per_group - 1 - char_in_group ))
ch=${group:$pos:1}
case "$ch" in
0) v=0 ;; 1) v=1 ;; 2) v=2 ;; 3) v=3 ;;
4) v=4 ;; 5) v=5 ;; 6) v=6 ;; 7) v=7 ;;
8) v=8 ;; 9) v=9 ;;
a|A) v=10 ;; b|B) v=11 ;; c|C) v=12 ;; d|D) v=13 ;;
e|E) v=14 ;; f|F) v=15 ;;
*) return 1 ;;
esac
if [ $(( (v >> bit_in_char) & 1 )) -eq 1 ]; then
return 0
else
return 1
fi
}
ev_contains() {
evstr="$1"
evbit="$2"
evstr=$(print "$evstr" | tr -d ' \t\r\n')
val=$(( 16#${evstr} ))
if [ $(( (val >> evbit) & 1 )) -eq 1 ]; then
return 0
else
return 1
fi
}
export "$@"
SYSPATH="/sys/${DEVPATH}"
if [[ "$SYSPATH" = */event* ]]; then
SYSPATH="$SYSPATH/device"
fi
EV=$(cat "$SYSPATH/capabilities/ev")
KEY=$(cat "$SYSPATH/capabilities/key")
ABS=$(cat "$SYSPATH/capabilities/abs")
REL=$(cat "$SYSPATH/capabilities/rel")
PROPS=$(cat "$SYSPATH/properties")
case "$MODE" in
ID_INPUT)
echo 1
;;
ID_INPUT_MOUSE)
if bitmap_contains "$KEY" "$BTN_MOUSE" && ev_contains "$EV" "$EV_REL" ; then
echo 1
else
exit 1
fi
;;
ID_INPUT_KEYBOARD)
for i in $ASCII_KEYS; do
if ! bitmap_contains "$KEY" "$i"; then
exit 1
fi
done
echo 1
;;
ID_INPUT_TOUCHPAD)
if ev_contains "$EV" "$EV_REL" && bitmap_contains "$PROPS" "$INPUT_PROP_POINTER"; then
echo 1
else
exit 1
fi
;;
ID_INPUT_TOUCHSCREEN)
if ev_contains "$EV" "$EV_ABS" && bitmap_contains "$PROPS" "$INPUT_PROP_DIRECT"; then
echo 1
else
exit 1
fi
;;
ID_INPUT_JOYSTICK)
exit 1
;;
*)
exit 1
;;
esac