10 Commits
doc ... travis

Author SHA1 Message Date
4c325b1862 Try to fix travis 2017-07-15 02:09:53 +02:00
28df945631 try_er! is obsolete, since it's the same as try_e! 2017-07-15 01:55:45 +02:00
7542d8beaf Document error macros 2017-07-15 01:54:22 +02:00
c57622fad5 Add top-level documentation 2017-07-15 01:43:57 +02:00
996ac058c4 Documentation done 2017-07-15 01:38:07 +02:00
a8e90cb8f3 Add documentation link 2017-07-15 01:36:01 +02:00
1428055c31 Cosmetics 2017-07-15 01:20:47 +02:00
4d4d11ebb1 Rename pixbuf_new_from_file! to pixbuf_new_from_png! 2017-07-15 01:19:27 +02:00
0343ec6221 Don't invoke alsa functions from the high-level UI code
Instead, call indirection functions from the audio subsystem, so
we don't break module hierarchy.
2017-07-15 01:19:10 +02:00
bdcf8b0436 Document code, fixes #3 2017-07-15 01:17:19 +02:00
21 changed files with 503 additions and 137 deletions

View File

@@ -7,30 +7,25 @@ rust:
- stable - stable
addons: addons:
apt: apt:
sources:
- sourceline: 'ppa:gnome3-team/gnome3-staging'
- sourceline: 'ppa:gnome3-team/gnome3'
packages: packages:
- libasound2-dev - libasound2-dev
- libgdk-pixbuf2.0-dev - libgdk-pixbuf2.0-dev
- libglib2.0-dev - libglib2.0-dev
- libgtk-3-dev - libgtk-3-dev
- libnotify-dev - libnotify-dev
- libpng-dev
- libx11-dev - libx11-dev
- pkg-config - pkg-config
# for build.sh
- gperf
- libegl1-mesa-dev
- libfreetype6-dev
- libmount-dev
- libpng-dev
- libxml-parser-perl
- libxtst-dev
- xutils-dev
before_script: before_script:
- | - |
pip install 'travis-cargo<0.2' --user && pip install 'travis-cargo<0.2' --user &&
export PATH=$HOME/.local/bin:$PATH export PATH=$HOME/.local/bin:$PATH
script: script:
- | - |
./.travis/build.sh travis-cargo build
# travis-cargo test && # travis-cargo test &&
# travis-cargo --only stable doc # travis-cargo --only stable doc
# after_success: # after_success:

View File

@@ -1,15 +0,0 @@
#!/bin/sh
set -x
set -e
BUNDLE="gtk-3.18.1-2"
WD="$PWD"
cd "$HOME"
git clone https://github.com/gkoz/gtk-bootstrap.git
cd gtk-bootstrap
./bootstrap.sh "$WD/.travis/manifest.txt"
cd "$WD"
export PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig"
travis-cargo build

View File

@@ -1,13 +0,0 @@
https://dbus.freedesktop.org/releases/dbus/dbus-1.11.14.tar.gz
https://launchpad.net/intltool/trunk/0.51.0/+download/intltool-0.51.0.tar.gz
https://github.com/anholt/libepoxy/releases/download/1.4.3/libepoxy-1.4.3.tar.xz
https://www.freedesktop.org/software/fontconfig/release/fontconfig-2.12.4.tar.gz
https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-1.4.6.tar.bz2
https://download.gnome.org/sources/glib/2.53/glib-2.53.3.tar.xz
https://download.gnome.org/sources/atk/2.25/atk-2.25.2.tar.xz
https://download.gnome.org/sources/at-spi2-core/2.25/at-spi2-core-2.25.4.tar.xz
https://download.gnome.org/sources/at-spi2-atk/2.25/at-spi2-atk-2.25.3.tar.xz
https://www.cairographics.org/releases/cairo-1.14.10.tar.xz
https://download.gnome.org/sources/pango/1.40/pango-1.40.6.tar.xz
https://download.gnome.org/sources/gdk-pixbuf/2.36/gdk-pixbuf-2.36.6.tar.xz
https://download.gnome.org/sources/gtk+/3.22/gtk+-3.22.16.tar.xz

View File

@@ -1,5 +1,6 @@
[![Build Status](https://travis-ci.org/hasufell/pnmixer-rust.svg)](https://travis-ci.org/hasufell/pnmixer-rust) [![Build Status](https://travis-ci.org/hasufell/pnmixer-rust.svg)](https://travis-ci.org/hasufell/pnmixer-rust)
[![Join the chat at https://gitter.im/hasufell/pnmixer-rust](https://badges.gitter.im/hasufell/pnmixer-rust.svg)](https://gitter.im/hasufell/pnmixer-rust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/hasufell/pnmixer-rust](https://badges.gitter.im/hasufell/pnmixer-rust.svg)](https://gitter.im/hasufell/pnmixer-rust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation (master)](https://img.shields.io/badge/documentation-master-yellow.svg)](https://hasufell.github.io/pnmixer-rust/pnmixerlib/)
[![License](https://img.shields.io/github/license/hasufell/pnmixer-rust.svg)](https://github.com/hasufell/pnmixer-rust) [![License](https://img.shields.io/github/license/hasufell/pnmixer-rust.svg)](https://github.com/hasufell/pnmixer-rust)
PNMixer-rs PNMixer-rs
@@ -52,4 +53,4 @@ TODO
- [ ] [hotkey support](https://github.com/hasufell/pnmixer-rust/issues/5) - [ ] [hotkey support](https://github.com/hasufell/pnmixer-rust/issues/5)
- [ ] [translation](https://github.com/hasufell/pnmixer-rust/issues/4) - [ ] [translation](https://github.com/hasufell/pnmixer-rust/issues/4)
- [ ] [documentation](https://github.com/hasufell/pnmixer-rust/issues/3) - [X] [documentation](https://github.com/hasufell/pnmixer-rust/issues/3)

View File

@@ -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::card::Card;
use alsa::mixer::SelemChannelId::*; use alsa::mixer::SelemChannelId::*;
use alsa::mixer::{Mixer, Selem, SelemId}; use alsa::mixer::{Mixer, Selem, SelemId};
@@ -18,24 +25,54 @@ use support_alsa::*;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
/// An "external" alsa card event, potentially triggered by anything.
pub enum AlsaEvent { pub enum AlsaEvent {
/// An error.
AlsaCardError, AlsaCardError,
/// Alsa card is disconnected.
AlsaCardDiconnected, AlsaCardDiconnected,
/// The values of the mixer changed, including mute state.
AlsaCardValuesChanged, 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 { pub struct AlsaCard {
_cannot_construct: (), _cannot_construct: (),
/// The raw alsa card.
pub card: Card, pub card: Card,
/// The raw mixer.
pub mixer: 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, 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<Vec<u32>>, pub watch_ids: Cell<Vec<u32>>,
/// Callback for the various `AlsaEvent`s.
pub cb: Rc<Fn(AlsaEvent)>, pub cb: Rc<Fn(AlsaEvent)>,
} }
impl AlsaCard { 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<AlsaCard>)` on success, `Err(error)` otherwise.
pub fn new(card_name: Option<String>, pub fn new(card_name: Option<String>,
elem_name: Option<String>, elem_name: Option<String>,
cb: Rc<Fn(AlsaEvent)>) cb: Rc<Fn(AlsaEvent)>)
@@ -100,11 +137,13 @@ impl AlsaCard {
} }
/// Get the name of the alsa card.
pub fn card_name(&self) -> Result<String> { pub fn card_name(&self) -> Result<String> {
return self.card.get_name().from_err(); return self.card.get_name().from_err();
} }
/// Get the name of the channel.
pub fn chan_name(&self) -> Result<String> { pub fn chan_name(&self) -> Result<String> {
let n = self.selem_id let n = self.selem_id
.get_name() .get_name()
@@ -113,11 +152,17 @@ impl AlsaCard {
} }
/// Get the `Selem`, looked up by the `SelemId`.
pub fn selem(&self) -> Selem { pub fn selem(&self) -> Selem {
return self.mixer.find_selem(&self.selem_id).unwrap(); 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<i64> { pub fn get_vol(&self) -> Result<i64> {
let selem = self.selem(); let selem = self.selem();
let volume = selem.get_playback_volume(FrontRight); 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<()> { pub fn set_vol(&self, new_vol: i64) -> Result<()> {
let selem = self.selem(); let selem = self.selem();
return selem.set_playback_volume_all(new_vol).from_err(); 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) { pub fn get_volume_range(&self) -> (i64, i64) {
let selem = self.selem(); let selem = self.selem();
return selem.get_playback_volume_range(); return selem.get_playback_volume_range();
} }
/// Whether the current card configuration can be muted.
pub fn has_mute(&self) -> bool { pub fn has_mute(&self) -> bool {
let selem = self.selem(); let selem = self.selem();
return selem.has_playback_switch(); return selem.has_playback_switch();
} }
/// Get the mute state of the current card configuration.
pub fn get_mute(&self) -> Result<bool> { pub fn get_mute(&self) -> Result<bool> {
let selem = self.selem(); let selem = self.selem();
let val = selem.get_playback_switch(FrontRight)?; 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<()> { pub fn set_mute(&self, mute: bool) -> Result<()> {
let selem = self.selem(); let selem = self.selem();
/* true -> mute, false -> unmute */ /* 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<pollfd>, fn watch_poll_descriptors(polls: Vec<pollfd>,
acard: &AlsaCard) acard: &AlsaCard)
-> Vec<c_uint> { -> Vec<c_uint> {
@@ -186,6 +249,7 @@ impl AlsaCard {
} }
/// Unwatch the given poll descriptors.
fn unwatch_poll_descriptors(watch_ids: &Vec<u32>) { fn unwatch_poll_descriptors(watch_ids: &Vec<u32>) {
for watch_id in watch_ids { for watch_id in watch_ids {
unsafe { unsafe {
@@ -197,19 +261,7 @@ impl AlsaCard {
impl Drop for AlsaCard { impl Drop for AlsaCard {
// call Box::new(x), transmute the Box into a raw pointer, and then /// Destructs the watch IDs corresponding to the current poll descriptors.
// 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<T>
fn drop(&mut self) { fn drop(&mut self) {
debug!("Destructing watch_ids: {:?}", self.watch_ids.get_mut()); debug!("Destructing watch_ids: {:?}", self.watch_ids.get_mut());
AlsaCard::unwatch_poll_descriptors(&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, extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel,
cond: glib_sys::GIOCondition, cond: glib_sys::GIOCondition,
data: glib_sys::gpointer) data: glib_sys::gpointer)

View File

@@ -1,3 +1,6 @@
//! Global application state.
use audio::{Audio, AudioUser}; use audio::{Audio, AudioUser};
use errors::*; use errors::*;
use gtk; use gtk;
@@ -12,17 +15,23 @@ use notif::*;
// TODO: destructors // TODO: destructors
/// The global application state struct.
pub struct AppS { pub struct AppS {
_cant_construct: (), _cant_construct: (),
/// Mostly static GUI state.
pub gui: Gui, pub gui: Gui,
/// Audio state.
pub audio: Audio, pub audio: Audio,
/// Preferences state.
pub prefs: RefCell<Prefs>, pub prefs: RefCell<Prefs>,
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
/// Notification state.
pub notif: Notif, pub notif: Notif,
} }
impl AppS { impl AppS {
/// Create an application state instance. There should really only be one.
pub fn new() -> AppS { pub fn new() -> AppS {
let builder_popup_window = let builder_popup_window =
gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), 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 */ /* some functions that need to be easily accessible */
/// Update the tray icon state.
pub fn update_tray_icon(&self) -> Result<()> { pub fn update_tray_icon(&self) -> Result<()> {
debug!("Update tray icon!"); debug!("Update tray icon!");
return self.gui.tray_icon.update_all(&self.prefs.borrow(), return self.gui.tray_icon.update_all(&self.prefs.borrow(),
@@ -66,25 +76,30 @@ impl AppS {
None); None);
} }
/// Update the Popup Window state.
pub fn update_popup_window(&self) -> Result<()> { pub fn update_popup_window(&self) -> Result<()> {
debug!("Update PopupWindow!"); debug!("Update PopupWindow!");
return self.gui.popup_window.update(&self.audio); return self.gui.popup_window.update(&self.audio);
} }
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
/// Update the notification state.
pub fn update_notify(&self) -> Result<()> { pub fn update_notify(&self) -> Result<()> {
return self.notif.reload(&self.prefs.borrow()); return self.notif.reload(&self.prefs.borrow());
} }
#[cfg(not(feature = "notify"))] #[cfg(not(feature = "notify"))]
/// Update the notification state.
pub fn update_notify(&self) -> Result<()> { pub fn update_notify(&self) -> Result<()> {
return Ok(()); return Ok(());
} }
/// Update the audio state.
pub fn update_audio(&self, user: AudioUser) -> Result<()> { pub fn update_audio(&self, user: AudioUser) -> Result<()> {
return audio_reload(&self.audio, &self.prefs.borrow(), user); return audio_reload(&self.audio, &self.prefs.borrow(), user);
} }
/// Update the config file.
pub fn update_config(&self) -> Result<()> { pub fn update_config(&self) -> Result<()> {
let prefs = self.prefs.borrow_mut(); let prefs = self.prefs.borrow_mut();
return prefs.store_config(); return prefs.store_config();

View File

@@ -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 alsa_card::*;
use errors::*; use errors::*;
use glib; use glib;
@@ -6,11 +16,13 @@ use std::cell::Ref;
use std::cell::RefCell; use std::cell::RefCell;
use std::f64; use std::f64;
use std::rc::Rc; use std::rc::Rc;
use support_alsa::*;
use support_audio::*; use support_audio::*;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
/// The volume level of the current audio configuration.
pub enum VolLevel { pub enum VolLevel {
Muted, Muted,
Low, Low,
@@ -20,6 +32,7 @@ pub enum VolLevel {
} }
/// An audio user, used to determine from where a signal originated.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum AudioUser { pub enum AudioUser {
Unknown, 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)] #[derive(Clone, Copy, Debug)]
pub enum AudioSignal { pub enum AudioSignal {
NoCard, NoCard,
@@ -42,6 +57,7 @@ pub enum AudioSignal {
#[derive(Clone)] #[derive(Clone)]
/// Convenience struct to make handling this madness easier.
pub struct Handlers { pub struct Handlers {
inner: Rc<RefCell<Vec<Box<Fn(AudioSignal, AudioUser)>>>>, inner: Rc<RefCell<Vec<Box<Fn(AudioSignal, AudioUser)>>>>,
} }
@@ -64,16 +80,39 @@ impl Handlers {
} }
/// High-level Audio struct, which could theoretically be backend
/// agnostic.
pub struct Audio { pub struct Audio {
_cannot_construct: (), _cannot_construct: (),
/// The alsa card.
pub acard: RefCell<Box<AlsaCard>>, pub acard: RefCell<Box<AlsaCard>>,
/// Last timestamp of an internal action we triggered, e.g.
/// by setting the volume or the mute state.
pub last_action_timestamp: Rc<RefCell<i64>>, pub last_action_timestamp: Rc<RefCell<i64>>,
/// A set of handlers that react to audio signals. We can
/// connect to these.
pub handlers: Handlers, pub handlers: Handlers,
/// The step at which to increase/decrease the volume.
/// This value is basically from the preferences.
pub scroll_step: Cell<u32>, pub scroll_step: Cell<u32>,
} }
impl Audio { 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<String>, pub fn new(card_name: Option<String>,
elem_name: Option<String>) elem_name: Option<String>)
-> Result<Audio> { -> Result<Audio> {
@@ -116,6 +155,10 @@ impl Audio {
} }
/// Switches the current alsa card. Behaves the same way in regards to
/// `card_name` and `elem_name` as the `Audio::new()` method.
/// ## `user`
/// Where the card switch originates from.
pub fn switch_acard(&self, pub fn switch_acard(&self,
card_name: Option<String>, card_name: Option<String>,
elem_name: Option<String>, elem_name: Option<String>,
@@ -141,9 +184,6 @@ impl Audio {
*ac = AlsaCard::new(card_name, elem_name, cb)?; *ac = AlsaCard::new(card_name, elem_name, cb)?;
} }
// invoke_handlers(&self.handlers.borrow(),
// AudioSignal::CardCleanedUp,
// user);
invoke_handlers(&self.handlers.borrow(), invoke_handlers(&self.handlers.borrow(),
AudioSignal::CardInitialized, AudioSignal::CardInitialized,
user); user);
@@ -152,6 +192,7 @@ impl Audio {
} }
/// Current volume.
pub fn vol(&self) -> Result<f64> { pub fn vol(&self) -> Result<f64> {
let alsa_vol = self.acard let alsa_vol = self.acard
.borrow() .borrow()
@@ -160,6 +201,8 @@ impl Audio {
} }
/// Current volume level, nicely usable for e.g. selecting from a set
/// of images.
pub fn vol_level(&self) -> VolLevel { pub fn vol_level(&self) -> VolLevel {
let muted = self.get_mute().unwrap_or(false); let muted = self.get_mute().unwrap_or(false);
if muted { if muted {
@@ -176,6 +219,16 @@ impl Audio {
} }
/// Set the current volume.
/// ## `new_vol`
/// Set the volume to this value.
/// ## `user`
/// Where the card switch originates from.
/// ## `dir`
/// The "direction" of the volume change, e.g. is it a decrease
/// or increase. This helps with rounding problems.
/// ## `auto_unmute`
/// Whether to automatically unmute if the volume changes.
pub fn set_vol(&self, pub fn set_vol(&self,
new_vol: f64, new_vol: f64,
user: AudioUser, user: AudioUser,
@@ -231,6 +284,10 @@ impl Audio {
} }
/// Increase the volume. The step to increasy by is taken from
/// `self.scroll_step`.
/// ## `user`
/// Where the card switch originates from.
pub fn increase_vol(&self, pub fn increase_vol(&self,
user: AudioUser, user: AudioUser,
auto_unmute: bool) auto_unmute: bool)
@@ -242,6 +299,10 @@ impl Audio {
} }
/// Decrease the volume. The step to decrease by is taken from
/// `self.scroll_step`.
/// ## `user`
/// Where the card switch originates from.
pub fn decrease_vol(&self, pub fn decrease_vol(&self,
user: AudioUser, user: AudioUser,
auto_unmute: bool) auto_unmute: bool)
@@ -253,16 +314,19 @@ impl Audio {
} }
/// Whether the current audio configuration can be muted.
pub fn has_mute(&self) -> bool { pub fn has_mute(&self) -> bool {
return self.acard.borrow().has_mute(); return self.acard.borrow().has_mute();
} }
/// Get the mute state of the current audio configuration.
pub fn get_mute(&self) -> Result<bool> { pub fn get_mute(&self) -> Result<bool> {
return self.acard.borrow().get_mute(); return self.acard.borrow().get_mute();
} }
/// Set the mute state of the current audio configuration.
pub fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()> { pub fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()> {
let mut rc = self.last_action_timestamp.borrow_mut(); let mut rc = self.last_action_timestamp.borrow_mut();
*rc = glib::get_monotonic_time(); *rc = glib::get_monotonic_time();
@@ -290,18 +354,42 @@ impl Audio {
} }
/// Toggle the mute state of the current audio configuration.
pub fn toggle_mute(&self, user: AudioUser) -> Result<()> { pub fn toggle_mute(&self, user: AudioUser) -> Result<()> {
let muted = self.get_mute()?; let muted = self.get_mute()?;
return self.set_mute(!muted, user); return self.set_mute(!muted, user);
} }
/// Connect a signal handler to the audio subsystem. This can
/// be done from anywhere, e.g. in the UI code to react to
/// certain signals. Multiple handlers for the same signals are fine,
/// they will be executed in order.
pub fn connect_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) { pub fn connect_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) {
self.handlers.add_handler(cb); self.handlers.add_handler(cb);
} }
/// Get the current card name.
pub fn card_name(&self) -> Result<String> {
return self.acard.borrow().card_name();
}
/// Get the currently playable channel names.
pub fn playable_chan_names(&self) -> Vec<String> {
return get_playable_selem_names(&self.acard.borrow().mixer);
}
/// Get the current active channel name.
pub fn chan_name(&self) -> Result<String> {
return self.acard.borrow().chan_name();
}
} }
/// Invokes the registered handlers.
fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>, fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
signal: AudioSignal, signal: AudioSignal,
user: AudioUser) { user: AudioUser) {
@@ -320,6 +408,9 @@ fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
} }
/// The callback for alsa events that is passed to the alsa subsystem.
/// This is the bridge between low-level alsa events and "high-level"
/// audio system signals.
fn on_alsa_event(last_action_timestamp: &mut i64, fn on_alsa_event(last_action_timestamp: &mut i64,
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>, handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
alsa_event: AlsaEvent) { alsa_event: AlsaEvent) {
@@ -354,5 +445,4 @@ fn on_alsa_event(last_action_timestamp: &mut i64,
} }
e => warn!("Unhandled alsa event: {:?}", e), e => warn!("Unhandled alsa event: {:?}", e),
} }
} }

View File

@@ -1,5 +1,6 @@
#![allow(missing_docs)]
use alsa; use alsa;
use glib;
use png; use png;
use std::convert::From; use std::convert::From;
use std; use std;
@@ -20,6 +21,8 @@ error_chain! {
#[macro_export] #[macro_export]
/// Try to unwrap a `Result<T, E>`. If there is a value `T`, yield it,
/// otherwise print a warning and `return ()` from the function.
macro_rules! try_w { macro_rules! try_w {
($expr:expr) => { ($expr:expr) => {
try_wr!($expr, ()) try_wr!($expr, ())
@@ -34,6 +37,8 @@ macro_rules! try_w {
#[macro_export] #[macro_export]
/// Try to unwrap a `Result<T, E>`. If there is a value `T`, yield it,
/// otherwise print a warning and return from the function with the given value.
macro_rules! try_wr { macro_rules! try_wr {
($expr:expr, $ret:expr) => (match $expr { ($expr:expr, $ret:expr) => (match $expr {
::std::result::Result::Ok(val) => val, ::std::result::Result::Ok(val) => val,
@@ -62,6 +67,8 @@ macro_rules! try_wr {
#[macro_export] #[macro_export]
/// Try to unwrap a `Result<T, E>`. If there is a value `T`, yield it,
/// otherwise return from the function with the given value.
macro_rules! try_r { macro_rules! try_r {
($expr:expr, $ret:expr) => (match $expr { ($expr:expr, $ret:expr) => (match $expr {
::std::result::Result::Ok(val) => val, ::std::result::Result::Ok(val) => val,
@@ -72,31 +79,18 @@ macro_rules! try_r {
} }
#[macro_export] #[macro_export]
/// Try to unwrap a `Result<T, E>`. If there is a value `T`, yield it,
/// otherwise print an error and exit the program.
macro_rules! try_e { macro_rules! try_e {
($expr:expr) => { ($expr:expr) => (match $expr {
try_er!($expr, ())
};
($expr:expr, $fmt:expr, $($arg:tt)+) => {
try_er!($expr, (), $fmt, $(arg)+)
};
($expr:expr, $fmt:expr) => {
try_er!($expr, (), $fmt)
}
}
#[macro_export]
macro_rules! try_er {
($expr:expr, $ret:expr) => (match $expr {
::std::result::Result::Ok(val) => val, ::std::result::Result::Ok(val) => val,
::std::result::Result::Err(err) => { ::std::result::Result::Err(err) => {
error!("{:?}", err); error!("{:?}", err);
::std::process::exit(1); ::std::process::exit(1);
}, },
}); });
($expr:expr, $ret:expr, $fmt:expr) => (match $expr { ($expr:expr, $fmt:expr) => (match $expr {
::std::result::Result::Ok(val) => val, ::std::result::Result::Ok(val) => val,
::std::result::Result::Err(err) => { ::std::result::Result::Err(err) => {
error!("Original error: {:?}", err); error!("Original error: {:?}", err);
@@ -104,7 +98,7 @@ macro_rules! try_er {
std::process::exit(1); std::process::exit(1);
}, },
}); });
($expr:expr, $ret:expr, $fmt:expr, $($arg:tt)+) => (match $expr { ($expr:expr, $fmt:expr, $($arg:tt)+) => (match $expr {
::std::result::Result::Ok(val) => val, ::std::result::Result::Ok(val) => val,
::std::result::Result::Err(err) => { ::std::result::Result::Err(err) => {
error!("Original error: {:?}", err); error!("Original error: {:?}", err);

View File

@@ -1,3 +1,5 @@
#![allow(missing_docs)]
#[macro_export] #[macro_export]
macro_rules! create_builder_item { macro_rules! create_builder_item {
($sname:ident, $($element: ident: $ty: ty),+) => { ($sname:ident, $($element: ident: $ty: ty),+) => {

View File

@@ -1,3 +1,34 @@
//! PNMixer-rs is a mixer for the system tray.
//!
//! # Design Overview
//!
//! The lowest level part of the code is the sound backend. Only Alsa is supported
//! at the moment, but more backends may be added in the future.
//!
//! The backend is hidden behind a frontend, defined in `audio.rs`. Only `audio.rs`
//! deals with audio backends. This means that the whole of the code is blissfully
//! ignorant of the audio backend in use.
//!
//! `audio.rs` is also in charge of emitting signals whenever a change happens.
//! This means that PNMixer-rs design is quite signal-oriented, so to say.
//!
//! The ui code is nothing fancy. Each ui element...
//!
//! * is defined in a single file
//! * strives to be standalone
//! * accesses the sound system with function calls
//! * listens to signals from the audio subsystem to update its appearance
//!
//! There's something you should keep in mind. Audio on a computer is a shared
//! resource. PNMixer-rs isn't the only one that can change it. At any moment the
//! audio volume may be modified by someone else, and we must update the ui
//! accordingly. So listening to changes from the audio subsystem (and therefore
//! having a signal-oriented design) is the most obvious solution to solve that
//! problem.
#![warn(missing_docs)]
#![feature(alloc_system)] #![feature(alloc_system)]
extern crate alloc_system; extern crate alloc_system;
@@ -57,4 +88,3 @@ pub mod ui_tray_icon;
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
pub mod notif; pub mod notif;

View File

@@ -1,31 +1,22 @@
//! The notification subsystem.
//!
//! This subsystem utilizes libnotify to send notifications as popups
//! to the desktop.
use app_state::*; use app_state::*;
use audio::*; use audio::*;
use errors::*; use errors::*;
use glib::Variant;
use glib::prelude::*; use glib::prelude::*;
use gtk::DialogExt;
use gtk::MessageDialogExt;
use gtk::WidgetExt;
use gtk::WindowExt;
use gtk;
use gtk_sys::{GTK_DIALOG_DESTROY_WITH_PARENT, GTK_RESPONSE_YES};
use libnotify; use libnotify;
use prefs::*; use prefs::*;
use std::cell::Cell; use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::thread;
use std::time::Duration;
use support_audio::*;
use support_ui::*;
use ui_popup_menu::*;
use ui_popup_window::*;
use ui_prefs_dialog::*;
use ui_tray_icon::*;
/// An expression of our notification system. Holds all the relevant information
/// needed by Gtk+ callbacks to interact with libnotify.
pub struct Notif { pub struct Notif {
enabled: Cell<bool>, enabled: Cell<bool>,
from_popup: Cell<bool>, from_popup: Cell<bool>,
@@ -38,6 +29,7 @@ pub struct Notif {
} }
impl Notif { impl Notif {
/// Create a new notification instance from the current preferences.
pub fn new(prefs: &Prefs) -> Result<Self> { pub fn new(prefs: &Prefs) -> Result<Self> {
let notif = Notif { let notif = Notif {
enabled: Cell::new(false), enabled: Cell::new(false),
@@ -54,6 +46,8 @@ impl Notif {
return Ok(notif); return Ok(notif);
} }
/// Reload the notification instance from the current
/// preferences.
pub fn reload(&self, prefs: &Prefs) -> Result<()> { pub fn reload(&self, prefs: &Prefs) -> Result<()> {
let timeout = prefs.notify_prefs.notifcation_timeout; let timeout = prefs.notify_prefs.notifcation_timeout;
@@ -74,6 +68,7 @@ impl Notif {
return Ok(()); return Ok(());
} }
/// Shows a volume notification, e.g. for volume or mute state change.
pub fn show_volume_notif(&self, audio: &Audio) -> Result<()> { pub fn show_volume_notif(&self, audio: &Audio) -> Result<()> {
let vol = audio.vol()?; let vol = audio.vol()?;
let vol_level = audio.vol_level(); let vol_level = audio.vol_level();
@@ -114,6 +109,7 @@ impl Notif {
} }
/// Shows a text notification, e.g. for warnings or errors.
pub fn show_text_notif(&self, summary: &str, body: &str) -> Result<()> { pub fn show_text_notif(&self, summary: &str, body: &str) -> Result<()> {
// TODO: error handling // TODO: error handling
self.text_notif.update(summary, Some(body), None).unwrap(); self.text_notif.update(summary, Some(body), None).unwrap();
@@ -126,6 +122,7 @@ impl Notif {
/// Initialize the notification subsystem.
pub fn init_notify(appstate: Rc<AppS>) { pub fn init_notify(appstate: Rc<AppS>) {
debug!("Blah"); debug!("Blah");
{ {

View File

@@ -1,3 +1,12 @@
#![allow(missing_docs)]
//! The preferences subsystem.
//!
//! These are the global application preferences, which can be set
//! by the user. They read from a file in TOML format, presented
//! in the preferences dialog and saved back to the file on request.
use errors::*; use errors::*;
use std::fmt::Display; use std::fmt::Display;
use std::fmt::Formatter; use std::fmt::Formatter;
@@ -18,6 +27,7 @@ const VOL_CONTROL_COMMANDS: [&str; 3] =
#[derive(Deserialize, Debug, Serialize, Clone, Copy)] #[derive(Deserialize, Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
/// When the tray icon is middle-clicked.
pub enum MiddleClickAction { pub enum MiddleClickAction {
ToggleMute, ToggleMute,
ShowPreferences, ShowPreferences,
@@ -60,6 +70,7 @@ impl From<MiddleClickAction> for i32 {
#[derive(Deserialize, Debug, Serialize)] #[derive(Deserialize, Debug, Serialize)]
#[serde(default)] #[serde(default)]
/// Device preferences tab.
pub struct DevicePrefs { pub struct DevicePrefs {
pub card: String, pub card: String,
pub channel: String, pub channel: String,
@@ -78,6 +89,7 @@ impl Default for DevicePrefs {
#[derive(Deserialize, Debug, Serialize)] #[derive(Deserialize, Debug, Serialize)]
#[serde(default)] #[serde(default)]
/// View preferences tab.
pub struct ViewPrefs { pub struct ViewPrefs {
pub draw_vol_meter: bool, pub draw_vol_meter: bool,
pub vol_meter_offset: i32, pub vol_meter_offset: i32,
@@ -100,6 +112,7 @@ impl Default for ViewPrefs {
#[derive(Deserialize, Debug, Serialize)] #[derive(Deserialize, Debug, Serialize)]
#[serde(default)] #[serde(default)]
/// Volume color setting in the view preferences tab.
pub struct VolColor { pub struct VolColor {
pub red: f64, pub red: f64,
pub green: f64, pub green: f64,
@@ -119,6 +132,7 @@ impl Default for VolColor {
#[derive(Deserialize, Debug, Serialize)] #[derive(Deserialize, Debug, Serialize)]
#[serde(default)] #[serde(default)]
/// Behavior preferences tab.
pub struct BehaviorPrefs { pub struct BehaviorPrefs {
pub unmute_on_vol_change: bool, pub unmute_on_vol_change: bool,
pub vol_control_cmd: Option<String>, pub vol_control_cmd: Option<String>,
@@ -145,6 +159,7 @@ impl Default for BehaviorPrefs {
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
#[derive(Deserialize, Debug, Serialize)] #[derive(Deserialize, Debug, Serialize)]
#[serde(default)] #[serde(default)]
/// Notifications preferences tab.
pub struct NotifyPrefs { pub struct NotifyPrefs {
pub enable_notifications: bool, pub enable_notifications: bool,
pub notifcation_timeout: i64, pub notifcation_timeout: i64,
@@ -170,6 +185,7 @@ impl Default for NotifyPrefs {
#[derive(Deserialize, Debug, Serialize, Default)] #[derive(Deserialize, Debug, Serialize, Default)]
#[serde(default)] #[serde(default)]
/// Main preferences struct, holding all sub-preferences.
pub struct Prefs { pub struct Prefs {
pub device_prefs: DevicePrefs, pub device_prefs: DevicePrefs,
pub view_prefs: ViewPrefs, pub view_prefs: ViewPrefs,
@@ -207,6 +223,7 @@ impl Prefs {
} }
/// Reload the current preferences from the config file.
pub fn reload_config(&mut self) -> Result<()> { pub fn reload_config(&mut self) -> Result<()> {
debug!("Reloading config..."); debug!("Reloading config...");
@@ -217,6 +234,7 @@ impl Prefs {
} }
/// Store the current preferences to the config file.
pub fn store_config(&self) -> Result<()> { pub fn store_config(&self) -> Result<()> {
let config_path = get_xdg_dirs().place_config_file("pnmixer.toml") let config_path = get_xdg_dirs().place_config_file("pnmixer.toml")
.from_err()?; .from_err()?;
@@ -230,11 +248,16 @@ impl Prefs {
} }
/// Conver the current preferences to a viewable String.
pub fn to_str(&self) -> String { pub fn to_str(&self) -> String {
return toml::to_string(self).unwrap(); return toml::to_string(self).unwrap();
} }
/// Get an available volume control command, which must exist in `$PATH`.
/// Tries hard to fine one,
/// starting with the given preference setting and falling back to the
/// `VOL_CONTROL_COMMANDS` slice.
pub fn get_avail_vol_control_cmd(&self) -> Option<String> { pub fn get_avail_vol_control_cmd(&self) -> Option<String> {
match self.behavior_prefs.vol_control_cmd { match self.behavior_prefs.vol_control_cmd {
Some(ref c) => return Some(c.clone()), Some(ref c) => return Some(c.clone()),
@@ -261,6 +284,7 @@ impl Display for Prefs {
} }
/// Get the set of XDG directories, relative to our project.
fn get_xdg_dirs() -> xdg::BaseDirectories { fn get_xdg_dirs() -> xdg::BaseDirectories {
return xdg::BaseDirectories::with_prefix("pnmixer-rs").unwrap(); return xdg::BaseDirectories::with_prefix("pnmixer-rs").unwrap();
} }

View File

@@ -1,28 +1,36 @@
//! Alsa audio helper functions.
//!
//! This mod wraps around a few low-level alsa functions and abstracts
//! out the details we don't care about.
use alsa::card::Card; use alsa::card::Card;
use alsa::mixer::{Mixer, Selem, SelemId, Elem}; use alsa::mixer::{Mixer, Selem, Elem};
use alsa; use alsa;
use errors::*; use errors::*;
use libc::c_int; use libc::c_int;
use std::iter::Map;
use std::iter::Filter;
/// Get the default alsa card. This is the one with the ID `0`.
pub fn get_default_alsa_card() -> Card { pub fn get_default_alsa_card() -> Card {
return get_alsa_card_by_id(0); return get_alsa_card_by_id(0);
} }
/// Get an alsa card corresponding to the given ID.
pub fn get_alsa_card_by_id(index: c_int) -> Card { pub fn get_alsa_card_by_id(index: c_int) -> Card {
return Card::new(index); return Card::new(index);
} }
/// Get all available alsa cards.
pub fn get_alsa_cards() -> alsa::card::Iter { pub fn get_alsa_cards() -> alsa::card::Iter {
return alsa::card::Iter::new(); return alsa::card::Iter::new();
} }
/// Get the first playable alsa card.
pub fn get_first_playable_alsa_card() -> Result<Card> { pub fn get_first_playable_alsa_card() -> Result<Card> {
for m_card in get_alsa_cards() { for m_card in get_alsa_cards() {
match m_card { match m_card {
@@ -39,6 +47,7 @@ pub fn get_first_playable_alsa_card() -> Result<Card> {
} }
/// Get the names of all playable alsa cards.
pub fn get_playable_alsa_card_names() -> Vec<String> { pub fn get_playable_alsa_card_names() -> Vec<String> {
let mut vec = vec![]; let mut vec = vec![];
for m_card in get_alsa_cards() { for m_card in get_alsa_cards() {
@@ -59,6 +68,7 @@ pub fn get_playable_alsa_card_names() -> Vec<String> {
} }
/// Get an alsa card by the given name.
pub fn get_alsa_card_by_name(name: String) -> Result<Card> { pub fn get_alsa_card_by_name(name: String) -> Result<Card> {
for r_card in get_alsa_cards() { for r_card in get_alsa_cards() {
let card = r_card?; let card = r_card?;
@@ -71,6 +81,7 @@ pub fn get_alsa_card_by_name(name: String) -> Result<Card> {
} }
/// Check whether the given alsa card as a playable `Selem`.
pub fn alsa_card_has_playable_selem(card: &Card) -> bool { pub fn alsa_card_has_playable_selem(card: &Card) -> bool {
let mixer = try_wr!(get_mixer(&card), false); let mixer = try_wr!(get_mixer(&card), false);
for selem in get_playable_selems(&mixer) { for selem in get_playable_selems(&mixer) {
@@ -82,11 +93,13 @@ pub fn alsa_card_has_playable_selem(card: &Card) -> bool {
} }
/// Get the `Mixer` for the given alsa card.
pub fn get_mixer(card: &Card) -> Result<Mixer> { pub fn get_mixer(card: &Card) -> Result<Mixer> {
return Mixer::new(&format!("hw:{}", card.get_index()), false).from_err(); return Mixer::new(&format!("hw:{}", card.get_index()), false).from_err();
} }
/// Get the `Selem` from the given `Elem`.
pub fn get_selem(elem: Elem) -> Selem { pub fn get_selem(elem: Elem) -> Selem {
/* in the ALSA API, there are currently only simple elements, /* in the ALSA API, there are currently only simple elements,
* so this unwrap() should be safe. * so this unwrap() should be safe.
@@ -95,6 +108,7 @@ pub fn get_selem(elem: Elem) -> Selem {
} }
/// Get all playable `Selem`s.
pub fn get_playable_selems(mixer: &Mixer) -> Vec<Selem> { pub fn get_playable_selems(mixer: &Mixer) -> Vec<Selem> {
let mut v = vec![]; let mut v = vec![];
for s in mixer.iter().map(get_selem).filter(selem_is_playable) { for s in mixer.iter().map(get_selem).filter(selem_is_playable) {
@@ -104,6 +118,7 @@ pub fn get_playable_selems(mixer: &Mixer) -> Vec<Selem> {
} }
/// Get the first playable `Selem`.
pub fn get_first_playable_selem(mixer: &Mixer) -> Result<Selem> { pub fn get_first_playable_selem(mixer: &Mixer) -> Result<Selem> {
for s in mixer.iter().map(get_selem).filter(selem_is_playable) { for s in mixer.iter().map(get_selem).filter(selem_is_playable) {
return Ok(s); return Ok(s);
@@ -113,6 +128,7 @@ pub fn get_first_playable_selem(mixer: &Mixer) -> Result<Selem> {
} }
/// Get the names from all playable `Selem`s.
pub fn get_playable_selem_names(mixer: &Mixer) -> Vec<String> { pub fn get_playable_selem_names(mixer: &Mixer) -> Vec<String> {
let mut vec = vec![]; let mut vec = vec![];
for selem in get_playable_selems(mixer) { for selem in get_playable_selems(mixer) {
@@ -127,6 +143,7 @@ pub fn get_playable_selem_names(mixer: &Mixer) -> Vec<String> {
} }
/// Get a playable `Selem` by the given name.
pub fn get_playable_selem_by_name(mixer: &Mixer, pub fn get_playable_selem_by_name(mixer: &Mixer,
name: String) name: String)
-> Result<Selem> { -> Result<Selem> {
@@ -143,6 +160,7 @@ pub fn get_playable_selem_by_name(mixer: &Mixer,
} }
/// Check whether the given `Selem` is playable.
pub fn selem_is_playable(selem: &Selem) -> bool { pub fn selem_is_playable(selem: &Selem) -> bool {
return selem.has_playback_volume(); return selem.has_playback_volume();
} }

View File

@@ -1,9 +1,19 @@
#![allow(missing_docs)] // enum
//! Helper functions of the audio subsystem.
//!
//! These functions are not directly connected to the `Audio` struct,
//! but are important helpers.
use audio::{Audio, AudioUser}; use audio::{Audio, AudioUser};
use errors::*; use errors::*;
use prefs::*; use prefs::*;
use support_alsa::*;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
/// The direction of a volume change.
pub enum VolDir { pub enum VolDir {
Up, Up,
Down, Down,
@@ -11,6 +21,15 @@ pub enum VolDir {
} }
/// Convert a volume change to the `VolDir` type.
/// ## `old`
/// The old volume value.
/// ## `new`
/// The new volume value.
///
/// # Returns
///
/// The direction of the volume change as `Voldir`.
pub fn vol_change_to_voldir(old: f64, new: f64) -> VolDir { pub fn vol_change_to_voldir(old: f64, new: f64) -> VolDir {
if old < new { if old < new {
return VolDir::Up; return VolDir::Up;
@@ -22,6 +41,9 @@ pub fn vol_change_to_voldir(old: f64, new: f64) -> VolDir {
} }
/// Kinda mimics `lrint` from libm. If the direction of the volume change
/// is `Up` then calls `ceil()`, if it's `Down`, then calls `floor()`, otherwise
/// returns the value unchanged.
pub fn lrint(v: f64, dir: VolDir) -> f64 { pub fn lrint(v: f64, dir: VolDir) -> f64 {
match dir { match dir {
VolDir::Up => v.ceil(), VolDir::Up => v.ceil(),
@@ -30,6 +52,8 @@ pub fn lrint(v: f64, dir: VolDir) -> f64 {
} }
} }
/// Reload the audio system.
pub fn audio_reload(audio: &Audio, pub fn audio_reload(audio: &Audio,
prefs: &Prefs, prefs: &Prefs,
user: AudioUser) user: AudioUser)
@@ -41,6 +65,9 @@ pub fn audio_reload(audio: &Audio,
} }
/// Converts the actual volume of the audio configuration, which depends
/// on the volume range, to a scale of 0-100, reprenting the percentage
/// of the volume level.
pub fn vol_to_percent(vol: i64, range: (i64, i64)) -> Result<f64> { pub fn vol_to_percent(vol: i64, range: (i64, i64)) -> Result<f64> {
let (min, max) = range; let (min, max) = range;
ensure!(min < max, ensure!(min < max,
@@ -52,6 +79,9 @@ pub fn vol_to_percent(vol: i64, range: (i64, i64)) -> Result<f64> {
} }
/// Converts the percentage of the volume level (0-100) back to the actual
/// low-level representation of the volume, which depends on the volume
/// range.
pub fn percent_to_vol(vol: f64, range: (i64, i64), dir: VolDir) -> Result<i64> { pub fn percent_to_vol(vol: f64, range: (i64, i64), dir: VolDir) -> Result<i64> {
let (min, max) = range; let (min, max) = range;
ensure!(min < max, ensure!(min < max,
@@ -62,3 +92,18 @@ pub fn percent_to_vol(vol: f64, range: (i64, i64), dir: VolDir) -> Result<i64> {
let _v = lrint(vol / 100.0 * ((max - min) as f64), dir) + (min as f64); let _v = lrint(vol / 100.0 * ((max - min) as f64), dir) + (min as f64);
return Ok(_v as i64); return Ok(_v as i64);
} }
/// Get all playable card names.
pub fn get_playable_card_names() -> Vec<String> {
return get_playable_alsa_card_names();
}
/// Get all playable channel names.
pub fn get_playable_chan_names(card_name: String) -> Vec<String> {
let card = try_r!(get_alsa_card_by_name(card_name), Vec::default());
let mixer = try_r!(get_mixer(&card), Vec::default());
return get_playable_selem_names(&mixer);
}

View File

@@ -1,3 +1,5 @@
//! Helper functions for invoking system commands.
use errors::*; use errors::*;
use glib; use glib;
use prefs::Prefs; use prefs::Prefs;
@@ -6,6 +8,9 @@ use std;
/// Execute an available volume control command asynchronously, starting with
/// the preferences and using some fallback values. If none of these
/// are valid executables in `$PATH`, then return `Err(err)`.
pub fn execute_vol_control_command(prefs: &Prefs) -> Result<()> { pub fn execute_vol_control_command(prefs: &Prefs) -> Result<()> {
let m_cmd = prefs.get_avail_vol_control_cmd(); let m_cmd = prefs.get_avail_vol_control_cmd();
@@ -16,6 +21,7 @@ pub fn execute_vol_control_command(prefs: &Prefs) -> Result<()> {
} }
/// Try to execute the given command asynchronously via gtk.
pub fn execute_command(cmd: &str) -> Result<()> { pub fn execute_command(cmd: &str) -> Result<()> {
return glib::spawn_command_line_async(cmd) return glib::spawn_command_line_async(cmd)
.map_err(|e| { .map_err(|e| {

View File

@@ -1,3 +1,6 @@
//! Helper functions for the UI, mostly pixbuf functions.
use errors::*; use errors::*;
use gdk_pixbuf; use gdk_pixbuf;
use gdk_pixbuf_sys; use gdk_pixbuf_sys;
@@ -9,6 +12,8 @@ use std::path::*;
/// Copy a `Pixbuf` explicitly, since they don't implement the `Copy` trait.
/// Currently does not call `gdk_pixbuf_copy_options()`.
pub fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf { pub fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf {
let new_pixbuf = unsafe { let new_pixbuf = unsafe {
@@ -21,6 +26,8 @@ pub fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf {
} }
/// Get a pixbuf by name from the given theme with the requested size.
/// Note that the size is not enforced, but rather a hint.
pub fn pixbuf_new_from_theme(icon_name: &str, pub fn pixbuf_new_from_theme(icon_name: &str,
size: i32, size: i32,
theme: &gtk::IconTheme) theme: &gtk::IconTheme)
@@ -42,7 +49,9 @@ pub fn pixbuf_new_from_theme(icon_name: &str,
#[macro_export] #[macro_export]
macro_rules! pixbuf_new_from_file { /// Create a pixbuf from the given PNG file. Includes the file as bytes
/// in the binary and decodes it.
macro_rules! pixbuf_new_from_png {
($name:expr) => { ($name:expr) => {
{ {
use gdk_pixbuf; use gdk_pixbuf;
@@ -73,4 +82,3 @@ macro_rules! pixbuf_new_from_file {
} }
} }
} }

View File

@@ -1,3 +1,6 @@
//! Global GUI state.
use app_state::*; use app_state::*;
use audio::{AudioUser, AudioSignal}; use audio::{AudioUser, AudioSignal};
use gtk::DialogExt; use gtk::DialogExt;
@@ -20,16 +23,23 @@ use notif::*;
/// The GUI struct mostly describing the main widgets (mostly wrapped)
/// the user interacts with.
pub struct Gui { pub struct Gui {
_cant_construct: (), _cant_construct: (),
/// The tray icon.
pub tray_icon: TrayIcon, pub tray_icon: TrayIcon,
/// The popup window.
pub popup_window: PopupWindow, pub popup_window: PopupWindow,
/// The popup menu.
pub popup_menu: PopupMenu, pub popup_menu: PopupMenu,
/* prefs_dialog is dynamically created and destroyed */ /* prefs_dialog is dynamically created and destroyed */
/// The preferences dialog.
pub prefs_dialog: RefCell<Option<PrefsDialog>>, pub prefs_dialog: RefCell<Option<PrefsDialog>>,
} }
impl Gui { impl Gui {
/// Constructor. The prefs dialog is initialized as `None`.
pub fn new(builder_popup_window: gtk::Builder, pub fn new(builder_popup_window: gtk::Builder,
builder_popup_menu: gtk::Builder, builder_popup_menu: gtk::Builder,
prefs: &Prefs) prefs: &Prefs)
@@ -45,6 +55,7 @@ impl Gui {
} }
/// Initialize the GUI system.
pub fn init(appstate: Rc<AppS>) { pub fn init(appstate: Rc<AppS>) {
{ {
/* "global" audio signal handler */ /* "global" audio signal handler */
@@ -79,6 +90,14 @@ pub fn init(appstate: Rc<AppS>) {
} }
/// Used to run a dialog when an audio error occured, suggesting the user
/// may reload the audio system either manually or by confirming the dialog
/// via the confirmation button.
///
/// # Returns
///
/// `GTK_RESPONSE_YES` if the user wants to reload the audio system,
/// `GTK_RESPONSE_NO` otherwise.
fn run_audio_error_dialog(parent: &gtk::Window) -> i32 { fn run_audio_error_dialog(parent: &gtk::Window) -> i32 {
error!("Connection with audio failed, you probably need to restart pnmixer."); error!("Connection with audio failed, you probably need to restart pnmixer.");

View File

@@ -1,5 +1,18 @@
#![allow(missing_docs)] // glade_helpers
//! The popup menu subsystem when the user right-clicks on the tray icon.
//!
//! Shows the menu with the following entries:
//!
//! * Mute
//! * Volume Control
//! * Preferences
//! * Reload Sound
//! * About
//! * Quit
use app_state::*; use app_state::*;
use audio::{AudioUser, AudioSignal}; use audio::AudioUser;
use gtk::prelude::*; use gtk::prelude::*;
use gtk; use gtk;
use std::rc::Rc; use std::rc::Rc;
@@ -26,6 +39,7 @@ create_builder_item!(PopupMenu,
/// Initialize the popup menu subsystem, registering all callbacks.
pub fn init_popup_menu(appstate: Rc<AppS>) { pub fn init_popup_menu(appstate: Rc<AppS>) {
/* audio.connect_handler */ /* audio.connect_handler */
{ {
@@ -113,6 +127,7 @@ pub fn init_popup_menu(appstate: Rc<AppS>) {
} }
/// When the about menu item is activated.
fn on_about_item_activate(appstate: &AppS) { fn on_about_item_activate(appstate: &AppS) {
let popup_menu = &appstate.gui.popup_menu.menu_window; let popup_menu = &appstate.gui.popup_menu.menu_window;
let about_dialog = create_about_dialog(); let about_dialog = create_about_dialog();
@@ -123,6 +138,7 @@ fn on_about_item_activate(appstate: &AppS) {
} }
/// Create the About dialog from scratch.
fn create_about_dialog() -> gtk::AboutDialog { fn create_about_dialog() -> gtk::AboutDialog {
let about_dialog: gtk::AboutDialog = gtk::AboutDialog::new(); let about_dialog: gtk::AboutDialog = gtk::AboutDialog::new();
@@ -153,12 +169,14 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.",
} }
/// When the Preferences item is activated.
fn on_prefs_item_activate(appstate: &Rc<AppS>) { fn on_prefs_item_activate(appstate: &Rc<AppS>) {
/* TODO: only create if needed */ /* TODO: only create if needed */
show_prefs_dialog(appstate); show_prefs_dialog(appstate);
} }
/// When the Mute item is checked.
fn set_mute_check(apps: &Rc<AppS>) { fn set_mute_check(apps: &Rc<AppS>) {
let mute_check = &apps.gui.popup_menu.mute_check; let mute_check = &apps.gui.popup_menu.mute_check;
let m_muted = apps.audio.get_mute(); let m_muted = apps.audio.get_mute();

View File

@@ -1,3 +1,9 @@
//! The popup window subsystem when the user left-clicks on the tray icon.
//!
//! This shows the manipulatable volume slider with the current volume and
//! the mute checkbox.
use app_state::*; use app_state::*;
use audio::*; use audio::*;
use errors::*; use errors::*;
@@ -17,18 +23,30 @@ use support_cmd::*;
/// The main struct for the popup window, holding all relevant sub-widgets
/// and some mutable state.
pub struct PopupWindow { pub struct PopupWindow {
_cant_construct: (), _cant_construct: (),
/// The main window for the popup window widget.
pub popup_window: gtk::Window, pub popup_window: gtk::Window,
/// The volume scale adjustment.
pub vol_scale_adj: gtk::Adjustment, pub vol_scale_adj: gtk::Adjustment,
/// The volume scale.
pub vol_scale: gtk::Scale, pub vol_scale: gtk::Scale,
/// The mute check button.
pub mute_check: gtk::CheckButton, pub mute_check: gtk::CheckButton,
/// The button to start the external mixer.
pub mixer_button: gtk::Button, pub mixer_button: gtk::Button,
pub toggle_signal: Cell<u64>, /// Signal for mute_check.connect_toggled callback,
pub changed_signal: Cell<u64>, /// so we can block it temporarily.
toggle_signal: Cell<u64>,
/// Signal for vol_scale_adj.connect_value_changed callback,
/// so we can block it temporarily.
changed_signal: Cell<u64>,
} }
impl PopupWindow { impl PopupWindow {
/// Constructor.
pub fn new(builder: gtk::Builder) -> PopupWindow { pub fn new(builder: gtk::Builder) -> PopupWindow {
return PopupWindow { return PopupWindow {
_cant_construct: (), _cant_construct: (),
@@ -42,6 +60,8 @@ impl PopupWindow {
}; };
} }
/// Update the popup window state, including the slider
/// and the mute checkbutton.
pub fn update(&self, audio: &Audio) -> Result<()> { pub fn update(&self, audio: &Audio) -> Result<()> {
let cur_vol = audio.vol()?; let cur_vol = audio.vol()?;
set_slider(&self.vol_scale_adj, cur_vol); set_slider(&self.vol_scale_adj, cur_vol);
@@ -51,6 +71,7 @@ impl PopupWindow {
return Ok(()); return Ok(());
} }
/// Update the mute checkbutton.
pub fn update_mute_check(&self, audio: &Audio) { pub fn update_mute_check(&self, audio: &Audio) {
let m_muted = audio.get_mute(); let m_muted = audio.get_mute();
@@ -76,6 +97,8 @@ impl PopupWindow {
self.toggle_signal.get()); self.toggle_signal.get());
} }
/// Set the page increment fro the volume scale adjustment based on the
/// preferences.
fn set_vol_increment(&self, prefs: &Prefs) { fn set_vol_increment(&self, prefs: &Prefs) {
self.vol_scale_adj self.vol_scale_adj
.set_page_increment(prefs.behavior_prefs.vol_scroll_step); .set_page_increment(prefs.behavior_prefs.vol_scroll_step);
@@ -85,7 +108,7 @@ impl PopupWindow {
} }
/// Initialize the popup window subsystem.
pub fn init_popup_window(appstate: Rc<AppS>) { pub fn init_popup_window(appstate: Rc<AppS>) {
/* audio.connect_handler */ /* audio.connect_handler */
{ {
@@ -188,6 +211,7 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
} }
/// When the popup window is shown.
fn on_popup_window_show(appstate: &AppS) { fn on_popup_window_show(appstate: &AppS) {
let popup_window = &appstate.gui.popup_window; let popup_window = &appstate.gui.popup_window;
appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow()); appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow());
@@ -201,6 +225,7 @@ fn on_popup_window_show(appstate: &AppS) {
} }
/// On key or button press event on the popup window.
fn on_popup_window_event(w: &gtk::Window, e: &gdk::Event) -> gtk::Inhibit { fn on_popup_window_event(w: &gtk::Window, e: &gdk::Event) -> gtk::Inhibit {
match gdk::Event::get_event_type(e) { match gdk::Event::get_event_type(e) {
gdk::EventType::GrabBroken => w.hide(), gdk::EventType::GrabBroken => w.hide(),
@@ -230,6 +255,7 @@ fn on_popup_window_event(w: &gtk::Window, e: &gdk::Event) -> gtk::Inhibit {
} }
/// When the volume scale slider is moved.
fn on_vol_scale_value_changed(appstate: &AppS) { fn on_vol_scale_value_changed(appstate: &AppS) {
let audio = &appstate.audio; let audio = &appstate.audio;
let old_vol = try_w!(audio.vol()); let old_vol = try_w!(audio.vol());
@@ -251,17 +277,20 @@ fn on_vol_scale_value_changed(appstate: &AppS) {
} }
/// When the mute checkbutton is toggled.
fn on_mute_check_toggled(appstate: &AppS) { fn on_mute_check_toggled(appstate: &AppS) {
let audio = &appstate.audio; let audio = &appstate.audio;
try_w!(audio.toggle_mute(AudioUser::Popup)) try_w!(audio.toggle_mute(AudioUser::Popup))
} }
/// Set the volume slider to the given value.
pub fn set_slider(vol_scale_adj: &gtk::Adjustment, scale: f64) { pub fn set_slider(vol_scale_adj: &gtk::Adjustment, scale: f64) {
vol_scale_adj.set_value(scale); vol_scale_adj.set_value(scale);
} }
/// Grab all devices, keyboard and mouse.
fn grab_devices(window: &gtk::Window) -> Result<()> { fn grab_devices(window: &gtk::Window) -> Result<()> {
let device = gtk::get_current_event_device().ok_or("No current device")?; let device = gtk::get_current_event_device().ok_or("No current device")?;

View File

@@ -1,16 +1,21 @@
//! The preferences window subsystem, when the user clicks the "Preferences"
//! menu item on the popup menu.
use app_state::*; use app_state::*;
use audio::{AudioUser, AudioSignal}; use audio::{AudioUser, AudioSignal};
use errors::*;
use gdk; use gdk;
use gtk::ResponseType; use gtk::ResponseType;
use gtk::prelude::*; use gtk::prelude::*;
use gtk; use gtk;
use prefs::*; use prefs::*;
use std::rc::Rc; use std::rc::Rc;
use support_alsa::*; use support_audio::*;
/// The main preferences dialog, holding all the relevant subwidgets we
/// need to convert its state to preferences and back.
pub struct PrefsDialog { pub struct PrefsDialog {
_cant_construct: (), _cant_construct: (),
prefs_dialog: gtk::Dialog, prefs_dialog: gtk::Dialog,
@@ -109,6 +114,7 @@ impl PrefsDialog {
} }
/// Import the given preferences into the preferences dialog state.
fn from_prefs(&self, prefs: &Prefs) { fn from_prefs(&self, prefs: &Prefs) {
/* DevicePrefs */ /* DevicePrefs */
/* filled on show signal with audio info */ /* filled on show signal with audio info */
@@ -171,6 +177,8 @@ impl PrefsDialog {
} }
/// Export the dialog state to the `Prefs` struct, which can be used
/// to write them to the config file.
fn to_prefs(&self) -> Prefs { fn to_prefs(&self) -> Prefs {
let card = self.card_combo.get_active_text(); let card = self.card_combo.get_active_text();
let channel = self.chan_combo.get_active_text(); let channel = self.chan_combo.get_active_text();
@@ -242,6 +250,8 @@ impl PrefsDialog {
} }
/// Show the preferences dialog. This is created and destroyed dynamically
/// and not persistent across the application lifetime.
pub fn show_prefs_dialog(appstate: &Rc<AppS>) { pub fn show_prefs_dialog(appstate: &Rc<AppS>) {
if appstate.gui if appstate.gui
.prefs_dialog .prefs_dialog
@@ -265,6 +275,8 @@ pub fn show_prefs_dialog(appstate: &Rc<AppS>) {
} }
/// Initialize the internal prefs dialog handler that connects to the audio
/// system.
pub fn init_prefs_callback(appstate: Rc<AppS>) { pub fn init_prefs_callback(appstate: Rc<AppS>) {
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler(Box::new(move |s, u| { appstate.audio.connect_handler(Box::new(move |s, u| {
@@ -288,6 +300,7 @@ pub fn init_prefs_callback(appstate: Rc<AppS>) {
} }
/// Initialize the preferences dialog gtk callbacks.
fn init_prefs_dialog(appstate: &Rc<AppS>) { fn init_prefs_dialog(appstate: &Rc<AppS>) {
/* prefs_dialog.connect_show */ /* prefs_dialog.connect_show */
@@ -355,16 +368,17 @@ fn init_prefs_dialog(appstate: &Rc<AppS>) {
} }
/// Fill the card combo box in the Devices tab.
fn fill_card_combo(appstate: &AppS) { fn fill_card_combo(appstate: &AppS) {
let m_cc = appstate.gui.prefs_dialog.borrow(); let m_cc = appstate.gui.prefs_dialog.borrow();
let card_combo = &m_cc.as_ref().unwrap().card_combo; let card_combo = &m_cc.as_ref().unwrap().card_combo;
card_combo.remove_all(); card_combo.remove_all();
let acard = appstate.audio.acard.borrow(); let audio = &appstate.audio;
/* set card combo */ /* set card combo */
let cur_card_name = try_w!(acard.card_name(), let cur_card_name = try_w!(audio.card_name(),
"Can't get current card name!"); "Can't get current card name!");
let available_card_names = get_playable_alsa_card_names(); let available_card_names = get_playable_card_names();
/* set_active_id doesn't work, so save the index */ /* set_active_id doesn't work, so save the index */
let mut c_index: i32 = -1; let mut c_index: i32 = -1;
@@ -381,21 +395,20 @@ fn fill_card_combo(appstate: &AppS) {
} }
/// Fill the channel combo box in the Devices tab.
fn fill_chan_combo(appstate: &AppS, cardname: Option<String>) { fn fill_chan_combo(appstate: &AppS, cardname: Option<String>) {
let m_cc = appstate.gui.prefs_dialog.borrow(); let m_cc = appstate.gui.prefs_dialog.borrow();
let chan_combo = &m_cc.as_ref().unwrap().chan_combo; let chan_combo = &m_cc.as_ref().unwrap().chan_combo;
chan_combo.remove_all(); chan_combo.remove_all();
let cur_acard = appstate.audio.acard.borrow(); let audio = &appstate.audio;
let card = match cardname { let available_chan_names = match cardname {
Some(name) => try_w!(get_alsa_card_by_name(name).from_err()), Some(name) => get_playable_chan_names(name),
None => cur_acard.as_ref().card, None => audio.playable_chan_names(),
}; };
/* set chan combo */ /* set chan combo */
let cur_chan_name = try_w!(cur_acard.chan_name()); let cur_chan_name = try_w!(audio.chan_name());
let mixer = try_w!(get_mixer(&card));
let available_chan_names = get_playable_selem_names(&mixer);
/* set_active_id doesn't work, so save the index */ /* set_active_id doesn't work, so save the index */
let mut c_index: i32 = -1; let mut c_index: i32 = -1;

View File

@@ -1,3 +1,9 @@
//! The tray icon subsystem.
//!
//! This manages the tray icon Pixbuf as well as the callbacks on left and
//! right-click.
use app_state::*; use app_state::*;
use audio::*; use audio::*;
use errors::*; use errors::*;
@@ -19,16 +25,24 @@ use ui_prefs_dialog::show_prefs_dialog;
const ICON_MIN_SIZE: i32 = 16; const ICON_MIN_SIZE: i32 = 16;
/// The tray icon struct, describing the complete visual state.
pub struct TrayIcon { pub struct TrayIcon {
_cant_construct: (), _cant_construct: (),
/// The volume meter to draw on the actual Pixbuf, if requested.
pub volmeter: RefCell<Option<VolMeter>>, pub volmeter: RefCell<Option<VolMeter>>,
/// The actual Pixbuf tray icon.
pub audio_pix: RefCell<AudioPix>, pub audio_pix: RefCell<AudioPix>,
/// The gtk `StatusIcon` widget, used to register callbacks.
pub status_icon: gtk::StatusIcon, pub status_icon: gtk::StatusIcon,
/// The current icon size.
pub icon_size: Cell<i32>, pub icon_size: Cell<i32>,
} }
impl TrayIcon { impl TrayIcon {
/// Constructor. `audio_pix` is initialized as empty GdkPixbuf, to save
/// one iteration of png decoding (`update_all()` is triggered immediately
/// on startup through `tray_icon.connect_size_changed`.
pub fn new(prefs: &Prefs) -> Result<TrayIcon> { pub fn new(prefs: &Prefs) -> Result<TrayIcon> {
let draw_vol_meter = prefs.view_prefs.draw_vol_meter; let draw_vol_meter = prefs.view_prefs.draw_vol_meter;
@@ -57,7 +71,12 @@ impl TrayIcon {
} }
fn update_vol_meter(&self, cur_vol: f64, vol_level: VolLevel) -> Result<()> { /// Update the volume meter Pixbuf, which is drawn on top of the
/// actual Pixbuf.
fn update_vol_meter(&self,
cur_vol: f64,
vol_level: VolLevel)
-> Result<()> {
let audio_pix = self.audio_pix.borrow(); let audio_pix = self.audio_pix.borrow();
let pixbuf = audio_pix.select_pix(vol_level); let pixbuf = audio_pix.select_pix(vol_level);
@@ -75,6 +94,7 @@ impl TrayIcon {
} }
/// Update the tooltip of the tray icon.
fn update_tooltip(&self, audio: &Audio) { fn update_tooltip(&self, audio: &Audio) {
let cardname = audio.acard let cardname = audio.acard
.borrow() .borrow()
@@ -105,6 +125,7 @@ impl TrayIcon {
} }
/// Update the whole tray icon state.
pub fn update_all(&self, pub fn update_all(&self,
prefs: &Prefs, prefs: &Prefs,
audio: &Audio, audio: &Audio,
@@ -138,6 +159,8 @@ impl TrayIcon {
/// The volume meter, describes by its colors, offset and width/row
/// properties.
pub struct VolMeter { pub struct VolMeter {
red: u8, red: u8,
green: u8, green: u8,
@@ -151,6 +174,7 @@ pub struct VolMeter {
impl VolMeter { impl VolMeter {
/// Constructor. `width` and `row` are initialized with default values.
fn new(prefs: &Prefs) -> VolMeter { fn new(prefs: &Prefs) -> VolMeter {
return VolMeter { return VolMeter {
red: (prefs.view_prefs.vol_meter_color.red * 255.0) as u8, red: (prefs.view_prefs.vol_meter_color.red * 255.0) as u8,
@@ -166,6 +190,7 @@ impl VolMeter {
} }
// TODO: cache input pixbuf? // TODO: cache input pixbuf?
/// Draw the volume meter on top of the actual tray icon Pixbuf.
fn meter_draw(&self, fn meter_draw(&self,
volume: i64, volume: i64,
pixbuf: &gdk_pixbuf::Pixbuf) pixbuf: &gdk_pixbuf::Pixbuf)
@@ -246,6 +271,7 @@ impl VolMeter {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// The actual tray icon Pixbuf, which depends on the current volume level.
pub struct AudioPix { pub struct AudioPix {
muted: gdk_pixbuf::Pixbuf, muted: gdk_pixbuf::Pixbuf,
low: gdk_pixbuf::Pixbuf, low: gdk_pixbuf::Pixbuf,
@@ -256,27 +282,28 @@ pub struct AudioPix {
impl Default for AudioPix { impl Default for AudioPix {
fn default() -> AudioPix { fn default() -> AudioPix {
let dummy_pixbuf = unsafe { let dummy_pixbuf =
gdk_pixbuf::Pixbuf::new( unsafe {
gdk_pixbuf_sys::GDK_COLORSPACE_RGB, gdk_pixbuf::Pixbuf::new(gdk_pixbuf_sys::GDK_COLORSPACE_RGB,
false, false,
8, 8,
1, 1,
1, 1)
).unwrap() .unwrap()
}; };
return AudioPix { return AudioPix {
muted: dummy_pixbuf.clone(), muted: dummy_pixbuf.clone(),
low: dummy_pixbuf.clone(), low: dummy_pixbuf.clone(),
medium: dummy_pixbuf.clone(), medium: dummy_pixbuf.clone(),
high: dummy_pixbuf.clone(), high: dummy_pixbuf.clone(),
off: dummy_pixbuf.clone(), off: dummy_pixbuf.clone(),
}; };
} }
} }
impl AudioPix { impl AudioPix {
/// Constructor.
fn new(size: i32, prefs: &Prefs) -> Result<AudioPix> { fn new(size: i32, prefs: &Prefs) -> Result<AudioPix> {
let system_theme = prefs.view_prefs.system_theme; let system_theme = prefs.view_prefs.system_theme;
@@ -324,11 +351,11 @@ impl AudioPix {
} }
} else { } else {
AudioPix { AudioPix {
muted: pixbuf_new_from_file!("../data/pixmaps/pnmixer-muted.png")?, muted: pixbuf_new_from_png!("../data/pixmaps/pnmixer-muted.png")?,
low: pixbuf_new_from_file!("../data/pixmaps/pnmixer-low.png")?, low: pixbuf_new_from_png!("../data/pixmaps/pnmixer-low.png")?,
medium: pixbuf_new_from_file!("../data/pixmaps/pnmixer-medium.png")?, medium: pixbuf_new_from_png!("../data/pixmaps/pnmixer-medium.png")?,
high: pixbuf_new_from_file!("../data/pixmaps/pnmixer-high.png")?, high: pixbuf_new_from_png!("../data/pixmaps/pnmixer-high.png")?,
off: pixbuf_new_from_file!("../data/pixmaps/pnmixer-off.png")?, off: pixbuf_new_from_png!("../data/pixmaps/pnmixer-off.png")?,
} }
} }
}; };
@@ -336,6 +363,7 @@ impl AudioPix {
} }
/// Select the try icon Pixbuf depending on the `VolLevel`.
fn select_pix(&self, vol_level: VolLevel) -> &gdk_pixbuf::Pixbuf { fn select_pix(&self, vol_level: VolLevel) -> &gdk_pixbuf::Pixbuf {
match vol_level { match vol_level {
VolLevel::Muted => &self.muted, VolLevel::Muted => &self.muted,
@@ -348,6 +376,7 @@ impl AudioPix {
} }
/// Initialize the tray icon subsystem.
pub fn init_tray_icon(appstate: Rc<AppS>) { pub fn init_tray_icon(appstate: Rc<AppS>) {
let tray_icon = &appstate.gui.tray_icon; let tray_icon = &appstate.gui.tray_icon;
@@ -359,8 +388,9 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) { appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) {
(_, _) => { (_, _) => {
apps.gui.tray_icon.update_tooltip(&apps.audio); apps.gui.tray_icon.update_tooltip(&apps.audio);
try_w!(apps.gui.tray_icon.update_vol_meter(try_w!(apps.audio.vol()), try_w!(apps.gui.tray_icon.update_vol_meter(try_w!(apps.audio
apps.audio.vol_level())); .vol()),
apps.audio.vol_level()));
} }
})); }));
} }
@@ -424,6 +454,7 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
} }
/// When the tray icon is activated.
fn on_tray_icon_activate(appstate: &AppS) { fn on_tray_icon_activate(appstate: &AppS) {
let popup_window = &appstate.gui.popup_window.popup_window; let popup_window = &appstate.gui.popup_window.popup_window;
@@ -435,6 +466,7 @@ fn on_tray_icon_activate(appstate: &AppS) {
} }
/// When the popup menu is shown, hide the popup window, if any.
fn on_tray_icon_popup_menu(appstate: &AppS) { fn on_tray_icon_popup_menu(appstate: &AppS) {
let popup_window = &appstate.gui.popup_window.popup_window; let popup_window = &appstate.gui.popup_window.popup_window;
let popup_menu = &appstate.gui.popup_menu.menu; let popup_menu = &appstate.gui.popup_menu.menu;
@@ -444,6 +476,8 @@ fn on_tray_icon_popup_menu(appstate: &AppS) {
} }
/// When the mouse scroll event happens while the mouse pointer is
/// on the tray icon.
fn on_tray_icon_scroll_event(appstate: &AppS, fn on_tray_icon_scroll_event(appstate: &AppS,
event: &gdk::EventScroll) event: &gdk::EventScroll)
-> bool { -> bool {
@@ -473,6 +507,9 @@ fn on_tray_icon_scroll_event(appstate: &AppS,
} }
/// Basically when the tray icon is clicked (although we connect to the `release`
/// event). This decides whether it was a left, right or middle-click and
/// takes appropriate actions.
fn on_tray_button_release_event(appstate: &Rc<AppS>, fn on_tray_button_release_event(appstate: &Rc<AppS>,
event_button: &gdk::EventButton) event_button: &gdk::EventButton)
-> bool { -> bool {