diff --git a/src/alsa_card.rs b/src/alsa_card.rs index 9e3e47216..e42f55f41 100644 --- a/src/alsa_card.rs +++ b/src/alsa_card.rs @@ -1,3 +1,10 @@ +//! Alsa audio subsystem. +//! +//! This mod mainly defines the `AlsaCard` struct, which is the only data +//! structure interacting directly with the alsa library. +//! No other struct should directly interact with the alsa bindings. + + use alsa::card::Card; use alsa::mixer::SelemChannelId::*; use alsa::mixer::{Mixer, Selem, SelemId}; @@ -18,24 +25,54 @@ use support_alsa::*; #[derive(Clone, Copy, Debug)] +/// An "external" alsa card event, potentially triggered by anything. pub enum AlsaEvent { + /// An error. AlsaCardError, + /// Alsa card is disconnected. AlsaCardDiconnected, + /// The values of the mixer changed, including mute state. AlsaCardValuesChanged, } +/// A fairly high-level alsa card struct. We save some redundant +/// information in order to access it more easily, in addition to +/// some information that is not purely alsa related (like callbacks). pub struct AlsaCard { _cannot_construct: (), + /// The raw alsa card. pub card: Card, + /// The raw mixer. pub mixer: Mixer, + /// The simple element ID. `Selem` doesn't implement the Copy trait + /// so we save the ID instead and can get the `Selem` by lookup. pub selem_id: SelemId, + /// Watch IDs from polling the alsa card. We need them when we + /// drop the card, so we can unregister the polling. pub watch_ids: Cell>, + /// Callback for the various `AlsaEvent`s. pub cb: Rc, } impl AlsaCard { + /// Create a new alsa card. Tries very hard to get a valid, playable + /// card and mixer, so this is not a 'strict' function. + /// ## `card_name` + /// If a card name is provided, it will be tried. If `None` is provided + /// or the given card name does not exist or is not playable, any other + /// playable card is tried. + /// ## `elem_name` + /// If an elem name is provided, it will be tried. If `None` is provided + /// or the given elem name does not exist or is not playable, any other + /// playable elem is tried. + /// ## `cb` + /// Callback for the various `AlsaEvent`s. + /// + /// # Returns + /// + /// `Ok(Box)` on success, `Err(error)` otherwise. pub fn new(card_name: Option, elem_name: Option, cb: Rc) @@ -100,11 +137,13 @@ impl AlsaCard { } + /// Get the name of the alsa card. pub fn card_name(&self) -> Result { return self.card.get_name().from_err(); } + /// Get the name of the channel. pub fn chan_name(&self) -> Result { let n = self.selem_id .get_name() @@ -113,11 +152,17 @@ impl AlsaCard { } + /// Get the `Selem`, looked up by the `SelemId`. pub fn selem(&self) -> Selem { return self.mixer.find_selem(&self.selem_id).unwrap(); } + /// Get the current volume. The returned value corresponds to the + /// volume range and might need to be interpreted (such as converting + /// to percentage). This always gets + /// the volume of the `FrontRight` channel, because the seems to be + /// the safest bet. pub fn get_vol(&self) -> Result { let selem = self.selem(); let volume = selem.get_playback_volume(FrontRight); @@ -126,24 +171,36 @@ impl AlsaCard { } + /// Sets the volume of the current card configuration. + /// ## `new_vol` + /// The volume corresponding to the volume range of the `Selem`. This + /// might need to be translated properly first from other formats + /// (like percentage). pub fn set_vol(&self, new_vol: i64) -> Result<()> { let selem = self.selem(); return selem.set_playback_volume_all(new_vol).from_err(); } + /// Gets the volume range of the currently selected card configuration. + /// + /// # Returns + /// + /// `(min, max)` pub fn get_volume_range(&self) -> (i64, i64) { let selem = self.selem(); return selem.get_playback_volume_range(); } + /// Whether the current card configuration can be muted. pub fn has_mute(&self) -> bool { let selem = self.selem(); return selem.has_playback_switch(); } + /// Get the mute state of the current card configuration. pub fn get_mute(&self) -> Result { let selem = self.selem(); let val = selem.get_playback_switch(FrontRight)?; @@ -151,6 +208,9 @@ impl AlsaCard { } + /// Set the mute state of the current card configuration. + /// ## `mute` + /// Passing `true` here means the card will be muted. pub fn set_mute(&self, mute: bool) -> Result<()> { let selem = self.selem(); /* true -> mute, false -> unmute */ @@ -159,6 +219,9 @@ impl AlsaCard { } + /// Watch the given alsa card poll descriptors and + /// return the corresponding watch IDs for saving + /// in the `AlsaCard` struct. fn watch_poll_descriptors(polls: Vec, acard: &AlsaCard) -> Vec { @@ -186,6 +249,7 @@ impl AlsaCard { } + /// Unwatch the given poll descriptors. fn unwatch_poll_descriptors(watch_ids: &Vec) { for watch_id in watch_ids { unsafe { @@ -197,19 +261,7 @@ impl AlsaCard { impl Drop for AlsaCard { - // call Box::new(x), transmute the Box into a raw pointer, and then - // std::mem::forget - // - // if you unregister the callback, you should keep a raw pointer to the - // box - // - // For instance, `register` could return a raw pointer to the - // Box + a std::marker::PhantomData with the appropriate - // lifetime (if applicable) - // - // The struct could implement Drop, which unregisters the - // callback and frees the Box, by simply transmuting the - // raw pointer to a Box + /// Destructs the watch IDs corresponding to the current poll descriptors. fn drop(&mut self) { debug!("Destructing watch_ids: {:?}", self.watch_ids.get_mut()); AlsaCard::unwatch_poll_descriptors(&self.watch_ids.get_mut()); @@ -217,6 +269,7 @@ impl Drop for AlsaCard { } +/// The C callback function registered in `watch_poll_descriptors()`. extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, cond: glib_sys::GIOCondition, data: glib_sys::gpointer) diff --git a/src/app_state.rs b/src/app_state.rs index c51f3a346..c0563f288 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,3 +1,6 @@ +//! Global application state. + + use audio::{Audio, AudioUser}; use errors::*; use gtk; @@ -12,17 +15,23 @@ use notif::*; // TODO: destructors +/// The global application state struct. pub struct AppS { _cant_construct: (), + /// Mostly static GUI state. pub gui: Gui, + /// Audio state. pub audio: Audio, + /// Preferences state. pub prefs: RefCell, #[cfg(feature = "notify")] + /// Notification state. pub notif: Notif, } impl AppS { + /// Create an application state instance. There should really only be one. pub fn new() -> AppS { let builder_popup_window = gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), @@ -59,6 +68,7 @@ impl AppS { /* some functions that need to be easily accessible */ + /// Update the tray icon state. pub fn update_tray_icon(&self) -> Result<()> { debug!("Update tray icon!"); return self.gui.tray_icon.update_all(&self.prefs.borrow(), @@ -66,25 +76,30 @@ impl AppS { None); } + /// Update the Popup Window state. pub fn update_popup_window(&self) -> Result<()> { debug!("Update PopupWindow!"); return self.gui.popup_window.update(&self.audio); } #[cfg(feature = "notify")] + /// Update the notification state. pub fn update_notify(&self) -> Result<()> { return self.notif.reload(&self.prefs.borrow()); } #[cfg(not(feature = "notify"))] + /// Update the notification state. pub fn update_notify(&self) -> Result<()> { return Ok(()); } + /// Update the audio state. pub fn update_audio(&self, user: AudioUser) -> Result<()> { return audio_reload(&self.audio, &self.prefs.borrow(), user); } + /// Update the config file. pub fn update_config(&self) -> Result<()> { let prefs = self.prefs.borrow_mut(); return prefs.store_config(); diff --git a/src/audio.rs b/src/audio.rs index 069f247c2..2a23c9c18 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,3 +1,13 @@ +#![allow(missing_docs)] // enums + +//! High-level audio subsystem. +//! +//! This is the middleman between the low-level audio backend (alsa), +//! and the high-level ui code. +//! This abstraction layer allows the high-level code to be completely unaware +//! of the underlying audio implementation, may it be alsa or whatever. + + use alsa_card::*; use errors::*; use glib; @@ -6,11 +16,13 @@ use std::cell::Ref; use std::cell::RefCell; use std::f64; use std::rc::Rc; +use support_alsa::*; use support_audio::*; #[derive(Clone, Copy, Debug)] +/// The volume level of the current audio configuration. pub enum VolLevel { Muted, Low, @@ -20,6 +32,7 @@ pub enum VolLevel { } +/// An audio user, used to determine from where a signal originated. #[derive(Clone, Copy, Debug)] pub enum AudioUser { Unknown, @@ -30,6 +43,8 @@ pub enum AudioUser { } +/// An audio signal. This will be used to connect callbacks to the +/// audio system and react appropriately. #[derive(Clone, Copy, Debug)] pub enum AudioSignal { NoCard, @@ -42,6 +57,7 @@ pub enum AudioSignal { #[derive(Clone)] +/// Convenience struct to make handling this madness easier. pub struct Handlers { inner: Rc>>>, } @@ -64,16 +80,39 @@ impl Handlers { } +/// High-level Audio struct, which could theoretically be backend +/// agnostic. pub struct Audio { _cannot_construct: (), + /// The alsa card. pub acard: RefCell>, + /// Last timestamp of an internal action we triggered, e.g. + /// by setting the volume or the mute state. pub last_action_timestamp: Rc>, + /// A set of handlers that react to audio signals. We can + /// connect to these. pub handlers: Handlers, + /// The step at which to increase/decrease the volume. + /// This value is basically from the preferences. pub scroll_step: Cell, } impl Audio { + /// Create a new Audio instance. This tries very hard to get + /// a working configuration from the backend. + /// ## `card_name` + /// If a card name is provided, it will be tried. If `None` is provided + /// or the given card name does not exist or is not playable, any other + /// playable card is tried. + /// ## `elem_name` + /// If an elem name is provided, it will be tried. If `None` is provided + /// or the given elem name does not exist or is not playable, any other + /// playable elem is tried. + /// + /// # Returns + /// + /// `Ok(Audio)` on success, `Err(error)` otherwise. pub fn new(card_name: Option, elem_name: Option) -> Result