This commit is contained in:
Ospald, Julian 2017-07-06 16:53:19 +02:00
parent e70e88690b
commit 47b3fe8b56
10 changed files with 481 additions and 392 deletions

View File

@ -36,10 +36,11 @@ pub struct AlsaCard {
impl AlsaCard { impl AlsaCard {
pub fn new(card_name: Option<String>, pub fn new(
elem_name: Option<String>, card_name: Option<String>,
cb: Rc<Fn(AlsaEvent)>) elem_name: Option<String>,
-> Result<Box<AlsaCard>> { cb: Rc<Fn(AlsaEvent)>,
) -> 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)?,
@ -50,26 +51,26 @@ 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 {
_cannot_construct: (), _cannot_construct: (),
card: card, card: card,
mixer: mixer, mixer: mixer,
selem_id: selem_id, selem_id: selem_id,
watch_ids: Cell::new(vec![]), watch_ids: Cell::new(vec![]),
cb: cb, cb: cb,
}); });
/* 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);
@ -82,9 +83,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);
} }
@ -113,7 +112,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(());
} }
@ -140,9 +141,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) };
@ -199,10 +201,11 @@ impl Drop for AlsaCard {
} }
extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, extern "C" fn watch_cb(
cond: glib_sys::GIOCondition, chan: *mut glib_sys::GIOChannel,
data: glib_sys::gpointer) cond: glib_sys::GIOCondition,
-> glib_sys::gboolean { data: glib_sys::gpointer,
) -> glib_sys::gboolean {
let acard = let acard =
unsafe { mem::transmute::<glib_sys::gpointer, &AlsaCard>(data) }; unsafe { mem::transmute::<glib_sys::gpointer, &AlsaCard>(data) };
@ -221,14 +224,15 @@ 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 {
glib_sys::G_IO_STATUS_AGAIN => { glib_sys::G_IO_STATUS_AGAIN => {

View File

@ -18,19 +18,21 @@ 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"),
);
let prefs = RefCell::new(Prefs::new().unwrap()); let prefs = RefCell::new(Prefs::new().unwrap());
let gui = let gui =
Gui::new(builder_popup_window, builder_popup_menu, &prefs.borrow()); Gui::new(builder_popup_window, builder_popup_menu, &prefs.borrow());
return AppS { return AppS {
gui: gui, gui: gui,
audio: Audio::new(None, Some(String::from("Master"))) audio: Audio::new(None, Some(String::from("Master"))).unwrap(),
.unwrap(), prefs: prefs,
prefs: prefs, };
};
} }
} }
@ -38,35 +40,67 @@ impl AppS {
pub struct Gui { pub struct Gui {
pub tray_icon: TrayIcon, pub tray_icon: TrayIcon,
pub popup_window: PopupWindow, pub popup_window: PopupWindow,
pub popup_menu: PopupMenu, pub popup_menu: PopupMenu,
/* prefs_dialog is dynamically created and destroyed */ /* prefs_dialog is dynamically created and destroyed,
* not part of the global app-state */
pub prefs_dialog: RefCell<Option<PrefsDialog>>,
} }
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,
prefs: &Prefs) builder_popup_menu: gtk::Builder,
-> Gui { prefs: &Prefs,
) -> Gui {
return Gui { return Gui {
tray_icon: TrayIcon::new(prefs).unwrap(), tray_icon: TrayIcon::new(prefs).unwrap(),
popup_window: PopupWindow::new(builder_popup_window), popup_window: PopupWindow::new(builder_popup_window),
popup_menu: PopupMenu::new(builder_popup_menu), popup_menu: PopupMenu::new(builder_popup_menu),
}; prefs_dialog: RefCell::new(None),
};
} }
} }
create_builder_item!(PopupWindow, create_builder_item!(
popup_window: gtk::Window, PopupWindow,
vol_scale_adj: gtk::Adjustment, popup_window: gtk::Window,
vol_scale: gtk::Scale, vol_scale_adj: gtk::Adjustment,
mute_check: gtk::CheckButton); vol_scale: gtk::Scale,
mute_check: gtk::CheckButton
);
create_builder_item!(PopupMenu, create_builder_item!(
menu_window: gtk::Window, PopupMenu,
menubar: gtk::MenuBar, menu_window: gtk::Window,
menu: gtk::Menu, menubar: gtk::MenuBar,
about_item: gtk::MenuItem, menu: gtk::Menu,
prefs_item: gtk::MenuItem); about_item: gtk::MenuItem,
prefs_item: gtk::MenuItem
);
pub struct PrefsDialog {
pub prefs_dialog: gtk::Dialog,
pub card_combo: gtk::ComboBoxText,
pub chan_combo: gtk::ComboBoxText,
pub response_callback: Box<Fn(PrefsDialog, i64)>,
}
impl PrefsDialog {
pub fn new(
response_callback: Box<Fn(PrefsDialog, i64)>,
) -> PrefsDialog {
let builder = gtk::Builder::new_from_string(
include_str!("../data/ui/prefs-dialog.glade"),
);
return PrefsDialog {
prefs_dialog: builder.get_object("prefs_dialog").unwrap(),
card_combo: builder.get_object("card_combo").unwrap(),
chan_combo: builder.get_object("chan_combo").unwrap(),
response_callback,
};
}
}

View File

@ -73,9 +73,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));
@ -84,10 +85,8 @@ 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)
})
}; };
let audio = Audio { let audio = Audio {
@ -102,44 +101,40 @@ impl Audio {
} }
pub fn switch_acard(&self, pub fn switch_acard(
card_name: Option<String>, &self,
elem_name: Option<String>, card_name: Option<String>,
user: AudioUser) elem_name: Option<String>,
-> Result<()> { user: AudioUser,
) -> 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());
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
AudioSignal::CardInitialized, &self.handlers.borrow(),
user); AudioSignal::CardInitialized,
user,
);
return Ok(()); return Ok(());
} }
@ -172,24 +167,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(), new_vol,
self.acard user
.borrow() );
.chan_name() self.acard.borrow().set_vol(new_vol)?;
.unwrap(),
new_vol,
user);
self.acard
.borrow()
.set_vol(new_vol)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
AudioSignal::ValuesChanged, &self.handlers.borrow(),
user); AudioSignal::ValuesChanged,
user,
);
return Ok(()); return Ok(());
} }
@ -202,23 +193,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(), (new_vol - old_vol),
self.acard new_vol
.borrow() );
.chan_name()
.unwrap(),
(new_vol - old_vol),
new_vol);
self.set_vol(new_vol, user)?; self.set_vol(new_vol, user)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
AudioSignal::ValuesChanged, &self.handlers.borrow(),
user); AudioSignal::ValuesChanged,
user,
);
return Ok(()); return Ok(());
} }
@ -231,23 +220,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(), (new_vol - old_vol),
self.acard new_vol
.borrow() );
.chan_name()
.unwrap(),
(new_vol - old_vol),
new_vol);
self.set_vol(new_vol, user)?; self.set_vol(new_vol, user)?;
invoke_handlers(&self.handlers.borrow(), invoke_handlers(
AudioSignal::ValuesChanged, &self.handlers.borrow(),
user); AudioSignal::ValuesChanged,
user,
);
return Ok(()); return Ok(());
} }
@ -266,25 +253,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!(
mute, "Setting mute to {} on card {:?} and chan {:?} by user {:?}",
self.acard mute,
.borrow() self.acard.borrow().card_name().unwrap(),
.card_name() self.acard.borrow().chan_name().unwrap(),
.unwrap(), user
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(
AudioSignal::ValuesChanged, &self.handlers.borrow(),
user); AudioSignal::ValuesChanged,
user,
);
return Ok(()); return Ok(());
} }
@ -301,12 +284,16 @@ impl Audio {
} }
fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>, fn invoke_handlers(
signal: AudioSignal, handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
user: AudioUser) { signal: AudioSignal,
debug!("Invoking handlers for signal {:?} by user {:?}", user: AudioUser,
signal, ) {
user); debug!(
"Invoking handlers for signal {:?} by user {:?}",
signal,
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);
@ -314,9 +301,11 @@ fn invoke_handlers(handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
} }
fn on_alsa_event(last_action_timestamp: &mut i64, fn on_alsa_event(
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>, last_action_timestamp: &mut i64,
alsa_event: AlsaEvent) { handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
alsa_event: AlsaEvent,
) {
let last: i64 = *last_action_timestamp; let last: i64 = *last_action_timestamp;
if last != 0 { if last != 0 {
@ -336,9 +325,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(
self::AudioSignal::ValuesChanged, handlers,
self::AudioUser::Unknown); self::AudioSignal::ValuesChanged,
self::AudioUser::Unknown,
);
} }
e => warn!("Unhandled alsa event: {:?}", e), e => warn!("Unhandled alsa event: {:?}", e),
} }

View File

@ -36,16 +36,16 @@ impl Default for MiddleClickAction {
#[serde(default)] #[serde(default)]
pub struct DevicePrefs { pub struct DevicePrefs {
pub card: String, pub card: String,
pub channel: String, pub channel: String,
// TODO: normalize volume? // TODO: normalize volume?
} }
impl Default for DevicePrefs { impl Default for DevicePrefs {
fn default() -> DevicePrefs { fn default() -> DevicePrefs {
return DevicePrefs { return DevicePrefs {
card: String::from("(default)"), card: String::from("(default)"),
channel: String::from("Master"), channel: String::from("Master"),
}; };
} }
} }
@ -56,18 +56,18 @@ pub struct ViewPrefs {
pub draw_vol_meter: bool, pub draw_vol_meter: bool,
pub vol_meter_offset: i64, pub vol_meter_offset: i64,
pub system_theme: bool, pub system_theme: bool,
pub vol_meter_color: VolColor, pub vol_meter_color: VolColor,
// TODO: Display text folume/text volume pos? // TODO: Display text folume/text volume pos?
} }
impl Default for ViewPrefs { impl Default for ViewPrefs {
fn default() -> ViewPrefs { fn default() -> ViewPrefs {
return ViewPrefs { return ViewPrefs {
draw_vol_meter: true, draw_vol_meter: true,
vol_meter_offset: 10, vol_meter_offset: 10,
system_theme: true, system_theme: true,
vol_meter_color: VolColor::default(), vol_meter_color: VolColor::default(),
}; };
} }
} }
@ -83,10 +83,10 @@ pub struct VolColor {
impl Default for VolColor { impl Default for VolColor {
fn default() -> VolColor { fn default() -> VolColor {
return VolColor { return VolColor {
red: 245, red: 245,
green: 180, green: 180,
blue: 0, blue: 0,
}; };
} }
} }
@ -96,17 +96,17 @@ impl Default for VolColor {
pub struct BehaviorPrefs { pub struct BehaviorPrefs {
pub vol_control_cmd: Option<String>, pub vol_control_cmd: Option<String>,
pub vol_scroll_step: f64, pub vol_scroll_step: f64,
pub middle_click_action: MiddleClickAction, pub middle_click_action: MiddleClickAction,
// TODO: fine scroll step? // TODO: fine scroll step?
} }
impl Default for BehaviorPrefs { impl Default for BehaviorPrefs {
fn default() -> BehaviorPrefs { fn default() -> BehaviorPrefs {
return BehaviorPrefs { return BehaviorPrefs {
vol_control_cmd: None, vol_control_cmd: None,
vol_scroll_step: 5.0, vol_scroll_step: 5.0,
middle_click_action: MiddleClickAction::default(), middle_click_action: MiddleClickAction::default(),
}; };
} }
} }
@ -118,19 +118,19 @@ pub struct NotifyPrefs {
pub notifcation_timeout: i64, pub notifcation_timeout: i64,
pub notify_mouse_scroll: bool, pub notify_mouse_scroll: bool,
pub notify_popup: bool, pub notify_popup: bool,
pub notify_external: bool, pub notify_external: bool,
// TODO: notify_hotkeys? // TODO: notify_hotkeys?
} }
impl Default for NotifyPrefs { impl Default for NotifyPrefs {
fn default() -> NotifyPrefs { fn default() -> NotifyPrefs {
return NotifyPrefs { return NotifyPrefs {
enable_notifications: true, enable_notifications: true,
notifcation_timeout: 1500, notifcation_timeout: 1500,
notify_mouse_scroll: true, notify_mouse_scroll: true,
notify_popup: true, notify_popup: true,
notify_external: true, notify_external: true,
}; };
} }
} }
@ -141,7 +141,7 @@ pub struct Prefs {
pub device_prefs: DevicePrefs, pub device_prefs: DevicePrefs,
pub view_prefs: ViewPrefs, pub view_prefs: ViewPrefs,
pub behavior_prefs: BehaviorPrefs, pub behavior_prefs: BehaviorPrefs,
pub notify_prefs: NotifyPrefs, pub notify_prefs: NotifyPrefs,
// TODO: HotKeys? // TODO: HotKeys?
} }
@ -184,8 +184,8 @@ impl Prefs {
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 =
.from_err()?; get_xdg_dirs().place_config_file("pnmixer.toml").from_err()?;
debug!("Storing config in {:?}", config_path); debug!("Storing config in {:?}", config_path);
@ -218,9 +218,10 @@ impl Prefs {
} }
impl Display for Prefs { impl Display for Prefs {
fn fmt(&self, fn fmt(
f: &mut Formatter) &self,
-> std::result::Result<(), std::fmt::Error> { f: &mut Formatter,
) -> std::result::Result<(), std::fmt::Error> {
let s = self.to_str(); let s = self.to_str();
return write!(f, "{}", s); return write!(f, "{}", s);
} }

View File

@ -81,9 +81,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

@ -21,18 +21,21 @@ pub fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf {
} }
pub fn pixbuf_new_from_theme(icon_name: &str, pub fn pixbuf_new_from_theme(
size: i32, icon_name: &str,
theme: &gtk::IconTheme) size: i32,
-> Result<gdk_pixbuf::Pixbuf> { theme: &gtk::IconTheme,
) -> 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!(
icon_name, "Loading stock icon {} from {:?}",
icon_info.get_filename().unwrap_or(PathBuf::new())); icon_name,
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();

View File

@ -14,21 +14,15 @@ 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
.popup_menu
.prefs_item;
prefs_item.connect_activate( prefs_item.connect_activate(
move |_| { on_prefs_item_activate(appstate.clone()); }, move |_| { on_prefs_item_activate(appstate.clone()); },
); );

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,20 +45,19 @@ 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)
}); });
} }
} }
fn on_popup_window_show(window: &gtk::Window, fn on_popup_window_show(
appstate: &AppS, window: &gtk::Window,
toggle_signal: u64) { appstate: &AppS,
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(
e: &gdk::Event, w: &gtk::Window,
appstate: &AppS) e: &gdk::Event,
-> gtk::Inhibit { appstate: &AppS,
) -> 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(
toggle_signal: u64, appstate: &AppS,
muted: Result<bool>) { toggle_signal: u64,
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(
GrabOwnership::None, &gdk_window,
true, GrabOwnership::None,
KEY_PRESS_MASK, true,
None, KEY_PRESS_MASK,
GDK_CURRENT_TIME as u32); None,
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

@ -7,21 +7,40 @@ use support_alsa::*;
create_builder_item!(PrefsDialog, // TODO: misbehavior when popup_window is open
prefs_dialog: gtk::Dialog,
card_combo: gtk::ComboBoxText,
chan_combo: gtk::ComboBoxText); pub struct PrefsDialog {
pub prefs_dialog: gtk::Dialog,
pub card_combo: gtk::ComboBoxText,
pub chan_combo: gtk::ComboBoxText,
pub response_callback: Box<Fn(PrefsDialog, i64)>,
}
impl PrefsDialog {
pub fn new(
builder: gtk::Builder,
response_callback: Box<Fn(PrefsDialog, i64)>,
) -> PrefsDialog {
return PrefsDialog {
prefs_dialog: builder.get_object("prefs_dialog").unwrap(),
card_combo: builder.get_object("card_combo").unwrap(),
chan_combo: builder.get_object("chan_combo").unwrap(),
response_callback,
};
}
}
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 prefs_dialog = Rc::new();
let prefs_dialog = Rc::new(PrefsDialog::new(builder_prefs_dialog));
init_prefs_dialog(&appstate, &prefs_dialog); init_prefs_dialog(&appstate, &prefs_dialog);
{ {
let prefs_dialog_w = &prefs_dialog.prefs_dialog; let prefs_dialog_w = &prefs_dialog.prefs_dialog;
prefs_dialog_w.set_transient_for(&appstate.gui.popup_menu.menu_window); prefs_dialog_w.set_transient_for(&appstate.gui.popup_menu.menu_window);
prefs_dialog_w.run(); prefs_dialog_w.present();
prefs_dialog_w.destroy(); // prefs_dialog_w.destroy();
} }
} }
@ -47,9 +66,9 @@ pub fn init_prefs_dialog(appstate: &Rc<AppS>, prefs_dialog: &Rc<PrefsDialog>) {
let pd = prefs_dialog.clone(); let pd = prefs_dialog.clone();
// TODO: refill channel combo // TODO: refill channel combo
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 */
{ {
@ -57,9 +76,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); },
}); );
} }
} }
@ -71,8 +90,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 = get_alsa_card_names(); let available_card_names = get_alsa_card_names();
/* set_active_id doesn't work, so save the index */ /* set_active_id doesn't work, so save the index */
@ -113,8 +132,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();
@ -122,9 +142,11 @@ 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), appstate.audio.switch_acard(
active_chan_item, Some(cur_card_name),
AudioUser::PrefsWindow); active_chan_item,
AudioUser::PrefsWindow,
);
} }
} }
@ -132,8 +154,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()
@ -144,8 +167,10 @@ 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, appstate.audio.switch_acard(
Some(active_chan_item), cur_card_name,
AudioUser::PrefsWindow); Some(active_chan_item),
AudioUser::PrefsWindow,
);
} }
} }

View File

@ -43,18 +43,19 @@ impl TrayIcon {
let status_icon = gtk::StatusIcon::new(); let status_icon = gtk::StatusIcon::new();
return Ok(TrayIcon { return Ok(TrayIcon {
volmeter, volmeter,
audio_pix: RefCell::new(audio_pix), audio_pix: RefCell::new(audio_pix),
status_icon, status_icon,
icon_size: Cell::new(ICON_MIN_SIZE), icon_size: Cell::new(ICON_MIN_SIZE),
}); });
} }
fn update(&self, fn update(
prefs: &Prefs, &self,
audio: &Audio, prefs: &Prefs,
m_size: Option<i32>) audio: &Audio,
-> Result<()> { m_size: Option<i32>,
) -> Result<()> {
match m_size { match m_size {
Some(s) => { Some(s) => {
if s < ICON_MIN_SIZE { if s < ICON_MIN_SIZE {
@ -104,30 +105,37 @@ pub struct VolMeter {
impl VolMeter { impl VolMeter {
pub fn new(prefs: &Prefs) -> VolMeter { pub fn new(prefs: &Prefs) -> VolMeter {
return VolMeter { return VolMeter {
red: prefs.view_prefs.vol_meter_color.red, red: prefs.view_prefs.vol_meter_color.red,
green: prefs.view_prefs.vol_meter_color.green, green: prefs.view_prefs.vol_meter_color.green,
blue: prefs.view_prefs.vol_meter_color.blue, blue: prefs.view_prefs.vol_meter_color.blue,
x_offset_pct: prefs.view_prefs.vol_meter_offset, x_offset_pct: prefs.view_prefs.vol_meter_offset,
y_offset_pct: 10, y_offset_pct: 10,
/* dynamic */ /* dynamic */
width: Cell::new(0), width: Cell::new(0),
row: RefCell::new(vec![]), row: RefCell::new(vec![]),
}; };
} }
// TODO: cache input pixbuf? // TODO: cache input pixbuf?
fn meter_draw(&self, fn meter_draw(
volume: i64, &self,
pixbuf: &gdk_pixbuf::Pixbuf) volume: i64,
-> Result<gdk_pixbuf::Pixbuf> { pixbuf: &gdk_pixbuf::Pixbuf,
) -> Result<gdk_pixbuf::Pixbuf> {
ensure!(pixbuf.get_colorspace() == GDK_COLORSPACE_RGB, ensure!(
"Invalid colorspace in pixbuf"); pixbuf.get_colorspace() == GDK_COLORSPACE_RGB,
ensure!(pixbuf.get_bits_per_sample() == 8, "Invalid colorspace in pixbuf"
"Invalid bits per sample 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_has_alpha(), "No alpha channel in pixbuf");
ensure!(pixbuf.get_n_channels() == 4, ensure!(
"Invalid number of channels in pixbuf"); pixbuf.get_n_channels() == 4,
"Invalid number of channels in pixbuf"
);
let i_width = pixbuf.get_width() as i64; let i_width = pixbuf.get_width() as i64;
let i_height = pixbuf.get_height() as i64; let i_height = pixbuf.get_height() as i64;
@ -136,16 +144,21 @@ impl VolMeter {
let vm_width = i_width / 6; let vm_width = i_width / 6;
let x = (self.x_offset_pct as f64 * let x = (self.x_offset_pct as f64 *
((i_width - vm_width) as f64 / 100.0)) as i64; ((i_width - vm_width) as f64 / 100.0)) as
ensure!(x >= 0 && (x + vm_width) <= i_width, i64;
"x coordinate invalid: {}", ensure!(
x); 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 y = (self.y_offset_pct as f64 * (i_height as f64 / 100.0)) as i64;
let vm_height = let vm_height =
((i_height - (y * 2)) as f64 * (volume as f64 / 100.0)) as i64; ((i_height - (y * 2)) as f64 * (volume as f64 / 100.0)) as i64;
ensure!(y >= 0 && (y + vm_height) <= i_height, ensure!(
"y coordinate invalid: {}", y >= 0 && (y + vm_height) <= i_height,
y); "y coordinate invalid: {}",
y
);
/* Let's check if the icon width changed, in which case we /* Let's check if the icon width changed, in which case we
* must reinit our internal row of pixels. * must reinit our internal row of pixels.
@ -181,8 +194,9 @@ impl VolMeter {
let p_index = ((row_offset * rowstride) + col_offset) as usize; let p_index = ((row_offset * rowstride) + col_offset) as usize;
let row = self.row.borrow(); let row = self.row.borrow();
pixels[p_index..p_index + row.len()] pixels[p_index..p_index + row.len()].copy_from_slice(
.copy_from_slice(row.as_ref()); row.as_ref(),
);
} }
} }
@ -211,28 +225,45 @@ impl AudioPix {
let pix = { let pix = {
if system_theme { if system_theme {
let theme: gtk::IconTheme = gtk::IconTheme::get_default().ok_or( let theme: gtk::IconTheme =
"Couldn't get default icon theme", gtk::IconTheme::get_default().ok_or(
)?; "Couldn't get default icon theme",
)?;
AudioPix { AudioPix {
muted: pixbuf_new_from_theme("audio-volume-muted", muted: pixbuf_new_from_theme(
size, &theme)?, "audio-volume-muted",
low: pixbuf_new_from_theme("audio-volume-low", size,
size, &theme)?, &theme,
medium: pixbuf_new_from_theme("audio-volume-medium", )?,
size, &theme)?, low: pixbuf_new_from_theme(
high: pixbuf_new_from_theme("audio-volume-high", "audio-volume-low",
size, &theme)?, size,
&theme,
)?,
medium: pixbuf_new_from_theme(
"audio-volume-medium",
size,
&theme,
)?,
high: pixbuf_new_from_theme(
"audio-volume-high",
size,
&theme,
)?,
/* 'audio-volume-off' is not available in every icon set. /* 'audio-volume-off' is not available in every icon set.
* Check freedesktop standard for more info: * Check freedesktop standard for more info:
* 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", off: pixbuf_new_from_theme(
size, &theme).or( "audio-volume-off",
pixbuf_new_from_theme("audio-volume-low", size,
size, &theme), &theme,
)?, ).or(pixbuf_new_from_theme(
"audio-volume-low",
size,
&theme,
))?,
} }
} else { } else {
AudioPix { AudioPix {
@ -273,7 +304,11 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
appstate.audio.connect_handler( appstate.audio.connect_handler(
Box::new(move |s, u| match (s, u) { Box::new(move |s, u| match (s, u) {
(AudioSignal::ValuesChanged, _) => { (AudioSignal::ValuesChanged, _) => {
try_w!(apps.gui.tray_icon.update(&apps.prefs.borrow_mut(), &apps.audio, None)); try_w!(apps.gui.tray_icon.update(
&apps.prefs.borrow_mut(),
&apps.audio,
None,
));
} }
_ => (), _ => (),
}), }),
@ -284,7 +319,14 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
{ {
let apps = appstate.clone(); let apps = appstate.clone();
tray_icon.status_icon.connect_size_changed(move |_, size| { tray_icon.status_icon.connect_size_changed(move |_, size| {
try_wr!(apps.gui.tray_icon.update(&apps.prefs.borrow_mut(), &apps.audio, Some(size)), false); try_wr!(
apps.gui.tray_icon.update(
&apps.prefs.borrow_mut(),
&apps.audio,
Some(size),
),
false
);
return false; return false;
}); });
} }
@ -292,9 +334,9 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
/* tray_icon.connect_activate */ /* tray_icon.connect_activate */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
tray_icon.status_icon.connect_activate(move |_| { tray_icon.status_icon.connect_activate(
on_tray_icon_activate(&apps) move |_| on_tray_icon_activate(&apps),
}); );
} }
/* tray_icon.connect_scroll_event */ /* tray_icon.connect_scroll_event */
@ -308,17 +350,17 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
/* tray_icon.connect_popup_menu */ /* tray_icon.connect_popup_menu */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
tray_icon.status_icon.connect_popup_menu( tray_icon.status_icon.connect_popup_menu(move |_, _, _| {
move |_, _, _| on_tray_icon_popup_menu(&apps), 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();
tray_icon.status_icon.connect_button_release_event( tray_icon.status_icon.connect_button_release_event(move |_, eb| {
move |_, eb| on_tray_button_release_event(&apps, eb), on_tray_button_release_event(&apps, eb)
); });
} }
} }
@ -343,9 +385,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 scroll_dir: gdk::ScrollDirection = event.get_direction(); let scroll_dir: gdk::ScrollDirection = event.get_direction();
match scroll_dir { match scroll_dir {
@ -362,9 +405,10 @@ fn on_tray_icon_scroll_event(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 {