dbus deamon wip

This commit is contained in:
Robin Appelman 2020-04-10 22:09:29 +02:00
commit 107c21b6a5
5 changed files with 851 additions and 559 deletions

100
Cargo.lock generated
View file

@ -1,5 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.0.0"
@ -24,6 +33,16 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "dbus"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f8875bb7afbc20dec12db09e18af3dcbd672b08592d2932950326a6437c616"
dependencies = [
"libc",
"libdbus-sys",
]
[[package]]
name = "derivative"
version = "2.1.0"
@ -60,18 +79,39 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
[[package]]
name = "libdbus-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
dependencies = [
"pkg-config",
]
[[package]]
name = "main_error"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3516df0fb44d98fe6d6e859d224adfb7b6686447937e5b96308d6061595eed04"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "nix"
version = "0.8.1"
@ -184,6 +224,37 @@ dependencies = [
"syn",
]
[[package]]
name = "parse-display"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718b422bc6b056b6374f7ffc3b2d9b55180a4af59a089835df1963994676d8b6"
dependencies = [
"lazy_static",
"parse-display-derive",
"regex",
]
[[package]]
name = "parse-display-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7cf2deb364a60cc0f633c1ffe619b42463993c91352ae367010b8420e442655"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"regex",
"regex-syntax",
"syn",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
[[package]]
name = "proc-macro-crate"
version = "0.1.4"
@ -248,6 +319,24 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
@ -264,9 +353,11 @@ checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
name = "shortcutd"
version = "0.1.0"
dependencies = [
"dbus",
"evdev",
"main_error",
"num_enum",
"parse-display",
]
[[package]]
@ -280,6 +371,15 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "toml"
version = "0.5.6"

View file

@ -7,4 +7,6 @@ edition = "2018"
[dependencies]
evdev = "0.10.1"
main_error = "0.1.0"
num_enum = "0.4.3"
num_enum = "0.4.3"
dbus = "0.8.2"
parse-display = "0.1.1"

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,27 @@
use evdev::Device;
pub use keycodes::Key;
use parse_display::{Display, FromStr, ParseError};
use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt::{self, Display};
use std::str::FromStr;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
mod keycodes;
pub use keycodes::Key;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Display, FromStr)]
pub enum Modifier {
AnyAlt,
Alt,
LeftAlt,
RightAlt,
AnyCtrl,
Ctrl,
LeftCtrl,
RightCtrl,
AnyShift,
Shift,
LeftShift,
RightShift,
AnyMeta,
Meta,
LeftMeta,
RightMeta,
}
@ -22,31 +29,93 @@ pub enum Modifier {
impl Modifier {
pub fn is_modifier(&self, key: Key) -> bool {
match self {
Modifier::AnyAlt => key == Key::KeyLeftAlt || key == Key::KeyRightAlt,
Modifier::Alt => key == Key::KeyLeftAlt || key == Key::KeyRightAlt,
Modifier::LeftAlt => key == Key::KeyLeftAlt,
Modifier::RightAlt => key == Key::KeyRightAlt,
Modifier::AnyCtrl => key == Key::KeyLeftCtrl || key == Key::KeyRightCtrl,
Modifier::Ctrl => key == Key::KeyLeftCtrl || key == Key::KeyRightCtrl,
Modifier::LeftCtrl => key == Key::KeyLeftCtrl,
Modifier::RightCtrl => key == Key::KeyRightCtrl,
Modifier::AnyMeta => key == Key::KeyLeftMeta || key == Key::KeyRightMeta,
Modifier::Meta => key == Key::KeyLeftMeta || key == Key::KeyRightMeta,
Modifier::LeftMeta => key == Key::KeyLeftMeta,
Modifier::RightMeta => key == Key::KeyRightMeta,
Modifier::AnyShift => key == Key::KeyLeftshift || key == Key::KeyRightshift,
Modifier::Shift => key == Key::KeyLeftshift || key == Key::KeyRightshift,
Modifier::LeftShift => key == Key::KeyLeftshift,
Modifier::RightShift => key == Key::KeyRightshift,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ModifierList(Vec<Modifier>);
impl Display for ModifierList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for modifier in self.0.iter() {
write!(f, "<{}>", modifier)?;
}
Ok(())
}
}
impl FromStr for ModifierList {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ModifierList(
s.split('>')
.filter(|part| !part.is_empty())
.map(|part| {
if !part.starts_with('<') {
Err(ParseError::with_message("Invalid modifier"))
} else {
Ok(part[1..].parse::<Modifier>()?)
}
})
.collect::<Result<Vec<Modifier>, ParseError>>()?,
))
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Display, FromStr)]
#[display("{modifiers}-{key}")]
pub struct Shortcut {
pub modifiers: Vec<Modifier>,
pub key: Key,
modifiers: ModifierList,
key: Key,
}
#[test]
fn test_shortcut_format() {
assert_eq!(
"<Ctrl>-KeyP",
format!("{}", Shortcut::new(vec![Modifier::Ctrl], Key::KeyP))
);
assert_eq!(
"<LeftCtrl><LeftAlt>-KeyLeft",
format!(
"{}",
Shortcut::new(vec![Modifier::LeftCtrl, Modifier::LeftAlt], Key::KeyLeft)
)
);
assert_eq!(
Shortcut::from_str("<LeftCtrl><LeftAlt>-KeyLeft").unwrap(),
Shortcut::new(vec![Modifier::LeftCtrl, Modifier::LeftAlt], Key::KeyLeft)
);
}
impl Shortcut {
pub fn new(modifiers: Vec<Modifier>, key: Key) -> Self {
Shortcut {
modifiers: ModifierList(modifiers),
key,
}
}
}
impl Shortcut {
pub fn is_triggered(&self, active_keys: &HashSet<Key>) -> bool {
for modifier in &self.modifiers {
for modifier in &self.modifiers.0 {
if active_keys.iter().any(|key| modifier.is_modifier(*key)) {
break;
}
@ -56,4 +125,60 @@ impl Shortcut {
active_keys.contains(&self.key)
}
}
}
pub struct ShortcutListener {
shortcuts: Arc<Mutex<HashSet<Shortcut>>>,
}
impl ShortcutListener {
pub fn new() -> Self {
ShortcutListener {
shortcuts: Arc::default(),
}
}
pub fn listen(&self, mut device: Device) -> Receiver<Shortcut> {
let (tx, rx) = channel();
let shortcuts = self.shortcuts.clone();
std::thread::spawn(move || {
let mut active_keys = HashSet::new();
loop {
let mut got_event = false;
for ev in device.events().unwrap() {
got_event = true;
if let Ok(key) = Key::try_from(ev.code) {
match ev.value {
1 => active_keys.insert(key),
0 => active_keys.remove(&key),
_ => false,
};
}
}
if got_event {
for shortcut in shortcuts.lock().unwrap().iter() {
if shortcut.is_triggered(&active_keys) {
tx.send(shortcut.clone()).unwrap()
}
}
}
}
});
rx
}
pub fn add(&self, shortcut: Shortcut) {
self.shortcuts.lock().unwrap().insert(shortcut);
}
pub fn remove(&self, shortcut: Shortcut) {
self.shortcuts.lock().unwrap().remove(&shortcut);
}
}

View file

@ -1,45 +1,109 @@
use crate::keyboard::{Key, Modifier, Shortcut, ShortcutListener};
use evdev::Device;
use main_error::MainError;
use std::collections::HashSet;
use crate::keyboard::{Key, Shortcut, Modifier};
use std::convert::TryFrom;
use dbus::blocking::LocalConnection;
use dbus::channel::Sender;
use dbus::tree::{Factory, MethodErr};
use std::error::Error;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
mod keyboard;
fn main() -> Result<(), MainError> {
let args: Vec<String> = std::env::args().collect();
let mut device = if args.len() > 1 {
let device = if args.len() > 1 {
Device::open(&args[1])?
} else {
eprintln!("Usage {} </dev/input/...>", args[0]);
return Ok(());
};
let mut keys = HashSet::new();
let shortcut = Shortcut {
modifiers: vec![Modifier::AnyCtrl, Modifier::AnyCtrl],
key: Key::KeyP
};
// let shortcut = Shortcut::new(vec![Modifier::Ctrl, Modifier::Ctrl], Key::KeyP);
let listener = ShortcutListener::new();
// listener.add(shortcut);
let rx = listener.listen(device);
// Let's start by starting up a connection to the session bus and request a name.
let mut connection: LocalConnection = LocalConnection::new_session()?;
connection.request_name("nl.icewind.shortcutd", false, true, false)?;
// The choice of factory tells us what type of tree we want,
// and if we want any extra data inside. We pick the simplest variant.
let f = Factory::new_fn::<()>();
// We create the signal first, since we'll need it in both inside the method callback
// and when creating the tree.
let signal = Arc::new(f.signal("ShortcutTriggered", ()).sarg::<&str, _>("sender"));
let signal2 = signal.clone();
let signal3 = signal.clone();
// We create a tree with one object path inside and make that path introspectable.
let tree = f
.tree(())
.add(
f.object_path("/shortcut", ()).introspectable().add(
// We add an interface to the object path...
f.interface("nl.icewind.shortcutd", ())
.add_m(
// ...and a method inside the interface.
f.method("Register", (), move |m| {
// This is the callback that will be called when another peer on the bus calls our method.
// the callback receives "MethodInfo" struct and can return either an error, or a list of
// messages to send back.
let name: &str = m.msg.read1()?;
match Shortcut::from_str(name) {
Ok(shortcut) => {
let s = format!("Ok: {}", shortcut);
listener.add(shortcut);
let mret = m.msg.method_return().append1(s);
Ok(vec![mret])
}
Err(_) => Err(MethodErr::invalid_arg("Failed to parse shortcut")),
}
// let s = format!("Hello {}!", name);
// let mret = m.msg.method_return().append1(s);
//
// // let sig = signal
// // .msg(m.path.get_name(), m.iface.get_name())
// // .append1(&*name);
//
// // Two messages will be returned - one is the method return (and should always be there),
// // and in our case we also have a signal we want to send at the same time.
// Ok(vec![mret, sig])
// Our method has one output argument and one input argument.
})
.outarg::<&str, _>("reply")
.inarg::<&str, _>("name"), // We also add the signal to the interface. This is mainly for introspection.
)
.add_s(signal2), // Also add the root path, to help introspection from debugging tools.
),
)
.add(f.object_path("/", ()).introspectable());
// We add the tree to the connection so that incoming method calls will be handled.
tree.start_receive(&connection);
// Serve clients forever.
loop {
let mut got_event = false;
connection.process(Duration::from_millis(50))?;
for ev in device.events()? {
got_event = true;
if let Ok(key) = Key::try_from(ev.code) {
match ev.value {
1 => keys.insert(key),
0 => keys.remove(&key),
_ => false,
};
}
}
if got_event {
if shortcut.is_triggered(&keys) {
println!("triggered");
}
while let Ok(shortcut) = rx.try_recv() {
connection
.send(
signal3
.msg(&"/shortcut".into(), &"nl.icewind.shortcutd".into())
.append1(&format!("{}", shortcut)),
)
.unwrap();
}
}