Skip to content

Commit

Permalink
Initial commit with working implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
zachfeldman committed Aug 27, 2023
1 parent 93e7aae commit 2ab109f
Show file tree
Hide file tree
Showing 16 changed files with 325 additions and 50 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions dbus-monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[Unit]
Description=framework-inputmodule-dbus-monitor monitors dbus for messages and makes input modules react to them

[Service]
ExecStart=/usr/bin/framework-inputmodule-dbus-monitor

[Install]
WantedBy=graphical.target
5 changes: 5 additions & 0 deletions dbus-monitor/release/package/DEBIAN/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Package: framework-inputmodule-dbus-monitor
Version: 0.0.1
Maintainer: Zach Feldman <[email protected]>
Architecture: all
Description: Monitors dbus for messages and makes input modules react to them
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[Unit]
Description=framework-inputmodule-dbus-monitor monitors dbus for messages and makes input modules react to them

[Service]
ExecStart=/usr/bin/framework-inputmodule-dbus-monitor

[Install]
WantedBy=graphical.target
32 changes: 32 additions & 0 deletions dbus-monitor/release/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

set -euxo pipefail

: 'Checking for fpm Ruby gem, installing if not present'
installed=`gem list -i fpm` || true

if [ $installed = 'false' ]; then
gem install fpm
fi

: 'Running the build'
cargo build

cp -f target/debug/framework-inputmodule-dbus-monitor release/package/usr/bin/

: "Packaging"
fpm \
-s dir -t deb \
-p release/framework-inputmodule-dbus-monitor-0.0.1.deb \
--name framework-inputmodule-dbus-monitor\
--version 0.0.1 \
--architecture all \
--description "framework-inputmodule-dbus-monitor monitors dbus for messages and makes input modules react to them" \
--url "https://frame.work" \
--maintainer "Framework <[email protected]>" \
--deb-no-default-config-files \
release/package/usr/bin/framework-inputmodule-dbus-monitor=/usr/bin/framework-inputmodule-dbus-monitor \
release/package/usr/lib/systemd/user/framework-inputmodule-dbus-monitor.service=/lib/systemd/user/framework-inputmodule-dbus-monitor.service \
release/package/DEBIAN/control

: 'Packaging successful, install with "sudo dpkg -i <pkg-name>.deb"'
120 changes: 120 additions & 0 deletions dbus-monitor/src/dbus_monitor.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
6 changes: 6 additions & 0 deletions dbus-monitor/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod dbus_monitor;
mod utils;

fn main() {
dbus_monitor::run_dbus_monitor();
}
27 changes: 27 additions & 0 deletions dbus-monitor/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<Child, Box<dyn std::error::Error>> {
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)
}
50 changes: 50 additions & 0 deletions inputmodule-control/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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<Commands>,

/// 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<String>,

/// Retry connecting to the device until it works
#[arg(long)]
pub wait_for_device: bool,
}
11 changes: 6 additions & 5 deletions inputmodule-control/src/inputmodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -97,7 +98,7 @@ fn match_serialdevs(
}
}

pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec<String>, bool) {
pub fn find_serialdevs(args: &ClapCli, wait_for_device: bool) -> (Vec<String>, bool) {
let mut serialdevs: Vec<String>;
let mut waited = false;
loop {
Expand Down Expand Up @@ -149,7 +150,7 @@ pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec<Str
}

/// Commands that interact with serial devices
pub fn serial_commands(args: &crate::ClapCli) {
pub fn serial_commands(args: &ClapCli) {
let (serialdevs, waited): (Vec<String>, bool) = find_serialdevs(args, args.wait_for_device);
if serialdevs.is_empty() {
println!("Failed to find serial devivce. Please manually specify with --serial-dev");
Expand All @@ -162,7 +163,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);
Expand Down Expand Up @@ -253,7 +254,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);
Expand Down Expand Up @@ -303,7 +304,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);
Expand Down
Loading

0 comments on commit 2ab109f

Please sign in to comment.