allow some bare shortcuts, example work

This commit is contained in:
Robin Appelman 2023-06-17 20:55:25 +02:00
commit 42237bc965
9 changed files with 232 additions and 63 deletions

View file

@ -65,16 +65,3 @@ jobs:
with:
name: ${{ matrix.artifact_name }}-${{ matrix.target }}
path: result/bin/${{ matrix.artifact_name }}
test:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#test

132
Cargo.lock generated
View file

@ -11,6 +11,55 @@ dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "async-broadcast"
version = "0.5.1"
@ -207,6 +256,54 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.18",
]
[[package]]
name = "clap_lex"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.2.0"
@ -324,9 +421,9 @@ dependencies = [
[[package]]
name = "evdev-shortcut"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8872ec4c0a6e6f5d6649384273a88a4941a0bd637a25712eadd74da2cb0748"
checksum = "cb870dc02f8a7955fa4ff451bbdd8bf9362d71b245a76bbe73a4c000cf012709"
dependencies = [
"async-stream",
"evdev",
@ -495,6 +592,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -546,6 +649,18 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -957,6 +1072,7 @@ dependencies = [
name = "shortcutd"
version = "0.1.0"
dependencies = [
"clap",
"evdev-shortcut",
"futures",
"test-case",
@ -1029,6 +1145,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "structmeta"
version = "0.2.0"
@ -1297,6 +1419,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "valuable"
version = "0.1.0"

View file

@ -11,8 +11,7 @@ The shortcutd daemon hooks into the evdev system and exposes a dbus interface fo
By separating out the code that hooks into evdev (which needs to be done as root) into a separate daemon
it allows non-privileged users to hook into global shortcuts.
Protection against clients using the shortcutd daemon for a keylogger is done by only allowing shortcuts that
contain at least one modifier key.
Protection against clients using the shortcutd daemon for a keylogger is done by only allowing 3 shortcuts without modifiers to be registered at the same time.
## Starting the daemon
@ -22,33 +21,32 @@ contain at least one modifier key.
## Rust api
```rust
use futures::{pin_mut, StreamExt};
use shortcutd::{Shortcut, ShortcutClient};
use std::error::Error;
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
let mut client = ShortcutClient::new()?;
let shortcut: Shortcut = "<Ctrl><Alt>-KeyP".parse()?;
client.register(shortcut, |s| {
eprintln!("shortcut1 {}", s);
})?;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = ShortcutClient::new().await?;
let shortcut: Shortcut = "<Ctrl><Alt>-KeyO".parse()?;
client.register(shortcut, |s| {
eprintln!("shortcut2 {}", s);
})?;
let stream = client.listen(shortcut).await?;
loop {
client.process(Duration::from_millis(1000))?;
pin_mut!(stream);
while let Some(event) = stream.next().await {
println!("{} {}", event.shortcut, event.state.as_str());
}
Ok(())
}
```
## D-Bus api
- register a new shortcut using the `Register` method at `nl.icewind.shortcutd`/`register`
- listen to the signal at the path returned from the `Register` method to get notified when the shortcut is triggered
- listen to the signal at the path returned from the `Register` method to get notified when the shortcut is triggered.
A boolean parameter is provided with the signal to distinguish shortcut presses from releases.

View file

@ -11,8 +11,9 @@ path = "src/lib.rs"
[dependencies]
futures = "0.3.28"
zbus = { version = "3.13.1", features = ["tokio"], default-features = false }
evdev-shortcut = { version = "0.1.1", default_features = false }
evdev-shortcut = { version = "0.1.2", default_features = false }
[dev-dependencies]
test-case = "3.1.0"
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
clap = { version = "4.3.4", features = ["derive"] }

View file

@ -1,19 +1,26 @@
use shortcutd::{ShortcutClient};
use std::error::Error;
use clap::Parser;
use evdev_shortcut::Shortcut;
use futures::pin_mut;
use futures::stream::iter;
use futures::StreamExt;
use shortcutd::ShortcutClient;
use std::error::Error;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Shortcut to listen to
shortcuts: Vec<Shortcut>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
let client = ShortcutClient::new().await?;
let streams = [
Box::pin(client.register("<Ctrl>-KeyM".parse()?).await?),
Box::pin(client.register("<Ctrl><Alt>-KeyO".parse()?).await?),
];
let stream = iter(streams).flatten_unordered(None);
let stream = iter(args.shortcuts)
.then(|shortcut| async { Box::pin(client.listen(shortcut).await.unwrap()) })
.flatten_unordered(None);
pin_mut!(stream);

19
client/examples/simple.rs Normal file
View file

@ -0,0 +1,19 @@
use futures::{pin_mut, StreamExt};
use shortcutd::{Shortcut, ShortcutClient};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = ShortcutClient::new().await?;
let shortcut: Shortcut = "<Ctrl><Alt>-KeyO".parse()?;
let stream = client.listen(shortcut).await?;
pin_mut!(stream);
while let Some(event) = stream.next().await {
println!("{} {}", event.shortcut, event.state.as_str());
}
Ok(())
}

View file

@ -1,9 +1,9 @@
pub use evdev_shortcut::Shortcut;
use evdev_shortcut::{ShortcutEvent, ShortcutState};
use futures::Stream;
use zbus::{Connection, fdo};
use zbus::dbus_proxy;
use futures::StreamExt;
use zbus::dbus_proxy;
use zbus::{fdo, Connection};
#[dbus_proxy(
interface = "nl.icewind.shortcutd",
@ -14,7 +14,6 @@ trait Register {
async fn register(&self, shortcut: &str) -> fdo::Result<String>;
}
#[dbus_proxy(
interface = "nl.icewind.shortcutd",
default_service = "nl.icewind.shortcutd"
@ -39,14 +38,17 @@ impl ShortcutClient {
ShortcutClient { connection }
}
pub async fn register(
pub async fn listen(
&self,
shortcut: Shortcut,
) -> Result<impl Stream<Item = ShortcutEvent> + '_, zbus::Error> {
let register = RegisterProxy::new(&self.connection).await?;
let path = register.register(&format!("{}", shortcut)).await?;
let p = ShortcutSignalProxy::builder(&self.connection).path(path.as_str())?.build().await?;
let p = ShortcutSignalProxy::builder(&self.connection)
.path(path.as_str())?
.build()
.await?;
let signals = p.receive_triggered().await?;
Ok(signals.filter_map(move |signal| {
@ -55,7 +57,11 @@ impl ShortcutClient {
let pressed = signal.args().ok()?.pressed;
Some(ShortcutEvent {
shortcut,
state: if pressed { ShortcutState::Pressed } else { ShortcutState::Released },
state: if pressed {
ShortcutState::Pressed
} else {
ShortcutState::Released
},
})
}
}))

View file

@ -11,7 +11,7 @@ path = "src/server.rs"
[dependencies]
main_error = "0.1.0"
glob = "0.3.0"
evdev-shortcut = "0.1.1"
evdev-shortcut = "0.1.3"
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
futures = "0.3.28"
zbus = { version = "3.13.1", features = ["tokio"], default-features = false }

View file

@ -1,21 +1,38 @@
use main_error::MainError;
use evdev_shortcut::{Shortcut, ShortcutEvent, ShortcutListener, ShortcutState};
use glob::GlobError;
use std::path::PathBuf;
use futures::stream::StreamExt;
use glob::GlobError;
use main_error::MainError;
use std::path::PathBuf;
use tracing::info;
use zbus::{ConnectionBuilder, dbus_interface, fdo, SignalContext, ObjectServer};
use zbus::export::futures_util::pin_mut;
use zbus::{dbus_interface, fdo, ConnectionBuilder, ObjectServer, SignalContext};
struct Register {
listener: ShortcutListener,
bare_count: usize,
}
const MAX_BARE: usize = 3;
#[dbus_interface(name = "nl.icewind.shortcutd")]
impl Register {
async fn register(&mut self, shortcut: &str, #[zbus(object_server)] server: &ObjectServer) -> Result<String, fdo::Error> {
async fn register(
&mut self,
shortcut: &str,
#[zbus(object_server)] server: &ObjectServer,
) -> Result<String, fdo::Error> {
match shortcut.parse::<Shortcut>() {
Ok(shortcut) => {
if shortcut.modifiers.is_empty() && !self.listener.has(&shortcut) {
dbg!(&shortcut);
if self.bare_count >= MAX_BARE {
return Err(fdo::Error::InvalidArgs(format!(
"Only {} shortcuts without modifiers are allowed",
MAX_BARE
)));
}
self.bare_count += 1;
}
info!(%shortcut, "registering shortcut");
self.listener.add(shortcut.clone());
let path = format!("/{}", shortcut.identifier());
@ -49,6 +66,7 @@ async fn main() -> Result<(), MainError> {
let bus = Register {
listener,
bare_count: 0,
};
let conn = ConnectionBuilder::system()?
.name("nl.icewind.shortcutd")?
@ -63,7 +81,12 @@ async fn main() -> Result<(), MainError> {
let event: ShortcutEvent = event;
let identifier = format!("/{}", event.shortcut.identifier());
if let Ok(signal_interface) = server.interface::<_, ShortcutSignal>(identifier).await {
if let Err(e) = ShortcutSignal::triggered(signal_interface.signal_context(), event.state == ShortcutState::Pressed).await {
if let Err(e) = ShortcutSignal::triggered(
signal_interface.signal_context(),
event.state == ShortcutState::Pressed,
)
.await
{
eprintln!("{e:#}");
}
}