mirror of
https://codeberg.org/icewind/shortcutd.git
synced 2026-06-03 17:24:08 +02:00
dynamic per shortcut signals
This commit is contained in:
parent
f7c7953464
commit
f5095b25c8
2 changed files with 121 additions and 75 deletions
|
|
@ -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)]
|
||||||
|
|
|
||||||
169
src/main.rs
169
src/main.rs
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue