+1
-1
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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>> {
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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,13 +748,16 @@ 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)]
|
||||||
pub unsafe extern "C" fn udev_enumerate_add_match_subsystem(
|
pub unsafe extern "C" fn udev_enumerate_add_match_subsystem(
|
||||||
@@ -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((®ex[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,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "db"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rustc-hash = "2"
|
||||||
|
serde_json = "1"
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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(®ex).map_err(Error::Regex)?;
|
let regex = Regex::new(®ex).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(®ex).map_err(Error::Regex)?;
|
let regex = Regex::new(®ex).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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
]
|
||||||
Executable
+125
@@ -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
|
||||||
Reference in New Issue
Block a user