aboutsummaryrefslogtreecommitdiff
path: root/src/volume-control.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/volume-control.vala')
-rw-r--r--src/volume-control.vala272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/volume-control.vala b/src/volume-control.vala
new file mode 100644
index 0000000..9475f53
--- /dev/null
+++ b/src/volume-control.vala
@@ -0,0 +1,272 @@
+/*
+ * 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;
+ private double _mic_volume = 0.0;
+
+ public signal void volume_changed (double v);
+ public signal void mic_volume_changed (double v);
+
+ /** true when connected to the pulse server */
+ public bool ready { get; set; }
+
+ /** true when a microphone is active **/
+ public bool active_mic { get; private set; default = false; }
+
+ 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 (context_state_callback);
+
+ 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)
+ {
+ switch (t & Context.SubscriptionEventType.FACILITY_MASK)
+ {
+ case Context.SubscriptionEventType.SINK:
+ update_sink ();
+ break;
+
+ case Context.SubscriptionEventType.SOURCE:
+ update_source ();
+ break;
+
+ case Context.SubscriptionEventType.SOURCE_OUTPUT:
+ switch (t & Context.SubscriptionEventType.TYPE_MASK)
+ {
+ case Context.SubscriptionEventType.NEW:
+ c.get_source_output_info (index, source_output_info_cb);
+ break;
+
+ case Context.SubscriptionEventType.REMOVE:
+ this.active_mic = false;
+ break;
+ }
+ break;
+ }
+ }
+
+ 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 source_info_cb (Context c, SourceInfo? i, int eol)
+ {
+ if (i == null)
+ return;
+
+ if (_mic_volume != volume_to_double (i.volume.values[0]))
+ {
+ _mic_volume = volume_to_double (i.volume.values[0]);
+ mic_volume_changed (_mic_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 update_sink ()
+ {
+ context.get_server_info (server_info_cb_for_props);
+ }
+
+ private void update_source ()
+ {
+ context.get_server_info ( (c, i) => {
+ if (i != null)
+ context.get_source_info_by_name (i.default_source_name, source_info_cb);
+ });
+ }
+
+ private void source_output_info_cb (Context c, SourceOutputInfo? i, int eol)
+ {
+ if (i == null)
+ return;
+
+ var role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE);
+ if (role == "phone" || role == "production")
+ this.active_mic = true;
+ }
+
+ private void context_state_callback (Context c)
+ {
+ if (c.get_state () == Context.State.READY)
+ {
+ c.subscribe (PulseAudio.Context.SubscriptionMask.SINK |
+ PulseAudio.Context.SubscriptionMask.SOURCE |
+ PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT);
+ c.set_subscribe_callback (context_events_cb);
+ update_sink ();
+ update_source ();
+ this.ready = true;
+ }
+ else
+ this.ready = false;
+ }
+
+ /* Mute operations */
+ public void set_mute (bool mute)
+ {
+ return_if_fail (context.get_state () == Context.State.READY);
+
+ 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)
+ {
+ return_if_fail (context.get_state () == Context.State.READY);
+
+ _volume = volume;
+
+ context.get_server_info (server_info_cb_for_set_volume);
+ }
+
+ void set_mic_volume_success_cb (Context c, int success)
+ {
+ if ((bool)success)
+ mic_volume_changed (_mic_volume);
+ }
+
+ public void set_mic_volume (double volume)
+ {
+ return_if_fail (context.get_state () == Context.State.READY);
+
+ _mic_volume = volume;
+
+ context.get_server_info ( (c, i) => {
+ if (i != null) {
+ unowned CVolume cvol = CVolume ();
+ cvol = vol_set (cvol, 1, double_to_volume (_mic_volume));
+ c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb);
+ }
+ });
+ }
+
+ public double get_volume ()
+ {
+ return _volume;
+ }
+
+ public double get_mic_volume ()
+ {
+ return _mic_volume;
+ }
+}