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
+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"
thiserror = "2"
tokio = { version = "1", features = ["net", "io-util"] }
rustc-hash = "2"
+9
View File
@@ -3,6 +3,7 @@ use crate::{
repr::{Error, Request, de_reply},
};
use rule::parser::When;
use rustc_hash::FxHashMap;
use serde::de::DeserializeOwned;
use std::{
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<()> {
self.buf.clear();
serde_json::to_writer(&mut self.buf, req).unwrap();
@@ -61,6 +66,10 @@ impl Client {
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> {
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")]
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 {
+1
View File
@@ -12,3 +12,4 @@ serde = { version = "1", features = ["derive"] }
thiserror = "2"
tokio = { version = "1", features = ["process", "time"] }
tracing = "0.1"
db = { path = "../db" }
+32 -49
View File
@@ -2,12 +2,12 @@ use crate::{
Error,
parser::{Action, OnError, Rule, RuleFile, Str, When},
};
use db::DbSection;
use regex::Regex;
use rustc_hash::FxHashMap;
use std::{
path::Path,
process::{ExitStatus, Stdio},
sync::{Arc, RwLock},
sync::Arc,
time::Duration,
};
use tokio::{io::AsyncReadExt, process::Child};
@@ -29,29 +29,25 @@ impl RuleSet {
);
continue;
};
for file in read_dir {
let Ok(file) = file else {
tracing::warn!(
"Error while reading directory \"{}\" for rule files.",
dir.as_ref().display()
);
break;
};
if file.file_name().as_encoded_bytes().starts_with(b".") {
let mut files: Vec<_> = read_dir.filter_map(|x| x.ok()).map(|x| x.path()).collect();
files.sort_unstable();
for file in files {
if file
.file_name()
.unwrap()
.as_encoded_bytes()
.starts_with(b".")
{
continue;
}
let rule_file = match RuleFile::parse(file.path()) {
let rule_file = match RuleFile::parse(&file) {
Ok(x) => x,
Err(e) => {
tracing::warn!(
"Failed to parse rule file \"{}\": {}",
file.path().display(),
e,
);
tracing::warn!("Failed to parse rule file \"{}\": {}", file.display(), e,);
continue;
}
};
tracing::debug!("Activating rule file \"{}\"", file.path().display());
tracing::debug!("Activating rule file \"{}\"", file.display());
rule_files.push(rule_file);
}
}
@@ -61,31 +57,20 @@ impl RuleSet {
#[derive(Debug)]
pub struct Engine {
properties: RwLock<FxHashMap<String, String>>,
pub properties: DbSection,
command_policy: CommandPolicy,
}
impl Engine {
pub fn new() -> Self {
pub fn with_properties(properties: DbSection) -> Self {
Self {
properties: Default::default(),
properties,
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> {
self.get_property(key)
self.properties
.get(key)
.unwrap_or_default()
.split('\0')
.filter(|x| !x.is_empty())
@@ -93,13 +78,15 @@ impl Engine {
.collect()
}
pub fn list_add(&self, key: String, val: &str) {
let orig = self.get_property(&key).unwrap_or_default();
self.set_property(key, format!("{orig}{val}\0"));
pub fn list_add(&self, key: &str, val: &str) {
let orig = self.properties.get(&key).unwrap_or_default();
self.properties.insert(&key, format!("{orig}{val}\0"));
}
pub async fn exec(&self, rule_set: &RuleSet) {
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() {
if let Err(err) = self.exec_rule(rule).await {
if !matches!(rule.on_error, OnError::Continue) {
@@ -149,7 +136,7 @@ impl Engine {
let regex = self.bake_str(regex)?;
let regex = format!("^{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);
};
Ok(regex.is_match(&val))
@@ -158,7 +145,7 @@ impl Engine {
let key = self.bake_str(key)?;
let regex = self.bake_str(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);
};
Ok(regex.is_match(&val))
@@ -171,15 +158,15 @@ impl Engine {
Action::Set(key, val) => {
let key = self.bake_str(key)?;
let val = self.bake_str(val)?;
self.set_property(key, val);
self.properties.insert(&key, val);
}
Action::Add(key, val) => {
let key = self.bake_str(key)?;
let val = self.bake_str(val)?;
self.list_add(key, &val);
self.list_add(&key, &val);
}
Action::Unset(key) => {
self.remove_property(&self.bake_str(key)?);
self.properties.remove(&self.bake_str(key)?);
}
Action::Command(cmd) => {
let cmd = self.bake_str(cmd)?;
@@ -189,7 +176,7 @@ impl Engine {
let key = self.bake_str(key)?;
let cmd = self.bake_str(cmd)?;
let val = self.command_policy.run_provider(&cmd).await?;
self.set_property(key, val);
self.properties.insert(&key, val);
}
}
Ok(())
@@ -216,7 +203,8 @@ impl Engine {
current_property = None;
} else if ch == '}' {
let value = self
.get_property(property)
.properties
.get(property)
.ok_or_else(|| Error::PropertyNotFound(property.clone()))?;
result.push_str(&value);
current_property = None;
@@ -239,11 +227,6 @@ impl Engine {
}
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CommandPolicy {