Compare commits
2 commits
6617652839
...
d2a7e36f5e
Author | SHA1 | Date | |
---|---|---|---|
d2a7e36f5e | |||
99d764eeaf |
8 changed files with 267 additions and 144 deletions
61
Cargo.lock
generated
61
Cargo.lock
generated
|
@ -49,9 +49,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.6"
|
version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
|
@ -111,6 +111,46 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim 0.11.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote 1.0.35",
|
||||||
|
"syn 2.0.52",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -192,7 +232,7 @@ dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.35",
|
"quote 1.0.35",
|
||||||
"strsim",
|
"strsim 0.10.0",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -224,7 +264,7 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197"
|
checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck 0.3.3",
|
||||||
"quote 0.3.15",
|
"quote 0.3.15",
|
||||||
"syn 0.11.11",
|
"syn 0.11.11",
|
||||||
]
|
]
|
||||||
|
@ -349,6 +389,12 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -543,6 +589,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
name = "picokontroller"
|
name = "picokontroller"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"directories",
|
"directories",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -684,6 +731,12 @@ version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.11.11"
|
version = "0.11.11"
|
||||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::config::NanoKeys;
|
use crate::config::NanoKeys;
|
||||||
use crate::config::{Config, KeyMapVariant, MprisAction, PulseAction};
|
use crate::config::{Config, KeyMapVariant, MprisAction};
|
||||||
|
use crate::interaction_server::InteractionServer;
|
||||||
use crate::midi_client::KeyEvent;
|
use crate::midi_client::KeyEvent;
|
||||||
use crate::mpris_client;
|
use crate::mpris_client;
|
||||||
use crate::pulse_client::{self, PulseMessage};
|
use crate::pulse_controller::{PulseController, PulseMessage};
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
|
@ -11,7 +12,8 @@ use std::thread;
|
||||||
|
|
||||||
pub fn run(in_channel: Receiver<KeyEvent>, config: Arc<Config>) {
|
pub fn run(in_channel: Receiver<KeyEvent>, config: Arc<Config>) {
|
||||||
let (sender, receiver) = unbounded::<PulseMessage>();
|
let (sender, receiver) = unbounded::<PulseMessage>();
|
||||||
pulse_client::run(receiver);
|
let pulse = PulseController::new(receiver);
|
||||||
|
pulse.start();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
exec(in_channel, sender, config);
|
exec(in_channel, sender, config);
|
||||||
});
|
});
|
||||||
|
|
5
src/interaction_server.rs
Normal file
5
src/interaction_server.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
pub trait InteractionServer {
|
||||||
|
fn start(self) -> JoinHandle<()>;
|
||||||
|
}
|
42
src/main.rs
42
src/main.rs
|
@ -1,26 +1,52 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod controller;
|
mod controller;
|
||||||
|
mod interaction_server;
|
||||||
mod midi_client;
|
mod midi_client;
|
||||||
mod mpris_client;
|
mod mpris_client;
|
||||||
mod pulse_client;
|
mod pulse_controller;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use crossbeam_channel::unbounded;
|
use crossbeam_channel::unbounded;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use interaction_server::InteractionServer;
|
||||||
|
use midi_client::MidiClient;
|
||||||
|
use std::{path::PathBuf, process::exit, sync::Arc};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(long, help = "List all available MIDI devices.")]
|
||||||
|
list_midi: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "List all available MPRIS players."
|
||||||
|
)]
|
||||||
|
list_mpris: bool,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
// Check if the debug flag was provided
|
||||||
|
if cli.list_midi {
|
||||||
|
midi_client::list_ports();
|
||||||
|
exit(0);
|
||||||
|
} else if cli.list_mpris {
|
||||||
|
mpris_client::list_players();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
let proj_dirs = ProjectDirs::from("com", "ghoscht", "picoKontroller");
|
let proj_dirs = ProjectDirs::from("com", "ghoscht", "picoKontroller");
|
||||||
let path: PathBuf = proj_dirs.unwrap().config_dir().join("config.toml");
|
let path: PathBuf = proj_dirs.unwrap().config_dir().join("config.toml");
|
||||||
// let path: &str = "./config.toml";
|
|
||||||
let cfg: config::Config = config::init(path);
|
let cfg: config::Config = config::init(path);
|
||||||
let arc_cfg = Arc::new(cfg);
|
let arc_cfg = Arc::new(cfg);
|
||||||
|
|
||||||
midi_client::list_ports();
|
|
||||||
println!("\nMPRIS Clients:");
|
|
||||||
mpris_client::list_clients();
|
|
||||||
|
|
||||||
let (sender, receiver) = unbounded::<midi_client::KeyEvent>();
|
let (sender, receiver) = unbounded::<midi_client::KeyEvent>();
|
||||||
|
let midi = MidiClient::new(arc_cfg.general.midi_device.clone(), sender);
|
||||||
|
|
||||||
controller::run(receiver, Arc::clone(&arc_cfg));
|
controller::run(receiver, Arc::clone(&arc_cfg));
|
||||||
midi_client::run(&arc_cfg.general.midi_device, sender).unwrap();
|
|
||||||
|
let server = midi.start();
|
||||||
|
let _ = server.join();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,72 @@
|
||||||
use crate::config::NanoKeys;
|
use crate::{config::NanoKeys, interaction_server::InteractionServer};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use midir::{Ignore, MidiInput};
|
use midir::{Ignore, MidiInput};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::error::Error;
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
|
const CLIENT_NAME: &str = "PicoKontroller";
|
||||||
|
const PORT_NAME: &str = "PicoController Input";
|
||||||
|
|
||||||
|
pub struct MidiClient {
|
||||||
|
pub device_name: String,
|
||||||
|
pub out_channel: Sender<KeyEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type KeyEvent = (NanoKeys, u8);
|
||||||
|
|
||||||
|
impl InteractionServer for MidiClient {
|
||||||
|
fn start(self) -> JoinHandle<()> {
|
||||||
|
thread::spawn(move || {
|
||||||
|
self.exec();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiClient {
|
||||||
|
pub fn new(device_name: String, out_channel: Sender<KeyEvent>) -> MidiClient {
|
||||||
|
MidiClient {
|
||||||
|
device_name,
|
||||||
|
out_channel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(self) {
|
||||||
|
let port_filter = Regex::new(&self.device_name).expect("Creating RegEx failed");
|
||||||
|
|
||||||
|
let mut midi_in = MidiInput::new(CLIENT_NAME).expect("Creating MIDI device failed");
|
||||||
|
midi_in.ignore(Ignore::None);
|
||||||
|
|
||||||
|
let in_ports = midi_in.ports();
|
||||||
|
let in_port = in_ports
|
||||||
|
.iter()
|
||||||
|
.find(|p| port_filter.is_match(&midi_in.port_name(p).unwrap()))
|
||||||
|
.expect("Couldn't find a port matching the supplied RegEx");
|
||||||
|
|
||||||
|
// _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
|
||||||
|
let _conn_in = midi_in.connect(
|
||||||
|
in_port,
|
||||||
|
PORT_NAME,
|
||||||
|
move |_, message, _| {
|
||||||
|
let key = NanoKeys::try_from(message[1]).unwrap();
|
||||||
|
let state = message[2];
|
||||||
|
let _ = self.out_channel.send((key, state));
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
loop {
|
||||||
|
// sleep forever, callback functions happen on separate thread
|
||||||
|
// couldn't reuse this thread, since _conn_in needs to be alive in this scope
|
||||||
|
std::thread::sleep(time::Duration::from_secs(u64::MAX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_ports() -> () {
|
pub fn list_ports() -> () {
|
||||||
let midi_in = MidiInput::new("PicoKontroller").expect("Creating Midi device failed");
|
let midi_in = MidiInput::new(CLIENT_NAME).expect("Creating MIDI device failed");
|
||||||
let in_ports = midi_in.ports();
|
let in_ports = midi_in.ports();
|
||||||
println!("All available midi devices:");
|
println!("All available MIDI devices:");
|
||||||
for (_, p) in in_ports.iter().enumerate() {
|
for (_, p) in in_ports.iter().enumerate() {
|
||||||
println!("{}", midi_in.port_name(p).unwrap());
|
println!("{}", midi_in.port_name(p).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(port_name: &str, out_channel: Sender<KeyEvent>) -> Result<(), Box<dyn Error>> {
|
|
||||||
let port_filter = Regex::new(port_name).expect("Creating RegEx failed");
|
|
||||||
|
|
||||||
let mut midi_in = MidiInput::new("PicoKontroller").expect("Creating Midi device failed");
|
|
||||||
midi_in.ignore(Ignore::None);
|
|
||||||
|
|
||||||
let in_ports = midi_in.ports();
|
|
||||||
let in_port = in_ports
|
|
||||||
.iter()
|
|
||||||
.find(|p| port_filter.is_match(&midi_in.port_name(p).unwrap()))
|
|
||||||
.expect("Couldn't find a port matching the supplied RegEx");
|
|
||||||
|
|
||||||
// _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
|
|
||||||
let _conn_in = midi_in.connect(
|
|
||||||
in_port,
|
|
||||||
"PicoController Input",
|
|
||||||
move |_, message, _| {
|
|
||||||
let key = NanoKeys::try_from(message[1]).unwrap();
|
|
||||||
let state = message[2];
|
|
||||||
let _ = out_channel.send((key, state));
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
)?;
|
|
||||||
loop {
|
|
||||||
// sleep forever, callback functions happen on separate thread
|
|
||||||
// couldn't reuse this thread, since _conn_in needs to be alive in this scope
|
|
||||||
std::thread::sleep(time::Duration::from_secs(u64::MAX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type KeyEvent = (NanoKeys, u8);
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
use itertools::Itertools;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use mpris::Player;
|
use mpris::Player;
|
||||||
use mpris::PlayerFinder;
|
use mpris::PlayerFinder;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub fn list_clients() -> () {
|
pub fn list_players() -> () {
|
||||||
|
println!("All available MPRIS players:");
|
||||||
PlayerFinder::new()
|
PlayerFinder::new()
|
||||||
.expect("Could not connect to D-Bus")
|
.expect("Could not connect to D-Bus")
|
||||||
.find_all()
|
.find_all()
|
||||||
.unwrap_or_else(|_| Vec::new())
|
.unwrap_or_else(|_| Vec::new())
|
||||||
.into_iter()
|
.iter()
|
||||||
.for_each(|player| {
|
.map(|p| p.identity().to_owned())
|
||||||
println!("{}", player.identity());
|
.dedup()
|
||||||
});
|
.for_each(|i| {println!("{}",i)});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn for_all_players<F>(player_id: &str, function: F)
|
fn for_all_players<F>(player_id: &str, function: F)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
extern crate libpulse_binding as pulse;
|
extern crate libpulse_binding as pulse;
|
||||||
|
|
||||||
use crate::config::{Config, KeyMapVariant, PulseAction};
|
use crate::config::PulseAction;
|
||||||
|
use crate::interaction_server::InteractionServer;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use log::{error, info, warn};
|
use log::error;
|
||||||
use pulse::context::{Context, FlagSet as ContextFlagSet};
|
use pulse::context::{Context, FlagSet as ContextFlagSet};
|
||||||
use pulse::mainloop::threaded::Mainloop;
|
use pulse::mainloop::threaded::Mainloop;
|
||||||
use pulse::proplist::Proplist;
|
use pulse::proplist::Proplist;
|
||||||
|
@ -11,97 +12,109 @@ use regex::Regex;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
|
const APPLICATION_NAME: &str = "picoKontroller";
|
||||||
|
const CONTEXT_NAME: &str = "picoKontrollerContext";
|
||||||
|
|
||||||
pub type PulseMessage = (Arc<Vec<String>>, PulseAction, u8);
|
pub type PulseMessage = (Arc<Vec<String>>, PulseAction, u8);
|
||||||
|
|
||||||
pub fn run(in_channel: Receiver<PulseMessage>) {
|
pub struct PulseController {
|
||||||
thread::spawn(move || {
|
pub in_channel: Receiver<PulseMessage>,
|
||||||
exec(in_channel);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
pub fn exec(in_channel: Receiver<PulseMessage>) {
|
|
||||||
let mut proplist = Proplist::new().unwrap();
|
|
||||||
proplist
|
|
||||||
.set_str(
|
|
||||||
pulse::proplist::properties::APPLICATION_NAME,
|
|
||||||
"picoKontroller",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mainloop = Arc::new(RefCell::new(
|
impl InteractionServer for PulseController {
|
||||||
Mainloop::new().expect("Failed to create mainloop"),
|
fn start(self) -> JoinHandle<()> {
|
||||||
));
|
thread::spawn(move || {
|
||||||
|
self.exec();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let context = Arc::new(RefCell::new(
|
impl PulseController {
|
||||||
Context::new_with_proplist(
|
pub fn new(in_channel: Receiver<PulseMessage>) -> PulseController {
|
||||||
mainloop.borrow().deref(),
|
PulseController { in_channel }
|
||||||
"picoKontrollerContext",
|
}
|
||||||
&proplist,
|
|
||||||
)
|
fn exec(&self) {
|
||||||
.expect("Failed to create new context"),
|
let mut proplist = Proplist::new().unwrap();
|
||||||
));
|
proplist
|
||||||
|
.set_str(
|
||||||
|
pulse::proplist::properties::APPLICATION_NAME,
|
||||||
|
APPLICATION_NAME,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mainloop = Arc::new(RefCell::new(
|
||||||
|
Mainloop::new().expect("Failed to create mainloop"),
|
||||||
|
));
|
||||||
|
|
||||||
|
let context = Arc::new(RefCell::new(
|
||||||
|
Context::new_with_proplist(mainloop.borrow().deref(), CONTEXT_NAME, &proplist)
|
||||||
|
.expect("Failed to create new context"),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Context state change callback
|
||||||
|
{
|
||||||
|
let ml_ref = Arc::clone(&mainloop);
|
||||||
|
let context_ref = Arc::clone(&context);
|
||||||
|
context
|
||||||
|
.borrow_mut()
|
||||||
|
.set_state_callback(Some(Box::new(move || {
|
||||||
|
let state = unsafe { (*context_ref.as_ptr()).get_state() };
|
||||||
|
match state {
|
||||||
|
pulse::context::State::Ready
|
||||||
|
| pulse::context::State::Failed
|
||||||
|
| pulse::context::State::Terminated => unsafe {
|
||||||
|
(*ml_ref.as_ptr()).signal(false);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
// Context state change callback
|
|
||||||
{
|
|
||||||
let ml_ref = Arc::clone(&mainloop);
|
|
||||||
let context_ref = Arc::clone(&context);
|
|
||||||
context
|
context
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_state_callback(Some(Box::new(move || {
|
.connect(None, ContextFlagSet::NOFLAGS, None)
|
||||||
let state = unsafe { (*context_ref.as_ptr()).get_state() };
|
.expect("Failed to connect context");
|
||||||
match state {
|
|
||||||
pulse::context::State::Ready
|
mainloop.borrow_mut().lock();
|
||||||
| pulse::context::State::Failed
|
mainloop
|
||||||
| pulse::context::State::Terminated => unsafe {
|
.borrow_mut()
|
||||||
(*ml_ref.as_ptr()).signal(false);
|
.start()
|
||||||
},
|
.expect("Failed to start mainloop");
|
||||||
_ => {}
|
|
||||||
|
// Wait for context to be ready
|
||||||
|
loop {
|
||||||
|
match context.borrow().get_state() {
|
||||||
|
pulse::context::State::Ready => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pulse::context::State::Failed | pulse::context::State::Terminated => {
|
||||||
|
eprintln!("Context state failed/terminated, quitting...");
|
||||||
|
mainloop.borrow_mut().unlock();
|
||||||
|
mainloop.borrow_mut().stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
mainloop.borrow_mut().wait();
|
||||||
}
|
}
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
|
||||||
.borrow_mut()
|
|
||||||
.connect(None, ContextFlagSet::NOFLAGS, None)
|
|
||||||
.expect("Failed to connect context");
|
|
||||||
|
|
||||||
mainloop.borrow_mut().lock();
|
|
||||||
mainloop
|
|
||||||
.borrow_mut()
|
|
||||||
.start()
|
|
||||||
.expect("Failed to start mainloop");
|
|
||||||
|
|
||||||
// Wait for context to be ready
|
|
||||||
loop {
|
|
||||||
match context.borrow().get_state() {
|
|
||||||
pulse::context::State::Ready => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pulse::context::State::Failed | pulse::context::State::Terminated => {
|
|
||||||
eprintln!("Context state failed/terminated, quitting...");
|
|
||||||
mainloop.borrow_mut().unlock();
|
|
||||||
mainloop.borrow_mut().stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
mainloop.borrow_mut().wait();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
context.borrow_mut().set_state_callback(None);
|
||||||
context.borrow_mut().set_state_callback(None);
|
mainloop.borrow_mut().unlock();
|
||||||
mainloop.borrow_mut().unlock();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let ml = Arc::clone(&mainloop);
|
let ml = Arc::clone(&mainloop);
|
||||||
let ctx = Arc::clone(&context);
|
let ctx = Arc::clone(&context);
|
||||||
let event_in = in_channel.recv();
|
let event_in = self.in_channel.recv();
|
||||||
match event_in {
|
match event_in {
|
||||||
Ok((ids, action, state)) => {
|
Ok((ids, action, state)) => {
|
||||||
parse_pulse_action(state, ids, &action, ml, ctx);
|
parse_pulse_action(state, ids, &action, ml, ctx);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed receiving event in controller thread: {err}");
|
error!("Failed receiving event in controller thread: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,11 +140,7 @@ fn parse_pulse_action(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_default_sink(
|
fn set_default_sink(mainloop: Arc<RefCell<Mainloop>>, context: Arc<RefCell<Context>>, name: &str) {
|
||||||
mainloop: Arc<RefCell<Mainloop>>,
|
|
||||||
context: Arc<RefCell<Context>>,
|
|
||||||
name: &str,
|
|
||||||
) {
|
|
||||||
mainloop.borrow_mut().lock();
|
mainloop.borrow_mut().lock();
|
||||||
let sink_filter = Regex::new(name).expect("Creating RegEx failed");
|
let sink_filter = Regex::new(name).expect("Creating RegEx failed");
|
||||||
let callback_context = context.clone();
|
let callback_context = context.clone();
|
||||||
|
@ -185,7 +194,7 @@ fn for_all_sink_inputs(
|
||||||
mainloop.borrow_mut().unlock();
|
mainloop.borrow_mut().unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mute_sink_input(
|
fn mute_sink_input(
|
||||||
mainloop: Arc<RefCell<Mainloop>>,
|
mainloop: Arc<RefCell<Mainloop>>,
|
||||||
context: Arc<RefCell<Context>>,
|
context: Arc<RefCell<Context>>,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -203,7 +212,7 @@ pub fn mute_sink_input(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_volume_sink_input(
|
fn set_volume_sink_input(
|
||||||
mainloop: Arc<RefCell<Mainloop>>,
|
mainloop: Arc<RefCell<Mainloop>>,
|
||||||
context: Arc<RefCell<Context>>,
|
context: Arc<RefCell<Context>>,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -228,7 +237,7 @@ pub fn set_volume_sink_input(
|
||||||
}
|
}
|
||||||
|
|
||||||
//taken from https://github.com/jantap/rsmixer
|
//taken from https://github.com/jantap/rsmixer
|
||||||
pub fn percent_to_volume(target_percent: u8) -> u32 {
|
fn percent_to_volume(target_percent: u8) -> u32 {
|
||||||
let base_delta = (volume::Volume::NORMAL.0 as f32 - volume::Volume::MUTED.0 as f32) / 100.0;
|
let base_delta = (volume::Volume::NORMAL.0 as f32 - volume::Volume::MUTED.0 as f32) / 100.0;
|
||||||
|
|
||||||
if target_percent == 100 {
|
if target_percent == 100 {
|
Loading…
Reference in a new issue