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,
}
}
pub fn identifier(&self) -> String {
self.to_string()
.replace('<', "")
.replace('>', "")
.replace('-', "_")
}
}
impl Shortcut {
@ -145,8 +152,8 @@ impl Shortcut {
#[cfg(test)]
mod triggered_tests {
use crate::keyboard::{Key, Modifier, Shortcut};
use std::collections::HashSet;
use crate::keyboard::{Key, Shortcut};
use test_case::test_case;
#[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 main_error::MainError;
use std::collections::HashSet;
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 dbus::tree::{DataType, Factory, MethodErr, MethodType, Signal, Tree};
use dbus::Message;
use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::time::Duration;
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> {
let args: Vec<String> = std::env::args().collect();
let device = if args.len() > 1 {
@ -23,89 +71,80 @@ fn main() -> Result<(), MainError> {
return Ok(());
};
// let shortcut = Shortcut::new(vec![Modifier::Ctrl, Modifier::Ctrl], Key::KeyP);
let listener = Arc::new(ShortcutListener::new());
let listener = ShortcutListener::new();
// listener.add(shortcut);
let mut signals: HashMap<String, Arc<Signal<()>>> = HashMap::default();
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)?;
let mut connection = LocalConnection::new_session()?;
connection.request_name(INTERFACE, 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::<()>();
let mtree = Arc::new(MutateTree::new(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();
let (new_shortcut_tx, new_shortcut_rx) = channel();
// 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.
mtree.mutate_tree(move |factory, tree| {
let new_shortcut_tx = new_shortcut_tx.clone();
tree.add(
factory.object_path("/register", ()).introspectable().add(
factory.interface(INTERFACE, ()).add_m(
factory
.method("Register", (), move |m| {
let shortcut_str: &str = m.msg.read1()?;
let name: &str = m.msg.read1()?;
match Shortcut::from_str(name) {
match shortcut_str.parse::<Shortcut>() {
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])
listener.add(shortcut.clone());
let path = shortcut.identifier();
new_shortcut_tx.send(shortcut).unwrap();
// 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")
.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.
.outarg::<&str, _>("path")
.inarg::<&str, _>("shortcut"),
),
),
)
.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.
tree.start_receive(&connection);
mtree.start_receive(&connection);
// Serve clients forever.
loop {
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() {
let identifier = format!("/{}", shortcut.identifier());
if let Some(signal) = signals.get(&identifier) {
connection
.send(
signal3
.msg(&"/shortcut".into(), &"nl.icewind.shortcutd".into())
signal
.clone()
.msg(&identifier.into(), &INTERFACE.into())
.append1(&format!("{}", shortcut)),
)
.unwrap();
}
}
Ok(())
}
}