dynamic per shortcut signals

This commit is contained in:
Robin Appelman 2020-04-11 01:13:20 +02:00
commit f5095b25c8
2 changed files with 121 additions and 75 deletions

View file

@ -126,6 +126,13 @@ impl Shortcut {
key, key,
} }
} }
pub fn identifier(&self) -> String {
self.to_string()
.replace('<', "")
.replace('>', "")
.replace('-', "_")
}
} }
impl Shortcut { impl Shortcut {
@ -145,8 +152,8 @@ impl Shortcut {
#[cfg(test)] #[cfg(test)]
mod triggered_tests { mod triggered_tests {
use crate::keyboard::{Key, Modifier, Shortcut}; use crate::keyboard::{Key, Shortcut};
use std::collections::HashSet;
use test_case::test_case; use test_case::test_case;
#[test_case("<Ctrl>-KeyP", & [] => false)] #[test_case("<Ctrl>-KeyP", & [] => false)]

View file

@ -1,19 +1,67 @@
use crate::keyboard::{Key, Modifier, Shortcut, ShortcutListener}; use crate::keyboard::{Shortcut, ShortcutListener};
use evdev::Device; use evdev::Device;
use main_error::MainError; use main_error::MainError;
use std::collections::HashSet;
use std::convert::TryFrom;
use dbus::blocking::LocalConnection; use dbus::blocking::LocalConnection;
use dbus::channel::Sender; use dbus::channel::Sender;
use dbus::tree::{Factory, MethodErr};
use std::error::Error; use dbus::tree::{DataType, Factory, MethodErr, MethodType, Signal, Tree};
use std::str::FromStr; use dbus::Message;
use std::sync::Arc; use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
mod keyboard; mod keyboard;
const INTERFACE: &'static str = "nl.icewind.shortcutd";
struct MutateTree<M: MethodType<D>, D: DataType> {
factory: Factory<M, D>,
tree: Arc<Mutex<Option<Tree<M, D>>>>,
}
impl<M: MethodType<D> + 'static, D: DataType + 'static> MutateTree<M, D> {
fn factory(&self) -> &Factory<M, D> {
&self.factory
}
fn mutate_tree(&self, f: impl FnOnce(&Factory<M, D>, Tree<M, D>) -> Tree<M, D>) {
let tree = self.tree.lock().unwrap().take().unwrap();
self.tree.lock().unwrap().replace(f(&self.factory, tree));
}
pub fn start_receive<C>(&self, connection: &C)
where
C: dbus::channel::MatchingReceiver<F = Box<dyn FnMut(Message, &C) -> bool>>
+ dbus::channel::Sender,
{
let tree = self.tree.clone();
connection.start_receive(
dbus::message::MatchRule::new_method_call(),
Box::new(move |msg, c| {
let tree_opt = tree.lock().unwrap();
let tree = tree_opt.as_ref().unwrap();
if let Some(replies) = tree.handle(&msg) {
for r in replies {
let _ = c.send(r);
}
}
true
}),
);
}
pub fn new(factory: Factory<M, D>, data: D::Tree) -> Self {
let tree = factory.tree(data);
MutateTree {
factory,
tree: Arc::new(Mutex::new(Some(tree))),
}
}
}
fn main() -> Result<(), MainError> { fn main() -> Result<(), MainError> {
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let device = if args.len() > 1 { let device = if args.len() > 1 {
@ -23,89 +71,80 @@ fn main() -> Result<(), MainError> {
return Ok(()); return Ok(());
}; };
// let shortcut = Shortcut::new(vec![Modifier::Ctrl, Modifier::Ctrl], Key::KeyP); let listener = Arc::new(ShortcutListener::new());
let listener = ShortcutListener::new(); let mut signals: HashMap<String, Arc<Signal<()>>> = HashMap::default();
// listener.add(shortcut);
let rx = listener.listen(device); 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::new_session()?;
let mut connection: LocalConnection = LocalConnection::new_session()?; connection.request_name(INTERFACE, false, true, false)?;
connection.request_name("nl.icewind.shortcutd", false, true, false)?;
// The choice of factory tells us what type of tree we want, let mtree = Arc::new(MutateTree::new(Factory::new_fn::<()>(), ()));
// 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 let (new_shortcut_tx, new_shortcut_rx) = channel();
// 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. mtree.mutate_tree(move |factory, tree| {
let tree = f let new_shortcut_tx = new_shortcut_tx.clone();
.tree(()) tree.add(
.add( factory.object_path("/register", ()).introspectable().add(
f.object_path("/shortcut", ()).introspectable().add( factory.interface(INTERFACE, ()).add_m(
// We add an interface to the object path... factory
f.interface("nl.icewind.shortcutd", ()) .method("Register", (), move |m| {
.add_m( let shortcut_str: &str = m.msg.read1()?;
// ...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_str.parse::<Shortcut>() {
match Shortcut::from_str(name) {
Ok(shortcut) => { Ok(shortcut) => {
let s = format!("Ok: {}", shortcut); listener.add(shortcut.clone());
listener.add(shortcut); let path = shortcut.identifier();
let mret = m.msg.method_return().append1(s); new_shortcut_tx.send(shortcut).unwrap();
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. Ok(vec![m.msg.method_return().append1(format!("/{}", path))])
}
Err(_) => Err(MethodErr::invalid_arg("Malformed shortcut")),
}
}) })
.outarg::<&str, _>("reply") .outarg::<&str, _>("path")
.inarg::<&str, _>("name"), // We also add the signal to the interface. This is mainly for introspection. .inarg::<&str, _>("shortcut"),
) ),
.add_s(signal2), // Also add the root path, to help introspection from debugging tools.
), ),
) )
.add(f.object_path("/", ()).introspectable()); .add(factory.object_path("/", ()).introspectable())
});
// We add the tree to the connection so that incoming method calls will be handled. mtree.start_receive(&connection);
tree.start_receive(&connection);
// Serve clients forever. // Serve clients forever.
loop { loop {
connection.process(Duration::from_millis(50))?; connection.process(Duration::from_millis(50))?;
while let Ok(shortcut) = new_shortcut_rx.try_recv() {
let identifier = format!("/{}", shortcut.identifier());
let signal = Arc::new(mtree.factory().signal("Triggered", ()));
signals.insert(identifier.clone(), signal.clone());
mtree.mutate_tree(move |factory, tree| {
tree.add(
factory
.object_path(identifier, ())
.introspectable()
.add(factory.interface(INTERFACE, ()).add_s(signal)),
)
});
}
while let Ok(shortcut) = rx.try_recv() { while let Ok(shortcut) = rx.try_recv() {
let identifier = format!("/{}", shortcut.identifier());
if let Some(signal) = signals.get(&identifier) {
connection connection
.send( .send(
signal3 signal
.msg(&"/shortcut".into(), &"nl.icewind.shortcutd".into()) .clone()
.msg(&identifier.into(), &INTERFACE.into())
.append1(&format!("{}", shortcut)), .append1(&format!("{}", shortcut)),
) )
.unwrap(); .unwrap();
} }
} }
}
Ok(())
} }