2017-07-14 15:23:42 +00:00
|
|
|
//! The popup window subsystem when the user left-clicks on the tray icon.
|
|
|
|
//!
|
|
|
|
//! This shows the manipulatable volume slider with the current volume and
|
|
|
|
//! the mute checkbox.
|
|
|
|
|
|
|
|
|
2017-06-26 07:08:37 +00:00
|
|
|
use app_state::*;
|
2017-07-19 10:12:08 +00:00
|
|
|
use audio::frontend::*;
|
2017-06-26 07:08:37 +00:00
|
|
|
use errors::*;
|
|
|
|
use gdk::DeviceExt;
|
2018-01-25 21:40:13 +00:00
|
|
|
use gdk::{GrabOwnership, GrabStatus};
|
2017-06-26 07:08:37 +00:00
|
|
|
use gdk;
|
|
|
|
use gdk_sys::{GDK_KEY_Escape, GDK_CURRENT_TIME};
|
2018-01-25 21:40:13 +00:00
|
|
|
use glib::translate::*;
|
2017-06-26 07:08:37 +00:00
|
|
|
use glib;
|
|
|
|
use gtk::ToggleButtonExt;
|
|
|
|
use gtk::prelude::*;
|
|
|
|
use gtk;
|
|
|
|
use prefs::*;
|
|
|
|
use std::cell::Cell;
|
|
|
|
use std::rc::Rc;
|
2017-07-19 10:12:08 +00:00
|
|
|
use support::audio::*;
|
|
|
|
use support::cmd::*;
|
2017-06-26 07:08:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The main struct for the popup window, holding all relevant sub-widgets
|
|
|
|
/// and some mutable state.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub struct PopupWindow {
|
|
|
|
_cant_construct: (),
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The main window for the popup window widget.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub popup_window: gtk::Window,
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The volume scale adjustment.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub vol_scale_adj: gtk::Adjustment,
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The volume scale.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub vol_scale: gtk::Scale,
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The mute check button.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub mute_check: gtk::CheckButton,
|
2017-07-14 15:23:42 +00:00
|
|
|
/// The button to start the external mixer.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub mixer_button: gtk::Button,
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Signal for mute_check.connect_toggled callback,
|
|
|
|
/// so we can block it temporarily.
|
|
|
|
toggle_signal: Cell<u64>,
|
|
|
|
/// Signal for vol_scale_adj.connect_value_changed callback,
|
|
|
|
/// so we can block it temporarily.
|
|
|
|
changed_signal: Cell<u64>,
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PopupWindow {
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Constructor.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub fn new(builder: gtk::Builder) -> PopupWindow {
|
|
|
|
return PopupWindow {
|
2017-07-19 10:12:08 +00:00
|
|
|
_cant_construct: (),
|
|
|
|
popup_window: builder.get_object("popup_window").unwrap(),
|
|
|
|
vol_scale_adj: builder.get_object("vol_scale_adj").unwrap(),
|
|
|
|
vol_scale: builder.get_object("vol_scale").unwrap(),
|
|
|
|
mute_check: builder.get_object("mute_check").unwrap(),
|
|
|
|
mixer_button: builder.get_object("mixer_button").unwrap(),
|
|
|
|
toggle_signal: Cell::new(0),
|
|
|
|
changed_signal: Cell::new(0),
|
|
|
|
};
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Update the popup window state, including the slider
|
|
|
|
/// and the mute checkbutton.
|
2017-07-18 15:20:17 +00:00
|
|
|
pub fn update<T>(&self, audio: &T) -> Result<()>
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
|
|
|
let cur_vol = audio.get_vol()?;
|
2017-06-26 07:08:37 +00:00
|
|
|
set_slider(&self.vol_scale_adj, cur_vol);
|
|
|
|
|
2017-07-18 15:20:17 +00:00
|
|
|
self.update_mute_check(audio);
|
2017-06-26 07:08:37 +00:00
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Update the mute checkbutton.
|
2017-07-18 15:20:17 +00:00
|
|
|
pub fn update_mute_check<T>(&self, audio: &T)
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
2017-06-26 07:08:37 +00:00
|
|
|
let m_muted = audio.get_mute();
|
|
|
|
|
2018-01-25 21:40:13 +00:00
|
|
|
glib::signal_handler_block(&self.mute_check,
|
|
|
|
&from_glib(self.toggle_signal.get()));
|
2017-06-26 07:08:37 +00:00
|
|
|
|
|
|
|
match m_muted {
|
|
|
|
Ok(val) => {
|
|
|
|
self.mute_check.set_sensitive(true);
|
|
|
|
self.mute_check.set_active(val);
|
|
|
|
self.mute_check.set_tooltip_text("");
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
/* can't figure out whether channel is muted, grey out */
|
|
|
|
self.mute_check.set_active(true);
|
|
|
|
self.mute_check.set_sensitive(false);
|
|
|
|
self.mute_check.set_tooltip_text(
|
|
|
|
"Soundcard has no mute switch",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-19 10:12:08 +00:00
|
|
|
glib::signal_handler_unblock(
|
|
|
|
&self.mute_check,
|
2018-01-25 21:40:13 +00:00
|
|
|
&from_glib(self.toggle_signal.get()),
|
2017-07-19 10:12:08 +00:00
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Set the page increment fro the volume scale adjustment based on the
|
|
|
|
/// preferences.
|
2017-06-26 07:08:37 +00:00
|
|
|
fn set_vol_increment(&self, prefs: &Prefs) {
|
2017-07-19 10:12:08 +00:00
|
|
|
self.vol_scale_adj.set_page_increment(
|
|
|
|
prefs.behavior_prefs.vol_scroll_step,
|
|
|
|
);
|
|
|
|
self.vol_scale_adj.set_step_increment(
|
|
|
|
prefs.behavior_prefs.vol_fine_scroll_step,
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Initialize the popup window subsystem.
|
2017-07-18 15:20:17 +00:00
|
|
|
pub fn init_popup_window<T>(appstate: Rc<AppS<T>>)
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend + 'static,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
2017-06-26 07:08:37 +00:00
|
|
|
/* audio.connect_handler */
|
|
|
|
{
|
|
|
|
let apps = appstate.clone();
|
|
|
|
appstate.audio.connect_handler(Box::new(move |s, u| {
|
|
|
|
/* skip if window is hidden */
|
2017-07-19 10:12:08 +00:00
|
|
|
if !apps.gui.popup_window.popup_window.get_visible() {
|
2017-06-26 07:08:37 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
match (s, u) {
|
|
|
|
/* Update only mute check here
|
|
|
|
* If the user changes the volume through the popup window,
|
|
|
|
* we MUST NOT update the slider value, it's been done already.
|
|
|
|
* It means that, as long as the popup window is visible,
|
|
|
|
* the slider value reflects the value set by user,
|
|
|
|
* and not the real value reported by the audio system.
|
|
|
|
*/
|
|
|
|
(_, AudioUser::Popup) => {
|
2017-07-19 10:12:08 +00:00
|
|
|
apps.gui.popup_window.update_mute_check(
|
|
|
|
apps.audio.as_ref(),
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
/* external change, safe to update slider too */
|
|
|
|
(_, _) => {
|
2017-07-18 15:20:17 +00:00
|
|
|
try_w!(apps.gui.popup_window.update(apps.audio.as_ref()));
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mute_check.connect_toggled */
|
|
|
|
{
|
|
|
|
let _appstate = appstate.clone();
|
2017-07-19 10:12:08 +00:00
|
|
|
let mute_check = &appstate.clone().gui.popup_window.mute_check;
|
|
|
|
let toggle_signal = mute_check.connect_toggled(move |_| {
|
|
|
|
on_mute_check_toggled(&_appstate)
|
|
|
|
});
|
2018-01-25 21:40:13 +00:00
|
|
|
appstate.gui.popup_window.toggle_signal.set(toggle_signal.to_glib());
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* popup_window.connect_show */
|
|
|
|
{
|
|
|
|
let _appstate = appstate.clone();
|
2017-07-19 10:12:08 +00:00
|
|
|
let popup_window = &appstate.clone().gui.popup_window.popup_window;
|
2017-06-26 07:08:37 +00:00
|
|
|
popup_window.connect_show(move |_| on_popup_window_show(&_appstate));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vol_scale_adj.connect_value_changed */
|
|
|
|
{
|
|
|
|
let _appstate = appstate.clone();
|
2017-07-19 10:12:08 +00:00
|
|
|
let vol_scale_adj = &appstate.clone().gui.popup_window.vol_scale_adj;
|
|
|
|
let changed_signal = vol_scale_adj.connect_value_changed(move |_| {
|
|
|
|
on_vol_scale_value_changed(&_appstate)
|
|
|
|
});
|
2017-06-26 07:08:37 +00:00
|
|
|
|
2018-01-25 21:40:13 +00:00
|
|
|
appstate.gui.popup_window.changed_signal.set(changed_signal.to_glib());
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* popup_window.connect_event */
|
|
|
|
{
|
2017-07-19 10:12:08 +00:00
|
|
|
let popup_window = &appstate.clone().gui.popup_window.popup_window;
|
2017-06-26 07:08:37 +00:00
|
|
|
popup_window.connect_event(move |w, e| on_popup_window_event(w, e));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mixer_button.connect_clicked */
|
|
|
|
{
|
|
|
|
let apps = appstate.clone();
|
2017-07-19 10:12:08 +00:00
|
|
|
let mixer_button = &appstate.clone().gui.popup_window.mixer_button;
|
2017-06-26 07:08:37 +00:00
|
|
|
mixer_button.connect_clicked(move |_| {
|
2017-07-19 10:12:08 +00:00
|
|
|
apps.gui.popup_window.popup_window.hide();
|
|
|
|
let _ = result_warn!(
|
|
|
|
execute_vol_control_command(&apps.prefs.borrow()),
|
|
|
|
Some(&apps.gui.popup_menu.menu_window)
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// When the popup window is shown.
|
2017-07-18 15:20:17 +00:00
|
|
|
fn on_popup_window_show<T>(appstate: &AppS<T>)
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
2017-06-26 07:08:37 +00:00
|
|
|
let popup_window = &appstate.gui.popup_window;
|
2017-07-19 10:12:08 +00:00
|
|
|
appstate.gui.popup_window.set_vol_increment(
|
|
|
|
&appstate.prefs.borrow(),
|
|
|
|
);
|
|
|
|
glib::signal_handler_block(
|
|
|
|
&popup_window.vol_scale_adj,
|
2018-01-25 21:40:13 +00:00
|
|
|
&from_glib(popup_window.changed_signal.get()),
|
2017-07-19 10:12:08 +00:00
|
|
|
);
|
2017-07-18 15:20:17 +00:00
|
|
|
try_w!(appstate.gui.popup_window.update(appstate.audio.as_ref()));
|
2017-07-19 10:12:08 +00:00
|
|
|
glib::signal_handler_unblock(
|
|
|
|
&popup_window.vol_scale_adj,
|
2018-01-25 21:40:13 +00:00
|
|
|
&from_glib(popup_window.changed_signal.get()),
|
2017-07-19 10:12:08 +00:00
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
popup_window.vol_scale.grab_focus();
|
|
|
|
try_w!(grab_devices(&appstate.gui.popup_window.popup_window));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// On key or button press event on the popup window.
|
2017-06-26 07:08:37 +00:00
|
|
|
fn on_popup_window_event(w: >k::Window, e: &gdk::Event) -> gtk::Inhibit {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// When the volume scale slider is moved.
|
2017-07-18 15:20:17 +00:00
|
|
|
fn on_vol_scale_value_changed<T>(appstate: &AppS<T>)
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
2017-06-26 07:08:37 +00:00
|
|
|
let audio = &appstate.audio;
|
2017-07-18 15:20:17 +00:00
|
|
|
let old_vol = try_w!(audio.get_vol());
|
2017-06-26 07:08:37 +00:00
|
|
|
|
2017-07-19 10:12:08 +00:00
|
|
|
let val = appstate.gui.popup_window.vol_scale.get_value();
|
2017-06-26 07:08:37 +00:00
|
|
|
|
|
|
|
let dir = vol_change_to_voldir(old_vol, val);
|
|
|
|
|
2017-07-19 10:12:08 +00:00
|
|
|
try_w!(audio.set_vol(
|
|
|
|
val,
|
|
|
|
AudioUser::Popup,
|
|
|
|
dir,
|
|
|
|
appstate.prefs.borrow().behavior_prefs.unmute_on_vol_change,
|
|
|
|
));
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// When the mute checkbutton is toggled.
|
2017-07-18 15:20:17 +00:00
|
|
|
fn on_mute_check_toggled<T>(appstate: &AppS<T>)
|
2017-07-19 10:12:08 +00:00
|
|
|
where
|
|
|
|
T: AudioFrontend,
|
2017-07-18 15:20:17 +00:00
|
|
|
{
|
2017-06-26 07:08:37 +00:00
|
|
|
let audio = &appstate.audio;
|
|
|
|
try_w!(audio.toggle_mute(AudioUser::Popup))
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Set the volume slider to the given value.
|
2017-06-26 07:08:37 +00:00
|
|
|
pub fn set_slider(vol_scale_adj: >k::Adjustment, scale: f64) {
|
|
|
|
vol_scale_adj.set_value(scale);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-14 15:23:42 +00:00
|
|
|
/// Grab all devices, keyboard and mouse.
|
2017-06-26 07:08:37 +00:00
|
|
|
fn grab_devices(window: >k::Window) -> Result<()> {
|
|
|
|
let device = gtk::get_current_event_device().ok_or("No current device")?;
|
|
|
|
|
|
|
|
let gdk_window = window.get_window().ok_or("No window?!")?;
|
|
|
|
|
|
|
|
/* Grab the mouse */
|
2017-07-19 10:12:08 +00:00
|
|
|
let m_grab_status = device.grab(
|
|
|
|
&gdk_window,
|
|
|
|
GrabOwnership::None,
|
|
|
|
true,
|
2018-01-25 21:40:13 +00:00
|
|
|
gdk::EventMask::BUTTON_PRESS_MASK,
|
2017-07-19 10:12:08 +00:00
|
|
|
None,
|
|
|
|
GDK_CURRENT_TIME as u32,
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
|
|
|
|
if m_grab_status != GrabStatus::Success {
|
2017-07-19 10:12:08 +00:00
|
|
|
warn!(
|
|
|
|
"Could not grab {}",
|
|
|
|
device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Grab the keyboard */
|
2017-07-19 10:12:08 +00:00
|
|
|
let k_dev = device.get_associated_device().ok_or(
|
|
|
|
"Couldn't get associated device",
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let k_grab_status = k_dev.grab(
|
|
|
|
&gdk_window,
|
|
|
|
GrabOwnership::None,
|
|
|
|
true,
|
2018-01-25 21:40:13 +00:00
|
|
|
gdk::EventMask::KEY_PRESS_MASK,
|
2017-07-19 10:12:08 +00:00
|
|
|
None,
|
|
|
|
GDK_CURRENT_TIME as u32,
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
if k_grab_status != GrabStatus::Success {
|
2017-07-19 10:12:08 +00:00
|
|
|
warn!(
|
|
|
|
"Could not grab {}",
|
|
|
|
k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))
|
|
|
|
);
|
2017-06-26 07:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|