Compare commits
15 Commits
39f1148cad
...
cdb2cd5144
| Author | SHA1 | Date | |
|---|---|---|---|
| cdb2cd5144 | |||
| 7d810a603a | |||
| 0107ad88f3 | |||
| edef922902 | |||
|
|
f31e7ee894 | ||
| a08728250a | |||
| 3f083d3ee5 | |||
| 94338d2e37 | |||
| 0e87327f06 | |||
| 4db37a1107 | |||
| 13d2579419 | |||
| eeea6938ad | |||
| 28ac91bca2 | |||
| cb413e8774 | |||
| 20083d1f60 |
@@ -8,7 +8,7 @@ There is currently no convenient way to install Mixrs. To use this application,
|
|||||||
## Requirements
|
## Requirements
|
||||||
- [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/)
|
- [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/)
|
||||||
- [playerctl](https://wiki.archlinux.org/title/MPRIS#Playerctl)
|
- [playerctl](https://wiki.archlinux.org/title/MPRIS#Playerctl)
|
||||||
- [libnotify](https://gitlab.gnome.org/GNOME/libnotify)
|
- [libnotify](https://gitlab.gnome.org/GNOME/libnotify) (required unless started with `--silent`)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Mixrs will create a unix socket at `/tmp/mixrs` and listen for instructions. Instructions are issued by sending a specific byte to the socket.
|
Mixrs will create a unix socket at `/tmp/mixrs` and listen for instructions. Instructions are issued by sending a specific byte to the socket.
|
||||||
@@ -24,7 +24,8 @@ Mixrs will create a unix socket at `/tmp/mixrs` and listen for instructions. Ins
|
|||||||
|2|ToggleMuteCurrent|Toggles the current sink input's muted state|
|
|2|ToggleMuteCurrent|Toggles the current sink input's muted state|
|
||||||
|3|IncreaseCurrent|Increases the current sink input's volume by 5%|
|
|3|IncreaseCurrent|Increases the current sink input's volume by 5%|
|
||||||
|4|DecreaseCurrent|Decreases the current sink input's volume by 5%|
|
|4|DecreaseCurrent|Decreases the current sink input's volume by 5%|
|
||||||
|5|GetCurrent|Displays the current sink input's name|
|
|5|GetCurrent|Displays the current sink input's name<br>*Has no effect when using `--silent`*|
|
||||||
|6|PlayPauseCurrent|Tells the current sink input to toggle its `playing` state.<br>*Behavior varies based on the current sink input's player*|
|
|6|PlayPauseCurrent|Tells the current sink input to toggle its `playing` state.<br>*Behavior varies based on the current sink input's player*|
|
||||||
|7|PlayNext|Tells the current sink input to play the next item (e.g. the next song).<br>*Behavior varies based on the current sink input's player*|
|
|7|PlayNext|Tells the current sink input to play the next item (e.g. the next song).<br>*Behavior varies based on the current sink input's player*|
|
||||||
|8|PlayPrevious|Tells the current sink input to play the previous item (e.g. the previous song).<br>*Behavior varies based on the current sink input's player*|
|
|8|PlayPrevious|Tells the current sink input to play the previous item (e.g. the previous song).<br>*Behavior varies based on the current sink input's player*|
|
||||||
|
|9|GetCurrentOutput|Gets information about the currently selected sink input and sends it through the requesting unix socket|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub enum MixerInstruction {
|
|||||||
PlayPauseCurrent,
|
PlayPauseCurrent,
|
||||||
PlayNext,
|
PlayNext,
|
||||||
PlayPrevious,
|
PlayPrevious,
|
||||||
|
GetCurrentOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixerInstruction {
|
impl MixerInstruction {
|
||||||
@@ -23,6 +24,7 @@ impl MixerInstruction {
|
|||||||
6 => Some(MixerInstruction::PlayPauseCurrent),
|
6 => Some(MixerInstruction::PlayPauseCurrent),
|
||||||
7 => Some(MixerInstruction::PlayNext),
|
7 => Some(MixerInstruction::PlayNext),
|
||||||
8 => Some(MixerInstruction::PlayPrevious),
|
8 => Some(MixerInstruction::PlayPrevious),
|
||||||
|
9 => Some(MixerInstruction::GetCurrentOutput),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@@ -1,19 +1,26 @@
|
|||||||
mod instructions;
|
mod instructions;
|
||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
|
pub mod playerctl;
|
||||||
pub mod pulseaudio;
|
pub mod pulseaudio;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod playerctl;
|
|
||||||
|
|
||||||
use mixer::Mixer;
|
use mixer::Mixer;
|
||||||
use pulseaudio::PulseInstruction;
|
use pulseaudio::PulseInstruction;
|
||||||
use std::sync::mpsc::channel;
|
use std::{env, sync::mpsc::channel};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mainloop = pulse::mainloop::standard::Mainloop::new().expect("Error getting PulseAudio main loop");
|
let mainloop =
|
||||||
|
pulse::mainloop::standard::Mainloop::new().expect("Error getting PulseAudio main loop");
|
||||||
|
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let silent_mode = match args.iter().nth(1) {
|
||||||
|
Some(arg) => arg == "--silent",
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
let (pulse_ix_tx, pulse_ix_rx) = channel::<PulseInstruction>();
|
let (pulse_ix_tx, pulse_ix_rx) = channel::<PulseInstruction>();
|
||||||
|
|
||||||
let mut mixer = Mixer::new(mainloop, pulse_ix_tx);
|
let mut mixer = Mixer::new(mainloop, pulse_ix_tx, silent_mode);
|
||||||
|
|
||||||
mixer.run(pulse_ix_rx);
|
mixer.run(pulse_ix_rx);
|
||||||
}
|
}
|
||||||
|
|||||||
80
src/mixer.rs
80
src/mixer.rs
@@ -4,9 +4,10 @@ use std::{
|
|||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
io::Read,
|
io::{Read, Write},
|
||||||
os::unix::net::UnixListener,
|
os::unix::net::{UnixListener, UnixStream},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
process::exit,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{channel, Receiver, Sender},
|
mpsc::{channel, Receiver, Sender},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
@@ -41,10 +42,15 @@ pub struct Mixer {
|
|||||||
selected_index: Arc<Mutex<Option<usize>>>,
|
selected_index: Arc<Mutex<Option<usize>>>,
|
||||||
mainloop: Mainloop,
|
mainloop: Mainloop,
|
||||||
context: pulse::context::Context,
|
context: pulse::context::Context,
|
||||||
|
silent_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
pub fn new(mut mainloop: Mainloop, pulse_ix_tx: Sender<PulseInstruction>) -> Self {
|
pub fn new(
|
||||||
|
mut mainloop: Mainloop,
|
||||||
|
pulse_ix_tx: Sender<PulseInstruction>,
|
||||||
|
silent_mode: bool,
|
||||||
|
) -> Self {
|
||||||
let mut context =
|
let mut context =
|
||||||
pulse::context::Context::new(&mainloop, "Mixrs").expect("Error creating pulse context");
|
pulse::context::Context::new(&mainloop, "Mixrs").expect("Error creating pulse context");
|
||||||
|
|
||||||
@@ -104,6 +110,7 @@ impl Mixer {
|
|||||||
selected_index,
|
selected_index,
|
||||||
mainloop,
|
mainloop,
|
||||||
context,
|
context,
|
||||||
|
silent_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +131,7 @@ impl Mixer {
|
|||||||
.create_socket_listener()
|
.create_socket_listener()
|
||||||
.expect("Error creating unix socket listener");
|
.expect("Error creating unix socket listener");
|
||||||
|
|
||||||
let (mixer_tx, mixer_rx) = channel::<MixerInstruction>();
|
let (mixer_tx, mixer_rx) = channel::<(MixerInstruction, UnixStream)>();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for client in listener.incoming() {
|
for client in listener.incoming() {
|
||||||
@@ -134,7 +141,7 @@ impl Mixer {
|
|||||||
stream.read_to_end(&mut buf).expect("Error reading stream");
|
stream.read_to_end(&mut buf).expect("Error reading stream");
|
||||||
|
|
||||||
match MixerInstruction::from_u8(buf[0]) {
|
match MixerInstruction::from_u8(buf[0]) {
|
||||||
Some(ix) => mixer_tx.send(ix).unwrap(),
|
Some(ix) => mixer_tx.send((ix, stream)).unwrap(),
|
||||||
None => println!("Invalid instruction: {}", buf[0]),
|
None => println!("Invalid instruction: {}", buf[0]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +188,7 @@ impl Mixer {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match mixer_rx.try_recv() {
|
match mixer_rx.try_recv() {
|
||||||
Ok(ix) => match ix {
|
Ok((ix, stream)) => match ix {
|
||||||
MixerInstruction::SelectNext => self.select_next(),
|
MixerInstruction::SelectNext => self.select_next(),
|
||||||
MixerInstruction::SelectPrevious => self.select_previous(),
|
MixerInstruction::SelectPrevious => self.select_previous(),
|
||||||
MixerInstruction::ToggleMuteCurrent => self.toggle_mute_current(),
|
MixerInstruction::ToggleMuteCurrent => self.toggle_mute_current(),
|
||||||
@@ -191,6 +198,7 @@ impl Mixer {
|
|||||||
MixerInstruction::PlayPauseCurrent => self.play_pause_current(),
|
MixerInstruction::PlayPauseCurrent => self.play_pause_current(),
|
||||||
MixerInstruction::PlayNext => self.play_next_current(),
|
MixerInstruction::PlayNext => self.play_next_current(),
|
||||||
MixerInstruction::PlayPrevious => self.play_previous_current(),
|
MixerInstruction::PlayPrevious => self.play_previous_current(),
|
||||||
|
MixerInstruction::GetCurrentOutput => self.get_current_output(stream),
|
||||||
},
|
},
|
||||||
Err(_) => (),
|
Err(_) => (),
|
||||||
}
|
}
|
||||||
@@ -229,6 +237,10 @@ impl Mixer {
|
|||||||
let sink_input = result.lock().unwrap().take();
|
let sink_input = result.lock().unwrap().take();
|
||||||
if let Some(sink_input) = sink_input {
|
if let Some(sink_input) = sink_input {
|
||||||
self.sink_inputs.insert(sink_index, sink_input);
|
self.sink_inputs.insert(sink_index, sink_input);
|
||||||
|
|
||||||
|
if self.selected_index.lock().unwrap().is_none() {
|
||||||
|
self.select_next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PulseInstruction::RemoveSinkInput(sink_index) => {
|
PulseInstruction::RemoveSinkInput(sink_index) => {
|
||||||
@@ -421,7 +433,9 @@ impl Mixer {
|
|||||||
.set_sink_input_volume(
|
.set_sink_input_volume(
|
||||||
*sink_index,
|
*sink_index,
|
||||||
&volume,
|
&volume,
|
||||||
Some(Box::new(move |success| {
|
match self.silent_mode {
|
||||||
|
true => None,
|
||||||
|
false => Some(Box::new(move |success| {
|
||||||
if success {
|
if success {
|
||||||
let volume = volume_to_percentage(volume);
|
let volume = volume_to_percentage(volume);
|
||||||
let _ = send_notification_with_progress(
|
let _ = send_notification_with_progress(
|
||||||
@@ -430,6 +444,7 @@ impl Mixer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +479,9 @@ impl Mixer {
|
|||||||
.set_sink_input_volume(
|
.set_sink_input_volume(
|
||||||
*sink_index,
|
*sink_index,
|
||||||
&volume,
|
&volume,
|
||||||
Some(Box::new(move |success| {
|
match self.silent_mode {
|
||||||
|
true => None,
|
||||||
|
false => Some(Box::new(move |success| {
|
||||||
if success {
|
if success {
|
||||||
let volume = volume_to_percentage(volume);
|
let volume = volume_to_percentage(volume);
|
||||||
let _ = send_notification_with_progress(
|
let _ = send_notification_with_progress(
|
||||||
@@ -473,10 +490,15 @@ impl Mixer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current(&self) {
|
pub fn get_current(&self) {
|
||||||
|
if self.silent_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let index_lock = self.selected_index.lock().unwrap();
|
let index_lock = self.selected_index.lock().unwrap();
|
||||||
|
|
||||||
let Some(index) = *index_lock else {
|
let Some(index) = *index_lock else {
|
||||||
@@ -495,7 +517,10 @@ impl Mixer {
|
|||||||
let _ = send_notification_with_progress(
|
let _ = send_notification_with_progress(
|
||||||
&format!(
|
&format!(
|
||||||
"({}/{}) {}: {}%",
|
"({}/{}) {}: {}%",
|
||||||
index + 1, sink_inputs_length, ¤t_sink.name, current_sink_volume_percent
|
index + 1,
|
||||||
|
sink_inputs_length,
|
||||||
|
¤t_sink.name,
|
||||||
|
current_sink_volume_percent
|
||||||
),
|
),
|
||||||
current_sink_volume_percent,
|
current_sink_volume_percent,
|
||||||
);
|
);
|
||||||
@@ -560,9 +585,44 @@ impl Mixer {
|
|||||||
Err(_) => (),
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_current_output(&self, mut stream: UnixStream) {
|
||||||
|
let index_lock = self.selected_index.lock().unwrap();
|
||||||
|
|
||||||
|
let Some(index) = *index_lock else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(index_lock);
|
||||||
|
|
||||||
|
let Some(sink_index) = self.sink_inputs.keys().nth(index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(sink_input) = self.sink_inputs.get(sink_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = stream.write_all(
|
||||||
|
sink_input
|
||||||
|
.get_output_data(index, self.sink_inputs.len(), *sink_index)
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
let _ = stream.shutdown(std::net::Shutdown::Both);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iterate_mainloop(mainloop: &mut pulse::mainloop::standard::Mainloop) {
|
pub fn iterate_mainloop(mainloop: &mut pulse::mainloop::standard::Mainloop) {
|
||||||
mainloop.borrow_mut().iterate(false);
|
match mainloop.borrow_mut().iterate(false) {
|
||||||
|
IterateResult::Success(s) => {
|
||||||
|
if s == 0 {
|
||||||
thread::sleep(Duration::from_millis(5));
|
thread::sleep(Duration::from_millis(5));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
IterateResult::Quit(_) => exit(0),
|
||||||
|
IterateResult::Err(e) => {
|
||||||
|
println!("Err: {:?}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,4 +29,17 @@ impl SinkInputMixerData {
|
|||||||
pub fn get_volume_percent(&self) -> u8 {
|
pub fn get_volume_percent(&self) -> u8 {
|
||||||
total_volume_to_percentage(self.volume)
|
total_volume_to_percentage(self.volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Formats the sink input data to a string separating fields by new lines
|
||||||
|
pub fn get_output_data(
|
||||||
|
&self,
|
||||||
|
selection_index: usize,
|
||||||
|
sink_count: usize,
|
||||||
|
sink_index: u32,
|
||||||
|
) -> String {
|
||||||
|
format!(
|
||||||
|
"selection: {}/{sink_count}\nid: {sink_index}\nname: {}\nvolume: {}\nvolume_percentage: {}\nmuted: {}\n",
|
||||||
|
selection_index + 1, self.name, self.volume, self.get_volume_percent(), self.muted
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user