This commit is contained in:
Ospald, Julian 2017-07-04 16:54:16 +02:00
parent a6201df328
commit 9de9a6abaa
10 changed files with 488 additions and 346 deletions

View File

@ -9,6 +9,7 @@ alsa-sys = "^0.1.1"
libc = "^0.2.23" libc = "^0.2.23"
gdk-sys = { git = "https://github.com/gtk-rs/sys" } gdk-sys = { git = "https://github.com/gtk-rs/sys" }
gdk-pixbuf = { git = "https://github.com/gtk-rs/gdk-pixbuf.git" } gdk-pixbuf = { git = "https://github.com/gtk-rs/gdk-pixbuf.git" }
gdk-pixbuf-sys = { git = "https://github.com/gtk-rs/sys" }
gtk-sys = { git = "https://github.com/gtk-rs/sys" } gtk-sys = { git = "https://github.com/gtk-rs/sys" }
glib = { git = "https://github.com/gtk-rs/glib.git" } glib = { git = "https://github.com/gtk-rs/glib.git" }
glib-sys = { git = "https://github.com/gtk-rs/sys" } glib-sys = { git = "https://github.com/gtk-rs/sys" }

View File

@ -38,10 +38,11 @@ pub struct AlsaCard {
impl AlsaCard { impl AlsaCard {
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)>,
-> Result<Box<AlsaCard>> { ) -> Result<Box<AlsaCard>> {
let card = { let card = {
match card_name { match card_name {
Some(name) => get_alsa_card_by_name(name)?, Some(name) => get_alsa_card_by_name(name)?,
@ -52,10 +53,10 @@ impl AlsaCard {
let vec_pollfd = PollDescriptors::get(&mixer)?; let vec_pollfd = PollDescriptors::get(&mixer)?;
let selem_id = let selem_id = get_selem_by_name(
get_selem_by_name(&mixer, &mixer,
elem_name.unwrap_or(String::from("Master"))) elem_name.unwrap_or(String::from("Master")),
.unwrap() ).unwrap()
.get_id(); .get_id();
let acard = Box::new(AlsaCard { let acard = Box::new(AlsaCard {
@ -70,8 +71,8 @@ impl AlsaCard {
/* TODO: callback is registered here, which must be unregistered /* TODO: callback is registered here, which must be unregistered
* when the card is destroyed!! * when the card is destroyed!!
* poll descriptors must be unwatched too */ * poll descriptors must be unwatched too */
let watch_ids = AlsaCard::watch_poll_descriptors(vec_pollfd, let watch_ids =
acard.as_ref()); AlsaCard::watch_poll_descriptors(vec_pollfd, acard.as_ref());
acard.watch_ids.set(watch_ids); acard.watch_ids.set(watch_ids);
return Ok(acard); return Ok(acard);
@ -84,9 +85,7 @@ impl AlsaCard {
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().map(|y| String::from(y))?;
.get_name()
.map(|y| String::from(y))?;
return Ok(n); return Ok(n);
} }
@ -115,7 +114,9 @@ impl AlsaCard {
} }
let range = selem.get_playback_volume_range(); let range = selem.get_playback_volume_range();
selem.set_playback_volume_all(percent_to_vol(new_vol, range))?; selem.set_playback_volume_all(
percent_to_vol(new_vol, range),
)?;
return Ok(()); return Ok(());
} }
@ -142,9 +143,10 @@ impl AlsaCard {
} }
fn watch_poll_descriptors(polls: Vec<pollfd>, fn watch_poll_descriptors(
acard: &AlsaCard) polls: Vec<pollfd>,
-> Vec<c_uint> { acard: &AlsaCard,
) -> Vec<c_uint> {
let mut watch_ids: Vec<c_uint> = vec![]; let mut watch_ids: Vec<c_uint> = vec![];
let acard_ptr = let acard_ptr =
unsafe { mem::transmute::<&AlsaCard, glib_sys::gpointer>(acard) }; unsafe { mem::transmute::<&AlsaCard, glib_sys::gpointer>(acard) };
@ -201,10 +203,11 @@ impl Drop for AlsaCard {
} }
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,
-> glib_sys::gboolean { ) -> glib_sys::gboolean {
let acard = let acard =
unsafe { mem::transmute::<glib_sys::gpointer, &AlsaCard>(data) }; unsafe { mem::transmute::<glib_sys::gpointer, &AlsaCard>(data) };
@ -223,13 +226,14 @@ extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel,
let mut buf: Vec<u8> = vec![0; 256]; let mut buf: Vec<u8> = vec![0; 256];
while sread > 0 { while sread > 0 {
let stat: glib_sys::GIOStatus = let stat: glib_sys::GIOStatus = unsafe {
unsafe { glib_sys::g_io_channel_read_chars(
glib_sys::g_io_channel_read_chars(chan, chan,
buf.as_mut_ptr() as *mut u8, buf.as_mut_ptr() as *mut u8,
256, 256,
&mut sread as *mut size_t, &mut sread as *mut size_t,
ptr::null_mut()) ptr::null_mut(),
)
}; };
match stat { match stat {
@ -324,9 +328,7 @@ pub fn get_selem_names(mixer: &Mixer) -> Vec<String> {
pub fn get_selem_by_name(mixer: &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() let n = selem.get_id().get_name().map(|y| String::from(y))?;
.get_name()
.map(|y| String::from(y))?;
if n == name { if n == name {
return Ok(selem); return Ok(selem);

View File

@ -14,14 +14,16 @@ pub struct AppS {
impl AppS { impl AppS {
pub fn new() -> AppS { pub fn new() -> AppS {
let builder_popup_window = let builder_popup_window = gtk::Builder::new_from_string(include_str!(
gtk::Builder::new_from_string(include_str!("../data/ui/popup-window.glade")); "../data/ui/popup-window.glade"
let builder_popup_menu = gtk::Builder::new_from_string(include_str!("../data/ui/popup-menu.glade")); ));
let builder_popup_menu = gtk::Builder::new_from_string(
include_str!("../data/ui/popup-menu.glade"),
);
return AppS { return AppS {
gui: Gui::new(builder_popup_window, builder_popup_menu), gui: Gui::new(builder_popup_window, builder_popup_menu),
audio: Audio::new(None, Some(String::from("Master"))) audio: Audio::new(None, Some(String::from("Master"))).unwrap(),
.unwrap(),
}; };
} }
} }
@ -36,9 +38,10 @@ pub struct Gui {
impl Gui { impl Gui {
pub fn new(builder_popup_window: gtk::Builder, pub fn new(
builder_popup_menu: gtk::Builder) builder_popup_window: gtk::Builder,
-> Gui { builder_popup_menu: gtk::Builder,
) -> Gui {
return Gui { return Gui {
status_icon: gtk::StatusIcon::new(), status_icon: gtk::StatusIcon::new(),
popup_window: PopupWindow::new(builder_popup_window), popup_window: PopupWindow::new(builder_popup_window),
@ -48,16 +51,20 @@ impl Gui {
} }
create_builder_item!(PopupWindow, create_builder_item!(
PopupWindow,
popup_window: gtk::Window, popup_window: gtk::Window,
vol_scale_adj: gtk::Adjustment, vol_scale_adj: gtk::Adjustment,
vol_scale: gtk::Scale, vol_scale: gtk::Scale,
mute_check: gtk::CheckButton); mute_check: gtk::CheckButton
);
create_builder_item!(PopupMenu, create_builder_item!(
PopupMenu,
menu_window: gtk::Window, menu_window: gtk::Window,
menubar: gtk::MenuBar, menubar: gtk::MenuBar,
menu: gtk::Menu, menu: gtk::Menu,
about_item: gtk::MenuItem, about_item: gtk::MenuItem,
prefs_item: gtk::MenuItem); prefs_item: gtk::MenuItem
);

View File

@ -72,9 +72,10 @@ pub struct Audio {
impl Audio { impl Audio {
pub fn new(card_name: Option<String>, pub fn new(
elem_name: Option<String>) card_name: Option<String>,
-> Result<Audio> { elem_name: Option<String>,
) -> Result<Audio> {
let handlers = Handlers::new(); let handlers = Handlers::new();
let last_action_timestamp = Rc::new(RefCell::new(0)); let last_action_timestamp = Rc::new(RefCell::new(0));
@ -83,9 +84,7 @@ impl Audio {
let myhandler = handlers.clone(); let myhandler = handlers.clone();
let ts = last_action_timestamp.clone(); let ts = last_action_timestamp.clone();
Rc::new(move |event| { Rc::new(move |event| {
on_alsa_event(&mut *ts.borrow_mut(), on_alsa_event(&mut *ts.borrow_mut(), &myhandler.borrow(), event)
&myhandler.borrow(),
event)
}) })
}; };
@ -101,39 +100,33 @@ impl Audio {
} }
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>,
-> Result<()> { ) -> Result<()> {
debug!("Switching cards"); debug!("Switching cards");
debug!("Old card name: {}", debug!(
self.acard "Old card name: {}",
.borrow() self.acard.borrow().card_name().unwrap()
.card_name() );
.unwrap()); debug!(
debug!("Old chan name: {}", "Old chan name: {}",
self.acard self.acard.borrow().chan_name().unwrap()
.borrow() );
.chan_name() let cb = self.acard.borrow().cb.clone();
.unwrap());
let cb = self.acard
.borrow()
.cb
.clone();
{ {
let mut ac = self.acard.borrow_mut(); let mut ac = self.acard.borrow_mut();
*ac = AlsaCard::new(card_name, elem_name, cb)?; *ac = AlsaCard::new(card_name, elem_name, cb)?;
} }
debug!("Old card name: {}", debug!(
self.acard "Old card name: {}",
.borrow() self.acard.borrow().card_name().unwrap()
.card_name() );
.unwrap()); debug!(
debug!("Old chan name: {}", "Old chan name: {}",
self.acard self.acard.borrow().chan_name().unwrap()
.borrow() );
.chan_name()
.unwrap());
return Ok(()); return Ok(());
// TODO: invoke handler // TODO: invoke handler
} }
@ -166,24 +159,20 @@ impl Audio {
*rc = glib::get_monotonic_time(); *rc = glib::get_monotonic_time();
} }
debug!("Setting vol on card {:?} and chan {:?} to {:?} by user {:?}", debug!(
self.acard "Setting vol on card {:?} and chan {:?} to {:?} by user {:?}",
.borrow() self.acard.borrow().card_name().unwrap(),
.card_name() self.acard.borrow().chan_name().unwrap(),
.unwrap(),
self.acard
.borrow()
.chan_name()
.unwrap(),
new_vol, new_vol,
user); user
self.acard );
.borrow() self.acard.borrow().set_vol(new_vol)?;
.set_vol(new_vol)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
&self.handlers.borrow(),
AudioSignal::ValuesChanged, AudioSignal::ValuesChanged,
user); user,
);
return Ok(()); return Ok(());
} }
@ -196,23 +185,21 @@ impl Audio {
let old_vol = self.vol()?; let old_vol = self.vol()?;
let new_vol = f64::ceil(old_vol + (self.scroll_step.get() as f64)); let new_vol = f64::ceil(old_vol + (self.scroll_step.get() as f64));
debug!("Increase vol on card {:?} and chan {:?} by {:?} to {:?}", debug!(
self.acard "Increase vol on card {:?} and chan {:?} by {:?} to {:?}",
.borrow() self.acard.borrow().card_name().unwrap(),
.card_name() self.acard.borrow().chan_name().unwrap(),
.unwrap(),
self.acard
.borrow()
.chan_name()
.unwrap(),
(new_vol - old_vol), (new_vol - old_vol),
new_vol); new_vol
);
self.set_vol(new_vol, user)?; self.set_vol(new_vol, user)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
&self.handlers.borrow(),
AudioSignal::ValuesChanged, AudioSignal::ValuesChanged,
user); user,
);
return Ok(()); return Ok(());
} }
@ -225,23 +212,21 @@ impl Audio {
let old_vol = self.vol()?; let old_vol = self.vol()?;
let new_vol = old_vol - (self.scroll_step.get() as f64); let new_vol = old_vol - (self.scroll_step.get() as f64);
debug!("Decrease vol on card {:?} and chan {:?} by {:?} to {:?}", debug!(
self.acard "Decrease vol on card {:?} and chan {:?} by {:?} to {:?}",
.borrow() self.acard.borrow().card_name().unwrap(),
.card_name() self.acard.borrow().chan_name().unwrap(),
.unwrap(),
self.acard
.borrow()
.chan_name()
.unwrap(),
(new_vol - old_vol), (new_vol - old_vol),
new_vol); new_vol
);
self.set_vol(new_vol, user)?; self.set_vol(new_vol, user)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
&self.handlers.borrow(),
AudioSignal::ValuesChanged, AudioSignal::ValuesChanged,
user); user,
);
return Ok(()); return Ok(());
} }
@ -260,25 +245,21 @@ impl Audio {
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();
debug!("Setting mute to {} on card {:?} and chan {:?} by user {:?}", debug!(
"Setting mute to {} on card {:?} and chan {:?} by user {:?}",
mute, mute,
self.acard self.acard.borrow().card_name().unwrap(),
.borrow() self.acard.borrow().chan_name().unwrap(),
.card_name() user
.unwrap(), );
self.acard
.borrow()
.chan_name()
.unwrap(),
user);
self.acard self.acard.borrow().set_mute(mute)?;
.borrow()
.set_mute(mute)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
&self.handlers.borrow(),
AudioSignal::ValuesChanged, AudioSignal::ValuesChanged,
user); user,
);
return Ok(()); return Ok(());
} }
@ -295,12 +276,16 @@ impl Audio {
} }
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,
debug!("Invoking handlers for signal {:?} by user {:?}", ) {
debug!(
"Invoking handlers for signal {:?} by user {:?}",
signal, signal,
user); user
);
for handler in handlers { for handler in handlers {
let unboxed = handler.as_ref(); let unboxed = handler.as_ref();
unboxed(signal, user); unboxed(signal, user);
@ -308,9 +293,11 @@ fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
} }
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,
) {
let last: i64 = *last_action_timestamp; let last: i64 = *last_action_timestamp;
if last != 0 { if last != 0 {
@ -330,9 +317,11 @@ fn on_alsa_event(last_action_timestamp: &mut i64,
AlsaEvent::AlsaCardDiconnected => debug!("AlsaCardDiconnected"), AlsaEvent::AlsaCardDiconnected => debug!("AlsaCardDiconnected"),
AlsaEvent::AlsaCardValuesChanged => { AlsaEvent::AlsaCardValuesChanged => {
debug!("AlsaCardValuesChanged"); debug!("AlsaCardValuesChanged");
invoke_handlers(handlers, invoke_handlers(
handlers,
self::AudioSignal::ValuesChanged, self::AudioSignal::ValuesChanged,
self::AudioUser::Unknown); self::AudioUser::Unknown,
);
} }
e => warn!("Unhandled alsa event: {:?}", e), e => warn!("Unhandled alsa event: {:?}", e),
} }

View File

@ -13,6 +13,7 @@ extern crate alsa_sys;
extern crate ffi; extern crate ffi;
extern crate gdk; extern crate gdk;
extern crate gdk_pixbuf; extern crate gdk_pixbuf;
extern crate gdk_pixbuf_sys;
extern crate gdk_sys; extern crate gdk_sys;
extern crate glib; extern crate glib;
extern crate glib_sys; extern crate glib_sys;
@ -52,7 +53,6 @@ fn main() {
.unwrap_or_else(|e| panic!("Logger initialization failed with {}", e)); .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
let apps = Rc::new(AppS::new()); let apps = Rc::new(AppS::new());
// let _ = apps.audio.switch_acard(None, None).unwrap();
ui_entry::init(apps); ui_entry::init(apps);

View File

@ -11,14 +11,15 @@ use ui_prefs_dialog::*;
pub fn init(appstate: Rc<AppS>) { pub fn init(appstate: Rc<AppS>) {
{ {
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) { appstate.audio.connect_handler(
(AudioSignal::ValuesChanged, Box::new(move |s, u| match (s, u) {
AudioUser::Unknown) => { (AudioSignal::ValuesChanged, AudioUser::Unknown) => {
debug!("External volume change!"); debug!("External volume change!");
} }
_ => debug!("Nix"), _ => debug!("Nix"),
})); }),
);
} }
init_tray_icon(appstate.clone()); init_tray_icon(appstate.clone());

View File

@ -14,24 +14,18 @@ pub fn init_popup_menu(appstate: Rc<AppS>) {
/* about_item.connect_activate_link */ /* about_item.connect_activate_link */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
let about_item = &appstate.clone() let about_item = &appstate.clone().gui.popup_menu.about_item;
.gui about_item.connect_activate(
.popup_menu move |_| { on_about_item_activate(&apps); },
.about_item; );
about_item.connect_activate(move |_| {
on_about_item_activate(&apps);
});
} }
/* about_item.connect_activate_link */ /* about_item.connect_activate_link */
{ {
let prefs_item = &appstate.clone() let prefs_item = &appstate.clone().gui.popup_menu.prefs_item;
.gui prefs_item.connect_activate(
.popup_menu move |_| { on_prefs_item_activate(appstate.clone()); },
.prefs_item; );
prefs_item.connect_activate(move |_| {
on_prefs_item_activate(appstate.clone());
});
} }
} }
@ -49,7 +43,8 @@ fn on_about_item_activate(appstate: &AppS) {
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();
about_dialog.set_license(Some("PNMixer is free software; you can redistribute it and/or modify it about_dialog.set_license(Some(
"PNMixer is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License v3 as published under the terms of the GNU General Public License v3 as published
by the Free Software Foundation. by the Free Software Foundation.
@ -60,7 +55,8 @@ See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with PNMixer; if not, write to the Free Software Foundation, along with PNMixer; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.")); Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.",
));
about_dialog.set_copyright(Some("Copyright © 2017 Julian Ospald")); about_dialog.set_copyright(Some("Copyright © 2017 Julian Ospald"));
about_dialog.set_authors(&["Julian Ospald"]); about_dialog.set_authors(&["Julian Ospald"]);
about_dialog.set_artists(&["Paul Davey"]); about_dialog.set_artists(&["Paul Davey"]);

View File

@ -18,37 +18,25 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
/* mute_check.connect_toggled */ /* mute_check.connect_toggled */
{ {
let _appstate = appstate.clone(); let _appstate = appstate.clone();
let mute_check = &appstate.clone() let mute_check = &appstate.clone().gui.popup_window.mute_check;
.gui toggle_signal = mute_check.connect_toggled(
.popup_window move |_| on_mute_check_toggled(&_appstate),
.mute_check; );
toggle_signal =
mute_check.connect_toggled(move |_| {
on_mute_check_toggled(&_appstate)
});
} }
/* popup_window.connect_show */ /* popup_window.connect_show */
{ {
let _appstate = appstate.clone(); let _appstate = appstate.clone();
let popup_window = &appstate.clone() let popup_window = &appstate.clone().gui.popup_window.popup_window;
.gui popup_window.connect_show(
.popup_window move |w| on_popup_window_show(w, &_appstate, toggle_signal),
.popup_window; );
popup_window.connect_show(move |w| {
on_popup_window_show(w,
&_appstate,
toggle_signal)
});
} }
/* vol_scale_adj.connect_value_changed */ /* vol_scale_adj.connect_value_changed */
{ {
let _appstate = appstate.clone(); let _appstate = appstate.clone();
let vol_scale_adj = &appstate.clone() let vol_scale_adj = &appstate.clone().gui.popup_window.vol_scale_adj;
.gui
.popup_window
.vol_scale_adj;
vol_scale_adj.connect_value_changed( vol_scale_adj.connect_value_changed(
move |_| on_vol_scale_value_changed(&_appstate), move |_| on_vol_scale_value_changed(&_appstate),
); );
@ -57,10 +45,7 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
/* popup_window.connect_event */ /* popup_window.connect_event */
{ {
let _appstate = appstate.clone(); let _appstate = appstate.clone();
let popup_window = &appstate.clone() let popup_window = &appstate.clone().gui.popup_window.popup_window;
.gui
.popup_window
.popup_window;
popup_window.connect_event(move |w, e| { popup_window.connect_event(move |w, e| {
on_popup_window_event(w, e, &_appstate) on_popup_window_event(w, e, &_appstate)
}); });
@ -68,9 +53,11 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
} }
fn on_popup_window_show(window: &gtk::Window, fn on_popup_window_show(
window: &gtk::Window,
appstate: &AppS, appstate: &AppS,
toggle_signal: u64) { toggle_signal: u64,
) {
let audio = &appstate.audio; let audio = &appstate.audio;
let popup_window = &appstate.gui.popup_window; let popup_window = &appstate.gui.popup_window;
@ -85,10 +72,11 @@ fn on_popup_window_show(window: &gtk::Window,
} }
fn on_popup_window_event(w: &gtk::Window, fn on_popup_window_event(
w: &gtk::Window,
e: &gdk::Event, e: &gdk::Event,
appstate: &AppS) appstate: &AppS,
-> gtk::Inhibit { ) -> 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(),
gdk::EventType::KeyPress => { gdk::EventType::KeyPress => {
@ -120,10 +108,7 @@ fn on_popup_window_event(w: &gtk::Window,
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 val = appstate.gui let val = appstate.gui.popup_window.vol_scale.get_value();
.popup_window
.vol_scale
.get_value();
try_w!(audio.set_vol(val, AudioUser::Popup)); try_w!(audio.set_vol(val, AudioUser::Popup));
} }
@ -135,9 +120,11 @@ fn on_mute_check_toggled(appstate: &AppS) {
} }
pub fn update_mute_check(appstate: &AppS, pub fn update_mute_check(
appstate: &AppS,
toggle_signal: u64, toggle_signal: u64,
muted: Result<bool>) { muted: Result<bool>,
) {
let check_button = &appstate.gui.popup_window.mute_check; let check_button = &appstate.gui.popup_window.mute_check;
glib::signal_handler_block(check_button, toggle_signal); glib::signal_handler_block(check_button, toggle_signal);
@ -170,32 +157,40 @@ fn grab_devices(window: &gtk::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 = let m_grab_status = device.grab(
device.grab(&gdk_window, &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!("Could not grab {}", warn!(
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))); "Could not grab {}",
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
);
} }
/* Grab the keyboard */ /* Grab the keyboard */
let k_dev = device.get_associated_device() let k_dev = device.get_associated_device().ok_or(
.ok_or("Couldn't get associated device")?; "Couldn't get associated device",
)?;
let k_grab_status = k_dev.grab(&gdk_window, let k_grab_status = k_dev.grab(
&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!("Could not grab {}", warn!(
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))); "Could not grab {}",
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
);
} }
return Ok(()); return Ok(());

View File

@ -5,16 +5,20 @@ use gtk;
use alsa_pn; use alsa_pn;
create_builder_item!(PrefsDialog, create_builder_item!(
PrefsDialog,
prefs_dialog: gtk::Dialog, prefs_dialog: gtk::Dialog,
card_combo: gtk::ComboBoxText, card_combo: gtk::ComboBoxText,
chan_combo: gtk::ComboBoxText); chan_combo: gtk::ComboBoxText
);
pub fn show_prefs_dialog(appstate: Rc<AppS>) { pub fn show_prefs_dialog(appstate: Rc<AppS>) {
let builder_prefs_dialog = gtk::Builder::new_from_string(include_str!("../data/ui/prefs-dialog.glade")); let builder_prefs_dialog = gtk::Builder::new_from_string(
include_str!("../data/ui/prefs-dialog.glade"),
);
let prefs_dialog = Rc::new(PrefsDialog::new(builder_prefs_dialog)); let prefs_dialog = Rc::new(PrefsDialog::new(builder_prefs_dialog));
init_prefs_dialog(&appstate, &prefs_dialog); init_prefs_dialog(&appstate, &prefs_dialog);
{ {
@ -33,7 +37,9 @@ pub fn init_prefs_dialog(appstate: &Rc<AppS>, prefs_dialog: &Rc<PrefsDialog>) {
{ {
let apps = appstate.clone(); let apps = appstate.clone();
let pd = prefs_dialog.clone(); let pd = prefs_dialog.clone();
prefs_dialog.clone().prefs_dialog.connect_show(move |_| { on_prefs_dialog_show(&apps, &pd); }); prefs_dialog.clone().prefs_dialog.connect_show(move |_| {
on_prefs_dialog_show(&apps, &pd);
});
} }
/* DEVICE TAB */ /* DEVICE TAB */
@ -44,9 +50,9 @@ pub fn init_prefs_dialog(appstate: &Rc<AppS>, prefs_dialog: &Rc<PrefsDialog>) {
let card_combo = &prefs_dialog.card_combo; let card_combo = &prefs_dialog.card_combo;
let pd = prefs_dialog.clone(); let pd = prefs_dialog.clone();
card_combo.connect_changed(move |_| { card_combo.connect_changed(
on_card_combo_changed(&apps, &pd); move |_| { on_card_combo_changed(&apps, &pd); },
}); );
} }
/* card_combo.connect_changed */ /* card_combo.connect_changed */
{ {
@ -54,9 +60,9 @@ pub fn init_prefs_dialog(appstate: &Rc<AppS>, prefs_dialog: &Rc<PrefsDialog>) {
let chan_combo = &prefs_dialog.chan_combo; let chan_combo = &prefs_dialog.chan_combo;
let pd = prefs_dialog.clone(); let pd = prefs_dialog.clone();
chan_combo.connect_changed(move |_| { chan_combo.connect_changed(
on_chan_combo_changed(&apps, &pd); move |_| { on_chan_combo_changed(&apps, &pd); },
}); );
} }
} }
@ -68,8 +74,8 @@ fn on_prefs_dialog_show(appstate: &AppS, prefs_dialog: &PrefsDialog) {
/* set card combo */ /* set card combo */
let cur_card_name = try_w!(acard.card_name(), let cur_card_name =
"Can't get current card name!"); try_w!(acard.card_name(), "Can't get current card name!");
let available_card_names = alsa_pn::get_alsa_card_names(); let available_card_names = alsa_pn::get_alsa_card_names();
/* set_active_id doesn't work, so save the index */ /* set_active_id doesn't work, so save the index */
@ -110,8 +116,9 @@ fn on_prefs_dialog_show(appstate: &AppS, prefs_dialog: &PrefsDialog) {
fn on_card_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) { fn on_card_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) {
let card_combo = &prefs_dialog.card_combo; let card_combo = &prefs_dialog.card_combo;
let chan_combo = &prefs_dialog.chan_combo; let chan_combo = &prefs_dialog.chan_combo;
let active_card_item = let active_card_item = try_w!(card_combo.get_active_text().ok_or(
try_w!(card_combo.get_active_text().ok_or("No active Card item found")); "No active Card item found",
));
let active_chan_item = chan_combo.get_active_id(); let active_chan_item = chan_combo.get_active_id();
let cur_card_name = { let cur_card_name = {
let acard = appstate.audio.acard.borrow(); let acard = appstate.audio.acard.borrow();
@ -119,7 +126,10 @@ fn on_card_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) {
}; };
if active_card_item != cur_card_name { if active_card_item != cur_card_name {
appstate.audio.switch_acard(Some(cur_card_name), active_chan_item); appstate.audio.switch_acard(
Some(cur_card_name),
active_chan_item,
);
} }
} }
@ -127,8 +137,9 @@ fn on_card_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) {
fn on_chan_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) { fn on_chan_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) {
let card_combo = &prefs_dialog.card_combo; let card_combo = &prefs_dialog.card_combo;
let chan_combo = &prefs_dialog.chan_combo; let chan_combo = &prefs_dialog.chan_combo;
let active_chan_item = let active_chan_item = try_w!(chan_combo.get_active_text().ok_or(
try_w!(chan_combo.get_active_text().ok_or("No active Chan item found")); "No active Chan item found",
));
let cur_card_name = { let cur_card_name = {
let acard = appstate.audio.acard.borrow(); let acard = appstate.audio.acard.borrow();
acard.card_name().ok() acard.card_name().ok()
@ -139,6 +150,9 @@ fn on_chan_combo_changed(appstate: &AppS, prefs_dialog: &PrefsDialog) {
}; };
if active_chan_item != cur_chan_name { if active_chan_item != cur_chan_name {
appstate.audio.switch_acard(cur_card_name, Some(active_chan_item)); appstate.audio.switch_acard(
cur_card_name,
Some(active_chan_item),
);
} }
} }

View File

@ -1,6 +1,8 @@
use app_state::*; use app_state::*;
use gdk; use gdk;
use gdk_pixbuf; use gdk_pixbuf;
use gdk_pixbuf_sys;
use gdk_pixbuf_sys::GDK_COLORSPACE_RGB;
use gdk_sys; use gdk_sys;
use glib; use glib;
use glib_sys; use glib_sys;
@ -8,45 +10,160 @@ use gtk;
use std::mem; use std::mem;
use gtk::prelude::*; use gtk::prelude::*;
use std::rc::Rc; use std::rc::Rc;
use std::cell::Cell;
use std::cell::RefCell; use std::cell::RefCell;
use libc; use libc;
use audio::*; use audio::*;
use errors::*; use errors::*;
use std::path::*; use std::path::*;
use glib::translate::ToGlibPtr;
use glib::translate::FromGlibPtrFull;
use glib::translate::FromGlibPtrNone;
use libc::memcpy;
const ICON_MIN_SIZE: i32 = 16; const ICON_MIN_SIZE: i32 = 16;
// struct VolMeter { fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf {
// pub red: u8,
// pub green: u8, let new_pixbuf = unsafe {
// pub blue: u8, let gdk_pixbuf = pixbuf.to_glib_full();
// pub x_offset_pct: i64, let copy = gdk_pixbuf_sys::gdk_pixbuf_copy(gdk_pixbuf);
// pub y_offset_pct: i64, FromGlibPtrFull::from_glib_full(copy)
// /* dynamic */ };
// pub pixbuf: gdk_pixbuf::Pixbuf,
// pub width: i64, return new_pixbuf;
// pub row: u8, }
// }
// impl VolMeter { struct VolMeter {
// pub fn new() -> VolMeter { pub red: u8,
// let pixbux = Pixbuf::new(); pub green: u8,
// return VolMeter { pub blue: u8,
// red: 255, pub x_offset_pct: i64,
// green: 255, pub y_offset_pct: i64,
// blue: 255, /* dynamic */
// x_offset_pct: 0, pub width: Cell<i64>,
// y_offset_pct: 0, pub row: RefCell<Vec<u8>>,
// pixbuf: , }
// width: ,
// row: ,
// } impl VolMeter {
// } // TODO: take settings
// } pub fn new() -> VolMeter {
return VolMeter {
red: 245,
green: 121,
blue: 0,
x_offset_pct: 10,
y_offset_pct: 10,
/* dynamic */
width: Cell::new(0),
row: RefCell::new(vec![]),
};
}
// TODO: cache input pixbuf?
fn meter_draw(
&self,
volume: i64,
pixbuf: &gdk_pixbuf::Pixbuf,
) -> Result<gdk_pixbuf::Pixbuf> {
ensure!(
pixbuf.get_colorspace() == GDK_COLORSPACE_RGB,
"Invalid colorspace in pixbuf"
);
ensure!(
pixbuf.get_bits_per_sample() == 8,
"Invalid bits per sample in pixbuf"
);
ensure!(pixbuf.get_has_alpha(), "No alpha channel in pixbuf");
ensure!(
pixbuf.get_n_channels() == 4,
"Invalid number of channels in pixbuf"
);
let i_width = pixbuf.get_width() as i64;
let i_height = pixbuf.get_height() as i64;
let new_pixbuf = copy_pixbuf(pixbuf);
let vm_width = i_width / 6;
let x = (self.x_offset_pct as f64 *
((i_width - vm_width) as f64 / 100.0)) as
i64;
ensure!(
x >= 0 && (x + vm_width) <= i_width,
"x coordinate invalid: {}",
x
);
let y = (self.y_offset_pct as f64 * (i_height as f64 / 100.0)) as i64;
let vm_height =
((i_height - (y * 2)) as f64 * (volume as f64 / 100.0)) as i64;
ensure!(
y >= 0 && (y + vm_height) <= i_height,
"y coordinate invalid: {}",
y
);
debug!("vm_height: {}", vm_height);
debug!("i_height: {}", i_height);
debug!("y: {}", y);
debug!("volume: {}", volume);
/* Let's check if the icon width changed, in which case we
* must reinit our internal row of pixels.
*/
if vm_width != self.width.get() {
self.width.set(vm_width);
let mut row = self.row.borrow_mut();
*row = vec![];
}
if self.row.borrow().len() == 0 {
debug!("Allocating vol meter row (width {})", vm_width);
let mut row = self.row.borrow_mut();
*row = [self.red, self.green, self.blue, 255]
.iter()
.cloned()
.cycle()
.take(vm_width - 1 as usize)
.collect();
}
/* Draw the volume meter.
* Rows in the image are stored top to bottom.
*/
{
let y = i_height - y;
let rowstride: i64 = new_pixbuf.get_rowstride() as i64;
let pixels: &mut [u8] = unsafe { new_pixbuf.get_pixels() };
for i in 0..(vm_height - 1) {
let row_offset: i64 = y - i;
let col_offset: i64 = x * 4;
let p_index = ((row_offset * rowstride) + col_offset) as isize;
unsafe {
let p = pixels.as_mut_ptr().offset(p_index) as
*mut libc::c_void;
memcpy(
p,
self.row.borrow().as_slice().as_ptr() as
*const libc::c_void,
self.row.borrow().len(),
);
}
}
}
return Ok(new_pixbuf);
}
}
// TODO: connect on icon theme change // TODO: connect on icon theme change
@ -63,9 +180,11 @@ struct AudioPix {
impl AudioPix { impl AudioPix {
// TODO: take settings
pub fn new_from_theme(size: i32) -> Result<AudioPix> { pub fn new_from_theme(size: i32) -> Result<AudioPix> {
let theme: gtk::IconTheme = let theme: gtk::IconTheme = gtk::IconTheme::get_default().ok_or(
gtk::IconTheme::get_default().ok_or("Couldn't get default icon theme")?; "Couldn't get default icon theme",
)?;
let pix = AudioPix { let pix = AudioPix {
muted: pixbuf_new_from_theme("audio-volume-muted", size, &theme)?, muted: pixbuf_new_from_theme("audio-volume-muted", size, &theme)?,
low: pixbuf_new_from_theme("audio-volume-low", size, &theme)?, low: pixbuf_new_from_theme("audio-volume-low", size, &theme)?,
@ -76,14 +195,18 @@ impl AudioPix {
* http://standards.freedesktop.org/icon-naming-spec/ * http://standards.freedesktop.org/icon-naming-spec/
* icon-naming-spec-latest.html * icon-naming-spec-latest.html
*/ */
off: pixbuf_new_from_theme("audio-volume-off", size, &theme).or(pixbuf_new_from_theme("audio-volume-low", size, &theme))?, off: pixbuf_new_from_theme("audio-volume-off", size, &theme).or(
pixbuf_new_from_theme("audio-volume-low", size, &theme),
)?,
}; };
return Ok(pix); return Ok(pix);
} }
pub fn new_from_pnmixer() -> Result<AudioPix> { pub fn new_from_pnmixer() -> Result<AudioPix> {
gtk::IconTheme::get_default().ok_or("Couldn't get default icon theme")?; gtk::IconTheme::get_default().ok_or(
"Couldn't get default icon theme",
)?;
let pix = AudioPix { let pix = AudioPix {
muted: pixbuf_new_from_file("pnmixer-muted.png")?, muted: pixbuf_new_from_file("pnmixer-muted.png")?,
low: pixbuf_new_from_file("pnmixer-low.png")?, low: pixbuf_new_from_file("pnmixer-low.png")?,
@ -107,18 +230,21 @@ impl AudioPix {
} }
fn pixbuf_new_from_theme(icon_name: &str, fn pixbuf_new_from_theme(
icon_name: &str,
size: i32, size: i32,
theme: &gtk::IconTheme) theme: &gtk::IconTheme,
-> Result<gdk_pixbuf::Pixbuf> { ) -> Result<gdk_pixbuf::Pixbuf> {
let icon_info = let icon_info = theme
theme.lookup_icon(icon_name, size, gtk::IconLookupFlags::empty()) .lookup_icon(icon_name, size, gtk::IconLookupFlags::empty())
.ok_or(format!("Couldn't find icon {}", icon_name))?; .ok_or(format!("Couldn't find icon {}", icon_name))?;
debug!("Loading stock icon {} from {:?}", debug!(
"Loading stock icon {} from {:?}",
icon_name, icon_name,
icon_info.get_filename().unwrap_or(PathBuf::new())); icon_info.get_filename().unwrap_or(PathBuf::new())
);
// TODO: propagate error // TODO: propagate error
let pixbuf = icon_info.load_icon().unwrap(); let pixbuf = icon_info.load_icon().unwrap();
@ -145,9 +271,15 @@ fn pixbuf_new_from_file(filename: &str) -> Result<gdk_pixbuf::Pixbuf> {
fn update_tray_icon(audio_pix: &AudioPix, appstate: &AppS) { fn update_tray_icon(audio_pix: &AudioPix, appstate: &AppS) {
let cur_vol = try_w!(appstate.audio.vol());
let status_icon = &appstate.gui.status_icon; let status_icon = &appstate.gui.status_icon;
let pixbuf = audio_pix.select_pix(appstate.audio.vol_level()); let pixbuf = audio_pix.select_pix(appstate.audio.vol_level());
status_icon.set_from_pixbuf(Some(pixbuf));
let volmeter = VolMeter::new();
let vol_pix = try_w!(volmeter.meter_draw(cur_vol as i64, &pixbuf));
status_icon.set_from_pixbuf(Some(&vol_pix));
} }
@ -159,14 +291,14 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
{ {
let _audio_pix = audio_pix.clone(); let _audio_pix = audio_pix.clone();
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler(Box::new(move |s, u| { appstate.audio.connect_handler(
match (s, u) { Box::new(move |s, u| match (s, u) {
(AudioSignal::ValuesChanged, _) => { (AudioSignal::ValuesChanged, _) => {
update_tray_icon(&_audio_pix.borrow(), &apps); update_tray_icon(&_audio_pix.borrow(), &apps);
},
_ => (),
} }
})); _ => (),
}),
);
} }
/* tray_icon.connect_size_changed */ /* tray_icon.connect_size_changed */
@ -174,7 +306,9 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
let apps = appstate.clone(); let apps = appstate.clone();
let tray_icon = &appstate.gui.status_icon; let tray_icon = &appstate.gui.status_icon;
let _audio_pix = audio_pix.clone(); let _audio_pix = audio_pix.clone();
tray_icon.connect_size_changed(move |_, size| on_tray_icon_size_changed(&apps, _audio_pix.as_ref(), size)); tray_icon.connect_size_changed(move |_, size| {
on_tray_icon_size_changed(&apps, _audio_pix.as_ref(), size)
});
tray_icon.set_visible(true); tray_icon.set_visible(true);
} }
@ -190,27 +324,27 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
{ {
let apps = appstate.clone(); let apps = appstate.clone();
let tray_icon = &appstate.clone().gui.status_icon; let tray_icon = &appstate.clone().gui.status_icon;
tray_icon.connect_scroll_event(move |_, e| { tray_icon.connect_scroll_event(
on_tray_icon_scroll_event(&apps, &e) move |_, e| on_tray_icon_scroll_event(&apps, &e),
}); );
} }
/* tray_icon.connect_popup_menu */ /* tray_icon.connect_popup_menu */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
let tray_icon = &appstate.clone().gui.status_icon; let tray_icon = &appstate.clone().gui.status_icon;
tray_icon.connect_popup_menu(move |_, _, _| { tray_icon.connect_popup_menu(
on_tray_icon_popup_menu(&apps) move |_, _, _| on_tray_icon_popup_menu(&apps),
}); );
} }
/* tray_icon.connect_button_release_event */ /* tray_icon.connect_button_release_event */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
let tray_icon = &appstate.clone().gui.status_icon; let tray_icon = &appstate.clone().gui.status_icon;
tray_icon.connect_button_release_event(move |_, eb| { tray_icon.connect_button_release_event(
on_tray_button_release_event(&apps, eb) move |_, eb| on_tray_button_release_event(&apps, eb),
}); );
} }
} }
@ -235,9 +369,10 @@ fn on_tray_icon_popup_menu(appstate: &AppS) {
} }
fn on_tray_icon_scroll_event(appstate: &AppS, fn on_tray_icon_scroll_event(
event: &gdk::EventScroll) appstate: &AppS,
-> bool { event: &gdk::EventScroll,
) -> bool {
let audio = &appstate.audio; let audio = &appstate.audio;
@ -256,10 +391,11 @@ fn on_tray_icon_scroll_event(appstate: &AppS,
} }
fn on_tray_icon_size_changed(appstate: &AppS, fn on_tray_icon_size_changed(
appstate: &AppS,
audio_pix: &RefCell<AudioPix>, audio_pix: &RefCell<AudioPix>,
size: i32) size: i32,
-> bool { ) -> bool {
debug!("Tray icon size is now {}", size); debug!("Tray icon size is now {}", size);
let mut size = size; let mut size = size;
@ -279,9 +415,10 @@ fn on_tray_icon_size_changed(appstate: &AppS,
} }
fn on_tray_button_release_event(appstate: &AppS, fn on_tray_button_release_event(
event_button: &gdk::EventButton) appstate: &AppS,
-> bool { event_button: &gdk::EventButton,
) -> bool {
let button = event_button.get_button(); let button = event_button.get_button();
if button != 2 { if button != 2 {