PulseAudio module

This commit is contained in:
Kenny Levinsen
2019-08-02 01:26:23 +02:00
parent 9ac29b2a94
commit 0cee090fa2
9 changed files with 645 additions and 42 deletions
Generated
+47
View File
@@ -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"
+2
View File
@@ -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"
+2 -2
View File
@@ -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,
+1
View File
@@ -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 {
+20 -2
View File
@@ -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<Local>,
}
impl Clock {
pub fn new() -> Clock {
pub fn new(ch: Sender<bool>) -> 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)
+4 -4
View File
@@ -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)
}
}
+20 -2
View File
@@ -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);
}
}
+34 -32
View File
@@ -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<bool>) -> 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();
});
+515
View File
@@ -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<RefCell<Mainloop>>,
context: Rc<RefCell<Context>>,
}
#[derive(Clone)]
struct PulseAudioSinkInfo {
volume: ChannelVolumes,
mute: bool,
}
struct PulseAudioClient {
sender: Sender<PulseAudioClientRequest>,
default_sink: String,
sinks: HashMap<String, PulseAudioSinkInfo>,
listener: Sender<bool>,
}
enum PulseAudioClientRequest {
GetDefaultDevice(Option<Sender<bool>>),
GetSinkInfoByIndex(Option<Sender<bool>>, u32),
GetSinkInfoByName(Option<Sender<bool>>, String),
SetSinkVolumeByName(Option<Sender<bool>>, String, ChannelVolumes),
SetSinkMuteByName(Option<Sender<bool>>, String, bool),
}
struct PulseAudioSoundDevice {
client: Arc<Mutex<PulseAudioClient>>,
name: Option<String>,
volume: Option<ChannelVolumes>,
volume_avg: u32,
muted: bool,
}
impl PulseAudioConnection {
fn new() -> Result<Self, ::std::io::Error> {
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<bool>) -> Result<Arc<Mutex<Self>>, ::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<Facility>,
_operation: Option<SubscribeOperation>,
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<bool>) -> Result<Arc<Mutex<Self>>, ::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<Mutex<PulseAudioSoundDevice>>,
dirty: bool,
}
impl PulseAudio {
pub fn new(listener: Sender<bool>) -> Result<Arc<Mutex<PulseAudio>>, ::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<Mutex<PulseAudio>> {
fn draw(
&self,
buf: &mut Buffer,
bg: &Color,
_time: &DateTime<Local>,
) -> Result<Vec<(i32, i32, i32, i32)>, ::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<Local>, force: bool) -> Result<bool, ::std::io::Error> {
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;
}
_ => {}
},
}
}
}