Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d286b51cde |
15
.travis.yml
15
.travis.yml
@@ -7,25 +7,30 @@ 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-cargo build
|
./.travis/build.sh
|
||||||
# travis-cargo test &&
|
# travis-cargo test &&
|
||||||
# travis-cargo --only stable doc
|
# travis-cargo --only stable doc
|
||||||
# after_success:
|
# after_success:
|
||||||
|
|||||||
15
.travis/build.sh
Executable file
15
.travis/build.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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
|
||||||
13
.travis/manifest.txt
Normal file
13
.travis/manifest.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
[](https://travis-ci.org/hasufell/pnmixer-rust)
|
[](https://travis-ci.org/hasufell/pnmixer-rust)
|
||||||
[](https://gitter.im/hasufell/pnmixer-rust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/hasufell/pnmixer-rust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[](https://hasufell.github.io/pnmixer-rust/pnmixerlib/)
|
|
||||||
[](https://github.com/hasufell/pnmixer-rust)
|
[](https://github.com/hasufell/pnmixer-rust)
|
||||||
|
|
||||||
PNMixer-rs
|
PNMixer-rs
|
||||||
@@ -53,4 +52,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)
|
||||||
- [X] [documentation](https://github.com/hasufell/pnmixer-rust/issues/3)
|
- [ ] [documentation](https://github.com/hasufell/pnmixer-rust/issues/3)
|
||||||
|
|||||||
@@ -25,54 +25,24 @@ 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)>)
|
||||||
@@ -137,13 +107,11 @@ 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()
|
||||||
@@ -152,17 +120,11 @@ 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);
|
||||||
@@ -171,36 +133,24 @@ 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)?;
|
||||||
@@ -208,9 +158,6 @@ 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 */
|
||||||
@@ -219,9 +166,6 @@ 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> {
|
||||||
@@ -249,7 +193,6 @@ 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 {
|
||||||
@@ -261,7 +204,19 @@ impl AlsaCard {
|
|||||||
|
|
||||||
|
|
||||||
impl Drop for AlsaCard {
|
impl Drop for AlsaCard {
|
||||||
/// Destructs the watch IDs corresponding to the current poll descriptors.
|
// 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<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());
|
||||||
@@ -269,7 +224,6 @@ 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)
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
//! Global application state.
|
|
||||||
|
|
||||||
|
|
||||||
use audio::{Audio, AudioUser};
|
use audio::{Audio, AudioUser};
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use gtk;
|
use gtk;
|
||||||
@@ -15,23 +12,17 @@ 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"),
|
||||||
@@ -68,7 +59,6 @@ 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(),
|
||||||
@@ -76,30 +66,25 @@ 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();
|
||||||
|
|||||||
98
src/audio.rs
98
src/audio.rs
@@ -1,13 +1,3 @@
|
|||||||
#![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;
|
||||||
@@ -16,13 +6,11 @@ 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,
|
||||||
@@ -32,7 +20,6 @@ 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,
|
||||||
@@ -43,8 +30,6 @@ 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,
|
||||||
@@ -57,7 +42,6 @@ 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)>>>>,
|
||||||
}
|
}
|
||||||
@@ -80,39 +64,16 @@ 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> {
|
||||||
@@ -155,10 +116,6 @@ 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>,
|
||||||
@@ -184,6 +141,9 @@ 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);
|
||||||
@@ -192,7 +152,6 @@ 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()
|
||||||
@@ -201,8 +160,6 @@ 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 {
|
||||||
@@ -219,16 +176,6 @@ 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,
|
||||||
@@ -284,10 +231,6 @@ 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)
|
||||||
@@ -299,10 +242,6 @@ 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)
|
||||||
@@ -314,19 +253,16 @@ 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();
|
||||||
@@ -354,42 +290,18 @@ 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) {
|
||||||
@@ -408,9 +320,6 @@ 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) {
|
||||||
@@ -445,4 +354,5 @@ fn on_alsa_event(last_action_timestamp: &mut i64,
|
|||||||
}
|
}
|
||||||
e => warn!("Unhandled alsa event: {:?}", e),
|
e => warn!("Unhandled alsa event: {:?}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#![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;
|
||||||
@@ -21,8 +20,6 @@ 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, ())
|
||||||
@@ -37,8 +34,6 @@ 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,
|
||||||
@@ -67,8 +62,6 @@ 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,
|
||||||
@@ -79,18 +72,31 @@ 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) => (match $expr {
|
($expr: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, $fmt:expr) => (match $expr {
|
($expr:expr, $ret: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);
|
||||||
@@ -98,7 +104,7 @@ macro_rules! try_e {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
($expr:expr, $fmt:expr, $($arg:tt)+) => (match $expr {
|
($expr:expr, $ret: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);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![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),+) => {
|
||||||
|
|||||||
32
src/lib.rs
32
src/lib.rs
@@ -1,34 +1,3 @@
|
|||||||
//! 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;
|
||||||
|
|
||||||
@@ -88,3 +57,4 @@ pub mod ui_tray_icon;
|
|||||||
|
|
||||||
#[cfg(feature = "notify")]
|
#[cfg(feature = "notify")]
|
||||||
pub mod notif;
|
pub mod notif;
|
||||||
|
|
||||||
|
|||||||
31
src/notif.rs
31
src/notif.rs
@@ -1,22 +1,31 @@
|
|||||||
//! 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>,
|
||||||
@@ -29,7 +38,6 @@ 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),
|
||||||
@@ -46,8 +54,6 @@ 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;
|
||||||
|
|
||||||
@@ -68,7 +74,6 @@ 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();
|
||||||
@@ -109,7 +114,6 @@ 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();
|
||||||
@@ -122,7 +126,6 @@ impl Notif {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Initialize the notification subsystem.
|
|
||||||
pub fn init_notify(appstate: Rc<AppS>) {
|
pub fn init_notify(appstate: Rc<AppS>) {
|
||||||
debug!("Blah");
|
debug!("Blah");
|
||||||
{
|
{
|
||||||
|
|||||||
24
src/prefs.rs
24
src/prefs.rs
@@ -1,12 +1,3 @@
|
|||||||
#![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;
|
||||||
@@ -27,7 +18,6 @@ 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,
|
||||||
@@ -70,7 +60,6 @@ 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,
|
||||||
@@ -89,7 +78,6 @@ 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,
|
||||||
@@ -112,7 +100,6 @@ 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,
|
||||||
@@ -132,7 +119,6 @@ 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>,
|
||||||
@@ -159,7 +145,6 @@ 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,
|
||||||
@@ -185,7 +170,6 @@ 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,
|
||||||
@@ -223,7 +207,6 @@ 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...");
|
||||||
|
|
||||||
@@ -234,7 +217,6 @@ 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()?;
|
||||||
@@ -248,16 +230,11 @@ 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()),
|
||||||
@@ -284,7 +261,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
//! Alsa audio helper functions.
|
//! Alsa audio helper functions.
|
||||||
//!
|
//!
|
||||||
//! This mod wraps around a few low-level alsa functions and abstracts
|
//! This mod wraps around a few low-level alsa functions and abstracts
|
||||||
//! out the details we don't care about.
|
//! out the details we don't care about. Mainly used by the
|
||||||
|
//! [alsa_card mod](./alsa_card.html).
|
||||||
|
|
||||||
|
|
||||||
use alsa::card::Card;
|
use alsa::card::Card;
|
||||||
use alsa::mixer::{Mixer, Selem, Elem};
|
use alsa::mixer::{Mixer, Selem, SelemId, 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 {
|
||||||
@@ -47,7 +46,6 @@ 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() {
|
||||||
@@ -68,7 +66,6 @@ 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?;
|
||||||
@@ -81,7 +78,6 @@ 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) {
|
||||||
@@ -93,13 +89,11 @@ 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.
|
||||||
@@ -108,7 +102,6 @@ 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) {
|
||||||
@@ -118,7 +111,6 @@ 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);
|
||||||
@@ -128,7 +120,6 @@ 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) {
|
||||||
@@ -143,7 +134,6 @@ 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> {
|
||||||
@@ -160,7 +150,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
#![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,
|
||||||
@@ -21,15 +11,6 @@ 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;
|
||||||
@@ -41,9 +22,6 @@ 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(),
|
||||||
@@ -52,8 +30,6 @@ 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)
|
||||||
@@ -65,9 +41,6 @@ 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,
|
||||||
@@ -79,9 +52,6 @@ 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,
|
||||||
@@ -92,18 +62,3 @@ 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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//! Helper functions for invoking system commands.
|
|
||||||
|
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use glib;
|
use glib;
|
||||||
use prefs::Prefs;
|
use prefs::Prefs;
|
||||||
@@ -8,9 +6,6 @@ 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();
|
||||||
|
|
||||||
@@ -21,7 +16,6 @@ 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| {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
//! 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;
|
||||||
@@ -12,8 +9,6 @@ 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 {
|
||||||
@@ -26,8 +21,6 @@ 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: >k::IconTheme)
|
theme: >k::IconTheme)
|
||||||
@@ -49,9 +42,7 @@ pub fn pixbuf_new_from_theme(icon_name: &str,
|
|||||||
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// Create a pixbuf from the given PNG file. Includes the file as bytes
|
macro_rules! pixbuf_new_from_file {
|
||||||
/// in the binary and decodes it.
|
|
||||||
macro_rules! pixbuf_new_from_png {
|
|
||||||
($name:expr) => {
|
($name:expr) => {
|
||||||
{
|
{
|
||||||
use gdk_pixbuf;
|
use gdk_pixbuf;
|
||||||
@@ -82,3 +73,4 @@ macro_rules! pixbuf_new_from_png {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
//! Global GUI state.
|
|
||||||
|
|
||||||
|
|
||||||
use app_state::*;
|
use app_state::*;
|
||||||
use audio::{AudioUser, AudioSignal};
|
use audio::{AudioUser, AudioSignal};
|
||||||
use gtk::DialogExt;
|
use gtk::DialogExt;
|
||||||
@@ -23,23 +20,16 @@ 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)
|
||||||
@@ -55,7 +45,6 @@ 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 */
|
||||||
@@ -90,14 +79,6 @@ 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: >k::Window) -> i32 {
|
fn run_audio_error_dialog(parent: >k::Window) -> i32 {
|
||||||
error!("Connection with audio failed, you probably need to restart pnmixer.");
|
error!("Connection with audio failed, you probably need to restart pnmixer.");
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
#![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;
|
use audio::{AudioUser, AudioSignal};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk;
|
use gtk;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -39,7 +26,6 @@ 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 */
|
||||||
{
|
{
|
||||||
@@ -127,7 +113,6 @@ 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();
|
||||||
@@ -138,7 +123,6 @@ 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();
|
||||||
|
|
||||||
@@ -169,14 +153,12 @@ 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();
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
//! 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::*;
|
||||||
@@ -23,30 +17,18 @@ 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,
|
||||||
/// Signal for mute_check.connect_toggled callback,
|
pub toggle_signal: Cell<u64>,
|
||||||
/// so we can block it temporarily.
|
pub changed_signal: Cell<u64>,
|
||||||
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: (),
|
||||||
@@ -60,8 +42,6 @@ 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);
|
||||||
@@ -71,7 +51,6 @@ 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();
|
||||||
|
|
||||||
@@ -97,8 +76,6 @@ 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);
|
||||||
@@ -108,7 +85,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 */
|
||||||
{
|
{
|
||||||
@@ -211,7 +188,6 @@ 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());
|
||||||
@@ -225,7 +201,6 @@ fn on_popup_window_show(appstate: &AppS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// On key or button press event on the popup window.
|
|
||||||
fn on_popup_window_event(w: >k::Window, e: &gdk::Event) -> gtk::Inhibit {
|
fn on_popup_window_event(w: >k::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(),
|
||||||
@@ -255,7 +230,6 @@ fn on_popup_window_event(w: >k::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());
|
||||||
@@ -277,20 +251,17 @@ 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: >k::Adjustment, scale: f64) {
|
pub fn set_slider(vol_scale_adj: >k::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: >k::Window) -> Result<()> {
|
fn grab_devices(window: >k::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")?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
//! 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_audio::*;
|
use support_alsa::*;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 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,
|
||||||
@@ -114,7 +109,6 @@ 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 */
|
||||||
@@ -177,8 +171,6 @@ 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();
|
||||||
@@ -250,8 +242,6 @@ 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
|
||||||
@@ -275,8 +265,6 @@ 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| {
|
||||||
@@ -300,7 +288,6 @@ 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 */
|
||||||
@@ -368,17 +355,16 @@ 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 audio = &appstate.audio;
|
let acard = appstate.audio.acard.borrow();
|
||||||
|
|
||||||
/* set card combo */
|
/* set card combo */
|
||||||
let cur_card_name = try_w!(audio.card_name(),
|
let cur_card_name = try_w!(acard.card_name(),
|
||||||
"Can't get current card name!");
|
"Can't get current card name!");
|
||||||
let available_card_names = get_playable_card_names();
|
let available_card_names = get_playable_alsa_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;
|
||||||
@@ -395,20 +381,21 @@ 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 audio = &appstate.audio;
|
let cur_acard = appstate.audio.acard.borrow();
|
||||||
let available_chan_names = match cardname {
|
let card = match cardname {
|
||||||
Some(name) => get_playable_chan_names(name),
|
Some(name) => try_w!(get_alsa_card_by_name(name).from_err()),
|
||||||
None => audio.playable_chan_names(),
|
None => cur_acard.as_ref().card,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* set chan combo */
|
/* set chan combo */
|
||||||
let cur_chan_name = try_w!(audio.chan_name());
|
let cur_chan_name = try_w!(cur_acard.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;
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
//! 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::*;
|
||||||
@@ -25,24 +19,16 @@ 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;
|
||||||
|
|
||||||
@@ -71,12 +57,7 @@ impl TrayIcon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Update the volume meter Pixbuf, which is drawn on top of the
|
fn update_vol_meter(&self, cur_vol: f64, vol_level: VolLevel) -> Result<()> {
|
||||||
/// 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);
|
||||||
|
|
||||||
@@ -94,7 +75,6 @@ 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()
|
||||||
@@ -125,7 +105,6 @@ 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,
|
||||||
@@ -159,8 +138,6 @@ 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,
|
||||||
@@ -174,7 +151,6 @@ 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,
|
||||||
@@ -190,7 +166,6 @@ 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)
|
||||||
@@ -271,7 +246,6 @@ 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,
|
||||||
@@ -282,28 +256,27 @@ pub struct AudioPix {
|
|||||||
|
|
||||||
impl Default for AudioPix {
|
impl Default for AudioPix {
|
||||||
fn default() -> AudioPix {
|
fn default() -> AudioPix {
|
||||||
let dummy_pixbuf =
|
let dummy_pixbuf = unsafe {
|
||||||
unsafe {
|
gdk_pixbuf::Pixbuf::new(
|
||||||
gdk_pixbuf::Pixbuf::new(gdk_pixbuf_sys::GDK_COLORSPACE_RGB,
|
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;
|
||||||
|
|
||||||
@@ -351,11 +324,11 @@ impl AudioPix {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AudioPix {
|
AudioPix {
|
||||||
muted: pixbuf_new_from_png!("../data/pixmaps/pnmixer-muted.png")?,
|
muted: pixbuf_new_from_file!("../data/pixmaps/pnmixer-muted.png")?,
|
||||||
low: pixbuf_new_from_png!("../data/pixmaps/pnmixer-low.png")?,
|
low: pixbuf_new_from_file!("../data/pixmaps/pnmixer-low.png")?,
|
||||||
medium: pixbuf_new_from_png!("../data/pixmaps/pnmixer-medium.png")?,
|
medium: pixbuf_new_from_file!("../data/pixmaps/pnmixer-medium.png")?,
|
||||||
high: pixbuf_new_from_png!("../data/pixmaps/pnmixer-high.png")?,
|
high: pixbuf_new_from_file!("../data/pixmaps/pnmixer-high.png")?,
|
||||||
off: pixbuf_new_from_png!("../data/pixmaps/pnmixer-off.png")?,
|
off: pixbuf_new_from_file!("../data/pixmaps/pnmixer-off.png")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -363,7 +336,6 @@ 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,
|
||||||
@@ -376,7 +348,6 @@ 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;
|
||||||
|
|
||||||
@@ -388,9 +359,8 @@ 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
|
try_w!(apps.gui.tray_icon.update_vol_meter(try_w!(apps.audio.vol()),
|
||||||
.vol()),
|
apps.audio.vol_level()));
|
||||||
apps.audio.vol_level()));
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -454,7 +424,6 @@ 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;
|
||||||
|
|
||||||
@@ -466,7 +435,6 @@ 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;
|
||||||
@@ -476,8 +444,6 @@ 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 {
|
||||||
@@ -507,9 +473,6 @@ 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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user