aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am3
-rw-r--r--src/service.vala46
-rw-r--r--src/volume-control.vala192
3 files changed, 232 insertions, 9 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 39ec6bf..2421683 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,6 +19,7 @@ glib_marshal_prefix = _sound_service_marshal
music_bridge_VALASOURCES = \
service.vala \
main.vala \
+ volume-control.vala \
music-player-bridge.vala \
transport-menu-item.vala \
specific-items-manager.vala \
@@ -48,6 +49,8 @@ music_bridge_VALAFLAGS = \
--pkg gdk-pixbuf-2.0 \
--pkg libxml-2.0 \
--pkg libpulse \
+ --pkg libpulse-mainloop-glib \
+ --pkg pulseaudio-mgr \
--target-glib=2.36
$(MAINTAINER_VALAFLAGS)
diff --git a/src/service.vala b/src/service.vala
index fc35dd7..cdb5b15 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -1,6 +1,7 @@
public class IndicatorSound.Service {
public Service () {
+ this.volume_control = new VolumeControl ();
}
public int run () {
@@ -20,21 +21,13 @@ public class IndicatorSound.Service {
const ActionEntry[] action_entries = {
{ "root", null, null, "('', 'audio-volume-high-panel', '', true)", null },
- { "mute", activate_mute, null, "false", null },
- { "volume", null, null, "0", volume_changed },
{ "settings", activate_settings, null, null, null }
};
MainLoop loop;
SimpleActionGroup actions;
Menu menu;
-
- void activate_mute (SimpleAction action, Variant? param) {
- bool muted = action.get_state ().get_boolean ();
- }
-
- void volume_changed (SimpleAction action, Variant val) {
- }
+ VolumeControl volume_control;
void activate_settings (SimpleAction action, Variant? param) {
try {
@@ -64,10 +57,45 @@ public class IndicatorSound.Service {
return menu;
}
+ Action create_mute_action () {
+ var mute_action = new SimpleAction.stateful ("mute", null, this.volume_control.mute);
+
+ mute_action.activate.connect ( (action, param) => {
+ action.change_state (!action.get_state ().get_boolean ());
+ });
+
+ mute_action.change_state.connect ( (action, val) => {
+ volume_control.set_mute (val.get_boolean ());
+ });
+
+ this.volume_control.notify["mute"].connect ( () => {
+ mute_action.set_state (this.volume_control.mute);
+ });
+
+ return mute_action;
+ }
+
+ Action create_volume_action () {
+ var volume_action = new SimpleAction.stateful ("volume", null, this.volume_control.get_volume ());
+
+ volume_action.change_state.connect ( (action, val) => {
+ volume_control.set_volume (val.get_double ());
+ });
+
+ this.volume_control.volume_changed.connect ( (volume) => {
+ volume_action.set_state (volume);
+ });
+
+ return volume_action;
+ }
+
void bus_acquired (DBusConnection connection, string name) {
this.actions = new SimpleActionGroup ();
this.actions.add_entries (action_entries, this);
+ this.actions.add_action (this.create_mute_action ());
+ this.actions.add_action (this.create_volume_action ());
+
this.menu = create_base_menu ();
try {
diff --git a/src/volume-control.vala b/src/volume-control.vala
new file mode 100644
index 0000000..8c88c92
--- /dev/null
+++ b/src/volume-control.vala
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2013 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alberto Ruiz <alberto.ruiz@canonical.com>
+ */
+
+using PulseAudio;
+
+[CCode(cname="pa_cvolume_set", cheader_filename = "pulse/volume.h")]
+extern unowned PulseAudio.CVolume? vol_set (PulseAudio.CVolume? cv, uint channels, PulseAudio.Volume v);
+
+public class VolumeControl : Object
+{
+ /* this is static to ensure it being freed after @context (loop does not have ref counting) */
+ private static PulseAudio.GLibMainLoop loop;
+
+ private PulseAudio.Context context;
+ private bool _mute = true;
+ private double _volume = 0.0;
+
+ public signal void ready ();
+ public signal void volume_changed (double v);
+
+ public VolumeControl ()
+ {
+ if (loop == null)
+ loop = new PulseAudio.GLibMainLoop ();
+
+ 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");
+
+ context = new PulseAudio.Context (loop.get_api(), null, props);
+
+ context.set_state_callback (notify_cb);
+
+ if (context.connect(null, Context.Flags.NOFAIL, null) < 0)
+ {
+ warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
+ return;
+ }
+ }
+
+ /* PulseAudio logic*/
+ private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index)
+ {
+ if ((t & Context.SubscriptionEventType.FACILITY_MASK) == Context.SubscriptionEventType.SINK)
+ {
+ get_properties ();
+ }
+ }
+
+ private void sink_info_cb_for_props (Context c, SinkInfo? i, int eol)
+ {
+ if (i == null)
+ return;
+
+ if (_mute != (bool)i.mute)
+ {
+ _mute = (bool)i.mute;
+ this.notify_property ("mute");
+ }
+
+ if (_volume != volume_to_double (i.volume.values[0]))
+ {
+ _volume = volume_to_double (i.volume.values[0]);
+ volume_changed (_volume);
+ }
+ }
+
+ private void server_info_cb_for_props (Context c, ServerInfo? i)
+ {
+ if (i == null)
+ return;
+ context.get_sink_info_by_name (i.default_sink_name, sink_info_cb_for_props);
+ }
+
+ private void get_properties ()
+ {
+ context.get_server_info (server_info_cb_for_props);
+ }
+
+ private void notify_cb (Context c)
+ {
+ if (c.get_state () == Context.State.READY)
+ {
+ c.subscribe (PulseAudio.Context.SubscriptionMask.SINK);
+ c.set_subscribe_callback (context_events_cb);
+ get_properties ();
+ ready ();
+ }
+ }
+
+ /* Mute operations */
+ public void set_mute (bool mute)
+ {
+ if (context.get_state () != Context.State.READY)
+ {
+ warning ("Could not mute: PulseAudio server connection is not ready.");
+ return;
+ }
+
+ context.get_sink_info_list ((context, sink, eol) => {
+ if (sink != null)
+ context.set_sink_mute_by_index (sink.index, mute, null);
+ });
+ }
+
+ public void toggle_mute ()
+ {
+ this.set_mute (!this._mute);
+ }
+
+ public bool mute
+ {
+ get
+ {
+ return this._mute;
+ }
+ }
+
+ /* Volume operations */
+ private 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)
+ {
+ double tmp = (double)(vol - PulseAudio.Volume.MUTED);
+ return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED);
+ }
+
+ private void set_volume_success_cb (Context c, int success)
+ {
+ if ((bool)success)
+ volume_changed (_volume);
+ }
+
+ private void sink_info_set_volume_cb (Context c, SinkInfo? i, int eol)
+ {
+ if (i == null)
+ return;
+
+ unowned CVolume cvol = vol_set (i.volume, 1, double_to_volume (_volume));
+ c.set_sink_volume_by_index (i.index, cvol, set_volume_success_cb);
+ }
+
+ private void server_info_cb_for_set_volume (Context c, ServerInfo? i)
+ {
+ if (i == null)
+ {
+ warning ("Could not get PulseAudio server info");
+ return;
+ }
+
+ context.get_sink_info_by_name (i.default_sink_name, sink_info_set_volume_cb);
+ }
+
+ public void set_volume (double volume)
+ {
+ if (context.get_state () != Context.State.READY)
+ {
+ warning ("Could not change volume: PulseAudio server connection is not ready.");
+ return;
+ }
+ _volume = volume;
+
+ context.get_server_info (server_info_cb_for_set_volume);
+ }
+
+ public double get_volume ()
+ {
+ return _volume;
+ }
+}