diff --git a/src/audio.rs b/src/audio.rs index bf5a6da00..da5a72a28 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -15,6 +15,7 @@ use std::mem; use std::ptr; use std::rc::Rc; use std::u8; +use std::f64; @@ -46,6 +47,7 @@ pub enum AlsaEvent { } +// TODO: Audio struct? Separate more cleanly // TODO: implement free/destructor pub struct AlsaCard { _cannot_construct: (), @@ -54,7 +56,8 @@ pub struct AlsaCard { pub selem_id: SelemId, pub watch_ids: Vec, pub last_action_timestamp: RefCell, - pub handlers: RefCell>>, + pub handlers: RefCell>>, + pub scroll_step: RefCell, } @@ -77,6 +80,7 @@ impl AlsaCard { .get_id(); let vec_pollfd = PollDescriptors::get(&mixer)?; + // TODO: rework, should probably be a Box? let acard = Rc::new(RefCell::new(AlsaCard { _cannot_construct: (), card: card, @@ -86,19 +90,27 @@ impl AlsaCard { last_action_timestamp: RefCell::new(0), handlers: RefCell::new(vec![]), + scroll_step: RefCell::new(3), })); /* TODO: callback is registered here, which must be unregistered * when the mixer is destroyed!! * poll descriptors must be unwatched too */ - let watch_ids = watch_poll_descriptors(vec_pollfd, - acard.clone().as_ptr()); + let watch_ids = watch_poll_descriptors(vec_pollfd, acard.as_ptr()); acard.borrow_mut().watch_ids = watch_ids; return Ok(acard.clone()); } + pub fn switch_card(old_card: Rc>, + card_name: Option, + elem_name: Option) + -> Result>> { + + } + + pub fn selem(&self) -> Selem { return get_selems(&self.mixer) .nth(self.selem_id.get_index() as usize) @@ -123,6 +135,34 @@ impl AlsaCard { } + pub fn increase_vol(&self, user: AudioUser) -> Result<()> { + { + let mut rc = self.last_action_timestamp.borrow_mut(); + *rc = glib::get_monotonic_time(); + } + let old_vol = self.vol()?; + let new_vol = f64::ceil(old_vol + (*self.scroll_step.borrow() as f64)); + + debug!("Increase vol by {:?} to {:?}", (new_vol - old_vol), new_vol); + + return self.set_vol(new_vol, user); + } + + + pub fn decrease_vol(&self, user: AudioUser) -> Result<()> { + { + let mut rc = self.last_action_timestamp.borrow_mut(); + *rc = glib::get_monotonic_time(); + } + let old_vol = self.vol()?; + let new_vol = old_vol - (*self.scroll_step.borrow() as f64); + + debug!("Decrease vol by {:?} to {:?}", (new_vol - old_vol), new_vol); + + return self.set_vol(new_vol, user); + } + + pub fn has_mute(&self) -> bool { return has_mute(&self.selem()); } @@ -176,23 +216,36 @@ impl AlsaCard { signal, user); let handlers = self.handlers.borrow(); - let x: &Vec> = &*handlers; - for handler in x { + let handlers_ref: &Vec> = + handlers.as_ref(); + for handler in handlers_ref { let unboxed = handler.as_ref(); - unboxed(&self, signal, user); + unboxed(signal, user); } } - pub fn connect_handler(&self, - cb: Box) { + pub fn connect_handler(&self, cb: Box) { self.handlers.borrow_mut().push(cb); } } impl Drop for AlsaCard { - fn drop (&mut self) { + // 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 + fn drop(&mut self) { debug!("Destructing watch_ids: {:?}", self.watch_ids); unwatch_poll_descriptors(&self.watch_ids); } diff --git a/src/ui_entry.rs b/src/ui_entry.rs index ae8963060..a1df1c13a 100644 --- a/src/ui_entry.rs +++ b/src/ui_entry.rs @@ -10,7 +10,7 @@ use ui_tray_icon::*; pub fn init(appstate: Rc) { { let apps = appstate.clone(); - appstate.acard.borrow().connect_handler(Box::new(move |a, s, u| { + appstate.acard.borrow().connect_handler(Box::new(move |s, u| { match (s, u) { (AudioSignal::AudioValuesChanged, AudioUser::AudioUserUnknown) => { diff --git a/src/ui_tray_icon.rs b/src/ui_tray_icon.rs index 5ada55f04..271b6fe54 100644 --- a/src/ui_tray_icon.rs +++ b/src/ui_tray_icon.rs @@ -1,16 +1,33 @@ use app_state::*; +use gdk; +use gdk_sys; +use glib; +use glib_sys; +use std::mem; use gtk::prelude::*; use std::rc::Rc; +use std::cell::RefCell; +use libc; +use audio::AudioUser::*; +use errors::*; pub fn init_tray_icon(appstate: Rc) { + { + let apps = appstate.clone(); + let tray_icon = &appstate.gui.status_icon; + tray_icon.connect_activate(move |_| on_tray_icon_activate(&apps)); + tray_icon.set_visible(true); + } + { + let tray_icon = &appstate.clone().gui.status_icon; + tray_icon.connect_scroll_event(move |_, e| { + on_tray_icon_scroll_event(&appstate.clone(), &e) + }); + tray_icon.set_visible(true); + } - let tray_icon = &appstate.clone().gui.status_icon; - tray_icon.connect_activate(move |_| { - on_tray_icon_activate(&appstate.clone()) - }); - tray_icon.set_visible(true); } @@ -23,3 +40,24 @@ fn on_tray_icon_activate(appstate: &AppS) { popup_window.show_now(); } } + + +fn on_tray_icon_scroll_event(appstate: &AppS, + event: &gdk::EventScroll) + -> bool { + + let scroll_dir = event.as_ref().direction; + match scroll_dir { + gdk_sys::GdkScrollDirection::Up => { + try_wr!(appstate.acard.borrow().increase_vol(AudioUserTrayIcon), + false); + } + gdk_sys::GdkScrollDirection::Down => { + try_wr!(appstate.acard.borrow().decrease_vol(AudioUserTrayIcon), + false); + } + _ => (), + } + + return false; +}