aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt59
-rw-r--r--src/Makefile.am.THIS39
-rw-r--r--src/accounts-service-user.vala4
-rw-r--r--src/info-notification.vala123
-rw-r--r--src/main.c14
-rw-r--r--src/media-player-mpris.vala6
-rw-r--r--src/media-player-user.vala3
-rw-r--r--src/mpris2-interfaces.vala18
-rw-r--r--src/notification.vala70
-rw-r--r--src/options-gsettings.vala87
-rw-r--r--src/options.vala28
-rw-r--r--src/service.vala545
-rw-r--r--src/volume-control-pulse.vala362
-rw-r--r--src/volume-control.vala32
-rw-r--r--src/volume-warning-pulse.vala211
-rw-r--r--src/volume-warning.vala216
-rw-r--r--src/warn-notification.vala59
17 files changed, 1068 insertions, 808 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 73a270c..57bf539 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,6 +32,7 @@ vala_init(indicator-sound-service
OPTIONS
--ccode
--thread
+ --target-glib=${GLIB_2_0_REQUIRED_VERSION}
--vapidir=${CMAKE_SOURCE_DIR}/vapi/
--vapidir=.
--pkg=url-dispatcher
@@ -39,23 +40,79 @@ vala_init(indicator-sound-service
)
vala_add(indicator-sound-service
+ notification.vala
+)
+vala_add(indicator-sound-service
+ info-notification.vala
+ DEPENDS
+ notification
+ volume-control
+ options
+)
+vala_add(indicator-sound-service
+ warn-notification.vala
+ DEPENDS
+ notification
+)
+vala_add(indicator-sound-service
service.vala
DEPENDS
sound-menu
volume-control
volume-control-pulse
+ notification
+ info-notification
+ volume-warning
+ options
+ options-gsettings
media-player
media-player-list
mpris2-interfaces
accounts-service-user
)
vala_add(indicator-sound-service
+ options.vala
+ DEPENDS
+ volume-control
+ volume-control-pulse
+)
+vala_add(indicator-sound-service
+ options-gsettings.vala
+ DEPENDS
+ options
+ volume-control-pulse
+ volume-control
+)
+vala_add(indicator-sound-service
volume-control.vala
+ DEPENDS
+ options
+ volume-control-pulse
)
vala_add(indicator-sound-service
volume-control-pulse.vala
DEPENDS
+ options
+ volume-control
+)
+vala_add(indicator-sound-service
+ volume-warning.vala
+ DEPENDS
+ options
+ volume-control-pulse
volume-control
+ warn-notification
+ notification
+)
+vala_add(indicator-sound-service
+ volume-warning-pulse.vala
+ DEPENDS
+ volume-warning
+ options
+ volume-control-pulse
+ volume-control
+ warn-notification
+ notification
)
vala_add(indicator-sound-service
media-player.vala
@@ -104,6 +161,8 @@ vala_add(indicator-sound-service
DEPENDS
media-player
volume-control
+ options
+ volume-control-pulse
)
vala_add(indicator-sound-service
accounts-service-user.vala
diff --git a/src/Makefile.am.THIS b/src/Makefile.am.THIS
deleted file mode 100644
index 1a82a18..0000000
--- a/src/Makefile.am.THIS
+++ /dev/null
@@ -1,39 +0,0 @@
-pkglibexec_PROGRAMS = indicator-sound-service
-
-indicator_sound_service_SOURCES = \
- service.vala \
- main.vala \
- volume-control.vala \
- media-player.vala \
- media-player-list.vala \
- mpris2-interfaces.vala \
- freedesktop-interfaces.vala \
- sound-menu.vala \
- bus-watch-namespace.c \
- bus-watch-namespace.h
-
-indicator_sound_service_VALAFLAGS = \
- --ccode \
- --vapidir=$(top_srcdir)/vapi/ \
- --vapidir=./ \
- --thread \
- --pkg config \
- --pkg gio-2.0 \
- --pkg gio-unix-2.0 \
- --pkg libxml-2.0 \
- --pkg libpulse \
- --pkg libpulse-mainloop-glib \
- --pkg bus-watcher \
- --target-glib=2.36
-
-# -w to disable warnings for vala-generated code
-indicator_sound_service_CFLAGS = $(PULSEAUDIO_CFLAGS) \
- $(SOUNDSERVICE_CFLAGS) \
- $(GCONF_CFLAGS) \
- $(COVERAGE_CFLAGS) \
- -DLIBEXECDIR=\"$(libexecdir)\" \
- -w \
- -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"
-
-indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_LIBS)
-indicator_sound_service_LDFLAGS = $(COVERAGE_LDFLAGS)
diff --git a/src/accounts-service-user.vala b/src/accounts-service-user.vala
index e8db7c4..1f9dcce 100644
--- a/src/accounts-service-user.vala
+++ b/src/accounts-service-user.vala
@@ -185,7 +185,7 @@ public class AccountsServiceUser : Object {
this.privacyproxy = Bus.get_proxy.end (res);
(this.privacyproxy as DBusProxy).g_properties_changed.connect((proxy, changed, invalid) => {
- var welcomeval = changed.lookup_value("MessagesWelcomeScreen", new VariantType("b"));
+ var welcomeval = changed.lookup_value("MessagesWelcomeScreen", VariantType.BOOLEAN);
if (welcomeval != null) {
debug("Messages on welcome screen changed");
this.showDataOnGreeter = welcomeval.get_boolean();
@@ -204,7 +204,7 @@ public class AccountsServiceUser : Object {
this.syssoundproxy = Bus.get_proxy.end (res);
(this.syssoundproxy as DBusProxy).g_properties_changed.connect((proxy, changed, invalid) => {
- var silentvar = changed.lookup_value("SilentMode", new VariantType("b"));
+ var silentvar = changed.lookup_value("SilentMode", VariantType.BOOLEAN);
if (silentvar != null) {
debug("Silent Mode changed");
this._silentMode = silentvar.get_boolean();
diff --git a/src/info-notification.vala b/src/info-notification.vala
new file mode 100644
index 0000000..2ce8ef6
--- /dev/null
+++ b/src/info-notification.vala
@@ -0,0 +1,123 @@
+/*
+ * 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 Notify;
+
+public class IndicatorSound.InfoNotification: Notification
+{
+ protected override Notify.Notification create_notification () {
+ return new Notify.Notification (_("Volume"), "", "audio-volume-muted");
+ }
+
+ public void show (VolumeControl.ActiveOutput active_output,
+ double volume,
+ bool is_high_volume) {
+ if (!notify_server_supports ("x-canonical-private-synchronous"))
+ return;
+
+ /* Determine Label */
+ unowned string volume_label = get_notification_label (active_output);
+
+ /* Choose an icon */
+ unowned string icon = get_volume_notification_icon (active_output, volume, is_high_volume);
+
+ /* Reset the notification */
+ var n = _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_high_volume ? "true" : "false");
+ n.set_hint ("value", ((int32)((volume * 100.0) + 0.5)).clamp(0, 100));
+ show_notification ();
+ }
+
+ private static unowned string get_notification_label (VolumeControl.ActiveOutput active_output) {
+
+ switch (active_output) {
+ case VolumeControl.ActiveOutput.SPEAKERS:
+ return _("Speakers");
+ case VolumeControl.ActiveOutput.HEADPHONES:
+ return _("Headphones");
+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
+ return _("Bluetooth headphones");
+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
+ return _("Bluetooth speaker");
+ case VolumeControl.ActiveOutput.USB_SPEAKER:
+ return _("Usb speaker");
+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
+ return _("Usb headphones");
+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
+ return _("HDMI speaker");
+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
+ return _("HDMI headphones");
+ default:
+ return "";
+ }
+ }
+
+ private static unowned string get_volume_notification_icon (VolumeControl.ActiveOutput active_output,
+ double volume,
+ bool is_high_volume) {
+
+ if (!is_high_volume)
+ return get_volume_icon (active_output, volume);
+
+ switch (active_output) {
+ case VolumeControl.ActiveOutput.SPEAKERS:
+ case VolumeControl.ActiveOutput.HEADPHONES:
+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
+ case VolumeControl.ActiveOutput.USB_SPEAKER:
+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
+ return "audio-volume-high";
+
+ default:
+ return "";
+ }
+ }
+
+ private static unowned string get_volume_icon (VolumeControl.ActiveOutput active_output,
+ double volume)
+ {
+ switch (active_output) {
+ case VolumeControl.ActiveOutput.SPEAKERS:
+ case VolumeControl.ActiveOutput.HEADPHONES:
+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
+ case VolumeControl.ActiveOutput.USB_SPEAKER:
+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
+ if (volume <= 0.0)
+ return "audio-volume-muted";
+ if (volume <= 0.3)
+ return "audio-volume-low";
+ if (volume <= 0.7)
+ return "audio-volume-medium";
+ return "audio-volume-high";
+
+ default:
+ return "";
+ }
+ }
+}
+
diff --git a/src/main.c b/src/main.c
index c368577..f4a59b2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -22,6 +22,7 @@
#include "config.h"
static IndicatorSoundService * service = NULL;
+static pa_glib_mainloop * pgloop = NULL;
static gboolean
sigterm_handler (gpointer data)
@@ -46,8 +47,10 @@ on_bus_acquired(GDBusConnection *connection,
gpointer user_data)
{
MediaPlayerList * playerlist = NULL;
+ IndicatorSoundOptions * options = NULL;
VolumeControlPulse * volume = NULL;
AccountsServiceUser * accounts = NULL;
+ VolumeWarning * warning = NULL;
if (g_strcmp0("lightdm", g_get_user_name()) == 0) {
@@ -57,20 +60,24 @@ on_bus_acquired(GDBusConnection *connection,
accounts = accounts_service_user_new();
}
- volume = volume_control_pulse_new();
+ pgloop = pa_glib_mainloop_new(NULL);
+ options = indicator_sound_options_gsettings_new();
+ volume = volume_control_pulse_new(options, pgloop);
+ warning = volume_warning_pulse_new(options, pgloop);
- service = indicator_sound_service_new (playerlist, volume, accounts);
+ service = indicator_sound_service_new (playerlist, volume, accounts, options, warning);
g_clear_object(&playerlist);
+ g_clear_object(&options);
g_clear_object(&volume);
g_clear_object(&accounts);
+ g_clear_object(&warning);
}
int
main (int argc, char ** argv)
{
GMainLoop * loop = NULL;
-
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
@@ -95,6 +102,7 @@ main (int argc, char ** argv)
g_main_loop_run(loop);
g_clear_object(&service);
+ g_clear_pointer(&pgloop, pa_glib_mainloop_free);
notify_uninit();
diff --git a/src/media-player-mpris.vala b/src/media-player-mpris.vala
index 408cdda..93ce34e 100644
--- a/src/media-player-mpris.vala
+++ b/src/media-player-mpris.vala
@@ -226,7 +226,7 @@ public class MediaPlayerMpris: MediaPlayer {
if (this.play_when_attached) {
/* wait a little before calling PlayPause, some players need some time to
set themselves up */
- Timeout.add (1000, () => { proxy.PlayPause.begin (); return false; } );
+ Timeout.add (1000, () => { proxy.PlayPause.begin (); return Source.REMOVE; } );
this.play_when_attached = false;
}
}
@@ -269,7 +269,7 @@ public class MediaPlayerMpris: MediaPlayer {
return;
}
- Timeout.add (500, () => { this.fetch_playlists (); return false; } );
+ Timeout.add (500, () => { this.fetch_playlists (); return Source.REMOVE; } );
}
/* some players (e.g. Spotify) don't follow the spec closely and pass single strings in metadata fields
@@ -295,7 +295,7 @@ public class MediaPlayerMpris: MediaPlayer {
this.playbackstatus_changed ();
}
- var metadata = changed_properties.lookup_value ("Metadata", new VariantType ("a{sv}"));
+ var metadata = changed_properties.lookup_value ("Metadata", VariantType.VARDICT);
if (metadata != null)
this.update_current_track (metadata);
}
diff --git a/src/media-player-user.vala b/src/media-player-user.vala
index 11678d5..1be1a18 100644
--- a/src/media-player-user.vala
+++ b/src/media-player-user.vala
@@ -75,8 +75,7 @@ public class MediaPlayerUser : MediaPlayer {
properties_queued.remove_all();
- /* Remove source */
- return false;
+ return Source.REMOVE;
}
/* Turns the DBus names into the object properties */
diff --git a/src/mpris2-interfaces.vala b/src/mpris2-interfaces.vala
index 0ed8719..f9060af 100644
--- a/src/mpris2-interfaces.vala
+++ b/src/mpris2-interfaces.vala
@@ -1,18 +1,18 @@
/*
-Copyright 2010 Canonical Ltd.
+Copyright 2010-2015 Canonical Ltd.
Authors:
Conor Curran <conor.curran@canonical.com>
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 3, as published
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
by the Free Software Foundation.
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranties of
-MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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
+You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -36,8 +36,8 @@ public interface MprisRoot : Object {
public interface MprisPlayer : Object {
// properties
public abstract HashTable<string, Variant?> Metadata{owned get; set;}
- public abstract int32 Position{owned get; set;}
- public abstract string? PlaybackStatus{owned get; set;}
+ public abstract int64 Position{owned get; set;}
+ public abstract string? PlaybackStatus{owned get; set;}
public abstract bool CanPlay{owned get; set;}
public abstract bool CanGoNext{owned get; set;}
public abstract bool CanGoPrevious{owned get; set;}
diff --git a/src/notification.vala b/src/notification.vala
new file mode 100644
index 0000000..3a3060f
--- /dev/null
+++ b/src/notification.vala
@@ -0,0 +1,70 @@
+/*
+ * 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 Notification () {
+ BusWatcher.watch_namespace (
+ GLib.BusType.SESSION,
+ "org.freedesktop.Notifications",
+ () => { debug ("Notifications name appeared"); },
+ () => { debug ("Notifications name vanshed"); _server_caps = null; });
+
+ _notification = create_notification ();
+ }
+
+ public void close () {
+ var n = _notification;
+
+ return_if_fail (n != null);
+
+ if (n.id != 0) {
+ try {
+ n.close ();
+ } catch (GLib.Error e) {
+ GLib.warning ("Unable to close notification: %s", e.message);
+ }
+ }
+ }
+
+ ~Notification () {
+ close ();
+ }
+
+ protected abstract Notify.Notification create_notification ();
+
+ protected void show_notification () {
+ try {
+ _notification.show ();
+ } catch (GLib.Error e) {
+ GLib.warning ("Unable to show notification: %s", e.message);
+ }
+ }
+
+ protected bool notify_server_supports (string cap) {
+ if (_server_caps == null)
+ _server_caps = Notify.get_server_caps ();
+
+ return _server_caps.find_custom (cap, strcmp) != null;
+ }
+
+ protected Notify.Notification _notification = null;
+
+ private static List<string> _server_caps = null;
+}
diff --git a/src/options-gsettings.vala b/src/options-gsettings.vala
new file mode 100644
index 0000000..85fdc66
--- /dev/null
+++ b/src/options-gsettings.vala
@@ -0,0 +1,87 @@
+/*
+ * -*- 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 class IndicatorSound.OptionsGSettings : Options
+{
+ public OptionsGSettings() {
+ init_max_volume();
+ init_loud_volume();
+ }
+
+ ~OptionsGSettings() {
+ }
+
+ private Settings _settings = new Settings ("com.canonical.indicator.sound");
+ private Settings _shared_settings = new Settings ("com.ubuntu.sound");
+
+ /** MAX VOLUME PROPERTY **/
+
+ private static const string AMP_dB_KEY = "amplified-volume-decibels";
+ private static const string NORMAL_dB_KEY = "normal-volume-decibels";
+ private static const string ALLOW_AMP_KEY = "allow-amplified-volume";
+
+ private void init_max_volume() {
+ _settings.changed[NORMAL_dB_KEY].connect(() => update_max_volume());
+ _settings.changed[AMP_dB_KEY].connect(() => update_max_volume());
+ _shared_settings.changed[ALLOW_AMP_KEY].connect(() => update_max_volume());
+ update_max_volume();
+ }
+ private void update_max_volume () {
+ set_max_volume_(calculate_max_volume());
+ }
+ protected void set_max_volume_ (double vol) {
+ if (max_volume != vol) {
+ debug("changing max_volume from %f to %f", this.max_volume, vol);
+ max_volume = vol;
+ }
+ }
+ private double calculate_max_volume () {
+ unowned string decibel_key = _shared_settings.get_boolean(ALLOW_AMP_KEY)
+ ? AMP_dB_KEY
+ : NORMAL_dB_KEY;
+ var volume_dB = _settings.get_double(decibel_key);
+ var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB);
+ return VolumeControlPulse.volume_to_double (volume_sw);
+ }
+
+
+ /** LOUD VOLUME **/
+
+ private static const string LOUD_ENABLED_KEY = "warning-volume-enabled";
+ private static const string LOUD_DECIBEL_KEY = "warning-volume-decibels";
+
+ private void init_loud_volume() {
+ _settings.changed[LOUD_ENABLED_KEY].connect(() => update_loud_volume());
+ _settings.changed[LOUD_DECIBEL_KEY].connect(() => update_loud_volume());
+ update_loud_volume();
+ }
+ private void update_loud_volume() {
+
+ var vol = PulseAudio.Volume.sw_from_dB (_settings.get_double (LOUD_DECIBEL_KEY));
+ if (loud_volume != vol)
+ loud_volume = vol;
+
+ var enabled = _settings.get_boolean(LOUD_ENABLED_KEY);
+ if (loud_warning_enabled != enabled)
+ loud_warning_enabled = enabled;
+ }
+}
diff --git a/src/options.vala b/src/options.vala
new file mode 100644
index 0000000..1aab852
--- /dev/null
+++ b/src/options.vala
@@ -0,0 +1,28 @@
+/*
+ * -*- 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>
+ */
+
+public abstract class IndicatorSound.Options : Object
+{
+ public double max_volume { get; protected set; default = 1.0; }
+
+ public uint loud_volume { get; protected set; default = PulseAudio.Volume.sw_from_dB(8); }
+
+ public bool loud_warning_enabled { get; protected set; default = true; }
+}
diff --git a/src/service.vala b/src/service.vala
index 0a7e108..29b8670 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -20,35 +20,25 @@
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) {
+ public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts, Options options, VolumeWarning volume_warning) {
+
try {
bus = Bus.get_sync(GLib.BusType.SESSION);
} catch (GLib.Error e) {
error("Unable to get DBus session bus: %s", e.message);
}
- info_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted");
+ _options = options;
+ _options.notify["max-volume"].connect(() => {
+ update_volume_action_state();
+ this.update_notification();
+ });
- 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();
- waiting_user_approve_warn=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"); },
- () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; });
this.settings = new Settings ("com.canonical.indicator.sound");
@@ -56,8 +46,26 @@ 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(() => {
+ bool headphones;
+ switch(volume_control.active_output()) {
+ case VolumeControl.ActiveOutput.HEADPHONES:
+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
+ headphones = true;
+ break;
+
+ default:
+ headphones = false;
+ break;
+ }
+ message("setting _volume_warning.headphones_active to %d", (int)headphones);
+ _volume_warning.headphones_active = headphones;
+
+ update_root_icon();
+ update_notification();
+ });
this.accounts_service = accounts;
/* If we're on the greeter, don't export */
@@ -94,7 +102,7 @@ public class IndicatorSound.Service: Object {
});
this.menus.@foreach ( (profile, menu) => {
- this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);
+ _volume_warning.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);
});
this.menus.@foreach ( (profile, menu) => {
@@ -112,7 +120,7 @@ public class IndicatorSound.Service: Object {
block_info_notifications = state.get_boolean();
if (block_info_notifications) {
debug("Indicator is shown");
- close_notification(info_notification);
+ _info_notification.close();
} else {
debug("Indicator is hidden");
}
@@ -128,33 +136,11 @@ public class IndicatorSound.Service: Object {
this.menus.@foreach ( (profile, menu) => menu.export (bus, @"/com/canonical/indicator/sound/$profile"));
}
- private void close_notification(Notify.Notification? n) {
- return_if_fail (n != null);
- if (n.id != 0) {
- try {
- n.close();
- } catch (GLib.Error e) {
- warning("Unable to close notification: %s", e.message);
- }
- }
- }
-
- private void show_notification(Notify.Notification? n) {
- return_if_fail (n != null);
- try {
- n.show ();
- } catch (GLib.Error e) {
- warning ("Unable to show notification: %s", e.message);
- }
- }
-
~Service() {
debug("Destroying 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;
@@ -203,19 +189,29 @@ public class IndicatorSound.Service: Object {
bool syncing_preferred_players = false;
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;
+ private IndicatorSound.InfoNotification _info_notification = new IndicatorSound.InfoNotification();
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) {
- var env = Environment.get_variable ("DESKTOP_SESSION");
+ unowned string env = Environment.get_variable ("DESKTOP_SESSION");
string cmd;
if (Environment.get_variable ("MIR_SOCKET") != null)
@@ -256,7 +252,7 @@ public class IndicatorSound.Service: Object {
void update_root_icon () {
double volume = this.volume_control.volume.volume;
- string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output);
+ unowned string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output());
string accessible_name;
if (this.volume_control.mute) {
@@ -270,7 +266,7 @@ public class IndicatorSound.Service: Object {
}
var root_action = actions.lookup_action ("root") as SimpleAction;
- var builder = new VariantBuilder (new VariantType ("a{sv}"));
+ var builder = new VariantBuilder (VariantType.VARDICT);
builder.add ("{sv}", "title", new Variant.string (_("Sound")));
builder.add ("{sv}", "accessible-desc", new Variant.string (accessible_name));
builder.add ("{sv}", "icon", serialize_themed_icon (icon));
@@ -278,397 +274,57 @@ public class IndicatorSound.Service: Object {
root_action.set_state (builder.end());
}
- 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;
- private bool waiting_user_approve_warn = false;
- private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output)
- {
- string icon = "";
- switch (active_output)
- {
+ private static unowned string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output) {
+ switch (active_output) {
case VolumeControl.ActiveOutput.SPEAKERS:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.USB_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.USB_HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.HDMI_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
if (volume <= 0.0)
- icon = "audio-volume-muted";
- else if (volume <= 0.3)
- icon = "audio-volume-low";
- else if (volume <= 0.7)
- icon = "audio-volume-medium";
- else
- icon = "audio-volume-high";
- break;
+ return "audio-volume-muted-panel";
+ if (volume <= 0.3)
+ return "audio-volume-low-panel";
+ if (volume <= 0.7)
+ return "audio-volume-medium-panel";
+ return "audio-volume-high-panel";
+
+ default:
+ return "";
}
- return icon;
}
- private string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output)
- {
- string icon = "";
- switch (active_output)
- {
+ private unowned string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) {
+ switch (active_output) {
case VolumeControl.ActiveOutput.SPEAKERS:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.USB_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.USB_HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.HDMI_SPEAKER:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
- if (volume <= 0.0)
- icon = "audio-volume-muted-panel";
- else if (volume <= 0.3)
- icon = "audio-volume-low-panel";
- else if (volume <= 0.7)
- icon = "audio-volume-medium-panel";
- else
- icon = "audio-volume-high-panel";
- break;
- }
- return icon;
- }
-
- private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) {
- string icon = "";
- if (loud) {
- switch (active_output)
- {
- case VolumeControl.ActiveOutput.SPEAKERS:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.HEADPHONES:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.USB_SPEAKER:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.USB_HEADPHONES:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.HDMI_SPEAKER:
- icon = "audio-volume-high";
- break;
- case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
- icon = "audio-volume-high";
- break;
- }
- } else {
- icon = get_volume_icon (volume, active_output);
- }
- return icon;
- }
-
- private string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) {
- string icon = "";
- switch (active_output)
- {
- case VolumeControl.ActiveOutput.SPEAKERS:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.HEADPHONES:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.USB_SPEAKER:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.USB_HEADPHONES:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.HDMI_SPEAKER:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
- if (mute || volume <= 0.0)
- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
- else if (this.accounts_service != null && this.accounts_service.silentMode)
- icon = "audio-volume-muted-panel";
- else
- icon = get_volume_root_icon_by_volume (volume, active_output);
- break;
- }
- return icon;
- }
+ return this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
+ if (this.accounts_service != null && this.accounts_service.silentMode)
+ return "audio-volume-muted-panel";
+ return get_volume_root_icon_by_volume (volume, active_output);
- private string get_notification_label () {
- string volume_label = "";
- switch (volume_control.active_output)
- {
- case VolumeControl.ActiveOutput.SPEAKERS:
- volume_label = _("Speakers");
- break;
- case VolumeControl.ActiveOutput.HEADPHONES:
- volume_label = _("Headphones");
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
- volume_label = _("Bluetooth headphones");
- break;
- case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
- volume_label = _("Bluetooth speaker");
- break;
- case VolumeControl.ActiveOutput.USB_SPEAKER:
- volume_label = _("Usb speaker");
- break;
- case VolumeControl.ActiveOutput.USB_HEADPHONES:
- volume_label = _("Usb headphones");
- break;
- case VolumeControl.ActiveOutput.HDMI_SPEAKER:
- volume_label = _("HDMI speaker");
- break;
- case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
- volume_label = _("HDMI headphones");
- break;
+ default:
+ return "";
}
-
- return volume_label;
}
private void update_notification () {
-
- 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_control.high_volume;
- bool ignore_warning_this_time = this.volume_control.ignore_high_volume;
- var warn = loud
- && this.notify_server_supports_actions
- && !this.volume_control.high_volume_approved
- && !ignore_warning_this_time;
- if (waiting_user_approve_warn && volume_control.below_warning_volume) {
- volume_control.set_warning_volume();
- close_notification(warn_notification);
- }
- if (warn) {
- 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_control.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;
-
- waiting_user_approve_warn = false;
- });
- warn_notification.add_action ("cancel", _("Cancel"), (n, a) => {
- _pre_warn_volume = null;
- waiting_user_approve_warn = false;
- increment_volume_sync_action();
- });
- waiting_user_approve_warn = true;
- show_notification(warn_notification);
- } else {
- if (!waiting_user_approve_warn) {
- 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);
- }
- }
+ if (!_volume_warning.active && !block_info_notifications) {
+ _info_notification.show(this.volume_control.active_output(),
+ get_volume_percent(),
+ _volume_warning.high_volume);
}
}
@@ -738,7 +394,7 @@ public class IndicatorSound.Service: Object {
this.mute_blocks_sound = false;
this.sound_was_blocked_timeout_id = 0;
this.update_root_icon ();
- return false;
+ return Source.REMOVE;
});
}
@@ -750,10 +406,10 @@ public class IndicatorSound.Service: Object {
/* return the current volume in the range of [0.0, 1.0] */
private double get_volume_percent() {
- return volume_control.volume.volume / this.volume_control.max_volume;
+ return volume_control.volume.volume / _options.max_volume;
}
- /* volume control's range can vary depending on its max_volume property,
+ /* volume control's range can vary depending on options.max_volume,
* but the action always needs to be in [0.0, 1.0]... */
private Variant create_volume_action_state() {
return new Variant.double (get_volume_percent());
@@ -768,14 +424,14 @@ public class IndicatorSound.Service: Object {
volume_action = new SimpleAction.stateful ("volume", VariantType.INT32, create_volume_action_state());
volume_action.change_state.connect ( (action, val) => {
- double v = val.get_double () * this.volume_control.max_volume;
+ double v = val.get_double () * _options.max_volume;
volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS);
});
/* activating this action changes the volume by the amount given in the parameter */
volume_action.activate.connect ((a,p) => activate_scroll_action(a,p));
- this.volume_control.notify["max-volume"].connect(() => {
+ _options.notify["max-volume"].connect(() => {
update_volume_action_state();
});
@@ -788,9 +444,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);
@@ -815,12 +468,19 @@ public class IndicatorSound.Service: Object {
return mic_volume_action;
}
+ private Variant create_high_volume_action_state() {
+ return new Variant.boolean (_volume_warning.high_volume);
+ }
+ private void update_high_volume_action_state() {
+ high_volume_action.set_state(create_high_volume_action_state());
+ }
+
SimpleAction high_volume_action;
Action create_high_volume_action () {
- high_volume_action = new SimpleAction.stateful("high-volume", null, new Variant.boolean (this.volume_control.high_volume));
+ high_volume_action = new SimpleAction.stateful("high-volume", null, create_high_volume_action_state());
- this.volume_control.notify["high-volume"].connect( () => {
- high_volume_action.set_state(new Variant.boolean (this.volume_control.high_volume));
+ _volume_warning.notify["high-volume"].connect( () => {
+ update_high_volume_action_state();
update_notification();
});
@@ -842,7 +502,7 @@ public class IndicatorSound.Service: Object {
uint export_actions = 0;
Variant action_state_for_player (MediaPlayer player, bool show_track = true) {
- var builder = new VariantBuilder (new VariantType ("a{sv}"));
+ var builder = new VariantBuilder (VariantType.VARDICT);
builder.add ("{sv}", "running", new Variant ("b", player.is_running));
builder.add ("{sv}", "state", new Variant ("s", player.state));
if (player.current_track != null && show_track) {
@@ -881,7 +541,7 @@ public class IndicatorSound.Service: Object {
clear_acts_player();
this.player_action_update_id = 0;
- return false;
+ return Source.REMOVE;
}
void eventually_update_player_actions () {
@@ -956,27 +616,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_control.clamp_to_high_volume();
- return false; // Source.REMOVE;
- }
}
diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala
index 4bd3076..6021447 100644
--- a/src/volume-control-pulse.vala
+++ b/src/volume-control-pulse.vala
@@ -22,9 +22,6 @@ using PulseAudio;
using Notify;
using Gee;
-[CCode(cname="pa_cvolume_set", cheader_filename = "pulse/volume.h")]
-extern unowned PulseAudio.CVolume? vol_set (PulseAudio.CVolume? cv, uint channels, PulseAudio.Volume v);
-
[DBus (name="com.canonical.UnityGreeter.List")]
interface GreeterListInterface : Object
{
@@ -34,19 +31,14 @@ interface GreeterListInterface : Object
public class VolumeControlPulse : VolumeControl
{
- /* this is static to ensure it being freed after @context (loop does not have ref counting) */
- private static PulseAudio.GLibMainLoop loop;
+ private unowned PulseAudio.GLibMainLoop loop = null;
private uint _reconnect_timer = 0;
private PulseAudio.Context context;
private bool _mute = true;
- private bool _is_playing = false;
- private bool _ignore_warning_this_time = false;
private VolumeControl.Volume _volume = new VolumeControl.Volume();
private double _mic_volume = 0.0;
- private Settings _settings = new Settings ("com.canonical.indicator.sound");
- private Settings _shared_settings = new Settings ("com.ubuntu.sound");
/* Used by the pulseaudio stream restore extension */
private DBusConnection _pconn;
@@ -57,22 +49,6 @@ public class VolumeControlPulse : VolumeControl
private bool _pulse_use_stream_restore = false;
private int32 _active_sink_input = -1;
private string[] _valid_roles = {"multimedia", "alert", "alarm", "phone"};
- public override string stream {
- get {
- if (_active_sink_input == -1)
- return "alert";
- var path = _sink_input_hash[_active_sink_input];
- if (path == _objp_role_multimedia)
- return "multimedia";
- if (path == _objp_role_alert)
- return "alert";
- if (path == _objp_role_alarm)
- return "alarm";
- if (path == _objp_role_phone)
- return "phone";
- return "alert";
- }
- }
private string? _objp_role_multimedia = null;
private string? _objp_role_alert = null;
private string? _objp_role_alarm = null;
@@ -87,40 +63,28 @@ public class VolumeControlPulse : VolumeControl
private uint _accountservice_volume_timer = 0;
private bool _send_next_local_volume = false;
private double _account_service_volume = 0.0;
- private bool _active_port_headphone = false;
private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS;
- /** true when connected to the pulse server */
- public override bool ready { get; private set; }
-
/** true when a microphone is active **/
public override bool active_mic { get; private set; default = false; }
- public VolumeControlPulse ()
+ public VolumeControlPulse (IndicatorSound.Options options, PulseAudio.GLibMainLoop loop)
{
+ base(options);
+
_volume.volume = 0.0;
_volume.reason = VolumeControl.VolumeReasons.PULSE_CHANGE;
- if (loop == null)
- loop = new PulseAudio.GLibMainLoop ();
+ this.loop = loop;
_mute_cancellable = new Cancellable ();
_volume_cancellable = new Cancellable ();
- init_all_properties();
-
setup_accountsservice.begin ();
this.reconnect_to_pulse ();
}
- private void init_all_properties()
- {
- init_max_volume();
- init_high_volume();
- init_high_volume_approved();
- }
-
~VolumeControlPulse ()
{
stop_all_timers();
@@ -134,10 +98,9 @@ public class VolumeControlPulse : VolumeControl
}
stop_local_volume_timer();
stop_account_service_volume_timer();
- stop_high_volume_approved_timer();
}
- private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) {
+ public static VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) {
VolumeControl.ActiveOutput ret_output = VolumeControl.ActiveOutput.SPEAKERS;
/* Check if the current active port is headset/headphone */
@@ -155,9 +118,8 @@ public class VolumeControlPulse : VolumeControl
(sink.active_port != null &&
(sink.active_port.name.contains("headset") ||
sink.active_port.name.contains("headphone")))) {
- _active_port_headphone = true;
// check if it's a bluetooth device
- var device_bus = sink.proplist.gets ("device.bus");
+ unowned string device_bus = sink.proplist.gets ("device.bus");
if (device_bus != null && device_bus == "bluetooth") {
ret_output = VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES;
} else if (device_bus != null && device_bus == "usb") {
@@ -169,8 +131,7 @@ public class VolumeControlPulse : VolumeControl
}
} else {
// speaker
- _active_port_headphone = false;
- var device_bus = sink.proplist.gets ("device.bus");
+ unowned string device_bus = sink.proplist.gets ("device.bus");
if (device_bus != null && device_bus == "bluetooth") {
ret_output = VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER;
} else if (device_bus != null && device_bus == "usb") {
@@ -245,28 +206,19 @@ public class VolumeControlPulse : VolumeControl
}
var playing = (i.state == PulseAudio.SinkState.RUNNING);
- if (_is_playing != playing)
- {
- _is_playing = playing;
- this.notify_property ("is-playing");
- }
+ if (is_playing != playing)
+ is_playing = playing;
- // store the current status of the active output
- VolumeControl.ActiveOutput active_output_before = active_output;
+ var oldval = _active_output;
+ var newval = calculate_active_output(i);
- // calculate the output
- _active_output = calculate_active_output (i);
-
- // check if the output has changed, if so... emit a signal
- VolumeControl.ActiveOutput active_output_now = active_output;
- if (active_output_now != active_output_before &&
- (active_output_now != VolumeControl.ActiveOutput.CALL_MODE &&
- active_output_before != VolumeControl.ActiveOutput.CALL_MODE)) {
- this.active_output_changed (active_output_now);
- if (active_output_now == VolumeControl.ActiveOutput.SPEAKERS) {
- _high_volume_approved = false;
- }
- update_high_volume();
+ _active_output = newval;
+
+ // Emit a change signal iff CALL_MODE wasn't involved. (FIXME: yuck.)
+ if ((oldval != VolumeControl.ActiveOutput.CALL_MODE) &&
+ (newval != VolumeControl.ActiveOutput.CALL_MODE) &&
+ (oldval != newval)) {
+ this.active_output_changed (newval);
}
if (_pulse_use_stream_restore == false &&
@@ -345,10 +297,6 @@ public class VolumeControlPulse : VolumeControl
var vol = new VolumeControl.Volume();
vol.volume = volume_to_double (lvolume);
vol.reason = VolumeControl.VolumeReasons.PULSE_CHANGE;
- // Ignore changes from PULSE to avoid issues with
- // some apps that change the volume in the sink
- // We only take into account volume changes from the user
- this._ignore_warning_this_time = true;
this.volume = vol;
}
}
@@ -358,6 +306,21 @@ public class VolumeControlPulse : VolumeControl
return message;
}
+ private VolumeControl.Stream calculate_active_stream()
+ {
+ if (_active_sink_input != -1) {
+ var path = _sink_input_hash[_active_sink_input];
+ if (path == _objp_role_multimedia)
+ return Stream.MULTIMEDIA;
+ if (path == _objp_role_alarm)
+ return Stream.ALARM;
+ if (path == _objp_role_phone)
+ return Stream.PHONE;
+ }
+
+ return VolumeControl.Stream.ALERT;
+ }
+
private async void update_active_sink_input (int32 index)
{
if ((index == -1) || (index != _active_sink_input && index in _sink_input_list)) {
@@ -365,10 +328,14 @@ public class VolumeControlPulse : VolumeControl
if (index != -1)
sink_input_objp = _sink_input_hash.get (index);
_active_sink_input = index;
+ var stream = calculate_active_stream();
+ if (active_stream != stream) {
+ active_stream = stream;
+ }
/* Listen for role volume changes from pulse itself (external clients) */
try {
- var builder = new VariantBuilder (new VariantType ("ao"));
+ var builder = new VariantBuilder (VariantType.OBJECT_PATH_ARRAY);
builder.add ("o", sink_input_objp);
yield _pconn.call ("org.PulseAudio.Core1", "/org/pulseaudio/core1",
@@ -393,10 +360,6 @@ public class VolumeControlPulse : VolumeControl
var vol = new VolumeControl.Volume();
vol.volume = volume_to_double (volume);
vol.reason = VolumeControl.VolumeReasons.VOLUME_STREAM_CHANGE;
- // Ignore changes from PULSE to avoid issues with
- // some apps that change the volume in the sink
- // We only take into account volume changes from the user
- this._ignore_warning_this_time = true;
this.volume = vol;
} catch (GLib.Error e) {
warning ("unable to get volume for active role %s (%s)", sink_input_objp, e.message);
@@ -407,7 +370,7 @@ public class VolumeControlPulse : VolumeControl
private void add_sink_input_into_list (SinkInputInfo sink_input)
{
/* We're only adding ones that are not corked and with a valid role */
- var role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE);
+ unowned string role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE);
if (role != null && role in _valid_roles) {
if (sink_input.corked == 0 || role == "phone") {
@@ -477,7 +440,7 @@ public class VolumeControlPulse : VolumeControl
if (i == null)
return;
- var role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE);
+ unowned string role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE);
if (role == "phone" || role == "production")
this.active_mic = true;
}
@@ -499,7 +462,7 @@ public class VolumeControlPulse : VolumeControl
c.set_subscribe_callback (context_events_cb);
update_sink ();
update_source ();
- this.ready = true;
+ this.ready = true; // true because we're connected to the pulse server
break;
case Context.State.FAILED:
@@ -518,7 +481,7 @@ public class VolumeControlPulse : VolumeControl
{
_reconnect_timer = 0;
reconnect_to_pulse ();
- return false; // G_SOURCE_REMOVE
+ return Source.REMOVE;
}
void reconnect_to_pulse ()
@@ -540,7 +503,7 @@ public class VolumeControlPulse : VolumeControl
this.context = new PulseAudio.Context (loop.get_api(), null, props);
this.context.set_state_callback (context_state_callback);
- var server_string = Environment.get_variable("PULSE_SERVER");
+ unowned string server_string = Environment.get_variable("PULSE_SERVER");
if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0)
warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
}
@@ -590,30 +553,19 @@ public class VolumeControlPulse : VolumeControl
}
}
- public override bool is_playing
+ public override VolumeControl.ActiveOutput active_output()
{
- get
- {
- return this._is_playing;
- }
- }
-
- public override VolumeControl.ActiveOutput active_output
- {
- get
- {
- return _active_output;
- }
+ return _active_output;
}
/* Volume operations */
- private static PulseAudio.Volume double_to_volume (double vol)
+ public static PulseAudio.Volume double_to_volume (double vol)
{
double tmp = (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED) * vol;
return (PulseAudio.Volume)tmp + PulseAudio.Volume.MUTED;
}
- private static double volume_to_double (PulseAudio.Volume vol)
+ public static double volume_to_double (PulseAudio.Volume vol)
{
double tmp = (double)(vol - PulseAudio.Volume.MUTED);
return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED);
@@ -668,8 +620,6 @@ public class VolumeControlPulse : VolumeControl
active_role_objp, "org.freedesktop.DBus.Properties", "Set",
new Variant ("(ssv)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume", volume),
null, DBusCallFlags.NONE, -1);
-
- debug ("Set volume to %f on path %s", vol, active_role_objp);
} catch (GLib.Error e) {
lock (_pa_volume_sig_count) {
_pa_volume_sig_count--;
@@ -687,7 +637,7 @@ public class VolumeControlPulse : VolumeControl
void set_mic_volume_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) {
if (i != null) {
unowned CVolume cvol = CVolume ();
- cvol = vol_set (cvol, 1, double_to_volume (_mic_volume));
+ cvol.set (1, double_to_volume (_mic_volume));
c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb);
}
}
@@ -716,170 +666,9 @@ public class VolumeControlPulse : VolumeControl
&& volume_changed) {
start_local_volume_timer();
}
-
- update_high_volume();
}
}
- /** MAX VOLUME PROPERTY **/
-
- private void init_max_volume() {
- _settings.changed["normal-volume-decibels"].connect(() => update_max_volume());
- _settings.changed["amplified-volume-decibels"].connect(() => update_max_volume());
- _shared_settings.changed["allow-amplified-volume"].connect(() => update_max_volume());
- update_max_volume();
- }
- private void update_max_volume () {
- var new_max_volume = calculate_max_volume();
- if (max_volume != new_max_volume) {
- debug("changing max_volume from %f to %f", this.max_volume, new_max_volume);
- max_volume = calculate_max_volume();
- }
- }
- private double calculate_max_volume () {
- unowned string decibel_key = _shared_settings.get_boolean("allow-amplified-volume")
- ? "amplified-volume-decibels"
- : "normal-volume-decibels";
- var volume_dB = _settings.get_double(decibel_key);
- var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB);
- return volume_to_double (volume_sw);
- }
-
- /** HIGH VOLUME PROPERTY **/
-
- private bool _warning_volume_enabled;
- private double _warning_volume_norms; /* 1.0 == PA_VOLUME_NORM */
- private bool _high_volume = false;
- public override bool ignore_high_volume {
- get {
- if (_ignore_warning_this_time) {
- warning("Ignore");
- _ignore_warning_this_time = false;
- return true;
- }
- return false;
- }
- set { }
- }
- public override bool high_volume {
- get { return this._high_volume; }
- private set { this._high_volume = value; }
- }
- public override bool below_warning_volume {
- get { return this._volume.volume < this._warning_volume_norms; }
- private set { }
- }
- private void init_high_volume() {
- _settings.changed["warning-volume-enabled"].connect(() => update_high_volume_cache());
- _settings.changed["warning-volume-decibels"].connect(() => update_high_volume_cache());
- update_high_volume_cache();
- }
- private void update_high_volume_cache() {
- var volume_dB = _settings.get_double ("warning-volume-decibels");
- var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB);
- var volume_norms = volume_to_double (volume_sw);
- _warning_volume_norms = volume_norms;
- _warning_volume_enabled = _settings.get_boolean("warning-volume-enabled");
- debug("updating high volume cache... enabled %d dB %f sw %lu norm %f", (int)_warning_volume_enabled, volume_dB, volume_sw, volume_norms);
- update_high_volume();
- }
- private void update_high_volume() {
- var new_high_volume = calculate_high_volume();
- if (high_volume != new_high_volume) {
- debug("changing high_volume from %d to %d", (int)high_volume, (int)new_high_volume);
- high_volume = new_high_volume;
- }
- }
- private bool calculate_high_volume() {
- return calculate_high_volume_from_volume(_volume.volume);
- }
- private bool calculate_high_volume_from_volume(double volume) {
- return _active_port_headphone
- && _warning_volume_enabled
- && volume > _warning_volume_norms
- && (stream == "multimedia");
- }
-
- public override void clamp_to_high_volume() {
- if (_high_volume && (_volume.volume > _warning_volume_norms)) {
- var vol = new VolumeControl.Volume();
- vol.volume = _volume.volume.clamp(0, _warning_volume_norms);
- vol.reason = _volume.reason;
- debug("Clamping from %f down to %f", _volume.volume, vol.volume);
- volume = vol;
- }
- }
-
- public override void set_warning_volume() {
- var vol = new VolumeControl.Volume();
- vol.volume = _warning_volume_norms;
- vol.reason = _volume.reason;
- debug("Setting warning level volume from %f down to %f", _volume.volume, vol.volume);
- volume = vol;
- }
-
- /** HIGH VOLUME APPROVED PROPERTY **/
-
- private bool _high_volume_approved = false;
- private uint _high_volume_approved_timer = 0;
- private int64 _high_volume_approved_at = 0;
- private int64 _high_volume_approved_ttl_usec = 0;
- public override bool high_volume_approved {
- get { return this._high_volume_approved; }
- private set { this._high_volume_approved = value; }
- }
- 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 false; /* 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);
- }
- public override void approve_high_volume() {
- _high_volume_approved_at = GLib.get_monotonic_time();
- update_high_volume_approved();
- update_high_volume_approved_timer();
- }
-
-
/** MIC VOLUME PROPERTY */
public override double mic_volume {
@@ -895,16 +684,11 @@ public class VolumeControlPulse : VolumeControl
}
}
- /* PulseAudio Dbus (Stream Restore) logic */
- private void reconnect_pulse_dbus ()
+ public static DBusConnection? create_pulse_dbus_connection()
{
unowned string pulse_dbus_server_env = Environment.get_variable ("PULSE_DBUS_SERVER");
string address;
- /* In case of a reconnect */
- _pulse_use_stream_restore = false;
- _pa_volume_sig_count = 0;
-
if (pulse_dbus_server_env != null) {
address = pulse_dbus_server_env;
} else {
@@ -915,7 +699,7 @@ public class VolumeControlPulse : VolumeControl
conn = Bus.get_sync (BusType.SESSION);
} catch (GLib.IOError e) {
warning ("unable to get the dbus session bus: %s", e.message);
- return;
+ return null;
}
try {
@@ -927,27 +711,41 @@ public class VolumeControlPulse : VolumeControl
address = props.get_string ();
} catch (GLib.Error e) {
warning ("unable to get pulse unix socket: %s", e.message);
- return;
+ return null;
}
}
- debug ("PulseAudio dbus unix socket: %s", address);
+ DBusConnection conn = null;
try {
- _pconn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT);
+ conn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT);
} catch (GLib.Error e) {
+ GLib.warning("Unable to connect to dbus server at '%s': %s", address, e.message);
/* If it fails, it means the dbus pulse extension is not available */
- return;
}
+ GLib.debug ("PulseAudio dbus address is '%s', connection is '%p'", address, conn);
+ return conn;
+ }
+
+ /* PulseAudio Dbus (Stream Restore) logic */
+ private void reconnect_pulse_dbus ()
+ {
+ /* In case of a reconnect */
+ _pulse_use_stream_restore = false;
+ _pa_volume_sig_count = 0;
+
+ _pconn = create_pulse_dbus_connection();
+ if (_pconn == null)
+ return;
/* For pulse dbus related events */
_pconn.add_filter (pulse_dbus_filter);
/* Check if the 4 currently supported media roles are already available in StreamRestore
* Roles: multimedia, alert, alarm and phone */
- _objp_role_multimedia = stream_restore_get_object_path ("sink-input-by-media-role:multimedia");
- _objp_role_alert = stream_restore_get_object_path ("sink-input-by-media-role:alert");
- _objp_role_alarm = stream_restore_get_object_path ("sink-input-by-media-role:alarm");
- _objp_role_phone = stream_restore_get_object_path ("sink-input-by-media-role:phone");
+ _objp_role_multimedia = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:multimedia");
+ _objp_role_alert = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:alert");
+ _objp_role_alarm = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:alarm");
+ _objp_role_phone = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:phone");
/* Only use stream restore if every used role is available */
if (_objp_role_multimedia != null && _objp_role_alert != null && _objp_role_alarm != null && _objp_role_phone != null) {
@@ -958,10 +756,10 @@ public class VolumeControlPulse : VolumeControl
}
}
- private string? stream_restore_get_object_path (string name) {
+ public static string? stream_restore_get_object_path (DBusConnection pconn, string name) {
string? objp = null;
try {
- Variant props_variant = _pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1",
+ Variant props_variant = pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1",
"/org/pulseaudio/stream_restore1", "org.PulseAudio.Ext.StreamRestore1",
"GetEntryByName", new Variant ("(s)", name), null, DBusCallFlags.NONE, -1);
/* Workaround for older versions of vala that don't provide get_objv */
@@ -977,7 +775,7 @@ public class VolumeControlPulse : VolumeControl
/* AccountsService operations */
private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties)
{
- Variant volume_variant = changed_properties.lookup_value ("Volume", new VariantType ("d"));
+ Variant volume_variant = changed_properties.lookup_value ("Volume", VariantType.DOUBLE);
if (volume_variant != null) {
var volume = volume_variant.get_double ();
if (volume >= 0) {
@@ -987,7 +785,7 @@ public class VolumeControlPulse : VolumeControl
}
}
- Variant mute_variant = changed_properties.lookup_value ("Muted", new VariantType ("b"));
+ Variant mute_variant = changed_properties.lookup_value ("Muted", VariantType.BOOLEAN);
if (mute_variant != null) {
var mute = mute_variant.get_boolean ();
set_mute_internal (mute);
@@ -1061,7 +859,7 @@ public class VolumeControlPulse : VolumeControl
yield setup_user_proxy ();
} else {
// We are in a user session. We just need our own proxy
- var username = Environment.get_variable ("USER");
+ unowned string username = Environment.get_variable ("USER");
if (username != "" && username != null) {
yield setup_user_proxy (username);
}
@@ -1128,7 +926,7 @@ public class VolumeControlPulse : VolumeControl
_send_next_local_volume = false;
start_local_volume_timer ();
}
- return false; // G_SOURCE_REMOVE
+ return Source.REMOVE;
}
private void start_account_service_volume_timer()
@@ -1160,6 +958,6 @@ public class VolumeControlPulse : VolumeControl
{
_accountservice_volume_timer = 0;
start_account_service_volume_timer ();
- return false; // G_SOURCE_REMOVE
+ return Source.REMOVE;
}
}
diff --git a/src/volume-control.vala b/src/volume-control.vala
index 90fc325..3d02f70 100644
--- a/src/volume-control.vala
+++ b/src/volume-control.vala
@@ -40,36 +40,39 @@ public abstract class VolumeControl : Object
CALL_MODE
}
+ public enum Stream {
+ ALERT,
+ MULTIMEDIA,
+ ALARM,
+ PHONE
+ }
+
public class Volume : Object {
public double volume;
public VolumeReasons reason;
}
- public virtual string stream { get { return ""; } }
- public virtual bool ready { get { return false; } set { } }
+ protected IndicatorSound.Options _options = null;
+
+ public VolumeControl(IndicatorSound.Options options) {
+ _options = options;
+ }
+
+ public Stream active_stream { get; protected set; default = Stream.ALERT; }
+ public bool ready { get; protected set; default = false; }
public virtual bool active_mic { get { return false; } set { } }
- public virtual bool high_volume { get { return false; } protected set { } }
- public virtual bool ignore_high_volume { get { return false; } protected set { } }
- public virtual bool below_warning_volume { get { return false; } protected set { } }
public virtual bool mute { get { return false; } }
- public virtual bool is_playing { get { return false; } }
- public virtual VolumeControl.ActiveOutput active_output { get { return VolumeControl.ActiveOutput.SPEAKERS; } }
+ public bool is_playing { get; protected set; default = false; }
private Volume _volume;
private double _pre_clamp_volume;
public virtual Volume volume { get { return _volume; } set { } }
public virtual double mic_volume { get { return 0.0; } set { } }
- public virtual double max_volume { get { return 1.0; } protected set { } }
-
- public virtual bool high_volume_approved { get { return false; } protected set { } }
- public virtual void approve_high_volume() { }
- public virtual void clamp_to_high_volume() { }
- public virtual void set_warning_volume() { }
public abstract void set_mute (bool mute);
public void set_volume_clamp (double unclamped, VolumeControl.VolumeReasons reason) {
var v = new VolumeControl.Volume();
- v.volume = unclamped.clamp (0.0, this.max_volume);
+ v.volume = unclamped.clamp (0.0, _options.max_volume);
v.reason = reason;
this.volume = v;
_pre_clamp_volume = unclamped;
@@ -79,5 +82,6 @@ public abstract class VolumeControl : Object
return _pre_clamp_volume;
}
+ public abstract VolumeControl.ActiveOutput active_output();
public signal void active_output_changed (VolumeControl.ActiveOutput active_output);
}
diff --git a/src/volume-warning-pulse.vala b/src/volume-warning-pulse.vala
new file mode 100644
index 0000000..2492cef
--- /dev/null
+++ b/src/volume-warning-pulse.vala
@@ -0,0 +1,211 @@
+/*
+ * -*- 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;
+
+/**
+ * A VolumeWarning that uses PulseAudio to
+ * (a) implement sound_system_set_multimedia_volume() and
+ * (b) keep the multimedia_active and multimedia_volume properties up-to-date
+ */
+public class VolumeWarningPulse : VolumeWarning
+{
+ public VolumeWarningPulse (IndicatorSound.Options options,
+ PulseAudio.GLibMainLoop pgloop) {
+ base (options);
+
+ _pgloop = pgloop;
+ pulse_reconnect ();
+ }
+
+ ~VolumeWarningPulse () {
+ clear_timer (ref _pulse_reconnect_timer);
+ clear_timer (ref _pending_sink_inputs_timer);
+ pulse_disconnect ();
+ }
+
+ protected override void preshow () {
+ /* showing the dialog can change the sink input index (bug #1484589)
+ * so cache it here for later use in sound_system_set_multimedia_volume() */
+ _target_sink_input_index = _multimedia_sink_input_index;
+ }
+
+ protected override void sound_system_set_multimedia_volume (PulseAudio.Volume volume) {
+ var index = _target_sink_input_index;
+
+ return_if_fail (_pulse_context != null);
+ return_if_fail (index != PulseAudio.INVALID_INDEX);
+ return_if_fail (volume != PulseAudio.Volume.INVALID);
+
+ unowned CVolume cvol = CVolume ();
+ cvol.set (1, volume);
+ debug ("setting multimedia (sink_input index %d) volume to %s", (int)index, cvol.to_string ());
+ _pulse_context.set_sink_input_volume (index, cvol);
+ }
+
+ private unowned PulseAudio.GLibMainLoop _pgloop = null;
+ private PulseAudio.Context _pulse_context = null;
+ private uint _pulse_reconnect_timer = 0;
+ private uint _pending_sink_inputs_timer = 0;
+ private GenericSet<uint32> _pending_sink_inputs = new GenericSet<uint32>(direct_hash, direct_equal);
+
+ private uint soon_interval_msec = 500;
+
+ private uint32 _target_sink_input_index = PulseAudio.INVALID_INDEX;
+ private uint32 _multimedia_sink_input_index = PulseAudio.INVALID_INDEX;
+
+ /***/
+
+ private bool is_active_multimedia (SinkInputInfo i) {
+ return (i.corked == 0) &&
+ (i.proplist.gets(PulseAudio.Proplist.PROP_MEDIA_ROLE) == "multimedia");
+
+ }
+
+ private void clear_multimedia () {
+ _multimedia_sink_input_index = PulseAudio.INVALID_INDEX;
+ multimedia_volume = PulseAudio.Volume.INVALID;
+ multimedia_active = false;
+ }
+
+ private void on_sink_input_info (Context c, SinkInputInfo? i, int eol) {
+
+ if (i == null)
+ return;
+
+ if (is_active_multimedia (i)) {
+ GLib.debug ("on_sink_input_info() setting multimedia sink input index to %d, sink index to %d", (int)i.index, (int)i.sink);
+ _multimedia_sink_input_index = i.index;
+ multimedia_volume = i.volume.max ();
+ multimedia_active = true;
+ }
+ else if (i.index == _multimedia_sink_input_index) {
+ clear_multimedia ();
+ }
+ }
+
+ private void update_all_sink_inputs () {
+ _pulse_context.get_sink_input_info_list (on_sink_input_info);
+ }
+ private void update_sink_input (uint32 index) {
+ _pulse_context.get_sink_input_info (index, on_sink_input_info);
+ }
+
+ private void update_sink_input_soon (uint32 index) {
+
+ _pending_sink_inputs.add (index);
+
+ if (_pending_sink_inputs_timer == 0) {
+ _pending_sink_inputs_timer = Timeout.add (soon_interval_msec, () => {
+ _pending_sink_inputs_timer = 0;
+ _pending_sink_inputs.foreach ((i) => update_sink_input (i));
+ _pending_sink_inputs.remove_all ();
+ return Source.REMOVE;
+ });
+ }
+ }
+
+ private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) {
+ switch (t & Context.SubscriptionEventType.FACILITY_MASK)
+ {
+ case Context.SubscriptionEventType.SINK_INPUT:
+ switch (t & Context.SubscriptionEventType.TYPE_MASK)
+ {
+ // if a SinkInput changed, get its updated info
+ // to keep our multimedia indices up-to-date
+ case Context.SubscriptionEventType.NEW:
+ case Context.SubscriptionEventType.CHANGE:
+ update_sink_input_soon (index);
+ break;
+
+ // if the multimedia sink input was removed,
+ // reset our mm fields and look for a new mm sink input
+ case Context.SubscriptionEventType.REMOVE:
+ if (index == _multimedia_sink_input_index) {
+ clear_multimedia ();
+ update_all_sink_inputs ();
+ }
+ break;
+
+ default:
+ GLib.debug ("Sink input event not known.");
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ private void pulse_context_state_callback (Context c) {
+ switch (c.get_state ()) {
+ case Context.State.READY:
+ c.set_subscribe_callback (context_events_cb);
+ c.subscribe (PulseAudio.Context.SubscriptionMask.SINK |
+ PulseAudio.Context.SubscriptionMask.SINK_INPUT);
+ update_all_sink_inputs ();
+ break;
+
+ case Context.State.FAILED:
+ case Context.State.TERMINATED:
+ pulse_reconnect_soon ();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ private void pulse_disconnect () {
+ if (_pulse_context != null) {
+ _pulse_context.disconnect ();
+ _pulse_context = null;
+ }
+ }
+
+ private void pulse_reconnect_soon () {
+ if (_pulse_reconnect_timer == 0) {
+ _pulse_reconnect_timer = Timeout.add_seconds (2, () => {
+ _pulse_reconnect_timer = 0;
+ pulse_reconnect ();
+ return Source.REMOVE;
+ });
+ }
+ }
+
+ void pulse_reconnect () {
+ pulse_disconnect ();
+
+ var props = new Proplist ();
+ props.sets (Proplist.PROP_APPLICATION_NAME, "Ubuntu Audio Settings");
+ props.sets (Proplist.PROP_APPLICATION_ID, "com.canonical.settings.sound");
+ props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control");
+ props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1");
+
+ _pulse_context = new PulseAudio.Context (_pgloop.get_api(), null, props);
+ _pulse_context.set_state_callback (pulse_context_state_callback);
+
+ unowned string server_string = Environment.get_variable ("PULSE_SERVER");
+ if (_pulse_context.connect (server_string, Context.Flags.NOFAIL, null) < 0)
+ GLib.warning ("pa_context_connect() failed: %s\n", PulseAudio.strerror(_pulse_context.errno()));
+ }
+}
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;
+ }
+}
diff --git a/src/warn-notification.vala b/src/warn-notification.vala
new file mode 100644
index 0000000..17129aa
--- /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 (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 () {
+
+ 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_notification();
+
+ return true;
+ }
+}