From c3d504bc66bbd360ca6c8d5742a121441dacbfe2 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 27 Mar 2013 13:06:26 -0400 Subject: Add VolumeControl and hook up mute and volume actions to it The VolumeControl class originated in the phablet branch of this indicator. --- src/Makefile.am | 3 + src/service.vala | 46 +++++++++--- src/volume-control.vala | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 9 deletions(-) create mode 100644 src/volume-control.vala (limited to 'src') 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 . + * + * Authors: + * Alberto Ruiz + */ + +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; + } +} -- cgit v1.2.3