diff --git a/Cargo.lock b/Cargo.lock index 9fd0487..d75a83c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,25 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "alsa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "alsa-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (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 = "andrew" version = "0.2.1" @@ -34,6 +54,11 @@ name = "autocfg" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.1.0" @@ -680,6 +705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "wldash" version = "0.2.0" dependencies = [ + "alsa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -720,10 +746,13 @@ dependencies = [ ] [metadata] +"checksum alsa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe6a5e4eb4879d7cb8d19161927fbf825f0d89761776359c9cf4f9c9c2f35c52" +"checksum alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58" "checksum andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" "checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" diff --git a/Cargo.toml b/Cargo.toml index 6f33e2e..f472f08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,11 @@ edition = "2018" lto = "fat" [features] +default = ["alsa-widget", "pulseaudio-widget"] damage_debug = [] +alsa-widget = ["alsa"] +pulseaudio-widget = ["libpulse-binding"] + [dependencies] smithay-client-toolkit = "0.6.4" @@ -20,7 +24,6 @@ memmap = "0.7" os_pipe = "0.8" wayland-client = { version = "0.23", features = ["dlopen"] } wayland-protocols = { version = "0.23", features = ["client", "unstable_protocols"] } -libpulse-binding = { version = "2.11"} dbus = "0.6" fuzzy-matcher = "0.2" lazy_static = "1.4" @@ -30,3 +33,6 @@ shlex = "0.1" serde_json = "1.0" serde_yaml = "0.8" serde = { version = "1.0", features = ["derive"] } + +libpulse-binding = { version = "2.11", optional = true } +alsa = { version = "0.3.0", optional = true } diff --git a/src/config.rs b/src/config.rs index 7a8e9a0..c72d478 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,10 +48,16 @@ pub enum Widget { font_size: f32, length: u32, }, + #[cfg(feature="pulseaudio-widget")] PulseAudio { font_size: f32, length: u32, }, + #[cfg(feature="alsa-widget")] + AlsaSound { + font_size: f32, + length: u32 + } } impl Widget { @@ -121,8 +127,16 @@ impl Widget { Err(_) => None, } } + #[cfg(feature="pulseaudio-widget")] Widget::PulseAudio { font_size, length } => { - match widgets::sound::PulseAudio::new(font_size, length, tx.clone()) { + match widgets::audio::PulseAudio::new(font_size, length, tx.clone()) { + Ok(w) => Some(w), + Err(_) => None, + } + } + #[cfg(feature="alsa-widget")] + Widget::AlsaSound { font_size, length } => { + match widgets::audio::Alsa::new(font_size, length) { Ok(w) => Some(w), Err(_) => None, } @@ -182,6 +196,7 @@ impl Default for Config { length: 600, }), }), + #[cfg(feature="pulseaudio-widget")] Box::new(Widget::Margin { margins: (0, 0, 0, 8), widget: Box::new(Widget::PulseAudio { diff --git a/src/widgets/audio/alsa_sound.rs b/src/widgets/audio/alsa_sound.rs new file mode 100644 index 0000000..fe93ecd --- /dev/null +++ b/src/widgets/audio/alsa_sound.rs @@ -0,0 +1,110 @@ + +use crate::color::Color; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; + +use alsa::mixer::{ Mixer, Selem, SelemChannelId, SelemId }; + +const CARD_NAME: &'static str = "default"; +const SELEM_NAME: &'static str = "Master"; +const SELEM_ID: u32 = 0u32; + +#[inline] +fn alsa_error_to_io_error(fail: &'static str, err: &alsa::Error) -> ::std::io::Error { + let kind = ::std::io::ErrorKind::Other; + let func = err.func(); + let errno = err.errno().map(|errno| format!("; errno = {}", errno)).unwrap_or(String::new()); + return ::std::io::Error::new(kind, format!("{}: {}{}", fail, func, errno)); +} + +#[inline] +fn alsa_volume_to_f32(volume: i64, min: i64, max: i64) -> f32 { + (volume - min) as f32 / (max - min) as f32 +} + +#[inline] +fn f32_to_alsa_volume(volume: f32, min: i64, max: i64) -> i64 { + min + ((max - min) as f32 * volume) as i64 +} + +pub struct Alsa { + pub mixer: Mixer, +} + +impl Alsa { + pub fn new(font_size: f32, length: u32) -> ::std::io::Result> { + let mixer = Mixer::new(CARD_NAME, true) + .map_err(|err| alsa_error_to_io_error("Failed to create ALSA mixer", &err))?; + let mixer = Self { mixer }; + mixer.get_master_volume()?; // probing + Ok(BarWidget::new_simple(font_size, length, Box::new(mixer))) + } + #[inline] + pub fn get_master<'a>(&'a self) -> ::std::io::Result> { + let master_id = SelemId::new(SELEM_NAME, SELEM_ID); + match self.mixer.find_selem(&master_id) { + Some(master) => Ok(master), + None => { + let kind = ::std::io::ErrorKind::NotFound; + let desc = "`Master` not found"; + Err(::std::io::Error::new(kind, desc)) + } + } + } + pub fn get_master_volume(&self) -> ::std::io::Result { + self.get_master() + .and_then(|master| { + let (min, max) = master.get_playback_volume_range(); + master.get_playback_volume(SelemChannelId::mono()) + .map(|volume| alsa_volume_to_f32(volume, min, max)) + .map_err(|e| alsa_error_to_io_error("Failed to get `Master` volume", &e)) + }) + } + pub fn set_master_volume(&self, volume: f32) -> ::std::io::Result<()> { + self.get_master() + .and_then(|master| { + let (min, max) = master.get_playback_volume_range(); + let volume = f32_to_alsa_volume(volume, min, max); + master.set_playback_volume_all(volume) + .map_err(|e| alsa_error_to_io_error("Failed to set `Master` volume", &e)) + }) + } + pub fn inc_master_volume(&self, diff: f32) -> ::std::io::Result<()> { + self.get_master() + .and_then(|master| { + let (min, max) = master.get_playback_volume_range(); + master.get_playback_volume(SelemChannelId::mono()) + .and_then(|volume| { + let volume = alsa_volume_to_f32(volume, min, max); + let volume = f32_to_alsa_volume(volume + diff, min, max); + master.set_playback_volume_all(volume) + }) + .map_err(|e| alsa_error_to_io_error("Failed to inc/dec `Master` volume", &e)) + }) + } +} + +impl BarWidgetImpl for Alsa { + fn name(&self) -> &str { + "volume" + } + fn value(&self) -> f32 { + match self.get_master_volume() { + Ok(v) => v, + Err(e) => panic!(e) + } + } + fn color(&self) -> Color { + Color::new(1.0, 1.0, 1.0, 1.0) + } + fn inc(&mut self, diff: f32) { + if let Err(e) = self.inc_master_volume(diff) { + panic!(e) + } + } + fn set(&mut self, abs: f32) { + if let Err(e) = self.set_master_volume(abs) { + panic!(e) + } + } + fn toggle(&mut self) {} +} diff --git a/src/widgets/audio/mod.rs b/src/widgets/audio/mod.rs new file mode 100644 index 0000000..4fbbb59 --- /dev/null +++ b/src/widgets/audio/mod.rs @@ -0,0 +1,11 @@ + +#[cfg(feature="alsa-widget")] +mod alsa_sound; +#[cfg(feature="alsa-widget")] +pub use alsa_sound::Alsa; + +#[cfg(feature="pulseaudio-widget")] +mod pulse_sound; +#[cfg(feature="pulseaudio-widget")] +pub use pulse_sound::PulseAudio; + diff --git a/src/widgets/sound.rs b/src/widgets/audio/pulse_sound.rs similarity index 100% rename from src/widgets/sound.rs rename to src/widgets/audio/pulse_sound.rs diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index ea0a17f..5d37476 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -5,4 +5,6 @@ pub mod calendar; pub mod clock; pub mod date; pub mod launcher; -pub mod sound; + +#[allow(dead_code)] +pub mod audio;