From 45cdc5f9ca0e59ace23429a800a7f70df65059cb Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sun, 9 Jul 2017 00:14:49 +0200 Subject: [PATCH] Update --- Cargo.toml | 23 ++++---- Makefile | 58 +++++++++++++++++++ .../{pnmixer.desktop.in => pnmixer.desktop} | 2 +- data/ui/hotkey-dialog.glade | 2 - data/ui/popup-menu.glade | 6 -- data/ui/popup-window.glade | 6 -- data/ui/prefs-dialog.glade | 39 ++++++++++--- src/alsa_card.rs | 17 ++++-- src/app_state.rs | 14 ++--- src/audio.rs | 52 ++++++++++------- src/main.rs | 1 + src/prefs.rs | 2 + src/support_ui.rs | 32 ++++++---- src/ui_entry.rs | 57 +++++++++++++++--- src/ui_popup_menu.rs | 4 +- src/ui_popup_window.rs | 10 +++- src/ui_prefs_dialog.rs | 12 +++- src/ui_tray_icon.rs | 44 ++++++++++++-- 18 files changed, 288 insertions(+), 93 deletions(-) create mode 100644 Makefile rename data/desktop/{pnmixer.desktop.in => pnmixer.desktop} (92%) diff --git a/Cargo.toml b/Cargo.toml index 149ead04c..fba25ec7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,23 +6,24 @@ authors = ["Julian Ospald "] [dependencies] alsa = "^0.1.8" alsa-sys = "^0.1.1" -libc = "^0.2.23" -gdk-sys = { git = "https://github.com/gtk-rs/sys" } +error-chain = { path = "3rdparty/error-chain" } +ffi = "^0.0.2" +flexi_logger = "^0.5.1" 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" } +gdk-sys = { git = "https://github.com/gtk-rs/sys" } +gio = { git = "https://github.com/gtk-rs/gio.git" } glib = { git = "https://github.com/gtk-rs/glib.git" } glib-sys = { git = "https://github.com/gtk-rs/sys" } gobject-sys = { git = "https://github.com/gtk-rs/sys" } -ffi = "^0.0.2" -flexi_logger = "^0.5.1" +gtk-sys = { git = "https://github.com/gtk-rs/sys" } +libc = "^0.2.23" log = "^0.3.8" -error-chain = { path = "3rdparty/error-chain" } -toml = "^0.4.2" -serde_derive = "^1.0.9" serde = "^1.0.9" -xdg = "*" +serde_derive = "^1.0.9" +toml = "^0.4.2" which = "*" +xdg = "*" [dependencies.gtk] git = "https://github.com/gtk-rs/gtk.git" @@ -33,12 +34,12 @@ git = "https://github.com/gtk-rs/gdk.git" features = [ "v3_10", "v3_12", "v3_22" ] [profile.dev] -opt-level = 2 # controls the `--opt-level` the compiler builds with +opt-level = 0 # controls the `--opt-level` the compiler builds with debug = true # controls whether the compiler passes `-C debuginfo` # a value of `true` is equivalent to `2` rpath = false # controls whether the compiler passes `-C rpath` lto = false # controls `-C lto` for binaries and staticlibs -debug-assertions = true # controls whether debug assertions are enabled +debug-assertions = false # controls whether debug assertions are enabled codegen-units = 1 # controls whether the compiler passes `-C codegen-units` # `codegen-units` is ignored when `lto = true` panic = 'unwind' # panic strategy (`-C panic=...`), can also be 'abort' diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..5265a99a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +INSTALL = install +INSTALL_DIR = $(INSTALL) -d +INSTALL_BIN = $(INSTALL) -m 755 +INSTALL_DATA = $(INSTALL) -m 644 + + +PREFIX=/usr/local +BINDIR=$(PREFIX)/bin +SHAREDIR=$(PREFIX)/share +DATADIR=$(SHAREDIR)/pnmixer +PIXMAPSDIR=$(DATADIR)/pixmaps +ICONSDIR=$(SHAREDIR)/icons/hicolor/128x128/apps +DESKTOPDIR=$(SHAREDIR)/applications + + +CARGO ?= cargo +CARGO_ARGS ?= +CARGO_BUILD_ARGS ?= --release +CARGO_BUILD ?= $(CARGO) $(CARGO_ARGS) build $(CARGO_BUILD_ARGS) +CARGO_INSTALL_ARGS ?= --root="$(DESTDIR)/$(PREFIX)" +CARGO_INSTALL ?= $(CARGO) $(CARGO_ARGS) install $(CARGO_INSTALL_ARGS) + + + +pnmixer-rs: Cargo.toml + PIXMAPSDIR=$(PIXMAPSDIR) $(CARGO_BUILD) + + +install: install-data + $(INSTALL_DIR) "$(DESTDIR)/$(BINDIR)" + $(INSTALL_BIN) target/release/pnmixer "$(DESTDIR)/$(BINDIR)/pnmixer" + + +install-data: install-pixmaps install-icons install-desktop + + +install-pixmaps: + $(INSTALL_DIR) "$(DESTDIR)/$(PIXMAPSDIR)" + $(INSTALL_DATA) data/pixmaps/pnmixer-about.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-about.png" + $(INSTALL_DATA) data/pixmaps/pnmixer-high.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-high.png" + $(INSTALL_DATA) data/pixmaps/pnmixer-low.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-low.png" + $(INSTALL_DATA) data/pixmaps/pnmixer-medium.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-medium.png" + $(INSTALL_DATA) data/pixmaps/pnmixer-muted.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-muted.png" + $(INSTALL_DATA) data/pixmaps/pnmixer-off.png "$(DESTDIR)/$(PIXMAPSDIR)/pnmixer-off.png" + + +install-icons: + $(INSTALL_DIR) "$(DESTDIR)/$(ICONSDIR)" + $(INSTALL_DATA) data/icons/pnmixer.png "$(DESTDIR)/$(ICONSDIR)/pnmixer.png" + + +install-desktop: + $(INSTALL_DIR) "$(DESTDIR)/$(DESKTOPDIR)" + $(INSTALL_DATA) data/desktop/pnmixer.desktop "$(DESTDIR)/$(DESKTOPDIR)/pnmixer.desktop" + + + +.PHONY: pnmixer-rs install install-data install-pixmaps install-icons install-desktop diff --git a/data/desktop/pnmixer.desktop.in b/data/desktop/pnmixer.desktop similarity index 92% rename from data/desktop/pnmixer.desktop.in rename to data/desktop/pnmixer.desktop index 830d1d131..a97748e8c 100644 --- a/data/desktop/pnmixer.desktop.in +++ b/data/desktop/pnmixer.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Name=PNMixer +Name=PNMixer-rs _GenericName=System Tray Mixer _Comment=An audio mixer for the system tray Exec=pnmixer diff --git a/data/ui/hotkey-dialog.glade b/data/ui/hotkey-dialog.glade index 01ab71204..a8a6d312e 100644 --- a/data/ui/hotkey-dialog.glade +++ b/data/ui/hotkey-dialog.glade @@ -12,8 +12,6 @@ True input-keyboard dialog - - True diff --git a/data/ui/popup-menu.glade b/data/ui/popup-menu.glade index b98e43b17..b3d9b469d 100644 --- a/data/ui/popup-menu.glade +++ b/data/ui/popup-menu.glade @@ -31,7 +31,6 @@ True False Mute/Unmute Volume - True @@ -71,7 +70,6 @@ True False Open Volume Control - True @@ -108,7 +106,6 @@ True False Preferences - True @@ -146,7 +143,6 @@ True False Reload Sound - True @@ -184,7 +180,6 @@ True False About - True @@ -229,7 +224,6 @@ True False Quit - True diff --git a/data/ui/popup-window.glade b/data/ui/popup-window.glade index 1271dabdc..0b4f18b20 100644 --- a/data/ui/popup-window.glade +++ b/data/ui/popup-window.glade @@ -16,9 +16,6 @@ True True False - - - True @@ -36,7 +33,6 @@ 0 0 200 - True @@ -58,7 +54,6 @@ True False True - False @@ -73,7 +68,6 @@ True True True - False diff --git a/data/ui/prefs-dialog.glade b/data/ui/prefs-dialog.glade index 3b4292ca0..4b1dd0182 100644 --- a/data/ui/prefs-dialog.glade +++ b/data/ui/prefs-dialog.glade @@ -69,7 +69,6 @@ False start True - 0 @@ -233,7 +232,7 @@ False - PNMixer Preferences + PNMixer-rs Preferences False preferences-system dialog @@ -447,7 +446,6 @@ True False - 1 @@ -581,7 +579,6 @@ False start True - 0 @@ -744,6 +741,35 @@ 0 + + + True + False + start + Fine Scroll Step: + + + 0 + 1 + + + + + True + True + + 0,00 + False + False + fine_scroll_step_adjustment + 2 + True + + + 1 + 1 + + @@ -805,7 +831,6 @@ True False - 1 @@ -891,7 +916,6 @@ False start True - False @@ -976,7 +1000,6 @@ True False - True @@ -997,7 +1020,6 @@ True False - True @@ -1018,7 +1040,6 @@ True False - True diff --git a/src/alsa_card.rs b/src/alsa_card.rs index 9fd3963a9..457944d68 100644 --- a/src/alsa_card.rs +++ b/src/alsa_card.rs @@ -233,6 +233,7 @@ extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, let acard = unsafe { mem::transmute::(data) }; + let cb = &acard.cb; unsafe { let mixer_ptr = @@ -261,15 +262,21 @@ extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, glib_sys::G_IO_STATUS_AGAIN => { debug!("G_IO_STATUS_AGAIN"); continue; - } + }, // TODO: handle these failure cases - glib_sys::G_IO_STATUS_NORMAL => debug!("G_IO_STATUS_NORMAL"), - glib_sys::G_IO_STATUS_ERROR => debug!("G_IO_STATUS_ERROR"), - glib_sys::G_IO_STATUS_EOF => debug!("G_IO_STATUS_EOF"), + glib_sys::G_IO_STATUS_NORMAL => { + error!("Alsa failed to clear the channel"); + cb(AlsaEvent::AlsaCardError); + }, + glib_sys::G_IO_STATUS_ERROR => (), + glib_sys::G_IO_STATUS_EOF => { + error!("GIO error has occurred"); + cb(AlsaEvent::AlsaCardError); + }, + _ => warn!("Unknown status from g_io_channel_read_chars()"), } return true as glib_sys::gboolean; } - let cb = &acard.cb; cb(AlsaEvent::AlsaCardValuesChanged); return true as glib_sys::gboolean; diff --git a/src/app_state.rs b/src/app_state.rs index 13f829d56..38d081272 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -5,6 +5,7 @@ use prefs::*; use std::cell::RefCell; use ui_entry::Gui; use ui_prefs_dialog::show_prefs_dialog; +use notif::*; // TODO: notify popups @@ -17,6 +18,7 @@ pub struct AppS { _cant_construct: (), pub gui: Gui, pub audio: Audio, + pub notif: Notif, pub prefs: RefCell, } @@ -42,11 +44,14 @@ impl AppS { .channel .clone(); + let notif = Notif::new(&prefs.borrow()); + return AppS { _cant_construct: (), - gui: gui, + gui, audio: Audio::new(Some(card_name), Some(chan_name)).unwrap(), - prefs: prefs, + notif, + prefs, }; } @@ -64,9 +69,4 @@ impl AppS { debug!("Update PopupWindow!"); return self.gui.popup_window.update(&self.audio); } - - // TODO - pub fn show_preferences(&self) { - // show_prefs_dialog(self); - } } diff --git a/src/audio.rs b/src/audio.rs index d2c992d4c..feb508600 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -90,9 +90,22 @@ impl Audio { }) }; + let acard = AlsaCard::new(card_name, elem_name, cb); + + /* additionally dispatch signals */ + if acard.is_err() { + invoke_handlers(&handlers.borrow(), + AudioSignal::NoCard, + AudioUser::Unknown); + } else { + invoke_handlers(&handlers.borrow(), + AudioSignal::CardInitialized, + AudioUser::Unknown); + } + let audio = Audio { _cannot_construct: (), - acard: RefCell::new(AlsaCard::new(card_name, elem_name, cb)?), + acard: RefCell::new(acard?), last_action_timestamp: last_action_timestamp.clone(), handlers: handlers.clone(), scroll_step: Cell::new(5), @@ -126,17 +139,10 @@ impl Audio { let mut ac = self.acard.borrow_mut(); *ac = AlsaCard::new(card_name, elem_name, cb)?; } - debug!("Old card name: {}", - self.acard - .borrow() - .card_name() - .unwrap()); - debug!("Old chan name: {}", - self.acard - .borrow() - .chan_name() - .unwrap()); + // invoke_handlers(&self.handlers.borrow(), + // AudioSignal::CardCleanedUp, + // user); invoke_handlers(&self.handlers.borrow(), AudioSignal::CardInitialized, user); @@ -216,9 +222,6 @@ impl Audio { self.set_vol(new_vol, user)?; - invoke_handlers(&self.handlers.borrow(), - AudioSignal::ValuesChanged, - user); return Ok(()); } @@ -245,9 +248,6 @@ impl Audio { self.set_vol(new_vol, user)?; - invoke_handlers(&self.handlers.borrow(), - AudioSignal::ValuesChanged, - user); return Ok(()); } @@ -307,6 +307,11 @@ fn invoke_handlers(handlers: &Vec>, debug!("Invoking handlers for signal {:?} by user {:?}", signal, user); + if handlers.is_empty() { + debug!("No handler found"); + } else { + debug!("Executing handlers") + } for handler in handlers { let unboxed = handler.as_ref(); unboxed(signal, user); @@ -332,10 +337,17 @@ fn on_alsa_event(last_action_timestamp: &mut i64, /* external change */ match alsa_event { // TODO: invoke handlers with AudioUserUnknown - AlsaEvent::AlsaCardError => debug!("AlsaCardError"), - AlsaEvent::AlsaCardDiconnected => debug!("AlsaCardDiconnected"), + AlsaEvent::AlsaCardError => { + invoke_handlers(handlers, + self::AudioSignal::CardError, + self::AudioUser::Unknown); + }, + AlsaEvent::AlsaCardDiconnected => { + invoke_handlers(handlers, + self::AudioSignal::CardDisconnected, + self::AudioUser::Unknown); + }, AlsaEvent::AlsaCardValuesChanged => { - debug!("AlsaCardValuesChanged"); invoke_handlers(handlers, self::AudioSignal::ValuesChanged, self::AudioUser::Unknown); diff --git a/src/main.rs b/src/main.rs index 3b40eb5cb..daeaab569 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ extern crate gdk; extern crate gdk_pixbuf; extern crate gdk_pixbuf_sys; extern crate gdk_sys; +extern crate gio; extern crate glib; extern crate glib_sys; extern crate gobject_sys; diff --git a/src/prefs.rs b/src/prefs.rs index 13d81940c..0fc764fa2 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -122,6 +122,7 @@ impl Default for VolColor { pub struct BehaviorPrefs { pub vol_control_cmd: Option, pub vol_scroll_step: f64, + pub vol_fine_scroll_step: f64, pub middle_click_action: MiddleClickAction, pub custom_command: Option, // TODO: fine scroll step? } @@ -131,6 +132,7 @@ impl Default for BehaviorPrefs { return BehaviorPrefs { vol_control_cmd: None, vol_scroll_step: 5.0, + vol_fine_scroll_step: 1.0, middle_click_action: MiddleClickAction::default(), custom_command: None, }; diff --git a/src/support_ui.rs b/src/support_ui.rs index 586ce76d3..7e5441c10 100644 --- a/src/support_ui.rs +++ b/src/support_ui.rs @@ -43,16 +43,28 @@ pub fn pixbuf_new_from_theme(icon_name: &str, pub fn pixbuf_new_from_file(filename: &str) -> Result { ensure!(!filename.is_empty(), "Filename is empty"); + let mut syspath = String::new(); + let sysdir = option_env!("PIXMAPSDIR").map(|s|{ + syspath = format!("{}/{}", s, filename); + Path::new(syspath.as_str()) + }); + let cargopath = format!("./data/pixmaps/{}", + filename); + let cargodir = Path::new(cargopath.as_str()); - let s = format!("{}/data/pixmaps/{}", env!("CARGO_MANIFEST_DIR"), filename); - let path = Path::new(s.as_str()); + // prefer local dir + let final_dir = { + if cargodir.exists() { + cargodir + } else if sysdir.is_some() && sysdir.unwrap().exists() { + sysdir.unwrap() + } else { + bail!("No valid path found") + } + }; - if path.exists() { - let str_path = path.to_str().ok_or("Path is not valid unicode")?; - - // TODO: propagate error - return Ok(gdk_pixbuf::Pixbuf::new_from_file(str_path).unwrap()); - } else { - bail!("Uh-oh"); - } + let str_path = final_dir.to_str().ok_or("Path is not valid unicode")?; + debug!("Loading icon from {}", str_path); + // TODO: propagate error + return Ok(gdk_pixbuf::Pixbuf::new_from_file(str_path).unwrap()); } diff --git a/src/ui_entry.rs b/src/ui_entry.rs index e5e475cfe..9d423abe8 100644 --- a/src/ui_entry.rs +++ b/src/ui_entry.rs @@ -1,13 +1,20 @@ use app_state::*; +use audio::{AudioUser, AudioSignal}; +use gtk::DialogExt; +use gtk::MessageDialogExt; +use gtk::WidgetExt; +use gtk::WindowExt; use gtk; +use gtk_sys::{GTK_DIALOG_DESTROY_WITH_PARENT, GTK_RESPONSE_YES}; +use notif::*; use prefs::*; use std::cell::RefCell; use std::rc::Rc; +use support_audio::*; use ui_popup_menu::*; use ui_popup_window::*; use ui_prefs_dialog::*; use ui_tray_icon::*; -use audio::{AudioUser, AudioSignal}; @@ -39,16 +46,25 @@ impl Gui { pub fn init(appstate: Rc) { { + /* "global" audio signal handler */ let apps = appstate.clone(); appstate.audio.connect_handler( Box::new(move |s, u| match (s, u) { - (AudioSignal::ValuesChanged, AudioUser::Unknown) => { - debug!("Appstate references: {}!", Rc::strong_count(&apps)); - - } - _ => debug!("Nix"), - }), - ); + (AudioSignal::CardDisconnected, _) => { + try_w!(audio_reload(&apps.audio, + &apps.prefs.borrow(), + AudioUser::Unknown)); + }, + (AudioSignal::CardError, _) => { + if run_audio_error_dialog(&apps.gui.popup_menu.menu_window) == (GTK_RESPONSE_YES as i32) { + try_w!(audio_reload(&apps.audio, + &apps.prefs.borrow(), + AudioUser::Unknown)); + } + }, + _ => (), + } + )); } @@ -57,3 +73,28 @@ pub fn init(appstate: Rc) { init_popup_menu(appstate.clone()); init_prefs_callback(appstate.clone()); } + + +fn run_audio_error_dialog(parent: >k::Window) -> i32 { + error!("Connection with audio failed, you probably need to restart pnmixer."); + + let dialog = gtk::MessageDialog::new( + Some(parent), + gtk::DIALOG_DESTROY_WITH_PARENT, + gtk::MessageType::Error, + gtk::ButtonsType::YesNo, + "Warning: Connection to sound system failed." + ); + dialog.set_property_secondary_text(Some("Do you want to re-initialize the audio connection ? + +If you do not, you will either need to restart PNMixer +or select the 'Reload Audio' option in the right-click +menu in order for PNMixer to function.")); + + dialog.set_title("PNMixer-rs Error"); + + let resp = dialog.run(); + dialog.destroy(); + + return resp; +} diff --git a/src/ui_popup_menu.rs b/src/ui_popup_menu.rs index 8dea6e062..f906e7da5 100644 --- a/src/ui_popup_menu.rs +++ b/src/ui_popup_menu.rs @@ -127,7 +127,7 @@ fn create_about_dialog() -> gtk::AboutDialog { 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 + "PNMixer-rs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation. @@ -143,7 +143,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.", about_dialog.set_copyright(Some("Copyright © 2017 Julian Ospald")); about_dialog.set_authors(&["Julian Ospald"]); about_dialog.set_artists(&["Paul Davey"]); - about_dialog.set_program_name("pnmixer-rs"); + about_dialog.set_program_name("PNMixer-rs"); about_dialog.set_logo_icon_name("pnmixer"); about_dialog.set_version(VERSION); about_dialog.set_website("https://github.com/hasufell/pnmixer-rust"); diff --git a/src/ui_popup_window.rs b/src/ui_popup_window.rs index dd2c216c7..0aab8743a 100644 --- a/src/ui_popup_window.rs +++ b/src/ui_popup_window.rs @@ -9,6 +9,7 @@ use glib; use gtk::ToggleButtonExt; use gtk::prelude::*; use gtk; +use prefs::*; use std::cell::Cell; use std::rc::Rc; use support_cmd::*; @@ -71,6 +72,13 @@ impl PopupWindow { glib::signal_handler_unblock(&self.mute_check, self.toggle_signal.get()); } + + fn set_vol_increment(&self, prefs: &Prefs) { + 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); + } } @@ -104,7 +112,6 @@ pub fn init_popup_window(appstate: Rc) { } } })); - } /* mute_check.connect_toggled */ @@ -174,6 +181,7 @@ pub fn init_popup_window(appstate: Rc) { fn on_popup_window_show(appstate: &AppS) { + appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow()); try_w!(appstate.gui.popup_window.update(&appstate.audio)); appstate.gui .popup_window diff --git a/src/ui_prefs_dialog.rs b/src/ui_prefs_dialog.rs index 0173b63c0..150b9bfe8 100644 --- a/src/ui_prefs_dialog.rs +++ b/src/ui_prefs_dialog.rs @@ -6,7 +6,6 @@ use gtk::ResponseType; use gtk::prelude::*; use gtk; use prefs::*; -use std::mem; use std::rc::Rc; use support_alsa::*; use support_audio::*; @@ -34,6 +33,7 @@ pub struct PrefsDialog { /* BehaviorPrefs */ vol_control_entry: gtk::Entry, scroll_step_spin: gtk::SpinButton, + fine_scroll_step_spin: gtk::SpinButton, middle_click_combo: gtk::ComboBoxText, custom_entry: gtk::Entry, @@ -68,6 +68,8 @@ impl PrefsDialog { vol_control_entry: builder.get_object("vol_control_entry").unwrap(), scroll_step_spin: builder.get_object("scroll_step_spin").unwrap(), + fine_scroll_step_spin: builder.get_object("fine_scroll_step_spin") + .unwrap(), middle_click_combo: builder.get_object("middle_click_combo") .unwrap(), custom_entry: builder.get_object("custom_entry").unwrap(), @@ -113,6 +115,8 @@ impl PrefsDialog { .unwrap_or(&String::from("")) .as_str()); self.scroll_step_spin.set_value(prefs.behavior_prefs.vol_scroll_step); + self.fine_scroll_step_spin + .set_value(prefs.behavior_prefs.vol_fine_scroll_step); // TODO: make sure these values always match, must be a better way // also check to_prefs() @@ -184,6 +188,7 @@ impl PrefsDialog { let behavior_prefs = BehaviorPrefs { vol_control_cmd, vol_scroll_step: self.scroll_step_spin.get_value(), + vol_fine_scroll_step: self.fine_scroll_step_spin.get_value(), middle_click_action: self.middle_click_combo.get_active().into(), custom_command, }; @@ -234,6 +239,11 @@ pub fn show_prefs_dialog(appstate: &Rc) { pub fn init_prefs_callback(appstate: Rc) { let apps = appstate.clone(); appstate.audio.connect_handler(Box::new(move |s, u| { + /* skip if prefs window is not present */ + if apps.gui.prefs_dialog.borrow().is_none() { + return; + } + match (s, u) { (AudioSignal::CardInitialized, _) => (), (AudioSignal::CardCleanedUp, _) => { diff --git a/src/ui_tray_icon.rs b/src/ui_tray_icon.rs index 6ac9454eb..29f2506fd 100644 --- a/src/ui_tray_icon.rs +++ b/src/ui_tray_icon.rs @@ -1,9 +1,11 @@ use app_state::*; use audio::*; use errors::*; +use glib; +use gio; use gdk; use gdk_pixbuf; -use gdk_pixbuf_sys::GDK_COLORSPACE_RGB; +use gdk_pixbuf_sys; use gtk::prelude::*; use gtk; use prefs::{Prefs, MiddleClickAction}; @@ -12,6 +14,7 @@ use std::cell::RefCell; use std::rc::Rc; use support_cmd::*; use support_ui::*; +use ui_prefs_dialog::show_prefs_dialog; // TODO tooltip @@ -78,6 +81,36 @@ impl TrayIcon { } + fn update_tooltip(&self, audio: &Audio) { + let cardname = audio.acard + .borrow() + .card_name() + .unwrap_or(String::from("Unknown card")); + let channame = audio.acard + .borrow() + .chan_name() + .unwrap_or(String::from("unknown channel")); + let vol = audio.vol() + .map(|s| format!("{}", s.round())) + .unwrap_or(String::from("unknown volume")); + let mute_info = { + if !audio.has_mute() { + "\nNo mute switch" + } else if audio.get_mute().unwrap_or(false) { + "\nMuted" + } else { + "" + } + }; + self.status_icon.set_tooltip_text(format!("{} ({})\nVolume: {}{}", + cardname, + channame, + vol, + mute_info) + .as_str()); + } + + pub fn update_all(&self, prefs: &Prefs, audio: &Audio, @@ -104,6 +137,7 @@ impl TrayIcon { *self.volmeter.borrow_mut() = Some(volmeter); } + self.update_tooltip(&audio); return self.update_pixbuf(audio.vol()?, audio.vol_level()); } } @@ -142,7 +176,7 @@ impl VolMeter { pixbuf: &gdk_pixbuf::Pixbuf) -> Result { - ensure!(pixbuf.get_colorspace() == GDK_COLORSPACE_RGB, + ensure!(pixbuf.get_colorspace() == gdk_pixbuf_sys::GDK_COLORSPACE_RGB, "Invalid colorspace in pixbuf"); ensure!(pixbuf.get_bits_per_sample() == 8, "Invalid bits per sample in pixbuf"); @@ -310,6 +344,7 @@ pub fn init_tray_icon(appstate: Rc) { let apps = appstate.clone(); appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) { (_, _) => { + apps.gui.tray_icon.update_tooltip(&apps.audio); try_w!(apps.gui.tray_icon.update_audio(&apps.audio)); } })); @@ -400,7 +435,7 @@ fn on_tray_icon_scroll_event(appstate: &AppS, } -fn on_tray_button_release_event(appstate: &AppS, +fn on_tray_button_release_event(appstate: &Rc, event_button: &gdk::EventButton) -> bool { let button = event_button.get_button(); @@ -421,7 +456,8 @@ fn on_tray_button_release_event(appstate: &AppS, try_wr!(audio.toggle_mute(AudioUser::Popup), false); } } - &MiddleClickAction::ShowPreferences => (), + // TODO + &MiddleClickAction::ShowPreferences => show_prefs_dialog(&appstate), &MiddleClickAction::VolumeControl => { try_wr!(execute_vol_control_command(&appstate.prefs.borrow()), false);