diff options
-rw-r--r-- | src/notification.vala | 81 | ||||
-rw-r--r-- | src/service.vala | 154 | ||||
-rw-r--r-- | src/volume-warning.vala | 105 | ||||
-rw-r--r-- | src/warn-notification.vala | 59 |
4 files changed, 273 insertions, 126 deletions
diff --git a/src/notification.vala b/src/notification.vala new file mode 100644 index 0000000..e5e1fd2 --- /dev/null +++ b/src/notification.vala @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +public abstract class IndicatorSound.Notification: Object +{ + public bool visible { get; protected set; default = false; } + + public Notification() { + BusWatcher.watch_namespace (GLib.BusType.SESSION, + "org.freedesktop.Notifications", + () => { debug("Notifications name appeared"); }, + () => { debug("Notifications name vanshed"); _server_caps = null; }); + + _notification = create_notification(); + _notification.closed.connect((n) => { + visible = false; + }); + } + + protected abstract Notify.Notification create_notification(); + + ~Notification() { + close(); + } + + protected void show_() { + try { + _notification.show (); + message("after calling show, n.id is %d", (int)_notification.id); + visible = true; + } catch (GLib.Error e) { + warning ("Unable to show notification: %s", e.message); + } + } + + public void close() { + var n = _notification; + + return_if_fail (n != null); + + message("closing id %d", n.id); + if (n.id != 0) { + try { + n.close(); + } catch (GLib.Error e) { + warning("Unable to close notification: %s", e.message); + } + } + } + + protected bool notify_server_supports(string cap) { + if (_server_caps == null) { + message("getting server caps"); + _server_caps = Notify.get_server_caps(); + } + + var ret = _server_caps.find_custom(cap, strcmp) != null; + message("%s --> %d", cap, (int)ret); + return ret; + } + + protected Notify.Notification _notification = null; + private List<string> _server_caps = null; + +} diff --git a/src/service.vala b/src/service.vala index 5f6b00e..acaf639 100644 --- a/src/service.vala +++ b/src/service.vala @@ -20,35 +20,24 @@ public class IndicatorSound.Service: Object { DBusConnection bus; - /** - * A copy of volume_control.volume made before just warn_notification - * is shown. Since the volume is clamped during the warning, we cache - * the previous volume to use iff the user hits "OK". - */ - VolumeControl.Volume _pre_warn_volume = null; - public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts, Options options, VolumeWarning volume_warning) { - _options = options; - _volume_warning = volume_warning; - try { bus = Bus.get_sync(GLib.BusType.SESSION); } catch (GLib.Error e) { error("Unable to get DBus session bus: %s", e.message); } + _options = options; + info_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted"); - warn_notification = new Notify.Notification(_("Volume"), _("High volume can damage your hearing."), "audio-volume-high"); - warn_notification.set_hint ("x-canonical-non-shaped-icon", "true"); - warn_notification.set_hint ("x-canonical-snap-decisions", "true"); - warn_notification.set_hint ("x-canonical-private-affirmative-tint", "true"); - warn_notification.closed.connect((n) => { - n.clear_actions (); - _volume_warning.active = false; - increment_volume_sync_action(); + _volume_warning = volume_warning; + _volume_warning.notify["active"].connect(() => { + this.increment_volume_sync_action(); + this.update_notification(); }); + BusWatcher.watch_namespace (GLib.BusType.SESSION, "org.freedesktop.Notifications", () => { debug("Notifications name appeared"); }, @@ -60,8 +49,8 @@ public class IndicatorSound.Service: Object { this.notify["visible"].connect ( () => this.update_root_icon () ); this.volume_control = volume; - this.volume_control.active_output_changed.connect (this.update_root_icon); - this.volume_control.active_output_changed.connect (this.update_notification); + this.volume_control.active_output_changed.connect(() => this.update_root_icon()); + this.volume_control.active_output_changed.connect(() => this.update_notification()); this.accounts_service = accounts; /* If we're on the greeter, don't export */ @@ -157,8 +146,6 @@ public class IndicatorSound.Service: Object { clear_acts_player(); - stop_clamp_to_high_timeout(); - if (this.player_action_update_id > 0) { Source.remove (this.player_action_update_id); this.player_action_update_id = 0; @@ -208,16 +195,24 @@ public class IndicatorSound.Service: Object { AccountsServiceUser? accounts_service = null; bool export_to_accounts_service = false; private Notify.Notification info_notification; - private Notify.Notification warn_notification; private Options _options; private VolumeWarning _volume_warning; const double volume_step_percentage = 0.06; private void activate_scroll_action (SimpleAction action, Variant? param) { - int delta = param.get_int32(); /* positive for up, negative for down */ - double v = volume_control.volume.volume + volume_step_percentage * delta; - volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS); + int direction = param.get_int32(); // positive for up, negative for down + message("scroll: %d", direction); + + if (_volume_warning.active) { + _volume_warning.user_keypress(direction>0 + ? VolumeWarning.Key.VOLUME_UP + : VolumeWarning.Key.VOLUME_DOWN); + } else { + double delta = volume_step_percentage * direction; + double v = volume_control.volume.volume + delta; + volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS); + } } void activate_desktop_settings (SimpleAction action, Variant? param) { @@ -285,7 +280,6 @@ public class IndicatorSound.Service: Object { } private bool notify_server_caps_checked = false; - private bool notify_server_supports_actions = false; private bool notify_server_supports_sync = false; private bool block_info_notifications = false; @@ -610,71 +604,33 @@ public class IndicatorSound.Service: Object { private void update_notification () { - if (!notify_server_caps_checked) { + if (!notify_server_caps_checked) + { List<string> caps = Notify.get_server_caps (); - notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null; notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null; notify_server_caps_checked = true; } - var loud = _volume_warning.high_volume; - bool ignore_warning_this_time = _volume_warning.ignore_high_volume; - var warn = loud - && this.notify_server_supports_actions - && !_volume_warning.high_volume_approved - && !ignore_warning_this_time; - if (_volume_warning.active && !_options.is_loud(volume_control.volume)) { - _volume_warning.set_warning_volume(); - close_notification(warn_notification); - } - if (warn) { + if (_volume_warning.active) + { close_notification(info_notification); - if (_pre_warn_volume == null) { - _pre_warn_volume = new VolumeControl.Volume(); - _pre_warn_volume.volume = volume_control.volume.volume; - _pre_warn_volume.reason = volume_control.volume.reason; - } - warn_notification.clear_actions(); - warn_notification.add_action ("ok", _("OK"), (n, a) => { - stop_clamp_to_high_timeout(); - _volume_warning.approve_high_volume (); - // restore the volume the user introduced - VolumeControl.Volume vol = new VolumeControl.Volume(); - vol.volume = volume_control.get_pre_clamped_volume(); - vol.reason = VolumeControl.VolumeReasons.USER_KEYPRESS; - _pre_warn_volume = null; - volume_control.volume = vol; - _volume_warning.active = false; - }); - warn_notification.add_action ("cancel", _("Cancel"), (n, a) => { - _pre_warn_volume = null; - _volume_warning.active = false; - increment_volume_sync_action(); - }); - _volume_warning.active = true; - show_notification(warn_notification); - } else { - if (!_volume_warning.active) { - close_notification(warn_notification); - - if (notify_server_supports_sync && !block_info_notifications && !ignore_warning_this_time) { - /* Determine Label */ - string volume_label = get_notification_label (); - - /* Choose an icon */ - string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); - - /* Reset the notification */ - var n = this.info_notification; - n.update (_("Volume"), volume_label, icon); - n.clear_hints(); - n.set_hint ("x-canonical-non-shaped-icon", "true"); - n.set_hint ("x-canonical-private-synchronous", "true"); - n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false"); - n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0)); - show_notification(n); - } - } + } + else if (notify_server_supports_sync && !block_info_notifications) + { + bool is_loud = _volume_warning.high_volume; + + string volume_label = get_notification_label (); + string icon = get_volume_notification_icon (volume_control.volume.volume, is_loud, volume_control.active_output); + + /* Reset the notification */ + var n = this.info_notification; + n.update (_("Volume"), volume_label, icon); + n.clear_hints(); + n.set_hint ("x-canonical-non-shaped-icon", "true"); + n.set_hint ("x-canonical-private-synchronous", "true"); + n.set_hint ("x-canonical-value-bar-tint", is_loud ? "true" : "false"); + n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0)); + show_notification(n); } } @@ -794,9 +750,6 @@ public class IndicatorSound.Service: Object { if (reason == VolumeControl.VolumeReasons.USER_KEYPRESS || reason == VolumeControl.VolumeReasons.DEVICE_OUTPUT_CHANGE) this.update_notification (); - - if ((warn_notification.id != 0) && (_pre_warn_volume != null)) - clamp_to_high_soon(); }); this.volume_control.bind_property ("ready", volume_action, "enabled", BindingFlags.SYNC_CREATE); @@ -969,27 +922,4 @@ public class IndicatorSound.Service: Object { this.update_preferred_players (); } - - /** VOLUME CLAMPING **/ - - private uint _clamp_to_high_timeout = 0; - - private void stop_clamp_to_high_timeout() { - if (_clamp_to_high_timeout != 0) { - Source.remove(_clamp_to_high_timeout); - _clamp_to_high_timeout = 0; - } - } - - private void clamp_to_high_soon() { - const uint interval_msec = 200; - if (_clamp_to_high_timeout == 0) - _clamp_to_high_timeout = Timeout.add(interval_msec, clamp_to_high_idle); - } - - private bool clamp_to_high_idle() { - _clamp_to_high_timeout = 0; - _volume_warning.clamp_to_high_volume(); - return false; // Source.REMOVE; - } } diff --git a/src/volume-warning.vala b/src/volume-warning.vala index 423f129..29a049b 100644 --- a/src/volume-warning.vala +++ b/src/volume-warning.vala @@ -40,8 +40,6 @@ public class VolumeWarning : VolumeControl warning("set_mute not supported for VolumeWarning"); } - private IndicatorSound.WarnNotification _notification = new IndicatorSound.WarnNotification(); - /* this is static to ensure it being freed after @context (loop does not have ref counting) */ private static PulseAudio.GLibMainLoop loop; @@ -103,7 +101,10 @@ public class VolumeWarning : VolumeControl init_all_properties(); this.reconnect_to_pulse (); - } + + _notification = new IndicatorSound.WarnNotification(); + _notification.user_responded.connect((n, response) => on_user_response(response)); + } private void init_all_properties() { @@ -123,6 +124,7 @@ public class VolumeWarning : VolumeControl _reconnect_timer = 0; } stop_high_volume_approved_timer(); + stop_clamp_to_loud_timeout(); } private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) { @@ -672,16 +674,6 @@ public class VolumeWarning : VolumeControl && (stream == "multimedia"); } - public void clamp_to_high_volume() { - if (_high_volume && _options.is_loud(_volume)) { - var vol = new VolumeControl.Volume(); - vol.volume = _volume.volume.clamp(0, volume_to_double(_options.loud_volume())); - vol.reason = _volume.reason; - debug("Clamping from %f down to %f", _volume.volume, vol.volume); - volume = vol; - } - } - public void set_warning_volume() { var vol = new VolumeControl.Volume(); vol.volume = volume_to_double(_options.loud_volume()); @@ -694,7 +686,7 @@ public class VolumeWarning : VolumeControl public bool high_volume_approved { get; private set; default = false; } - public void approve_high_volume() { + private void approve_high_volume() { _high_volume_approved_at = GLib.get_monotonic_time(); update_high_volume_approved(); update_high_volume_approved_timer(); @@ -843,4 +835,89 @@ public class VolumeWarning : VolumeControl } return objp; } + + private void set_multimedia_volume(VolumeControl.Volume volume) + { + // FIXME + } + + // NOTIFICATION + + private IndicatorSound.WarnNotification _notification = new IndicatorSound.WarnNotification(); + + private VolumeControl.Volume _cancel_volume = null; + private VolumeControl.Volume _ok_volume = null; + + public void show(VolumeControl.Volume volume) { + + // the volume to use if user hits 'cancel' + _cancel_volume = new VolumeControl.Volume(); + _cancel_volume.volume = VolumeControlPulse.volume_to_double(_options.loud_volume()); + _cancel_volume.reason = VolumeControl.VolumeReasons.USER_KEYPRESS; + + // the volume to use if user hits 'ok' + _ok_volume = new VolumeControl.Volume(); + _ok_volume.volume = volume.volume; + _ok_volume.reason = VolumeControl.VolumeReasons.USER_KEYPRESS; + + _notification.show(); + this.active = true; + } + + public enum Key { + VOLUME_UP, + VOLUME_DOWN + } + + public void user_keypress(Key key) { + if (key == Key.VOLUME_DOWN) + on_user_response(IndicatorSound.WarnNotification.Response.CANCEL); + } + + private void on_user_response(IndicatorSound.WarnNotification.Response response) { + _notification.close(); + stop_clamp_to_loud_timeout(); + + if (response == IndicatorSound.WarnNotification.Response.OK) { + approve_high_volume(); + set_multimedia_volume(_ok_volume); + } else { // WarnNotification.CANCEL + set_multimedia_volume(_cancel_volume); + } + + _cancel_volume = null; + _ok_volume = null; + + this.active = false; + } + + // VOLUME CLAMPING + + private uint _clamp_to_loud_timeout = 0; + + private void stop_clamp_to_loud_timeout() { + if (_clamp_to_loud_timeout != 0) { + Source.remove(_clamp_to_loud_timeout); + _clamp_to_loud_timeout = 0; + } + } + + private void clamp_to_loud_soon() { + const uint interval_msec = 200; + if (_clamp_to_loud_timeout == 0) + _clamp_to_loud_timeout = Timeout.add(interval_msec, clamp_to_loud_idle); + } + + private bool clamp_to_loud_idle() { + _clamp_to_loud_timeout = 0; + clamp_to_loud_volume(); + return false; // Source.REMOVE; + } + + private void clamp_to_loud_volume() { + if ((_cancel_volume != null) && (_volume.volume > _cancel_volume.volume)) { + debug("Clamping from %f down to %f", _volume.volume, _cancel_volume.volume); + set_multimedia_volume (_cancel_volume); + } + } } diff --git a/src/warn-notification.vala b/src/warn-notification.vala new file mode 100644 index 0000000..4f45a5c --- /dev/null +++ b/src/warn-notification.vala @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +public class IndicatorSound.WarnNotification: Notification +{ + public enum Response { + CANCEL, + OK + } + + public signal void user_responded(WarnNotification.Response response); + + protected override Notify.Notification create_notification() { + var n = new Notify.Notification(_("Volume"), _("High volume can damage your hearing."), "audio-volume-high"); + n.set_hint ("x-canonical-non-shaped-icon", "true"); + n.set_hint ("x-canonical-snap-decisions", "true"); + n.set_hint ("x-canonical-private-affirmative-tint", "true"); + n.closed.connect((n) => { + n.clear_actions(); + }); + return n; + } + + public bool show() { + close(); + + if (!notify_server_supports("actions")) + return false; + + _notification.clear_actions(); + _notification.add_action ("ok", _("OK"), (n, a) => { + user_responded(Response.OK); + }); + + _notification.add_action ("cancel", _("Cancel"), (n, a) => { + user_responded(Response.CANCEL); + }); + + show_(); + + return true; + } +} |