diff options
Diffstat (limited to 'src/volume-warning.vala')
-rw-r--r-- | src/volume-warning.vala | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/volume-warning.vala b/src/volume-warning.vala new file mode 100644 index 0000000..3c0f1e6 --- /dev/null +++ b/src/volume-warning.vala @@ -0,0 +1,216 @@ +/* + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- + * + * 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> + */ + +using PulseAudio; + +public abstract class VolumeWarning : Object +{ + // true if headphones are in use + public bool headphones_active { get; set; default = false; } + + // true if the warning dialog is being shown + public bool active { get; protected set; default = false; } + + // true if we're playing unapproved loud multimedia over headphones + public bool high_volume { get; protected set; default = false; } + + public enum Key { + VOLUME_UP, + VOLUME_DOWN + } + + public void user_keypress (Key key) { + if ((key == Key.VOLUME_DOWN) && active) { + _notification.close (); + on_user_response (IndicatorSound.WarnNotification.Response.CANCEL); + } + } + + public VolumeWarning (IndicatorSound.Options options) { + + _options = options; + + init_high_volume (); + init_approved (); + + _notification.user_responded.connect ((n, r) => on_user_response (r)); + } + + ~VolumeWarning () { + clear_timer (ref _approved_timer); + } + + /*** + **** + ***/ + + // true if the user has approved high volumes recently + protected bool approved { get; set; default = false; } + + // true if multimedia is currently playing + protected bool multimedia_active { get; set; default = false; } + + /* Cached value of the multimedia volume reported by pulse. + Setting this only updates the cache -- to change the volume, + use sound_system_set_multimedia_volume. + NB: This PulseAudio.Volume is typed as uint to unconfuse valac. */ + protected uint multimedia_volume { get; set; default = PulseAudio.Volume.INVALID; } + + protected abstract void sound_system_set_multimedia_volume (PulseAudio.Volume volume); + + protected void clear_timer (ref uint timer) { + if (timer != 0) { + Source.remove (timer); + timer = 0; + } + } + + private IndicatorSound.Options _options; + + /** + *** HIGH VOLUME PROPERTY + **/ + + private void init_high_volume () { + const string self_keys[] = { + "multimedia-volume", + "multimedia-active", + "headphones-active", + "high-volume-approved" + }; + foreach (var key in self_keys) + this.notify[key].connect (() => update_high_volume ()); + + const string options_keys[] = { + "loud-volume", + "loud-warning-enabled" + }; + foreach (var key in options_keys) + _options.notify[key].connect (() => update_high_volume ()); + + update_high_volume (); + } + + private void update_high_volume () { + + var newval = _options.loud_warning_enabled + && headphones_active + && multimedia_active + && !approved + && (multimedia_volume != PulseAudio.Volume.INVALID) + && (multimedia_volume >= _options.loud_volume); + + if (high_volume != newval) { + debug ("changing high_volume from %d to %d", (int)high_volume, (int)newval); + if (newval && !active) + activate (); + high_volume = newval; + } + } + + /** + *** HIGH VOLUME APPROVED PROPERTY + **/ + + private Settings _settings = new Settings ("com.canonical.indicator.sound"); + private static const string TTL_KEY = "warning-volume-confirmation-ttl"; + private uint _approved_timer = 0; + private int64 _approved_at = 0; + private int64 _approved_ttl_usec = 0; + + private void approve_high_volume () { + _approved_at = GLib.get_monotonic_time (); + update_approved (); + update_approved_timer (); + } + + private void init_approved () { + _settings.changed[TTL_KEY].connect (() => update_approved_cache ()); + update_approved_cache (); + } + private void update_approved_cache () { + _approved_ttl_usec = _settings.get_int (TTL_KEY); + _approved_ttl_usec *= 1000000; + + update_approved (); + update_approved_timer (); + } + private void update_approved_timer () { + + clear_timer (ref _approved_timer); + + if (_approved_at == 0) + return; + + int64 expires_at = _approved_at + _approved_ttl_usec; + int64 now = GLib.get_monotonic_time (); + if (expires_at > now) { + var seconds_left = 1 + ((expires_at - now) / 1000000); + _approved_timer = Timeout.add_seconds ((uint)seconds_left, () => { + _approved_timer = 0; + update_approved (); + return Source.REMOVE; + }); + } + } + private void update_approved () { + var new_approved = calculate_approved (); + if (approved != new_approved) { + debug ("changing approved from %d to %d", (int)approved, (int)new_approved); + approved = new_approved; + } + } + private bool calculate_approved () { + int64 now = GLib.get_monotonic_time (); + return (_approved_at != 0) + && (_approved_at + _approved_ttl_usec >= now); + } + + // NOTIFICATION + + private IndicatorSound.WarnNotification _notification = new IndicatorSound.WarnNotification (); + private PulseAudio.Volume _ok_volume = PulseAudio.Volume.INVALID; + + protected virtual void preshow () {} + + private void activate () { + preshow (); + _ok_volume = multimedia_volume; + + _notification.show (); + this.active = true; + + // lower the volume to just under the warning level + sound_system_set_multimedia_volume (_options.loud_volume-1); + } + + private void on_user_response (IndicatorSound.WarnNotification.Response response) { + + this.active = false; + + if (response == IndicatorSound.WarnNotification.Response.OK) { + approve_high_volume (); + sound_system_set_multimedia_volume (_ok_volume); + } + + _ok_volume = PulseAudio.Volume.INVALID; + } +} |