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 alsa;
|
||||||
extern crate std;
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
use self::alsa::card::Card;
|
use self::alsa::card::Card;
|
||||||
@ -24,7 +23,7 @@ pub fn get_alsa_cards() -> alsa::card::Iter {
|
|||||||
return alsa::card::Iter::new();
|
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();
|
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);
|
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) {
|
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 {
|
if n == name {
|
||||||
return Ok(selem);
|
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);
|
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 (min, max) = selem.get_playback_volume_range();
|
||||||
let volume = selem.get_playback_volume(FrontRight).map(|v| {
|
let volume = selem.get_playback_volume(FrontRight).map(|v| {
|
||||||
return ((v - min) as f64) / ((max - min) as f64) * 100.0;
|
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();
|
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>
|
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;
|
type Item = A;
|
||||||
fn cherr(self) -> Result<Self::Item> {
|
fn cherr(self) -> Result<Self::Item> {
|
||||||
return self.map_err(From::from);
|
return self.map_err(From::from);
|
||||||
@ -30,6 +31,9 @@ macro_rules! try_w {
|
|||||||
};
|
};
|
||||||
($expr:expr, $fmt:expr, $($arg:tt)+) => {
|
($expr:expr, $fmt:expr, $($arg:tt)+) => {
|
||||||
try_wr!($expr, (), $fmt, $(arg)+)
|
try_wr!($expr, (), $fmt, $(arg)+)
|
||||||
|
};
|
||||||
|
($expr:expr, $fmt:expr) => {
|
||||||
|
try_wr!($expr, (), $fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +46,14 @@ macro_rules! try_wr {
|
|||||||
return $ret;
|
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 {
|
($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) => {
|
||||||
@ -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?!")?;
|
let gdk_window = window.get_window().ok_or("No window?!")?;
|
||||||
|
|
||||||
/* Grab the mouse */
|
/* Grab the mouse */
|
||||||
let m_grab_status = device.grab(
|
let m_grab_status =
|
||||||
&gdk_window,
|
device.grab(&gdk_window,
|
||||||
GrabOwnership::None,
|
GrabOwnership::None,
|
||||||
true,
|
true,
|
||||||
BUTTON_PRESS_MASK,
|
BUTTON_PRESS_MASK,
|
||||||
None,
|
None,
|
||||||
GDK_CURRENT_TIME as u32,
|
GDK_CURRENT_TIME as u32);
|
||||||
);
|
|
||||||
|
|
||||||
if m_grab_status != GrabStatus::Success {
|
if m_grab_status != GrabStatus::Success {
|
||||||
warn!(
|
warn!("Could not grab {}",
|
||||||
"Could not grab {}",
|
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE")));
|
||||||
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grab the keyboard */
|
/* Grab the keyboard */
|
||||||
let k_dev = device.get_associated_device().ok_or(
|
let k_dev = device.get_associated_device()
|
||||||
"Couldn't get associated device",
|
.ok_or("Couldn't get associated device")?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let k_grab_status = k_dev.grab(
|
let k_grab_status = k_dev.grab(&gdk_window,
|
||||||
&gdk_window,
|
GrabOwnership::None,
|
||||||
GrabOwnership::None,
|
true,
|
||||||
true,
|
KEY_PRESS_MASK,
|
||||||
KEY_PRESS_MASK,
|
None,
|
||||||
None,
|
GDK_CURRENT_TIME as u32);
|
||||||
GDK_CURRENT_TIME as u32,
|
|
||||||
);
|
|
||||||
if k_grab_status != GrabStatus::Success {
|
if k_grab_status != GrabStatus::Success {
|
||||||
warn!(
|
warn!("Could not grab {}",
|
||||||
"Could not grab {}",
|
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE")));
|
||||||
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
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 gdk_sys;
|
||||||
extern crate alsa;
|
extern crate alsa;
|
||||||
|
|
||||||
// use std::ops::Deref;
|
|
||||||
|
|
||||||
// use std::boxed::Box;
|
|
||||||
// use std::rc::Rc;
|
|
||||||
// use std::sync::Arc;
|
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
use gdk_sys::GDK_KEY_Escape;
|
use gdk_sys::GDK_KEY_Escape;
|
||||||
|
use app_state::*;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::boxed::Box;
|
||||||
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
mod audio;
|
mod audio;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod debug;
|
mod gui_callbacks;
|
||||||
|
mod app_state;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
gtk::init().unwrap();
|
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()
|
flexi_logger::LogOptions::new()
|
||||||
.log_to_file(false)
|
.log_to_file(false)
|
||||||
// ... your configuration options go here ...
|
// ... your configuration options go here ...
|
||||||
.init(Some("info".to_string()))
|
.init(Some("info".to_string()))
|
||||||
.unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
|
.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");
|
gui_callbacks::init(apps);
|
||||||
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);
|
|
||||||
|
|
||||||
gtk::main();
|
gtk::main();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user