Update
This commit is contained in:
parent
3f19417f75
commit
0daff4d6b8
19
src/app_state.rs
Normal file
19
src/app_state.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use gtk;
|
||||
|
||||
use alsa::card::Card;
|
||||
use alsa::mixer::{Mixer, Selem};
|
||||
use std::cell::Cell;
|
||||
|
||||
pub struct AppS {
|
||||
/* we keep this to ensure the lifetime is across the whole application */
|
||||
pub status_icon: gtk::StatusIcon,
|
||||
|
||||
pub builder_popup: gtk::Builder,
|
||||
}
|
||||
|
||||
pub struct AlsaCard<'a> {
|
||||
pub card: Cell<Card>,
|
||||
pub mixer: Cell<Mixer>,
|
||||
pub selem: Cell<Selem<'a>>,
|
||||
}
|
||||
|
27
src/audio.rs
27
src/audio.rs
@ -1,5 +1,4 @@
|
||||
extern crate alsa;
|
||||
extern crate std;
|
||||
extern crate libc;
|
||||
|
||||
use self::alsa::card::Card;
|
||||
@ -24,7 +23,7 @@ pub fn get_alsa_cards() -> alsa::card::Iter {
|
||||
return alsa::card::Iter::new();
|
||||
}
|
||||
|
||||
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).cherr();
|
||||
}
|
||||
|
||||
@ -39,9 +38,11 @@ pub fn get_selems(mixer: &Mixer) -> Map<alsa::mixer::Iter, fn(Elem) -> Selem> {
|
||||
return mixer.iter().map(get_selem);
|
||||
}
|
||||
|
||||
pub fn get_selem_by_name<'a>(mixer: &'a Mixer, name: String) -> Result<Selem> {
|
||||
pub fn get_selem_by_name(mixer: &Mixer, name: String) -> Result<Selem> {
|
||||
for selem in get_selems(mixer) {
|
||||
let n = selem.get_id().get_name().map(|y| String::from(y))?;
|
||||
let n = selem.get_id()
|
||||
.get_name()
|
||||
.map(|y| String::from(y))?;
|
||||
|
||||
if n == name {
|
||||
return Ok(selem);
|
||||
@ -50,7 +51,7 @@ pub fn get_selem_by_name<'a>(mixer: &'a Mixer, name: String) -> Result<Selem> {
|
||||
bail!("Not found a matching selem named {}", name);
|
||||
}
|
||||
|
||||
pub fn get_vol(selem: Selem) -> Result<f64> {
|
||||
pub fn get_vol(selem: &Selem) -> Result<f64> {
|
||||
let (min, max) = selem.get_playback_volume_range();
|
||||
let volume = selem.get_playback_volume(FrontRight).map(|v| {
|
||||
return ((v - min) as f64) / ((max - min) as f64) * 100.0;
|
||||
@ -58,3 +59,19 @@ pub fn get_vol(selem: Selem) -> Result<f64> {
|
||||
|
||||
return volume.cherr();
|
||||
}
|
||||
|
||||
pub fn has_mute(selem: &Selem) -> bool {
|
||||
return selem.has_playback_switch();
|
||||
}
|
||||
|
||||
pub fn get_mute(selem: &Selem) -> Result<bool> {
|
||||
let val = selem.get_playback_switch(FrontRight)?;
|
||||
return Ok(val == 0);
|
||||
}
|
||||
|
||||
pub fn set_mute(selem: &Selem, mute: bool) -> Result<()> {
|
||||
/* true -> mute, false -> unmute */
|
||||
let _ = selem.set_playback_switch_all(!mute as i32)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,8 @@ pub trait CHErr {
|
||||
}
|
||||
|
||||
impl<A, E: std::error::Error> CHErr for std::result::Result<A, E>
|
||||
where Error: std::convert::From<E> {
|
||||
where Error: std::convert::From<E>
|
||||
{
|
||||
type Item = A;
|
||||
fn cherr(self) -> Result<Self::Item> {
|
||||
return self.map_err(From::from);
|
||||
@ -30,6 +31,9 @@ macro_rules! try_w {
|
||||
};
|
||||
($expr:expr, $fmt:expr, $($arg:tt)+) => {
|
||||
try_wr!($expr, (), $fmt, $(arg)+)
|
||||
};
|
||||
($expr:expr, $fmt:expr) => {
|
||||
try_wr!($expr, (), $fmt)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +46,14 @@ macro_rules! try_wr {
|
||||
return $ret;
|
||||
},
|
||||
});
|
||||
($expr:expr, $ret:expr, $fmt:expr) => (match $expr {
|
||||
std::result::Result::Ok(val) => val,
|
||||
std::result::Result::Err(err) => {
|
||||
warn!("Original error: {:?}", err);
|
||||
warn!($fmt);
|
||||
return $ret;
|
||||
},
|
||||
});
|
||||
($expr:expr, $ret:expr, $fmt:expr, $($arg:tt)+) => (match $expr {
|
||||
std::result::Result::Ok(val) => val,
|
||||
std::result::Result::Err(err) => {
|
||||
@ -51,3 +63,13 @@ macro_rules! try_wr {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! try_r {
|
||||
($expr:expr, $ret:expr) => (match $expr {
|
||||
std::result::Result::Ok(val) => val,
|
||||
std::result::Result::Err(err) => {
|
||||
return $ret;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
47
src/gui.rs
47
src/gui.rs
@ -23,42 +23,33 @@ pub fn grab_devices(window: >k::Window) -> Result<()> {
|
||||
let gdk_window = window.get_window().ok_or("No window?!")?;
|
||||
|
||||
/* Grab the mouse */
|
||||
let m_grab_status = device.grab(
|
||||
&gdk_window,
|
||||
GrabOwnership::None,
|
||||
true,
|
||||
BUTTON_PRESS_MASK,
|
||||
None,
|
||||
GDK_CURRENT_TIME as u32,
|
||||
);
|
||||
let m_grab_status =
|
||||
device.grab(&gdk_window,
|
||||
GrabOwnership::None,
|
||||
true,
|
||||
BUTTON_PRESS_MASK,
|
||||
None,
|
||||
GDK_CURRENT_TIME as u32);
|
||||
|
||||
if m_grab_status != GrabStatus::Success {
|
||||
warn!(
|
||||
"Could not grab {}",
|
||||
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
||||
);
|
||||
warn!("Could not grab {}",
|
||||
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE")));
|
||||
}
|
||||
|
||||
/* Grab the keyboard */
|
||||
let k_dev = device.get_associated_device().ok_or(
|
||||
"Couldn't get associated device",
|
||||
)?;
|
||||
let k_dev = device.get_associated_device()
|
||||
.ok_or("Couldn't get associated device")?;
|
||||
|
||||
let k_grab_status = k_dev.grab(
|
||||
&gdk_window,
|
||||
GrabOwnership::None,
|
||||
true,
|
||||
KEY_PRESS_MASK,
|
||||
None,
|
||||
GDK_CURRENT_TIME as u32,
|
||||
);
|
||||
let k_grab_status = k_dev.grab(&gdk_window,
|
||||
GrabOwnership::None,
|
||||
true,
|
||||
KEY_PRESS_MASK,
|
||||
None,
|
||||
GDK_CURRENT_TIME as u32);
|
||||
if k_grab_status != GrabStatus::Success {
|
||||
warn!(
|
||||
"Could not grab {}",
|
||||
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
||||
);
|
||||
warn!("Could not grab {}",
|
||||
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE")));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
134
src/gui_callbacks.rs
Normal file
134
src/gui_callbacks.rs
Normal file
@ -0,0 +1,134 @@
|
||||
extern crate gtk;
|
||||
extern crate gtk_sys;
|
||||
extern crate gdk;
|
||||
extern crate gdk_sys;
|
||||
extern crate alsa;
|
||||
extern crate std;
|
||||
|
||||
use gtk::prelude::*;
|
||||
use gdk_sys::GDK_KEY_Escape;
|
||||
|
||||
use gui;
|
||||
use audio;
|
||||
use app_state::*;
|
||||
use errors::*;
|
||||
|
||||
|
||||
pub fn init<'a>(appstate: &'a AppS) {
|
||||
|
||||
init_tray_icon(&appstate);
|
||||
init_popup_window(&appstate);
|
||||
}
|
||||
|
||||
|
||||
fn init_tray_icon(appstate: &AppS) {
|
||||
|
||||
let ref tray_icon = appstate.status_icon;
|
||||
|
||||
let popup_window: gtk::Window =
|
||||
appstate.builder_popup.get_object("popup_window").unwrap();
|
||||
let vol_scale: gtk::Scale =
|
||||
appstate.builder_popup.get_object("vol_scale").unwrap();
|
||||
|
||||
tray_icon.connect_activate(move |_| if popup_window.get_visible() {
|
||||
popup_window.hide();
|
||||
} else {
|
||||
popup_window.show_now();
|
||||
vol_scale.grab_focus();
|
||||
try_w!(gui::grab_devices(&popup_window));
|
||||
});
|
||||
tray_icon.set_visible(true);
|
||||
}
|
||||
|
||||
fn init_popup_window(appstate: &AppS) {
|
||||
/* popup_window.connect_show */
|
||||
{
|
||||
let popup_window: gtk::Window =
|
||||
appstate.builder_popup.get_object("popup_window").unwrap();
|
||||
let vol_scale_adj: gtk::Adjustment =
|
||||
appstate.builder_popup.get_object("vol_scale_adj").unwrap();
|
||||
let mute_check: gtk::CheckButton =
|
||||
appstate.builder_popup.get_object("mute_check").unwrap();
|
||||
|
||||
popup_window.connect_show(move |_| {
|
||||
let alsa_card = audio::get_default_alsa_card();
|
||||
let mixer = try_w!(audio::get_mixer(&alsa_card));
|
||||
let selem = try_w!(audio::get_selem_by_name(
|
||||
&mixer,
|
||||
String::from("Master"),
|
||||
));
|
||||
let cur_vol = try_w!(audio::get_vol(&selem));
|
||||
gui::set_slider(&vol_scale_adj, cur_vol);
|
||||
|
||||
let muted = audio::get_mute(&selem);
|
||||
update_mute_check(&mute_check, muted);
|
||||
});
|
||||
}
|
||||
|
||||
/* mute_check.connect_toggled */
|
||||
{
|
||||
let mute_check: gtk::CheckButton =
|
||||
appstate.builder_popup.get_object("mute_check").unwrap();
|
||||
|
||||
mute_check.connect_toggled(move |_| {
|
||||
let alsa_card = audio::get_default_alsa_card();
|
||||
let mixer = try_w!(audio::get_mixer(&alsa_card));
|
||||
let selem = try_w!(audio::get_selem_by_name(
|
||||
&mixer,
|
||||
String::from("Master"),
|
||||
));
|
||||
|
||||
let muted = try_w!(audio::get_mute(&selem));
|
||||
let _ = try_w!(audio::set_mute(&selem, !muted));
|
||||
});
|
||||
}
|
||||
|
||||
/* popup_window.connect_event */
|
||||
{
|
||||
let popup_window: gtk::Window =
|
||||
appstate.builder_popup.get_object("popup_window").unwrap();
|
||||
popup_window.connect_event(move |w, e| {
|
||||
match gdk::Event::get_event_type(e) {
|
||||
gdk::EventType::GrabBroken => w.hide(),
|
||||
gdk::EventType::KeyPress => {
|
||||
let key: gdk::EventKey = e.clone().downcast().unwrap();
|
||||
if key.get_keyval() == (GDK_KEY_Escape as u32) {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
gdk::EventType::ButtonPress => {
|
||||
let device = try_wr!(
|
||||
gtk::get_current_event_device().ok_or(
|
||||
"No current event device!",
|
||||
),
|
||||
Inhibit(false)
|
||||
);
|
||||
let (window, _, _) =
|
||||
gdk::DeviceExt::get_window_at_position(&device);
|
||||
if window.is_none() {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return Inhibit(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_mute_check(check_button: >k::CheckButton, muted: Result<bool>) {
|
||||
match muted {
|
||||
Ok(val) => {
|
||||
check_button.set_active(val);
|
||||
check_button.set_tooltip_text("");
|
||||
},
|
||||
Err(_) => {
|
||||
/* can't figure out whether channel is muted, grey out */
|
||||
check_button.set_active(true);
|
||||
check_button.set_sensitive(false);
|
||||
check_button.set_tooltip_text("Soundcard has no mute switch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
src/main.rs
102
src/main.rs
@ -11,103 +11,51 @@ extern crate gdk;
|
||||
extern crate gdk_sys;
|
||||
extern crate alsa;
|
||||
|
||||
// use std::ops::Deref;
|
||||
|
||||
// use std::boxed::Box;
|
||||
// use std::rc::Rc;
|
||||
// use std::sync::Arc;
|
||||
|
||||
use gtk::prelude::*;
|
||||
|
||||
|
||||
use gdk_sys::GDK_KEY_Escape;
|
||||
use app_state::*;
|
||||
use std::cell::Cell;
|
||||
use std::boxed::Box;
|
||||
|
||||
|
||||
#[macro_use]
|
||||
mod errors;
|
||||
|
||||
mod audio;
|
||||
mod gui;
|
||||
mod debug;
|
||||
mod gui_callbacks;
|
||||
mod app_state;
|
||||
|
||||
|
||||
fn main() {
|
||||
gtk::init().unwrap();
|
||||
|
||||
let ref apps = AppS {
|
||||
status_icon: gtk::StatusIcon::new_from_icon_name("pnmixer"),
|
||||
builder_popup: gtk::Builder::new_from_string(include_str!("../data/ui/popup-window-vertical.glade")),
|
||||
};
|
||||
|
||||
let alsa_card = audio::get_default_alsa_card();
|
||||
let mixer = audio::get_mixer(&alsa_card).unwrap();
|
||||
let selem = audio::get_selem_by_name(
|
||||
&mixer,
|
||||
String::from("Master"),
|
||||
).unwrap();
|
||||
|
||||
let ref acard = AlsaCard {
|
||||
card: Cell::new(alsa_card),
|
||||
mixer: Cell::new(mixer),
|
||||
selem: Cell::new(selem),
|
||||
};
|
||||
|
||||
flexi_logger::LogOptions::new()
|
||||
.log_to_file(false)
|
||||
// ... your configuration options go here ...
|
||||
.init(Some("info".to_string()))
|
||||
.unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
|
||||
|
||||
let tray_icon = gtk::StatusIcon::new_from_icon_name("pnmixer");
|
||||
|
||||
let glade_src = include_str!("../data/ui/popup-window-vertical.glade");
|
||||
let builder_popup = gtk::Builder::new_from_string(glade_src);
|
||||
|
||||
{
|
||||
let popup_window: gtk::Window =
|
||||
builder_popup.get_object("popup_window").unwrap();
|
||||
let vol_scale: gtk::Scale =
|
||||
builder_popup.get_object("vol_scale").unwrap();
|
||||
|
||||
tray_icon.connect_activate(move |_| if popup_window.get_visible() {
|
||||
popup_window.hide();
|
||||
} else {
|
||||
popup_window.show_now();
|
||||
vol_scale.grab_focus();
|
||||
gui::grab_devices(&popup_window);
|
||||
});
|
||||
}
|
||||
{
|
||||
let popup_window: gtk::Window =
|
||||
builder_popup.get_object("popup_window").unwrap();
|
||||
let vol_scale_adj: gtk::Adjustment =
|
||||
builder_popup.get_object("vol_scale_adj").unwrap();
|
||||
popup_window.connect_show(move |_| {
|
||||
let alsa_card = audio::get_default_alsa_card();
|
||||
let mixer = try_w!(audio::get_mixer(alsa_card));
|
||||
let selem = try_w!(audio::get_selem_by_name(
|
||||
&mixer,
|
||||
String::from("Master"),
|
||||
));
|
||||
let cur_vol = try_w!(audio::get_vol(selem));
|
||||
gui::set_slider(&vol_scale_adj, cur_vol);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let popup_window: gtk::Window =
|
||||
builder_popup.get_object("popup_window").unwrap();
|
||||
popup_window.connect_event(move |w, e| {
|
||||
match gdk::Event::get_event_type(e) {
|
||||
gdk::EventType::GrabBroken => w.hide(),
|
||||
gdk::EventType::KeyPress => {
|
||||
let key: gdk::EventKey = e.clone().downcast().unwrap();
|
||||
if key.get_keyval() == (GDK_KEY_Escape as u32) {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
gdk::EventType::ButtonPress => {
|
||||
let device = try_wr!(
|
||||
gtk::get_current_event_device().ok_or(
|
||||
"No current event device!",
|
||||
),
|
||||
Inhibit(false)
|
||||
);
|
||||
let (window, _, _) =
|
||||
gdk::DeviceExt::get_window_at_position(&device);
|
||||
if window.is_none() {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return Inhibit(false);
|
||||
});
|
||||
}
|
||||
|
||||
tray_icon.set_visible(true);
|
||||
gui_callbacks::init(apps);
|
||||
|
||||
gtk::main();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user