diff --git a/Cargo.lock b/Cargo.lock index 542dc6f..31c6954 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,6 +606,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + [[package]] name = "debug-helper" version = "0.3.13" @@ -820,6 +831,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "framework-inputmodule-dbus-monitor" +version = "0.0.1" +dependencies = [ + "clap", + "dbus", + "inputmodule-control", + "libdbus-sys", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1128,6 +1149,16 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" diff --git a/Cargo.toml b/Cargo.toml index ec5e8cb..fe07178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "ledmatrix", "fl16-inputmodules", "inputmodule-control", + "dbus-monitor" ] # Don't build all of them by default. # Because that'll lead to all features enabled in `fl16-inputmodules` and it diff --git a/dbus-monitor/Cargo.toml b/dbus-monitor/Cargo.toml new file mode 100644 index 0000000..4849a80 --- /dev/null +++ b/dbus-monitor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +edition = "2021" +name = "framework-inputmodule-dbus-monitor" +version = "0.0.1" + +[dependencies] +dbus = { version = "0.9.7", features = ["vendored"] } +clap = { version = "4.0", features = ["derive"] } + +[dependencies.libdbus-sys] +default-features = false +features = ["vendored"] +version = "0.2.5" + +[dependencies.inputmodule-control] +path = "../inputmodule-control" \ No newline at end of file diff --git a/dbus-monitor/src/dbus_monitor.rs b/dbus-monitor/src/dbus_monitor.rs new file mode 100644 index 0000000..811cb1e --- /dev/null +++ b/dbus-monitor/src/dbus_monitor.rs @@ -0,0 +1,120 @@ +// Mostly taken from https://github.com/diwic/dbus-rs/blob/366a6dca3d20745f5dcfa006b1b1311c376d420e/dbus/examples/monitor.rs + +// This programs implements the equivalent of running the "dbus-monitor" tool +// modified to only search for messages in the org.freedesktop.Notifications interface +use dbus::blocking::Connection; +use dbus::channel::MatchingReceiver; +use dbus::message::MatchRule; + +use dbus::Message; +use dbus::MessageType; + +use std::process::Command; +use std::time::Duration; + +use crate::utils; + +use inputmodule_control::inputmodule::find_serialdevs; +use inputmodule_control::commands::ClapCli; +use inputmodule_control::inputmodule::{serial_commands}; +use clap::{Parser, Subcommand}; + +fn handle_message(msg: &Message) { + println!("Got message from DBus: {:?}", msg); + + run_inputmodule_command(vec!["led-matrix", "--pattern", "all-on", "--blinking"]); + // Can't seem to get to this second command + run_inputmodule_command(vec!["led-matrix", "--brightness", "0"]); + + println!("Message handled"); +} + +pub fn run_inputmodule_command(args: Vec<&str>){ + let bin_placeholder = vec!["bin-placeholder"]; + let full_args = [&bin_placeholder[..], &args[..]].concat(); + let args = ClapCli::parse_from(full_args); + + serial_commands(&args); +} + +pub fn run_dbus_monitor() { + // First open up a connection to the desired bus. + let conn = Connection::new_session().expect("D-Bus connection failed"); + println!("Connection to DBus session monitor opened"); + + // Second create a rule to match messages we want to receive; in this example we add no + // further requirements, so all messages will match + let rule = MatchRule::new() + .with_type(MessageType::MethodCall) + .with_interface("org.freedesktop.Notifications") + .with_member("Notify"); + + // Try matching using new scheme + let proxy = conn.with_proxy( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + Duration::from_millis(5000), + ); + let result: Result<(), dbus::Error> = proxy.method_call( + "org.freedesktop.DBus.Monitoring", + "BecomeMonitor", + (vec![rule.match_str()], 0u32), + ); + println!("Monitoring DBus channel..."); + + match result { + // BecomeMonitor was successful, start listening for messages + Ok(_) => { + conn.start_receive( + rule, + Box::new(|msg, _| { + println!("Start listening"); + handle_message(&msg); + true + }), + ); + } + // BecomeMonitor failed, fallback to using the old scheme + Err(e) => { + eprintln!( + "Failed to BecomeMonitor: '{}', falling back to eavesdrop", + e + ); + + // First, we'll try "eavesdrop", which as the name implies lets us receive + // *all* messages, not just ours. + let rule_with_eavesdrop = { + let mut rule = rule.clone(); + rule.eavesdrop = true; + rule + }; + + let result = conn.add_match(rule_with_eavesdrop, |_: (), _, msg| { + handle_message(&msg); + true + }); + + match result { + Ok(_) => { + // success, we're now listening + } + // This can sometimes fail, for example when listening to the system bus as a non-root user. + // So, just like `dbus-monitor`, we attempt to fallback without `eavesdrop=true`: + Err(e) => { + eprintln!("Failed to eavesdrop: '{}', trying without it", e); + conn.add_match(rule, |_: (), _, msg| { + handle_message(&msg); + true + }) + .expect("add_match failed"); + } + } + } + } + + // Loop and print out all messages received (using handle_message()) as they come. + // Some can be quite large, e.g. if they contain embedded images.. + loop { + conn.process(Duration::from_millis(1000)).unwrap(); + } +} diff --git a/dbus-monitor/src/main.rs b/dbus-monitor/src/main.rs new file mode 100644 index 0000000..ddcfa2a --- /dev/null +++ b/dbus-monitor/src/main.rs @@ -0,0 +1,6 @@ +mod dbus_monitor; +mod utils; + +fn main() { + dbus_monitor::run_dbus_monitor(); +} diff --git a/dbus-monitor/src/utils.rs b/dbus-monitor/src/utils.rs new file mode 100644 index 0000000..93d4914 --- /dev/null +++ b/dbus-monitor/src/utils.rs @@ -0,0 +1,27 @@ +use std::process::Child; +use std::process::Command; +use std::time::Duration; +use std::time::Instant; + +pub fn run_command_with_timeout( + command: &str, + timeout_seconds: u64, +) -> Result> { + let mut child_process = Command::new("bash").arg("-c").arg(command).spawn()?; + + let start_time = Instant::now(); + while start_time.elapsed() < Duration::from_secs(timeout_seconds) { + if let Some(exit_status) = child_process.try_wait()? { + println!( + "Command finished before the specified duration. Exit status: {:?}", + exit_status + ); + return Ok(child_process); + } + } + + child_process.kill()?; // Attempt to kill the process + + println!("Command terminated after {} seconds", timeout_seconds); + Ok(child_process) +} diff --git a/inputmodule-control/src/commands.rs b/inputmodule-control/src/commands.rs new file mode 100644 index 0000000..fa122aa --- /dev/null +++ b/inputmodule-control/src/commands.rs @@ -0,0 +1,50 @@ +#![allow(clippy::needless_range_loop)] +#![allow(clippy::single_match)] + +use crate::inputmodule::{B1_LCD_PID, LED_MATRIX_PID}; +use crate::b1display::B1DisplaySubcommand; +use crate::c1minimal::C1MinimalSubcommand; +use crate::ledmatrix::LedMatrixSubcommand; + +use clap::{Parser, Subcommand}; + +#[derive(Subcommand, Debug)] +pub enum Commands { + LedMatrix(LedMatrixSubcommand), + B1Display(B1DisplaySubcommand), + C1Minimal(C1MinimalSubcommand), +} + +impl Commands { + pub fn to_pid(&self) -> u16 { + match self { + Self::LedMatrix(_) => LED_MATRIX_PID, + Self::B1Display(_) => B1_LCD_PID, + Self::C1Minimal(_) => 0x22, + } + } +} + +/// RAW HID and VIA commandline for QMK devices +#[derive(Parser, Debug)] +#[command(version, arg_required_else_help = true)] +pub struct ClapCli { + #[command(subcommand)] + pub command: Option, + + /// List connected HID devices + #[arg(short, long)] + pub list: bool, + + /// Verbose outputs to the console + #[arg(short, long)] + pub verbose: bool, + + /// Serial device, like /dev/ttyACM0 or COM0 + #[arg(long)] + pub serial_dev: Option, + + /// Retry connecting to the device until it works + #[arg(long)] + pub wait_for_device: bool, +} diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 6258b94..5d01a24 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -12,6 +12,7 @@ use crate::b1display::{B1Pattern, Fps, PowerMode}; use crate::c1minimal::Color; use crate::font::{convert_font, convert_symbol}; use crate::ledmatrix::{Game, GameOfLifeStartParam, Pattern}; +use crate::commands::ClapCli; const FWK_MAGIC: &[u8] = &[0x32, 0xAC]; pub const FRAMEWORK_VID: u16 = 0x32AC; @@ -98,7 +99,7 @@ fn match_serialdevs( } } -pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec, bool) { +pub fn find_serialdevs(args: &ClapCli, wait_for_device: bool) -> (Vec, bool) { let mut serialdevs: Vec; let mut waited = false; loop { @@ -150,7 +151,7 @@ pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec, bool) = find_serialdevs(args, args.wait_for_device); if serialdevs.is_empty() { println!("Failed to find serial devivce. Please manually specify with --serial-dev"); @@ -163,7 +164,7 @@ pub fn serial_commands(args: &crate::ClapCli) { match &args.command { // TODO: Handle generic commands without code deduplication - Some(crate::Commands::LedMatrix(ledmatrix_args)) => { + Some(crate::commands::Commands::LedMatrix(ledmatrix_args)) => { for serialdev in &serialdevs { if args.verbose { println!("Selected serialdev: {:?}", serialdev); @@ -258,7 +259,7 @@ pub fn serial_commands(args: &crate::ClapCli) { clock_cmd(&serialdevs); } } - Some(crate::Commands::B1Display(b1display_args)) => { + Some(crate::commands::Commands::B1Display(b1display_args)) => { for serialdev in &serialdevs { if args.verbose { println!("Selected serialdev: {:?}", serialdev); @@ -308,7 +309,7 @@ pub fn serial_commands(args: &crate::ClapCli) { } } } - Some(crate::Commands::C1Minimal(c1minimal_args)) => { + Some(crate::commands::Commands::C1Minimal(c1minimal_args)) => { for serialdev in &serialdevs { if args.verbose { println!("Selected serialdev: {:?}", serialdev); diff --git a/inputmodule-control/src/lib.rs b/inputmodule-control/src/lib.rs new file mode 100644 index 0000000..a421c53 --- /dev/null +++ b/inputmodule-control/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(clippy::needless_range_loop)] + +mod b1display; +mod c1minimal; +mod font; +pub mod inputmodule; +mod ledmatrix; +pub mod commands; + diff --git a/inputmodule-control/src/main.rs b/inputmodule-control/src/main.rs index 8348ad7..ba2aee9 100644 --- a/inputmodule-control/src/main.rs +++ b/inputmodule-control/src/main.rs @@ -5,60 +5,21 @@ mod c1minimal; mod font; mod inputmodule; mod ledmatrix; +mod commands; -use clap::{Parser, Subcommand}; +use clap::{Parser}; use inputmodule::find_serialdevs; -use crate::b1display::B1DisplaySubcommand; -use crate::c1minimal::C1MinimalSubcommand; -use crate::inputmodule::{serial_commands, B1_LCD_PID, LED_MATRIX_PID}; -use crate::ledmatrix::LedMatrixSubcommand; +use crate::inputmodule::serial_commands; -#[derive(Subcommand, Debug)] -enum Commands { - LedMatrix(LedMatrixSubcommand), - B1Display(B1DisplaySubcommand), - C1Minimal(C1MinimalSubcommand), -} - -impl Commands { - pub fn to_pid(&self) -> u16 { - match self { - Self::LedMatrix(_) => LED_MATRIX_PID, - Self::B1Display(_) => B1_LCD_PID, - Self::C1Minimal(_) => 0x22, - } - } -} - -/// RAW HID and VIA commandline for QMK devices -#[derive(Parser, Debug)] -#[command(version, arg_required_else_help = true)] -pub struct ClapCli { - #[command(subcommand)] - command: Option, - - /// List connected HID devices - #[arg(short, long)] - list: bool, - - /// Verbose outputs to the console - #[arg(short, long)] - verbose: bool, - - /// Serial device, like /dev/ttyACM0 or COM0 - #[arg(long)] - pub serial_dev: Option, - - /// Retry connecting to the device until it works - #[arg(long)] - wait_for_device: bool, -} +use crate::commands::ClapCli; fn main() { let args: Vec = std::env::args().collect(); + println!("Args were {:?}", args); let args = ClapCli::parse_from(args); + println!("Args are {:?}", args); match args.command { Some(_) => serial_commands(&args), None => {