commit f1ca8d643137f487eafb9f32304ab789de5eea3b Author: Agatha Rose Date: Thu Jul 22 00:09:32 2021 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c61219d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.vscode/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ee25e1f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "synth-notifications" +version = "0.1.0" +authors = ["Agatha Rose "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dbus = "0.9.3" +midi-control = "0.2.0" +midir = "0.7.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cded31d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,104 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use dbus::blocking::Connection; +use dbus::message::MatchRule; +use dbus::Message; +use dbus::{arg::messageitem::MessageItem, channel::MatchingReceiver}; +use midi_control::*; +use midir::*; + +fn main() { + let midi_device = MidiOutput::new("synth output meow").expect("could not find device"); + let ports = midi_device.ports(); + // assumes that the device is connected on port 1 + // TODO: add port selection + let midi_out = midi_device + .connect(&ports[1], "meow port") + .expect("oh dear"); + let midi_out = Arc::new(Mutex::new(midi_out)); + + // monitor dbus notifications + let conn = Connection::new_session().unwrap(); + let mut rule = MatchRule::new(); + + 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.Notifications", + "BecomeMonitor", + (vec![rule.match_str()], 0u32), + ); + + if result.is_ok() { + // Start matching using new scheme + conn.start_receive( + rule, + Box::new(move |msg, _| { + midi_hook(midi_out.clone(), &msg); + true + }), + ); + } else { + // Start matching using old scheme + rule.eavesdrop = true; + conn.add_match(rule, move |_: (), _, msg| { + midi_hook(midi_out.clone(), &msg); + true + }) + .expect("add_match failed"); + } + + loop { + conn.process(Duration::from_millis(1000)).unwrap(); + } + // TODO: use the ctrlc crate to handle the midi connection closing +} + +fn midi_hook(midi_out: Arc>, msg: &Message) { + let args = msg.get_items(); + + // do nothing if args are empty + if args.is_empty() { + return; + } + + // only apply to firefox notifications + // TODO: make browser name configurable + if args[0] == "Nightly".into() { + // get the dict of values inside the message's args + let dict_values = match &args[6] { + MessageItem::Dict(v) => v.clone().into_vec(), + _ => Vec::new(), + }; + + { + // if the key equals "sender-pid", the message is a duplicate of the last one + let (v, _) = &dict_values[&dict_values.len() - 1]; + if v == &"sender-pid".into() { + return; + } + } + + println!("Got a notification from {:?}!", args[0]); + + // TODO: make note pitch and duration configurable + let play_note = midi_control::note_on(Channel::Ch1, 40, 127); + let stop_note = midi_control::note_off(Channel::Ch1, 40, 127); + + let mut midi_out = midi_out.lock().expect("couldn't acquire midi connection"); + + midi_out + .send_message(play_note) + .expect("could not play note"); + std::thread::sleep(Duration::from_millis(2000)); + midi_out + .send_message(stop_note) + .expect("could not stop play note"); + } +}