diff --git a/Cargo.lock b/Cargo.lock index f297ff6..57b5ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,15 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dbus" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libdbus-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dlib" version = "0.4.1" @@ -88,6 +97,14 @@ name = "libc" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libdbus-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libloading" version = "0.5.2" @@ -97,6 +114,24 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libpulse-binding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-sys 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libpulse-sys" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "line_drawing" version = "0.7.0" @@ -177,6 +212,11 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -356,6 +396,8 @@ version = "0.1.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-binding 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "os_pipe 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -385,11 +427,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "ce400c638d48ee0e9ab75aef7997609ec57367ccfe1463f21bf53c3eca67bf46" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" +"checksum dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819" "checksum dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" "checksum downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b92dfd5c2f75260cbf750572f95d387e7ca0ba5e3fbe9e1a33f23025be020f" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" +"checksum libdbus-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "69382cb3be797cfe62ba89ac5e594a0e3022914ff44867ffbcfc03daa24eebd7" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +"checksum libpulse-binding 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7378bffd76492e876a0afb9a016359379a6b5a859fecd039248f76ce719fb" +"checksum libpulse-sys 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dcac117c7e9fb50fe162d5fbc6b3818819bd173922f648fae017f913de68520" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" @@ -399,6 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum os_pipe 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce1d819d394515aae0530eae02156fedfc98b4f0359bf3048a442be89a95157b" +"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" diff --git a/Cargo.toml b/Cargo.toml index 915d2ac..b0aec3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ memmap = "0.7" os_pipe = "0.8.1" wayland-client = { version = "0.23", features = ["dlopen"] } wayland-protocols = { version = "0.23", features = ["client", "unstable_protocols"] } +libpulse-binding = { version = "2.6.0"} +dbus = "0.6" diff --git a/src/backlight.rs b/src/backlight.rs index c5fefdc..a196f72 100644 --- a/src/backlight.rs +++ b/src/backlight.rs @@ -3,12 +3,13 @@ use crate::color::Color; use crate::draw::{draw_bar, draw_text, ROBOTO_REGULAR}; use crate::module::{Input, ModuleImpl}; -use chrono::{DateTime, Local}; use std::fs::OpenOptions; use std::io::{Error, ErrorKind}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; +use chrono::{DateTime, Local}; + pub struct Backlight { device_path: PathBuf, cur_brightness: u64, @@ -111,7 +112,6 @@ impl ModuleImpl for Backlight { )?; draw_bar( &mut buf.subdimensions((128, 0, 384, 24)), - bg, &Color::new(1.0, 1.0, 1.0, 1.0), 384, 24, diff --git a/src/calendar.rs b/src/calendar.rs index 3ee2eba..4550de5 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -2,6 +2,7 @@ use crate::buffer::Buffer; use crate::color::Color; use crate::draw::{draw_text, DEJAVUSANS_MONO, ROBOTO_REGULAR}; use crate::module::{Input, ModuleImpl}; + use chrono::{Date, DateTime, Datelike, Local}; pub struct Calendar { diff --git a/src/clock.rs b/src/clock.rs index 91597b0..c1c5861 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -2,14 +2,31 @@ use crate::buffer::Buffer; use crate::color::Color; use crate::draw::{draw_text, draw_text_fixed_width, ROBOTO_REGULAR}; use crate::module::{Input, ModuleImpl}; -use chrono::{DateTime, Datelike, Local, Timelike}; + +use std::sync::mpsc::Sender; + +use chrono::{DateTime, Datelike, Duration, Local, Timelike}; pub struct Clock { cur_time: DateTime, } impl Clock { - pub fn new() -> Clock { + pub fn new(ch: Sender) -> Clock { + std::thread::spawn(move || loop { + let n = Local::now(); + let target = (n + Duration::seconds(60)) + .with_second(0) + .unwrap() + .with_nanosecond(0) + .unwrap(); + + let d = target - n; + + std::thread::sleep(d.to_std().unwrap()); + ch.send(true).unwrap(); + }); + Clock { cur_time: Local::now(), } @@ -60,6 +77,7 @@ impl ModuleImpl for Clock { || force { self.cur_time = time.clone(); + println!("UPDATE"); Ok(true) } else { Ok(false) diff --git a/src/color.rs b/src/color.rs index 13d74fa..98832e8 100644 --- a/src/color.rs +++ b/src/color.rs @@ -58,9 +58,9 @@ impl Color { } pub fn as_argb8888(&self) -> u32 { - ((255.0 * self.opacity) as u32) << 24 - | ((255.0 * self.red) as u32) << 16 - | ((255.0 * self.green) as u32) << 8 - | ((255.0 * self.red) as u32) + ((255.0 * self.opacity) as u32 & 0xFF) << 24 + | ((255.0 * self.red) as u32 & 0xFF) << 16 + | ((255.0 * self.green) as u32 & 0xFF) << 8 + | ((255.0 * self.blue) as u32 & 0xFF) } } diff --git a/src/draw.rs b/src/draw.rs index a3b9bab..c484224 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -102,9 +102,27 @@ pub fn draw_text_fixed_width( Ok(()) } +pub fn draw_box( + buf: &mut Buffer, + c: &Color, + dim: (u32, u32), +) -> Result<(), ::std::io::Error> { + let mut buf = buf.subdimensions((0, 0, dim.0, dim.1)); + + for x in 0..dim.0 { + buf.put((x, 0), c); + buf.put((x, dim.1 - 1), c); + } + for y in 0..dim.1 { + buf.put((0, y), c); + buf.put((dim.0 - 1, y), c); + } + + Ok(()) +} + pub fn draw_bar( buf: &mut Buffer, - background_color: &Color, color: &Color, length: u32, height: u32, @@ -118,7 +136,7 @@ pub fn draw_bar( } for y in 0..height { for x in 0..fill_pos { - buf.put((x, y), &background_color.blend(color, 1.0)); + buf.put((x, y), color); } } diff --git a/src/main.rs b/src/main.rs index 8aa7b2e..a7672ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,13 @@ use std::collections::VecDeque; use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; +use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; use nix::poll::{poll, PollFd, PollFlags}; use os_pipe::pipe; -use chrono::{Duration as ChronoDuration, Local, Timelike}; +use chrono::{Local}; use smithay_client_toolkit::keyboard::{ map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatKind, @@ -26,6 +27,7 @@ mod clock; mod color; mod draw; mod module; +mod sound; use crate::backlight::Backlight; use crate::buffer::Buffer; @@ -33,6 +35,8 @@ use crate::calendar::Calendar; use crate::clock::Clock; use crate::color::Color; use crate::module::{Input, Module}; +use crate::sound::PulseAudio; +use crate::draw::draw_box; enum Cmd { Exit, @@ -62,6 +66,8 @@ impl App { let (buf_x, buf_y) = self.dimensions; + let bg = Color::new(0.0, 0.0, 0.0, 0.9); + // resize the pool if relevant pool.resize((4 * buf_x * buf_y) as usize) @@ -69,16 +75,16 @@ impl App { let mmap = pool.mmap(); let mut buf = Buffer::new(mmap, self.dimensions); - let mut margin_buf = buf.subdimensions((20, 20, buf_x - 40, buf_y - 40)); - - let bg = Color::new(0.0, 0.0, 0.0, 0.9); - let mut damage = vec![]; - for module in self.modules.iter() { - if module.update(&time, force)? { - let mut b = &mut margin_buf.subdimensions(module.get_bounds()); - let mut d = module.draw(&mut b, &bg, &time)?; - damage.append(&mut d); + + { + let mut margin_buf = buf.subdimensions((20, 20, buf_x - 40, buf_y - 40)); + for module in self.modules.iter() { + if module.update(&time, force)? { + let mut b = &mut margin_buf.subdimensions(module.get_bounds()); + let mut d = module.draw(&mut b, &bg, &time)?; + damage.append(&mut d); + } } } @@ -134,7 +140,21 @@ impl App { None } - fn new(dimensions: (u32, u32)) -> App { + fn new(dimensions: (u32, u32), tx: Sender) -> App { + let mut modules = vec![ + Module::new(Box::new(Clock::new(tx.clone())), (0, 0, 720, 320)), + Module::new(Box::new(Calendar::new()), (0, 384, 1280, 344)), + ]; + + let mut vert_off = 0; + if let Ok(m) = Backlight::new() { + modules.push(Module::new(Box::new(m), (720, vert_off, 512, 32))); + vert_off += 32; + } + if let Ok(m) = PulseAudio::new(tx.clone()) { + modules.push(Module::new(Box::new(m), (720, vert_off, 512, 32))); + } + let cmd_queue = Arc::new(Mutex::new(VecDeque::new())); let (display, mut event_queue) = Display::connect_to_env().unwrap(); @@ -263,7 +283,6 @@ impl App { surface.commit(); event_queue.sync_roundtrip().unwrap(); - // // Cursor processing // @@ -354,15 +373,6 @@ impl App { }) .unwrap(); - let mut modules = vec![ - Module::new(Box::new(Clock::new()), (0, 0, 720, 320)), - Module::new(Box::new(Calendar::new()), (0, 384, 1280, 344)), - ]; - - if let Ok(m) = Backlight::new() { - modules.push(Module::new(Box::new(m), (720, 0, 512, 32))); - } - event_queue.sync_roundtrip().unwrap(); App { @@ -379,21 +389,13 @@ impl App { fn main() { let (mut rx_pipe, mut tx_pipe) = pipe().unwrap(); + let (tx_draw, rx_draw) = channel(); - let mut app = App::new((1320u32, 784u32)); + let mut app = App::new((1320u32, 784u32), tx_draw); let worker_queue = app.cmd_queue(); std::thread::spawn(move || loop { - let n = Local::now(); - let target = (n + ChronoDuration::seconds(60)) - .with_second(0) - .unwrap() - .with_nanosecond(0) - .unwrap(); - - let d = target - n; - - std::thread::sleep(d.to_std().unwrap()); + rx_draw.recv().unwrap(); worker_queue.lock().unwrap().push_back(Cmd::Draw); tx_pipe.write_all(&[0x1]).unwrap(); }); diff --git a/src/sound.rs b/src/sound.rs new file mode 100644 index 0000000..6d8ec7f --- /dev/null +++ b/src/sound.rs @@ -0,0 +1,515 @@ +use crate::buffer::Buffer; +use crate::color::Color; +use crate::draw::{draw_bar, draw_text, ROBOTO_REGULAR}; +use crate::module::{Input, ModuleImpl}; + +use std::cell::RefCell; +use std::cmp::{max, min}; +use std::collections::HashMap; +use std::ops::Deref; +use std::rc::Rc; +use std::sync::mpsc::{channel, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread; + +use chrono::{DateTime, Local}; + +use libpulse_binding::callbacks::ListResult; +use libpulse_binding::context::{ + flags, introspect::ServerInfo, introspect::SinkInfo, subscribe::subscription_masks, + subscribe::Facility, subscribe::Operation as SubscribeOperation, Context, State as PulseState, +}; +use libpulse_binding::mainloop::standard::IterateResult; +use libpulse_binding::mainloop::standard::Mainloop; +use libpulse_binding::proplist::{properties, Proplist}; +use libpulse_binding::volume::{ChannelVolumes, VOLUME_MAX, VOLUME_NORM}; + +struct PulseAudioConnection { + mainloop: Rc>, + context: Rc>, +} + +#[derive(Clone)] +struct PulseAudioSinkInfo { + volume: ChannelVolumes, + mute: bool, +} + +struct PulseAudioClient { + sender: Sender, + default_sink: String, + sinks: HashMap, + listener: Sender, +} + +enum PulseAudioClientRequest { + GetDefaultDevice(Option>), + GetSinkInfoByIndex(Option>, u32), + GetSinkInfoByName(Option>, String), + SetSinkVolumeByName(Option>, String, ChannelVolumes), + SetSinkMuteByName(Option>, String, bool), +} + +struct PulseAudioSoundDevice { + client: Arc>, + name: Option, + volume: Option, + volume_avg: u32, + muted: bool, +} + +impl PulseAudioConnection { + fn new() -> Result { + let mut proplist = Proplist::new().unwrap(); + proplist + .sets(properties::APPLICATION_NAME, "wldash") + .unwrap(); + + let mainloop = Rc::new(RefCell::new(Mainloop::new().unwrap())); + + let context = Rc::new(RefCell::new( + Context::new_with_proplist(mainloop.borrow().deref(), "wldash_context", &proplist) + .unwrap(), + )); + + context + .borrow_mut() + .connect(None, flags::NOFLAGS, None) + .map_err(|_e| { + ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to connect to pulseaudio context", + ) + })?; + + let mut connection = PulseAudioConnection { mainloop, context }; + + // Wait for context to be ready + loop { + connection.iterate(false)?; + match connection.context.borrow().get_state() { + PulseState::Ready => { + break; + } + PulseState::Failed | PulseState::Terminated => {} + _ => {} + } + } + + Ok(connection) + } + + fn iterate(&mut self, blocking: bool) -> Result<(), ::std::io::Error> { + match self.mainloop.borrow_mut().iterate(blocking) { + IterateResult::Quit(_) | IterateResult::Err(_) => Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to iterate pulseaudio state", + )), + IterateResult::Success(_) => Ok(()), + } + } +} + +impl PulseAudioClient { + fn new(listener: Sender) -> Result>, ::std::io::Error> { + let (tx, rx) = channel(); + + let client = Arc::new(Mutex::new(PulseAudioClient { + sender: tx, + default_sink: "@DEFAULT_SINK@".to_string(), + sinks: HashMap::new(), + listener: listener, + })); + + let cl1 = client.clone(); + let (tx1, rx1) = channel(); + thread::spawn(move || { + let mut conn = match PulseAudioConnection::new() { + Ok(v) => { + tx1.send(true).unwrap(); + v + } + Err(_) => { + tx1.send(false).unwrap(); + return; + } + }; + + // make sure mainloop dispatched everything + for _ in 0..10 { + conn.iterate(false).unwrap(); + } + + loop { + let cl11 = cl1.clone(); + let cl12 = cl1.clone(); + let cl13 = cl1.clone(); + match rx.recv() { + Err(_) => {} + Ok(req) => { + let mut introspector = conn.context.borrow_mut().introspect(); + + match req { + PulseAudioClientRequest::GetDefaultDevice(s) => { + introspector.get_server_info(move |info| { + let _res = cl11.lock().unwrap().server_info_callback(info); + if let Some(s) = &s { + s.send(true).unwrap(); + } + }); + } + PulseAudioClientRequest::GetSinkInfoByIndex(s, index) => { + introspector.get_sink_info_by_index(index, move |res| { + cl12.lock().unwrap().sink_info_callback(res); + if let Some(s) = &s { + s.send(true).unwrap(); + } + }); + } + PulseAudioClientRequest::GetSinkInfoByName(s, name) => { + introspector.get_sink_info_by_name(&name, move |res| { + cl13.lock().unwrap().sink_info_callback(res); + if let Some(s) = &s { + s.send(true).unwrap(); + } + }); + } + PulseAudioClientRequest::SetSinkVolumeByName(s, name, volumes) => { + introspector.set_sink_volume_by_name(&name, &volumes, None); + if let Some(s) = &s { + s.send(true).unwrap(); + } + } + PulseAudioClientRequest::SetSinkMuteByName(s, name, mute) => { + introspector.set_sink_mute_by_name(&name, mute, None); + if let Some(s) = &s { + s.send(true).unwrap(); + } + } + }; + + // send request and receive response + conn.iterate(true).unwrap(); + conn.iterate(true).unwrap(); + } + } + } + }); + if !rx1.recv().unwrap() { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to start pulseaudio thread", + )); + } + + // subscribe + let cl2 = client.clone(); + let (tx2, rx2) = channel(); + thread::spawn(move || { + let conn = match PulseAudioConnection::new() { + Ok(v) => { + tx2.send(true).unwrap(); + v + } + Err(_) => { + tx2.send(false).unwrap(); + return; + } + }; + // subcribe for events + conn.context + .borrow_mut() + .set_subscribe_callback(Some(Box::new(move |facility, operation, index| { + cl2.lock() + .unwrap() + .subscribe_callback(facility, operation, index) + }))); + conn.context.borrow_mut().subscribe( + subscription_masks::SERVER | subscription_masks::SINK, + |_| {}, + ); + + conn.mainloop.borrow_mut().run().unwrap(); + }); + if !rx2.recv().unwrap() { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to start pulseaudio thread", + )); + } + + Ok(client) + } + + fn send(&self, request: PulseAudioClientRequest) -> Result<(), ::std::io::Error> { + let res = self.sender.send(request).map_err(|_e| { + ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to send pulseaudio request", + ) + }); + res + } + + fn server_info_callback(&mut self, server_info: &ServerInfo) { + match server_info.default_sink_name.clone() { + None => {} + Some(default_sink) => { + self.default_sink = default_sink.into(); + } + } + } + + fn sink_info_callback(&mut self, result: ListResult<&SinkInfo>) { + match result { + ListResult::End | ListResult::Error => {} + ListResult::Item(sink_info) => { + match sink_info.name.clone() { + None => {} + Some(name) => { + let info = PulseAudioSinkInfo { + volume: sink_info.volume, + mute: sink_info.mute, + }; + self.sinks.insert(name.into(), info); + self.listener.send(true).unwrap(); + } + } + } + } + } + + fn subscribe_callback( + &self, + facility: Option, + _operation: Option, + index: u32, + ) { + match facility { + None => {} + Some(facility) => match facility { + Facility::Server => { + let _ = self.send(PulseAudioClientRequest::GetDefaultDevice(None)); + } + Facility::Sink => { + let _ = self.send(PulseAudioClientRequest::GetSinkInfoByIndex(None, index)); + } + _ => {} + }, + } + } +} + +impl PulseAudioSoundDevice { + fn new(listener: Sender) -> Result>, ::std::io::Error> { + let client = PulseAudioClient::new(listener)?; + let cl = client.clone(); + let (tx, rx) = channel(); + { + let cl = client.lock().unwrap(); + cl.send(PulseAudioClientRequest::GetDefaultDevice(Some(tx)))?; + } + rx.recv().unwrap(); + let name = { + let cl = client.lock().unwrap(); + cl.default_sink.to_string() + }; + let device = Arc::new(Mutex::new(PulseAudioSoundDevice { + client: cl, + name: Some(name.to_string()), + volume: None, + volume_avg: 0, + muted: false, + })); + let (tx, rx) = channel(); + { + let cl = client.lock().unwrap(); + cl.send(PulseAudioClientRequest::GetSinkInfoByName(Some(tx), name))?; + } + rx.recv().unwrap(); + device.lock().unwrap().get_info()?; + + Ok(device) + } + + fn name(&self) -> String { + self.name + .clone() + .unwrap_or_else(|| self.client.lock().unwrap().default_sink.clone()) + } + + fn update_volume(&mut self, volume: ChannelVolumes) { + self.volume = Some(volume); + self.volume_avg = (volume.avg().0 as f32 / VOLUME_NORM.0 as f32 * 100.0).round() as u32; + } + + fn volume(&self) -> u32 { + self.volume_avg + } + + fn get_info(&mut self) -> Result<(), ::std::io::Error> { + let sink_info = match self.client.lock().unwrap().sinks.get(&self.name()) { + None => return Ok(()), + Some(sink_info) => (*sink_info).clone(), + }; + self.update_volume(sink_info.volume); + self.muted = sink_info.mute; + + Ok(()) + } + + fn set_volume(&mut self, step: f32) -> Result<(), ::std::io::Error> { + let mut volume = match self.volume { + Some(volume) => volume, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "unable to set volume", + )) + } + }; + + // apply step to volumes + let step = (step * VOLUME_NORM.0 as f32 / 100.0).round() as i32; + for vol in volume.values.iter_mut() { + vol.0 = min(max(0, vol.0 as i32 + step) as u32, VOLUME_MAX.0); + } + + // update volumes + self.update_volume(volume); + self.client + .lock() + .unwrap() + .send(PulseAudioClientRequest::SetSinkVolumeByName( + None, + self.name(), + volume, + ))?; + + Ok(()) + } + + fn toggle(&mut self) -> Result<(), ::std::io::Error> { + self.muted = !self.muted; + self.client + .lock() + .unwrap() + .send(PulseAudioClientRequest::SetSinkMuteByName( + None, + self.name(), + self.muted, + ))?; + + Ok(()) + } +} + +pub struct PulseAudio { + device: Arc>, + dirty: bool, +} + +impl PulseAudio { + pub fn new(listener: Sender) -> Result>, ::std::io::Error> { + let (tx, rx) = channel(); + let device = PulseAudioSoundDevice::new(tx)?; + let d = device.clone(); + let pa = Arc::new(Mutex::new(PulseAudio { + device: device, + dirty: true, + })); + let p = pa.clone(); + std::thread::spawn(move|| loop { + rx.recv().unwrap(); + d.lock().unwrap().get_info().unwrap(); + p.lock().unwrap().dirty = true; + listener.send(true).unwrap(); + }); + Ok(pa) + } +} + +impl ModuleImpl for Arc> { + fn draw( + &self, + buf: &mut Buffer, + bg: &Color, + _time: &DateTime, + ) -> Result, ::std::io::Error> { + let s = self.lock().unwrap(); + buf.memset(bg); + draw_text( + ROBOTO_REGULAR, + &mut buf.subdimensions((0, 0, 128, 24)), + bg, + &Color::new(1.0, 1.0, 1.0, 1.0), + 24.0, + "volume", + )?; + let vol = if s.device.lock().unwrap().muted { 0.0 } else { (s.device.lock().unwrap().volume() as f32) / 100.0 }; + draw_bar( + &mut buf.subdimensions((128, 0, 384, 24)), + &Color::new(1.0, 1.0, 1.0, 1.0), + 384, + 24, + vol, + )?; + if vol > 1.0 { + draw_bar( + &mut buf.subdimensions((128, 0, 384, 24)), + &Color::new(0.75, 0.25, 0.25, 1.0), + 384, + 24, + vol - 1.0, + )?; + } + if vol > 2.0 { + draw_bar( + &mut buf.subdimensions((128, 0, 384, 24)), + &Color::new(0.50, 0.10, 0.10, 1.0), + 384, + 24, + vol - 2.0, + )?; + } + Ok(vec![buf.get_signed_bounds()]) + } + + fn update(&mut self, _time: &DateTime, force: bool) -> Result { + let mut s = self.lock().unwrap(); + if s.dirty || force { + s.dirty = false; + Ok(true) + } else { + Ok(false) + } + } + + fn input(&mut self, input: Input) { + match input { + Input::Scroll { + pos: _pos, + x: _x, + y, + } => { + let mut s = self.lock().unwrap(); + s + .device + .lock() + .unwrap() + .set_volume((y * -1.0) as f32 / 8.0) + .unwrap(); + s.dirty = true; + + } + Input::Click { pos: _pos, button } => match button { + 273 => { + let mut s = self.lock().unwrap(); + s.device.lock().unwrap().toggle().unwrap(); + s.dirty = true; + } + _ => {} + }, + } + } +}