PulseAudio module
This commit is contained in:
Generated
+47
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user