/*
* -*- 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 .
*
* Authors:
* Charles Kerr
*/
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_all_properties();
_notification = new IndicatorSound.WarnNotification ();
_notification.user_responded.connect((n, r) => on_user_response(r));
}
~VolumeWarning () {
stop_all_timers();
}
/***
****
***/
// true if the user has approved high volumes recently
protected bool high_volume_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);
/***
****
***/
// FIXME: what to do with this now?
private bool _ignore_warning_this_time = false;
private IndicatorSound.Options _options;
private void init_all_properties()
{
init_high_volume();
init_high_volume_approved();
}
private void stop_all_timers()
{
stop_high_volume_approved_timer();
}
/**
*** HIGH VOLUME PROPERTY
**/
public bool ignore_high_volume {
get {
if (_ignore_warning_this_time) {
warning("Ignore");
_ignore_warning_this_time = false;
return true;
}
return false;
}
set { }
}
private void init_high_volume () {
_options.loud_changed.connect(() => update_high_volume());
this.notify["multimedia-volume"].connect(() => update_high_volume());
this.notify["multimedia-active"].connect(() => update_high_volume());
this.notify["headphones-active"].connect(() => update_high_volume());
this.notify["high-volume-approved"].connect(() => update_high_volume());
update_high_volume();
}
private void update_high_volume () {
PulseAudio.Volume mm_vol = multimedia_volume;
var approved = high_volume_approved;
var hp_active = headphones_active;
var mm_active = multimedia_active;
GLib.message("calculating high volume... headphones_active %d high_volume_approved %d multimedia_active %d multimedia_volume %d is_invalid %d, is_loud %d", (int)hp_active, (int)approved, (int)mm_active, (int)mm_vol, (int)(mm_vol == PulseAudio.Volume.INVALID), (int)_options.is_loud_pulse(mm_vol));
var new_high_volume = hp_active && !approved && mm_active && (mm_vol != PulseAudio.Volume.INVALID) && _options.is_loud_pulse(mm_vol);
GLib.message("so the new high_volume is %d, was %d", (int)new_high_volume, (int)high_volume);
if (high_volume != new_high_volume) {
debug("changing high_volume from %d to %d", (int)high_volume, (int)new_high_volume);
if (new_high_volume && !active)
show();
high_volume = new_high_volume;
}
}
/**
*** HIGH VOLUME APPROVED PROPERTY
**/
private Settings _settings = new Settings ("com.canonical.indicator.sound");
private uint _high_volume_approved_timer = 0;
private int64 _high_volume_approved_at = 0;
private int64 _high_volume_approved_ttl_usec = 0;
private void approve_high_volume() {
_high_volume_approved_at = GLib.get_monotonic_time();
update_high_volume_approved();
update_high_volume_approved_timer();
}
private void init_high_volume_approved() {
_settings.changed["warning-volume-confirmation-ttl"].connect(() => update_high_volume_approved_cache());
update_high_volume_approved_cache();
}
private void update_high_volume_approved_cache() {
_high_volume_approved_ttl_usec = _settings.get_int("warning-volume-confirmation-ttl");
_high_volume_approved_ttl_usec *= 1000000;
update_high_volume_approved();
update_high_volume_approved_timer();
}
private void update_high_volume_approved_timer() {
stop_high_volume_approved_timer();
if (_high_volume_approved_at != 0) {
int64 expires_at = _high_volume_approved_at + _high_volume_approved_ttl_usec;
int64 now = GLib.get_monotonic_time();
if (expires_at > now) {
var seconds_left = 1 + ((expires_at - now) / 1000000);
_high_volume_approved_timer = Timeout.add_seconds((uint)seconds_left, on_high_volume_approved_timer);
}
}
}
private void stop_high_volume_approved_timer() {
if (_high_volume_approved_timer != 0) {
Source.remove (_high_volume_approved_timer);
_high_volume_approved_timer = 0;
}
}
private bool on_high_volume_approved_timer() {
_high_volume_approved_timer = 0;
update_high_volume_approved();
return Source.REMOVE;
}
private void update_high_volume_approved() {
var new_high_volume_approved = calculate_high_volume_approved();
if (high_volume_approved != new_high_volume_approved) {
debug("changing high_volume_approved from %d to %d", (int)high_volume_approved, (int)new_high_volume_approved);
high_volume_approved = new_high_volume_approved;
}
}
private bool calculate_high_volume_approved() {
int64 now = GLib.get_monotonic_time();
return (_high_volume_approved_at != 0)
&& (_high_volume_approved_at + _high_volume_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 show() {
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;
}
}