diff options
author | Conor Curran <conor.curran@canonical.com> | 2011-02-07 18:12:54 +0000 |
---|---|---|
committer | Conor Curran <conor.curran@canonical.com> | 2011-02-07 18:12:54 +0000 |
commit | 1691ba2e034d688d58fba39fa88d8ef10bf67fe6 (patch) | |
tree | 0c729f8b6106119be61d56a40c25eaab2cc72bba | |
parent | aa063e3e1c57346a6af86cc0a6cee81a846b1154 (diff) | |
parent | fba8f5aa4d7619b6e796ebb7cd4c96d30f5fd0ff (diff) | |
download | ayatana-indicator-sound-1691ba2e034d688d58fba39fa88d8ef10bf67fe6.tar.gz ayatana-indicator-sound-1691ba2e034d688d58fba39fa88d8ef10bf67fe6.tar.bz2 ayatana-indicator-sound-1691ba2e034d688d58fba39fa88d8ef10bf67fe6.zip |
finally a code base which is sane
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/active-sink.c | 293 | ||||
-rw-r--r-- | src/active-sink.h | 72 | ||||
-rw-r--r-- | src/mute-menu-item.c | 12 | ||||
-rw-r--r-- | src/pulse-manager.c | 589 | ||||
-rw-r--r-- | src/pulseaudio-mgr.c | 404 | ||||
-rw-r--r-- | src/pulseaudio-mgr.h (renamed from src/pulse-manager.h) | 28 | ||||
-rw-r--r-- | src/slider-menu-item.c | 46 | ||||
-rw-r--r-- | src/slider-menu-item.h | 4 | ||||
-rw-r--r-- | src/sound-service-dbus.c | 179 | ||||
-rw-r--r-- | src/sound-service-dbus.h | 10 | ||||
-rw-r--r-- | src/sound-service.c | 3 |
12 files changed, 841 insertions, 808 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index ed64aeb..63d6d5a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -72,7 +72,6 @@ music_bridge_VALAFLAGS = \ --pkg gdk-pixbuf-2.0 \ --pkg libxml-2.0 - $(MAINTAINER_VALAFLAGS) music_bridge_APIFILES = \ @@ -89,8 +88,10 @@ indicator_sound_service_SOURCES = \ common-defs.h \ sound-service.h \ sound-service.c \ - pulse-manager.h \ - pulse-manager.c \ + pulseaudio-mgr.h \ + pulseaudio-mgr.c \ + active-sink.c \ + active-sink.h \ sound-service-dbus.h \ sound-service-dbus.c \ slider-menu-item.h \ @@ -108,7 +109,7 @@ indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_ # Service xml compilation ######################### DBUS_SPECS = \ - sound-service.xml + sound-service.xml gen-%.xml.h: %.xml @echo "Building $@ from $<" diff --git a/src/active-sink.c b/src/active-sink.c new file mode 100644 index 0000000..b7954be --- /dev/null +++ b/src/active-sink.c @@ -0,0 +1,293 @@ +/* +Copyright 2011 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 +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 +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/>. +*/ +#include <libdbusmenu-glib/menuitem.h> + +#include "active-sink.h" +#include "slider-menu-item.h" +#include "mute-menu-item.h" + +#include "pulseaudio-mgr.h" + +typedef struct _ActiveSinkPrivate ActiveSinkPrivate; + +struct _ActiveSinkPrivate +{ + SliderMenuItem* volume_slider_menuitem; + MuteMenuItem* mute_menuitem; + SoundState current_sound_state; + SoundServiceDbus* service; + gint index; + gchar* name; + pa_cvolume volume; + pa_channel_map channel_map; + pa_volume_t base_volume; +}; + +#define ACTIVE_SINK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ACTIVE_SINK_TYPE, ActiveSinkPrivate)) + +/* Prototypes */ +static void active_sink_class_init (ActiveSinkClass *klass); +static void active_sink_init (ActiveSink *self); +static void active_sink_dispose (GObject *object); +static void active_sink_finalize (GObject *object); + +static SoundState active_sink_get_state_from_volume (ActiveSink* self); +static pa_cvolume active_sink_construct_mono_volume (const pa_cvolume* vol); +static void active_sink_volume_update (ActiveSink* self, gdouble percent); +static void active_sink_mute_update (ActiveSink* self, gboolean muted); + + +G_DEFINE_TYPE (ActiveSink, active_sink, G_TYPE_OBJECT); + +static void +active_sink_class_init (ActiveSinkClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ActiveSinkPrivate)); + + gobject_class->dispose = active_sink_dispose; + gobject_class->finalize = active_sink_finalize; +} + +static void +active_sink_init (ActiveSink *self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + priv->mute_menuitem = NULL; + priv->volume_slider_menuitem = NULL; + priv->current_sound_state = UNAVAILABLE; + priv->index = -1; + priv->name = NULL; + priv->service = NULL; + + // Init our menu items. + priv->mute_menuitem = g_object_new (MUTE_MENU_ITEM_TYPE, NULL); + priv->volume_slider_menuitem = slider_menu_item_new (self); + mute_menu_item_enable (priv->mute_menuitem, FALSE); + slider_menu_item_enable (priv->volume_slider_menuitem, FALSE); +} + +static void +active_sink_dispose (GObject *object) +{ + G_OBJECT_CLASS (active_sink_parent_class)->dispose (object); +} + +static void +active_sink_finalize (GObject *object) +{ + G_OBJECT_CLASS (active_sink_parent_class)->finalize (object); +} + +void +active_sink_populate (ActiveSink* sink, + const pa_sink_info* update) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE(sink); + + priv->name = g_strdup (update->name); + priv->index = update->index; + active_sink_mute_update (sink, update->mute); + priv->volume = active_sink_construct_mono_volume (&update->volume); + priv->base_volume = update->base_volume; + priv->channel_map = update->channel_map; + + pa_volume_t vol = pa_cvolume_max (&update->volume); + gdouble volume_percent = ((gdouble) vol * 100) / PA_VOLUME_NORM; + + active_sink_volume_update (sink, volume_percent); + active_sink_mute_update (sink, update->mute); + mute_menu_item_enable (priv->mute_menuitem, TRUE); + slider_menu_item_enable (priv->volume_slider_menuitem, TRUE); + + g_debug ("Active sink has been populated - volume %f", volume_percent); +} + +void +active_sink_update (ActiveSink* sink, + const pa_sink_info* update) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (sink); + active_sink_mute_update (sink, update->mute); + priv->volume = active_sink_construct_mono_volume (&update->volume); + priv->base_volume = update->base_volume; + priv->channel_map = update->channel_map; + + pa_volume_t vol = pa_cvolume_max (&update->volume); + gdouble volume_percent = ((gdouble) vol * 100) / PA_VOLUME_NORM; + + active_sink_volume_update (sink, volume_percent); + active_sink_mute_update (sink, update->mute); +} + +// To the UI +static void +active_sink_volume_update (ActiveSink* self, gdouble percent) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + slider_menu_item_update (priv->volume_slider_menuitem, percent); + SoundState state = active_sink_get_state_from_volume (self); + if (priv->current_sound_state != state){ + priv->current_sound_state = state; + sound_service_dbus_update_sound_state (priv->service, + priv->current_sound_state); + } +} + +// From the UI +void +active_sink_update_volume (ActiveSink* self, gdouble percent) +{ + pa_cvolume new_volume; + pa_cvolume_init(&new_volume); + new_volume.channels = 1; + pa_volume_t new_volume_value = (pa_volume_t) ((percent * PA_VOLUME_NORM) / 100); + pa_cvolume_set(&new_volume, 1, new_volume_value); + + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + + pa_cvolume_set(&priv->volume, priv->channel_map.channels, new_volume_value); + pm_update_volume (priv->index, new_volume); +} + + +static void +active_sink_mute_update (ActiveSink* self, gboolean muted) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + mute_menu_item_update (priv->mute_menuitem, muted); + SoundState state = active_sink_get_state_from_volume (self); + + if (muted == TRUE){ + state = MUTED; + } + if (priv->current_sound_state != state){ + priv->current_sound_state = state; + sound_service_dbus_update_sound_state (priv->service, state); + } +} + +void +active_sink_ensure_sink_is_unmuted (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + if (mute_menu_item_is_muted (priv->mute_menuitem)){ + pm_update_mute (FALSE); + } +} + + +static SoundState +active_sink_get_state_from_volume (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + GVariant* v = dbusmenu_menuitem_property_get_variant (DBUSMENU_MENUITEM(priv->volume_slider_menuitem), + DBUSMENU_VOLUME_MENUITEM_LEVEL); + gdouble volume_percent = g_variant_get_double (v); + + SoundState state = LOW_LEVEL; + + if (volume_percent < 30.0 && volume_percent > 0) { + state = LOW_LEVEL; + } + else if (volume_percent < 70.0 && volume_percent >= 30.0) { + state = MEDIUM_LEVEL; + } + else if (volume_percent >= 70.0) { + state = HIGH_LEVEL; + } + else if (volume_percent == 0.0) { + state = ZERO_LEVEL; + } + return state; +} + +static pa_cvolume +active_sink_construct_mono_volume (const pa_cvolume* vol) +{ + pa_cvolume new_volume; + pa_cvolume_init(&new_volume); + new_volume.channels = 1; + pa_volume_t max_vol = pa_cvolume_max(vol); + pa_cvolume_set(&new_volume, 1, max_vol); + return new_volume; +} + +void +active_sink_determine_blocking_state (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + if (mute_menu_item_is_muted (priv->mute_menuitem)){ + /** + We don't want to set the current state to blocking + as this is a fire and forget event. + */ + sound_service_dbus_update_sound_state (priv->service, + BLOCKED); + } +} + +gint +active_sink_get_index (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + return priv->index; +} + +gboolean +active_sink_is_populated (ActiveSink* sink) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (sink); + return (priv->index != -1); +} + +void +active_sink_deactivate (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + priv->current_sound_state = UNAVAILABLE; + sound_service_dbus_update_sound_state (priv->service, + priv->current_sound_state); + mute_menu_item_enable (priv->mute_menuitem, FALSE); + slider_menu_item_enable (priv->volume_slider_menuitem, FALSE); + priv->index = -1; + g_free(priv->name); + priv->name = NULL; +} + +SoundState +active_sink_get_state (ActiveSink* self) +{ + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self); + return priv->current_sound_state; +} + +ActiveSink* +active_sink_new (SoundServiceDbus* service) +{ + ActiveSink* sink = g_object_new (ACTIVE_SINK_TYPE, NULL); + ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (sink); + priv->service = service; + sound_service_dbus_build_sound_menu (service, + mute_menu_item_get_button (priv->mute_menuitem), + DBUSMENU_MENUITEM (priv->volume_slider_menuitem)); + pm_establish_pulse_connection (sink); + return sink; +} diff --git a/src/active-sink.h b/src/active-sink.h new file mode 100644 index 0000000..ab05ebc --- /dev/null +++ b/src/active-sink.h @@ -0,0 +1,72 @@ +/* + * Copyright 2010 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 + * 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 + * 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/>. + */ + +#ifndef __ACTIVE_SINK_H__ +#define __ACTIVE_SINK_H__ + +#include <glib.h> +#include <glib-object.h> + +#include "common-defs.h" +#include "sound-service-dbus.h" + +#include <pulse/pulseaudio.h> + +G_BEGIN_DECLS + +#define ACTIVE_SINK_TYPE (active_sink_get_type ()) +#define ACTIVE_SINK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ACTIVE_SINK_TYPE, ActiveSink)) +#define ACTIVE_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), ACTIVE_SINK_TYPE, ActiveSinkClass)) +#define IS_ACTIVE_SINK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ACTIVE_SINK_TYPE)) +#define IS_ACTIVE_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ACTIVE_SINK_TYPE)) +#define ACTIVE_SINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ACTIVE_SINK_TYPE, ActiveSinkClass)) + +typedef struct _ActiveSink ActiveSink; +typedef struct _ActiveSinkClass ActiveSinkClass; + +struct _ActiveSink { + GObject parent; +}; + +struct _ActiveSinkClass { + GObjectClass parent_class; +}; + +GType active_sink_get_type (void) G_GNUC_CONST; + +void active_sink_populate (ActiveSink* sink, const pa_sink_info* update); +void active_sink_update (ActiveSink* sink, const pa_sink_info* update); + +gboolean active_sink_is_populated (ActiveSink* sink); +void active_sink_determine_blocking_state (ActiveSink* self); + +gint active_sink_get_index (ActiveSink* self); +SoundState active_sink_get_state (ActiveSink* self); + +void active_sink_deactivate (ActiveSink* self); + +void active_sink_update_mute (ActiveSink* self, gboolean mute_update); +void active_sink_update_volume (ActiveSink* self, gdouble percent); +void active_sink_ensure_sink_is_unmuted (ActiveSink* self); + +ActiveSink* active_sink_new (SoundServiceDbus* service); + +G_END_DECLS + +#endif diff --git a/src/mute-menu-item.c b/src/mute-menu-item.c index f7f3824..8409b9f 100644 --- a/src/mute-menu-item.c +++ b/src/mute-menu-item.c @@ -24,7 +24,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "common-defs.h" #include "mute-menu-item.h" -#include "pulse-manager.h" +#include "pulseaudio-mgr.h" typedef struct _MuteMenuItemPrivate MuteMenuItemPrivate; @@ -62,6 +62,9 @@ mute_menu_item_init (MuteMenuItem *self) g_debug("Building new Mute Menu Item"); MuteMenuItemPrivate* priv = MUTE_MENU_ITEM_GET_PRIVATE(self); priv->button = dbusmenu_menuitem_new(); + dbusmenu_menuitem_property_set_bool (priv->button, + DBUSMENU_MENUITEM_PROP_VISIBLE, + TRUE); g_signal_connect (G_OBJECT (priv->button), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, @@ -90,10 +93,8 @@ mute_menu_item_set_global_mute_from_ui (gpointer user_data) DbusmenuMenuitem* button = DBUSMENU_MENUITEM (user_data); gboolean current_value = dbusmenu_menuitem_property_get_bool (button, DBUSMENU_MUTE_MENUITEM_VALUE); - gboolean new_value = !current_value; - // pa manager api - to be refactored - toggle_global_mute (new_value); + pm_update_mute (new_value); } void @@ -113,6 +114,9 @@ void mute_menu_item_enable (MuteMenuItem* item, gboolean active) { MuteMenuItemPrivate* priv = MUTE_MENU_ITEM_GET_PRIVATE (item); + dbusmenu_menuitem_property_set_bool (priv->button, + DBUSMENU_MENUITEM_PROP_VISIBLE, + TRUE); dbusmenu_menuitem_property_set_bool (priv->button, DBUSMENU_MENUITEM_PROP_ENABLED, diff --git a/src/pulse-manager.c b/src/pulse-manager.c deleted file mode 100644 index 457992b..0000000 --- a/src/pulse-manager.c +++ /dev/null @@ -1,589 +0,0 @@ -/* -A small wrapper utility to load indicators and put them as menu items -into the gnome-panel using it's applet interface. - -Copyright 2010 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 -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 -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/>. -*/ - -#include <pulse/glib-mainloop.h> -#include <pulse/error.h> -#include <pulse/gccmacro.h> - -#include "pulse-manager.h" - -#define RECONNECT_DELAY 5 - -static GHashTable *sink_hash = NULL; -static SoundServiceDbus *dbus_service = NULL; -static gint DEFAULT_SINK_INDEX = -1; -static gboolean pa_server_available = FALSE; -static gint reconnect_idle_id = 0; -static pa_context *pulse_context = NULL; -static pa_glib_mainloop *pa_main_loop = NULL; - -static void context_state_callback(pa_context *c, void *userdata); -static gboolean reconnect_to_pulse(); -static void pulse_sink_info_callback(pa_context *c, const pa_sink_info *sink_info, int eol, void *userdata); -static void context_success_callback(pa_context *c, int success, void *userdata); -static void pulse_sink_input_info_callback(pa_context *c, const pa_sink_input_info *info, int eol, void *userdata); -static void pulse_server_info_callback(pa_context *c, const pa_server_info *info, void *userdata); -static void update_sink_info(pa_context *c, const pa_sink_info *info, int eol, void *userdata); -static void destroy_sink_info(void *value); -static gboolean determine_sink_availability(); - -static gboolean has_volume_changed(const pa_sink_info* new_sink, sink_info* cached_sink); -static pa_cvolume construct_mono_volume(const pa_cvolume* vol); - -/** -Future Refactoring notes - - rewrite in vala. - - make sure all state is kept in the service for volume icon switching. -**/ - -/** -Entry point -**/ -void establish_pulse_activities(SoundServiceDbus *service) -{ - dbus_service = service; - pa_main_loop = pa_glib_mainloop_new(g_main_context_default()); - g_assert(pa_main_loop); - pulse_context = pa_context_new(pa_glib_mainloop_get_api(pa_main_loop), - "com.canonical.indicators.sound"); - g_assert(pulse_context); - - sink_hash = g_hash_table_new_full(g_direct_hash, - g_direct_equal, - NULL, - destroy_sink_info); - - // Establish event callback registration - pa_context_set_state_callback (pulse_context, context_state_callback, NULL); - sound_service_dbus_update_pa_state (dbus_service, FALSE, FALSE, 0); - pa_context_connect (pulse_context, NULL, PA_CONTEXT_NOFAIL, NULL); -} - -/** -get_context() -Needed for testing - bah! -**/ -pa_context* get_context() -{ - return pulse_context; -} - -static gboolean -reconnect_to_pulse() -{ - g_debug("Attempt to reconnect to pulse"); - // reset - if (pulse_context != NULL) { - pa_context_unref(pulse_context); - pulse_context = NULL; - } - - if (sink_hash != NULL) { - g_hash_table_destroy(sink_hash); - sink_hash = NULL; - } - pulse_context = pa_context_new( pa_glib_mainloop_get_api( pa_main_loop ), - "com.canonical.indicators.sound" ); - g_assert(pulse_context); - sink_hash = g_hash_table_new_full( g_direct_hash, g_direct_equal, - NULL, - destroy_sink_info ); - // Establish event callback registration - pa_context_set_state_callback (pulse_context, context_state_callback, NULL); - int result = pa_context_connect (pulse_context, NULL, PA_CONTEXT_NOFAIL, NULL); - - if (result < 0) { - g_warning ("Failed to connect context: %s", - pa_strerror (pa_context_errno (pulse_context))); - } - // we always want to cancel any continious callbacks with the existing timeout - // if the connection failed the new context created above will catch any updates - // to do with the state of pulse and thus take care of business. - reconnect_idle_id = 0; - return FALSE; -} - -/** -close_pulse_activites() -Gracefully close our connection with the Pulse async library. -**/ -void close_pulse_activites() -{ - if (pulse_context != NULL) { - /* g_debug("freeing the pulse context");*/ - pa_context_unref(pulse_context); - pulse_context = NULL; - } - g_hash_table_destroy(sink_hash); - pa_glib_mainloop_free(pa_main_loop); - pa_main_loop = NULL; - /* g_debug("I just closed communication with Pulse");*/ -} - -/** -destroy_sink_info() -item destructor method for the sink_info hash -**/ -static void destroy_sink_info(void *value) -{ - sink_info *sink = (sink_info*)value; - g_free(sink->name); - g_free(sink); -} - -/* -Controllers & Utilities -*/ -static gboolean determine_sink_availability() -{ - // Firstly check to see if we have any sinks - // if not get the hell out of here ! - if (g_hash_table_size(sink_hash) < 1) { - g_debug("Sink_available returning false because sinks_hash is empty !!!"); - DEFAULT_SINK_INDEX = -1; - return FALSE; - } - // Secondly, make sure the default sink index is set - // If the default sink index has not been set - // (via the server or has been reset because default sink has been removed), - // it will attempt to set it to the value of the first - // index in the array of keys from the sink_hash. - GList* keys = g_hash_table_get_keys(sink_hash); - GList* key = g_list_first(keys); - - DEFAULT_SINK_INDEX = (DEFAULT_SINK_INDEX < 0) ? GPOINTER_TO_INT(key->data) : DEFAULT_SINK_INDEX; - - // Thirdly ensure the default sink index does not have the name "auto_null" - sink_info* s = g_hash_table_lookup(sink_hash, GINT_TO_POINTER(DEFAULT_SINK_INDEX)); - // Up until now the most robust method to test this is to manually remove the available sink device - // kernel module and then reload (rmmod & modprobe). - // TODO: Edge case of dynamic loading and unloading of sinks should be handled also. - /* g_debug("About to test for to see if the available sink is null - s->name = %s", s->name);*/ - gboolean available = g_ascii_strncasecmp("auto_null", s->name, 9) != 0; - /* g_debug("PA_Manager -> determine_sink_availability: %i", available);*/ - return available; -} - -gboolean default_sink_is_muted() -{ - if (DEFAULT_SINK_INDEX < 0) - return FALSE; - if (g_hash_table_size(sink_hash) < 1) - return FALSE; - sink_info *s = g_hash_table_lookup(sink_hash, GINT_TO_POINTER(DEFAULT_SINK_INDEX)); - return s->mute; -} - -static void check_sink_input_while_muted_event(gint sink_index) -{ - /* g_debug("SINKINPUTWHILEMUTED SIGNAL EVENT TO BE SENT FROM PA MANAGER - check trace for value");*/ - - if (default_sink_is_muted(sink_index) == TRUE) { - sound_service_dbus_update_sound_state(dbus_service, BLOCKED); - } -// Why do you need to send a false for a blocked event, it times out after 5 secs anyway -//} else { - // sound_service_dbus_sink_input_while_muted(dbus_service, FALSE); - //} -} - -static gdouble get_default_sink_volume() -{ - if (DEFAULT_SINK_INDEX < 0) - return 0; - sink_info *s = g_hash_table_lookup(sink_hash, GINT_TO_POINTER(DEFAULT_SINK_INDEX)); - pa_volume_t vol = pa_cvolume_avg(&s->volume); - gdouble volume_percent = ((gdouble) vol * 100) / PA_VOLUME_NORM; - /* g_debug("software volume = %f", volume_percent);*/ - return volume_percent; -} - -static void mute_each_sink(gpointer key, gpointer value, gpointer user_data) -{ - sink_info *info = (sink_info*)value; - pa_operation_unref(pa_context_set_sink_mute_by_index(pulse_context, info->index, GPOINTER_TO_INT(user_data), context_success_callback, NULL)); - if (GPOINTER_TO_INT(user_data) == 1) { - sound_service_dbus_update_sink_mute(dbus_service, TRUE); - } else { - sound_service_dbus_update_volume(dbus_service, get_default_sink_volume()); - } -} - -void toggle_global_mute(gboolean mute_value) -{ - g_hash_table_foreach(sink_hash, mute_each_sink, GINT_TO_POINTER(mute_value)); - /* g_debug("in the pulse manager: toggle global mute value %i", mute_value);*/ -} - - -/* -Refine the resolution of the slider or binary scale it to achieve a more subtle volume control. -Use the base volume stored in the sink struct to calculate actual linear volumes. -*/ -void set_sink_volume(gdouble percent) -{ - if (pa_server_available == FALSE) - return; - /* g_debug("in the pulse manager:set_sink_volume with percent %f", percent);*/ - - if (DEFAULT_SINK_INDEX < 0) { - g_warning("We have no default sink !!! - returning after not attempting to set any volume of any sink"); - return; - } - - sink_info *cached_sink = g_hash_table_lookup(sink_hash, GINT_TO_POINTER(DEFAULT_SINK_INDEX)); - - pa_cvolume new_volume; - pa_cvolume_init(&new_volume); - new_volume.channels = 1; - pa_volume_t new_volume_value = (pa_volume_t) ((percent * PA_VOLUME_NORM) / 100); - pa_cvolume_set(&new_volume, 1, new_volume_value); - pa_cvolume_set(&cached_sink->volume, cached_sink->channel_map.channels, new_volume_value); - pa_operation_unref(pa_context_set_sink_volume_by_index(pulse_context, DEFAULT_SINK_INDEX, &new_volume, NULL, NULL)); -} - - -static pa_cvolume construct_mono_volume(const pa_cvolume* vol) -{ - pa_cvolume new_volume; - pa_cvolume_init(&new_volume); - new_volume.channels = 1; - pa_volume_t max_vol = pa_cvolume_max(vol); - pa_cvolume_set(&new_volume, 1, max_vol); - return new_volume; -} - -/**********************************************************************************************************************/ -// Pulse-Audio asychronous call-backs -/**********************************************************************************************************************/ - -static void gather_pulse_information(pa_context *c, void *userdata) -{ - pa_operation *operation; - if (!(operation = pa_context_get_server_info(c, pulse_server_info_callback, userdata))) { - g_warning("pa_context_get_server_info failed"); - if (!(operation = pa_context_get_sink_info_list(c, pulse_sink_info_callback, NULL))) { - g_warning("pa_context_get_sink_info_list() failed - cannot fetch server or sink info - leaving . . ."); - return; - } - } - pa_operation_unref(operation); - return; -} - - -static void context_success_callback(pa_context *c, int success, void *userdata) -{ - /* g_debug("Context Success Callback - result = %i", success);*/ -} - -/** -On Service startup this callback will be called multiple times resulting our sinks_hash container to be filled with the -available sinks. -For now this callback assumes it only used at startup. It may be necessary to use if sinks become available after startup. -Major candidate for refactoring. -**/ -static void pulse_sink_info_callback(pa_context *c, const pa_sink_info *sink, int eol, void *userdata) -{ - if (eol > 0) { - - gboolean device_available = determine_sink_availability(); - if (device_available == TRUE) { - sound_service_dbus_update_pa_state( dbus_service, - device_available, - default_sink_is_muted(), - get_default_sink_volume() ); - } else { - //Update the indicator to show PA either is not ready or has no available sink - g_warning("Cannot find a suitable default sink ..."); - sound_service_dbus_update_pa_state( dbus_service, - device_available, - default_sink_is_muted(), - get_default_sink_volume() ); - } - } else { - /* g_debug("About to add an item to our hash");*/ - sink_info *value; - value = g_new0(sink_info, 1); - value->index = sink->index; - value->name = g_strdup(sink->name); - value->mute = !!sink->mute; - value->volume = construct_mono_volume(&sink->volume); - value->base_volume = sink->base_volume; - value->channel_map = sink->channel_map; - g_hash_table_insert(sink_hash, GINT_TO_POINTER(sink->index), value); - /* g_debug("After adding an item to our hash");*/ - } -} - -static void pulse_default_sink_info_callback(pa_context *c, const pa_sink_info *info, int eol, void *userdata) -{ - if (eol > 0) { - return; - } else { - DEFAULT_SINK_INDEX = info->index; - /* g_debug("Just set the default sink index to %i", DEFAULT_SINK_INDEX); */ - GList *keys = g_hash_table_get_keys(sink_hash); - gint position = g_list_index(keys, GINT_TO_POINTER(info->index)); - // Only update sink-list if the index is not in our already fetched list. - if (position < 0) { - pa_operation_unref(pa_context_get_sink_info_list(c, pulse_sink_info_callback, NULL)); - } else { - sound_service_dbus_update_pa_state(dbus_service, - determine_sink_availability(), - default_sink_is_muted(), - get_default_sink_volume()); - } - } -} - -static void pulse_sink_input_info_callback(pa_context *c, const pa_sink_input_info *info, int eol, void *userdata) -{ - if (eol > 0) { - if (pa_context_errno(c) == PA_ERR_NOENTITY) - return; - /* g_warning("Sink INPUT info callback failure");*/ - return; - } else { - if (info == NULL) { - // TODO: watch this carefully - PA async api should not be doing this . . . - /* g_warning("\n Sink input info callback : SINK INPUT INFO IS NULL BUT EOL was not POSITIVE!!!");*/ - return; - } - /* g_debug("\n SINK INPUT INFO sink index : %d \n", info->sink);*/ - check_sink_input_while_muted_event(info->sink); - } -} - -static void update_sink_info(pa_context *c, const pa_sink_info *info, int eol, void *userdata) -{ - if (eol > 0) { - if (pa_context_errno(c) == PA_ERR_NOENTITY) - return; - /* g_warning("Sink INPUT info callback failure");*/ - return; - } - gint position = -1; - GList *keys = g_hash_table_get_keys(sink_hash); - - if (info == NULL) - return; - - position = g_list_index(keys, GINT_TO_POINTER(info->index)); - - if (position >= 0) { // => index is within the keys of the hash. - sink_info *s = g_hash_table_lookup(sink_hash, GINT_TO_POINTER(info->index)); - s->name = g_strdup(info->name); - gboolean mute_changed = s->mute != !!info->mute; - s->mute = !!info->mute; - gboolean volume_changed = has_volume_changed(info, s); - - /* g_debug("new balance : %i", (int)(pa_cvolume_get_balance(&info->volume, &info->channel_map) * 100));*/ - /* g_debug("cached balance : %i", (int)(pa_cvolume_get_balance(&s->volume, &s->channel_map) * 100));*/ - /* g_debug("update_sink_info: new_volume input : %f", (gdouble)(pa_cvolume_max(&info->volume)));*/ - /* g_debug("update sink info: cached volume is at: %f", (gdouble)(pa_cvolume_max(&s->volume)));*/ - /* g_debug("update sink info : volume changed = %i", volume_changed);*/ - /* g_debug("update sink info : compatibility = %i", pa_cvolume_compatible_with_channel_map(&info->volume, &s->channel_map));*/ - - s->volume = construct_mono_volume(&info->volume); - s->channel_map = info->channel_map; - - if (DEFAULT_SINK_INDEX == s->index) { - //update the UI - if (volume_changed == TRUE && s->mute == FALSE) { - pa_volume_t vol = pa_cvolume_max(&s->volume); - gdouble volume_percent = ((gdouble) vol * 100) / PA_VOLUME_NORM; - /* g_debug("Updating volume from PA manager with volume = %f", volume_percent);*/ - sound_service_dbus_update_volume(dbus_service, volume_percent); - } - - if (mute_changed == TRUE) { - /* g_debug("Updating Mute from PA manager with mute = %i", s->mute);*/ - sound_service_dbus_update_sink_mute(dbus_service, s->mute); - if (s->mute == FALSE) { - pa_volume_t vol = pa_cvolume_max(&s->volume); - gdouble volume_percent = ((gdouble) vol * 100) / PA_VOLUME_NORM; - /* g_debug("Updating volume from PA manager with volume = %f", volume_percent);*/ - sound_service_dbus_update_volume(dbus_service, volume_percent); - } - } - } - } else { - sink_info *value; - value = g_new0(sink_info, 1); - value->index = info->index; - value->name = g_strdup(info->name); - value->mute = !!info->mute; - value->volume = construct_mono_volume(&info->volume); - value->channel_map = info->channel_map; - value->base_volume = info->base_volume; - g_hash_table_insert(sink_hash, GINT_TO_POINTER(value->index), value); - /* g_debug("pulse-manager:update_sink_info -> After adding a new sink to our hash");*/ - sound_service_dbus_update_sound_state(dbus_service, AVAILABLE); - } -} - - -static gboolean has_volume_changed(const pa_sink_info* new_sink, sink_info* cached_sink) -{ - if (pa_cvolume_compatible_with_channel_map(&new_sink->volume, &cached_sink->channel_map) == FALSE) - return FALSE; - - pa_cvolume new_vol = construct_mono_volume(&new_sink->volume); - - if (pa_cvolume_equal(&new_vol, &(cached_sink->volume)) == TRUE) { - /* g_debug("has_volume_changed: volumes appear to be equal? no change triggered!"); */ - return FALSE; - } - - return TRUE; -} - - -static void pulse_server_info_callback(pa_context *c, - const pa_server_info *info, - void *userdata) -{ - /* g_debug("server info callback");*/ - pa_operation *operation; - if (info == NULL) { - g_warning("No server - get the hell out of here"); - sound_service_dbus_update_pa_state(dbus_service, FALSE, TRUE, 0); - pa_server_available = FALSE; - return; - } - pa_server_available = TRUE; - if (info->default_sink_name != NULL) { - if (!(operation = pa_context_get_sink_info_by_name(c, - info->default_sink_name, - pulse_default_sink_info_callback, - userdata))) { - g_warning("pa_context_get_sink_info_by_name() failed"); - } else { - pa_operation_unref(operation); - return; - } - } - if (!(operation = pa_context_get_sink_info_list(c, pulse_sink_info_callback, NULL))) { - g_warning("pa_context_get_sink_info_list() failed"); - return; - } - pa_operation_unref(operation); -} - - -static void subscribed_events_callback(pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) -{ - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { - case PA_SUBSCRIPTION_EVENT_SINK: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (index == DEFAULT_SINK_INDEX) - sound_service_dbus_update_sound_state(dbus_service, UNAVAILABLE); - - /* g_debug("Subscribed_events_callback - removing sink of index %i from our sink hash - keep the cache tidy !", index);*/ - g_hash_table_remove(sink_hash, GINT_TO_POINTER(index)); - - if (index == DEFAULT_SINK_INDEX) { - /* g_debug("subscribed_events_callback - PA_SUBSCRIPTION_EVENT_SINK REMOVAL: default sink %i has been removed.", DEFAULT_SINK_INDEX); */ - DEFAULT_SINK_INDEX = -1; - determine_sink_availability(); - } - /* g_debug("subscribed_events_callback - Now what is our default sink : %i", DEFAULT_SINK_INDEX); */ - } else { - /* g_debug("subscribed_events_callback - PA_SUBSCRIPTION_EVENT_SINK: a generic sink event - will trigger an update"); */ - pa_operation_unref(pa_context_get_sink_info_by_index(c, index, update_sink_info, userdata)); - } - break; - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - /* g_debug("subscribed_events_callback - PA_SUBSCRIPTION_EVENT_SINK_INPUT event triggered!!");*/ - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - //handle the sink input remove event - not relevant for current design - } else { - pa_operation_unref(pa_context_get_sink_input_info(c, index, pulse_sink_input_info_callback, userdata)); - } - break; - case PA_SUBSCRIPTION_EVENT_SERVER: - g_debug("subscribed_events_callback - PA_SUBSCRIPTION_EVENT_SERVER change of some description ???"); - pa_operation *o; - if (!(o = pa_context_get_server_info(c, pulse_server_info_callback, userdata))) { - g_warning("subscribed_events_callback - pa_context_get_server_info() failed"); - return; - } - pa_operation_unref(o); - break; - } -} - - -static void context_state_callback(pa_context *c, void *userdata) -{ - switch (pa_context_get_state(c)) { - case PA_CONTEXT_UNCONNECTED: - g_debug("unconnected"); - break; - case PA_CONTEXT_CONNECTING: - g_debug("connecting - waiting for the server to become available"); - break; - case PA_CONTEXT_AUTHORIZING: - /* g_debug("authorizing");*/ - break; - case PA_CONTEXT_SETTING_NAME: - /* g_debug("context setting name");*/ - break; - case PA_CONTEXT_FAILED: - g_warning("PA_CONTEXT_FAILED - Is PulseAudio Daemon running ?"); - pa_server_available = FALSE; - sound_service_dbus_update_pa_state( dbus_service, - pa_server_available, - default_sink_is_muted(), - get_default_sink_volume() ); - - if (reconnect_idle_id == 0){ - reconnect_idle_id = g_timeout_add_seconds (RECONNECT_DELAY, - reconnect_to_pulse, - NULL); - } - break; - case PA_CONTEXT_TERMINATED: - /* g_debug("context terminated");*/ - break; - case PA_CONTEXT_READY: - g_debug("PA_CONTEXT_READY"); - pa_operation *o; - - pa_context_set_subscribe_callback(c, subscribed_events_callback, userdata); - - if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) - (PA_SUBSCRIPTION_MASK_SINK| - PA_SUBSCRIPTION_MASK_SINK_INPUT| - PA_SUBSCRIPTION_MASK_SERVER), NULL, NULL))) { - g_warning("pa_context_subscribe() failed"); - return; - } - pa_operation_unref(o); - - gather_pulse_information(c, userdata); - - break; - } -} - diff --git a/src/pulseaudio-mgr.c b/src/pulseaudio-mgr.c new file mode 100644 index 0000000..1a2b3e0 --- /dev/null +++ b/src/pulseaudio-mgr.c @@ -0,0 +1,404 @@ +/* +Copyright 2011 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 +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 +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/>. +*/ + +/**Notes + * + * Approach now is to set up the communication channels then query the server + * fetch its default sink. If this fails then fetch the list of sinks and take + * the first one which is not the auto-null sink. + * TODO: need to handle the situation where one chink in this linear chain breaks + * i.e. start off the process again and count the attempts (note different to + reconnect attempts) + */ +#include <pulse/gccmacro.h> +#include <pulse/glib-mainloop.h> +#include <pulse/error.h> + +#include "pulseaudio-mgr.h" + +#define RECONNECT_DELAY 5 + + +static void pm_context_state_callback(pa_context *c, void *userdata); +static void pm_subscribed_events_callback (pa_context *c, + enum pa_subscription_event_type t, + uint32_t index, + void* userdata); +static void pm_server_info_callback (pa_context *c, + const pa_server_info *info, + void *userdata); +static void pm_default_sink_info_callback (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata); +static void pm_sink_info_callback (pa_context *c, + const pa_sink_info *sink, + int eol, + void *userdata); +static void pm_sink_input_info_callback (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata); +static void pm_update_active_sink (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata); +static void pm_toggle_mute_for_every_sink_callback (pa_context *c, + const pa_sink_info *sink, + int eol, + void* userdata); + +static gboolean reconnect_to_pulse (gpointer user_data); + +static gint connection_attempts = 0; +static gint reconnect_idle_id = 0; +static pa_context *pulse_context = NULL; +static pa_glib_mainloop *pa_main_loop = NULL; + +/** + Entry Point + **/ +void +pm_establish_pulse_connection (ActiveSink* active_sink) +{ + pa_main_loop = pa_glib_mainloop_new (g_main_context_default ()); + g_assert (pa_main_loop); + reconnect_to_pulse ((gpointer)active_sink); +} + +/** +close_pulse_activites() +Gracefully close our connection with the Pulse async library. +**/ +void close_pulse_activites() +{ + if (pulse_context != NULL) { + pa_context_unref(pulse_context); + pulse_context = NULL; + } + pa_glib_mainloop_free(pa_main_loop); + pa_main_loop = NULL; +} + +/** +reconnect_to_pulse (gpointer user_data) +Method which connects to the pulse server and is used to track reconnects. + */ +static gboolean +reconnect_to_pulse (gpointer user_data) +{ + g_debug("Attempt to reconnect to pulse"); + // reset + connection_attempts += 1; + if (pulse_context != NULL) { + pa_context_unref(pulse_context); + pulse_context = NULL; + } + + pulse_context = pa_context_new( pa_glib_mainloop_get_api( pa_main_loop ), + "com.canonical.indicators.sound" ); + g_assert(pulse_context); + pa_context_set_state_callback (pulse_context, + pm_context_state_callback, + user_data); + int result = pa_context_connect (pulse_context, + NULL, + PA_CONTEXT_NOFAIL, + user_data); + + if (result < 0) { + g_warning ("Failed to connect context: %s", + pa_strerror (pa_context_errno (pulse_context))); + } + + reconnect_idle_id = 0; + if (connection_attempts > 5){ + return FALSE; + } + else{ + return TRUE; + } +} + +void +pm_update_volume (gint sink_index, pa_cvolume new_volume) +{ + pa_operation_unref (pa_context_set_sink_volume_by_index (pulse_context, + sink_index, + &new_volume, + NULL, + NULL) ); +} + +void +pm_update_mute (gboolean update) +{ + pa_operation_unref (pa_context_get_sink_info_list (pulse_context, + pm_toggle_mute_for_every_sink_callback, + GINT_TO_POINTER (update))); +} + +/**********************************************************************************************************************/ +// Pulse-Audio asychronous call-backs +/**********************************************************************************************************************/ + + +static void +pm_subscribed_events_callback (pa_context *c, + enum pa_subscription_event_type t, + uint32_t index, + void* userdata) +{ + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + if (IS_ACTIVE_SINK (userdata) == FALSE){ + g_warning ("subscribed events callback - our userdata is not what we think it should be"); + return; + } + ActiveSink* sink = ACTIVE_SINK (userdata); + // We don't care about any other sink other than the active one. + if (index != active_sink_get_index (sink)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + active_sink_deactivate (ACTIVE_SINK (userdata)); + + } + else{ + pa_operation_unref (pa_context_get_sink_info_by_index (c, + index, + pm_update_active_sink, + userdata) ); + } + break; + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + // We don't care about sink input removals. + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_REMOVE) { + pa_operation_unref (pa_context_get_sink_input_info (c, + index, + pm_sink_input_info_callback, userdata)); + } + break; + case PA_SUBSCRIPTION_EVENT_SERVER: + g_debug("PA_SUBSCRIPTION_EVENT_SERVER event triggered."); + pa_operation *o; + if (!(o = pa_context_get_server_info (c, pm_server_info_callback, userdata))) { + g_warning("subscribed_events_callback - pa_context_get_server_info() failed"); + return; + } + pa_operation_unref(o); + break; + } +} + + +static void +pm_context_state_callback (pa_context *c, void *userdata) +{ + switch (pa_context_get_state(c)) { + case PA_CONTEXT_UNCONNECTED: + g_debug("unconnected"); + break; + case PA_CONTEXT_CONNECTING: + g_debug("connecting - waiting for the server to become available"); + break; + case PA_CONTEXT_AUTHORIZING: + break; + case PA_CONTEXT_SETTING_NAME: + break; + case PA_CONTEXT_FAILED: + g_warning("PA_CONTEXT_FAILED - Is PulseAudio Daemon running ?"); + active_sink_deactivate (ACTIVE_SINK (userdata)); + if (reconnect_idle_id == 0){ + reconnect_idle_id = g_timeout_add_seconds (RECONNECT_DELAY, + reconnect_to_pulse, + userdata); + } + break; + case PA_CONTEXT_TERMINATED: + break; + case PA_CONTEXT_READY: + connection_attempts = 0; + g_debug("PA_CONTEXT_READY"); + pa_operation *o; + + pa_context_set_subscribe_callback(c, pm_subscribed_events_callback, userdata); + + if (!(o = pa_context_subscribe (c, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK| + PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SERVER), NULL, NULL))) { + g_warning("pa_context_subscribe() failed"); + + } + + if (!(o = pa_context_get_server_info (c, pm_server_info_callback, userdata))) { + g_warning("Initial - pa_context_get_server_info() failed"); + } + pa_operation_unref(o); + + break; + } +} + +/** + After startup we go straight for the server info to see if it has details of + the default sink. If so it makes things much easier. + **/ +static void +pm_server_info_callback (pa_context *c, + const pa_server_info *info, + void *userdata) +{ + pa_operation *operation; + g_debug ("server info callback"); + + if (info == NULL) { + g_warning("No PA server - get the hell out of here"); + active_sink_deactivate (ACTIVE_SINK (userdata)); + return; + } + if (info->default_sink_name != NULL) { + g_debug ("default sink name from the server ain't null'"); + if (!(operation = pa_context_get_sink_info_by_name (c, + info->default_sink_name, + pm_default_sink_info_callback, + userdata) )) { + } + else{ + pa_operation_unref(operation); + return; + } + } + else if (!(operation = pa_context_get_sink_info_list(c, + pm_sink_info_callback, + NULL))) { + g_warning("pa_context_get_sink_info_list() failed"); + return; + } + pa_operation_unref(operation); +} + +// If the server doesn't have a default sink to give us +// we should attempt to pick up the first of the list of sinks which doesn't have +// the name 'auto_null' (that was all really I was doing before) +static void +pm_sink_info_callback (pa_context *c, + const pa_sink_info *sink, + int eol, + void* userdata) +{ + if (eol > 0) { + return; + } + else { + if (IS_ACTIVE_SINK (userdata) == FALSE){ + g_warning ("sink info callback - our user data is not what we think it should be"); + return; + } + ActiveSink* a_sink = ACTIVE_SINK (userdata); + if (active_sink_is_populated (a_sink) == FALSE && + g_ascii_strncasecmp("auto_null", sink->name, 9) != 0){ + active_sink_populate (a_sink, sink); + } + } +} + +static void +pm_default_sink_info_callback (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata) +{ + if (eol > 0) { + return; + } + else { + if (IS_ACTIVE_SINK (userdata) == FALSE){ + g_warning ("Default sink info callback - our user data is not what we think it should be"); + return; + } + g_debug ("server has handed us a default sink"); + active_sink_populate (ACTIVE_SINK (userdata), info); + } +} + +static void +pm_sink_input_info_callback (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata) +{ + if (eol > 0) { + return; + } + else { + if (info == NULL) { + // TODO: watch this carefully - PA async api should not be doing this . . . + g_warning("\n Sink input info callback : SINK INPUT INFO IS NULL BUT EOL was not POSITIVE!!!"); + return; + } + if (IS_ACTIVE_SINK (userdata) == FALSE){ + g_warning ("sink input info callback - our user data is not what we think it should be"); + return; + } + + ActiveSink* a_sink = ACTIVE_SINK (userdata); + if (active_sink_get_index (a_sink) == info->sink){ + active_sink_determine_blocking_state (a_sink); + } + } +} + +static void +pm_update_active_sink (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata) +{ + if (eol > 0) { + return; + } + else{ + if (IS_ACTIVE_SINK (userdata) == FALSE){ + g_warning ("update_active_sink - our user data is not what we think it should be"); + return; + } + active_sink_update (ACTIVE_SINK(userdata), info); + } +} + +static void +pm_toggle_mute_for_every_sink_callback (pa_context *c, + const pa_sink_info *sink, + int eol, + void* userdata) +{ + if (eol > 0) { + return; + } + else { + pa_operation_unref (pa_context_set_sink_mute_by_index (c, + sink->index, + GPOINTER_TO_INT(userdata), + NULL, + NULL)); + } +} + diff --git a/src/pulse-manager.h b/src/pulseaudio-mgr.h index 5895aeb..c0ab9c0 100644 --- a/src/pulse-manager.h +++ b/src/pulseaudio-mgr.h @@ -1,7 +1,5 @@ -#ifndef __INCLUDE_PULSE_MANAGER_H__ -#define __INCLUDE_PULSE_MANAGER_H__ /* -Copyright 2010 Canonical Ltd. +Copyright 2011 Canonical Ltd. Authors: Conor Curran <conor.curran@canonical.com> @@ -19,27 +17,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "active-sink.h" + +void pm_establish_pulse_connection (ActiveSink* active_sink); +void close_pulse_activites(); +void pm_update_volume (gint sink_index, pa_cvolume new_volume); +void pm_update_mute (gboolean update); -#include <pulse/pulseaudio.h> -#include <glib.h> -#include "sound-service-dbus.h" -typedef struct { - gchar* name; - gint index; - pa_cvolume volume; - pa_channel_map channel_map; - gboolean mute; - pa_volume_t base_volume; -} sink_info; -pa_context* get_context(void); -void establish_pulse_activities(SoundServiceDbus *service); -void set_sink_volume(gdouble percent); -void toggle_global_mute(gboolean mute_value); -void close_pulse_activites(); -gboolean default_sink_is_muted(); -#endif diff --git a/src/slider-menu-item.c b/src/slider-menu-item.c index a20bb00..b89f5ca 100644 --- a/src/slider-menu-item.c +++ b/src/slider-menu-item.c @@ -22,13 +22,12 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <glib/gi18n.h> #include "slider-menu-item.h" -#include "pulse-manager.h" #include "common-defs.h" - typedef struct _SliderMenuItemPrivate SliderMenuItemPrivate; struct _SliderMenuItemPrivate { + ActiveSink* a_sink; }; #define SLIDER_MENU_ITEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SLIDER_MENU_ITEM_TYPE, SliderMenuItemPrivate)) @@ -43,7 +42,8 @@ static void handle_event (DbusmenuMenuitem * mi, const gchar * name, G_DEFINE_TYPE (SliderMenuItem, slider_menu_item, DBUSMENU_TYPE_MENUITEM); -static void slider_menu_item_class_init (SliderMenuItemClass *klass) +static void +slider_menu_item_class_init (SliderMenuItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); @@ -57,13 +57,18 @@ static void slider_menu_item_class_init (SliderMenuItemClass *klass) return; } -static void slider_menu_item_init (SliderMenuItem *self) +static void +slider_menu_item_init (SliderMenuItem *self) { g_debug("Building new Slider Menu Item"); + dbusmenu_menuitem_property_set( DBUSMENU_MENUITEM(self), + DBUSMENU_MENUITEM_PROP_TYPE, + DBUSMENU_VOLUME_MENUITEM_TYPE ); return; } -static void slider_menu_item_dispose (GObject *object) +static void +slider_menu_item_dispose (GObject *object) { G_OBJECT_CLASS (slider_menu_item_parent_class)->dispose (object); return; @@ -89,27 +94,26 @@ handle_event (DbusmenuMenuitem * mi, gboolean volume_input = g_variant_get_double(input); if (value != NULL){ - set_sink_volume(volume_input); - // TODO -when the ACTIVESINK instance exists this will be handled nicely - // PA MANAGER will be refactored first. - if (default_sink_is_muted () == TRUE){ - toggle_global_mute (FALSE); - } + if (IS_SLIDER_MENU_ITEM (mi)) { + SliderMenuItemPrivate* priv = SLIDER_MENU_ITEM_GET_PRIVATE (SLIDER_MENU_ITEM (mi)); + active_sink_update_volume (priv->a_sink, volume_input); + active_sink_ensure_sink_is_unmuted (priv->a_sink); + } } } -void slider_menu_item_update (SliderMenuItem* item, +void +slider_menu_item_update (SliderMenuItem* item, gdouble update) { - // TODO - // Check if that variant below will leak !!! GVariant* new_volume = g_variant_new_double(update); dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(item), DBUSMENU_VOLUME_MENUITEM_LEVEL, new_volume); } -void slider_menu_item_enable (SliderMenuItem* item, +void +slider_menu_item_enable (SliderMenuItem* item, gboolean active) { dbusmenu_menuitem_property_set_bool( DBUSMENU_MENUITEM(item), @@ -117,15 +121,11 @@ void slider_menu_item_enable (SliderMenuItem* item, active ); } -SliderMenuItem* slider_menu_item_new (gboolean sinks_available, - gdouble start_volume) +SliderMenuItem* +slider_menu_item_new (ActiveSink* sink) { SliderMenuItem *self = g_object_new(SLIDER_MENU_ITEM_TYPE, NULL); - dbusmenu_menuitem_property_set( DBUSMENU_MENUITEM(self), - DBUSMENU_MENUITEM_PROP_TYPE, - DBUSMENU_VOLUME_MENUITEM_TYPE ); - slider_menu_item_update (self, start_volume); - slider_menu_item_enable (self, sinks_available); - + SliderMenuItemPrivate* priv = SLIDER_MENU_ITEM_GET_PRIVATE (self); + priv->a_sink = sink; return self; }
\ No newline at end of file diff --git a/src/slider-menu-item.h b/src/slider-menu-item.h index 51336ae..f094c71 100644 --- a/src/slider-menu-item.h +++ b/src/slider-menu-item.h @@ -23,6 +23,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <glib-object.h> #include <libdbusmenu-glib/menuitem.h> +#include "active-sink.h" G_BEGIN_DECLS @@ -49,8 +50,7 @@ GType slider_menu_item_get_type (void); void slider_menu_item_update(SliderMenuItem* item, gdouble update); void slider_menu_item_enable(SliderMenuItem* item, gboolean active); -SliderMenuItem* slider_menu_item_new (gboolean sinks_available, - gdouble current_vol); +SliderMenuItem* slider_menu_item_new (ActiveSink* sink); G_END_DECLS diff --git a/src/sound-service-dbus.c b/src/sound-service-dbus.c index 637bee4..58367f4 100644 --- a/src/sound-service-dbus.c +++ b/src/sound-service-dbus.c @@ -29,13 +29,9 @@ #include <libdbusmenu-glib/client.h> #include "sound-service-dbus.h" - +#include "active-sink.h" #include "gen-sound-service.xml.h" #include "dbus-shared-names.h" -#include "pulse-manager.h" -#include "slider-menu-item.h" -#include "mute-menu-item.h" -#include "pulse-manager.h" // DBUS methods static void bus_method_call (GDBusConnection * connection, @@ -57,16 +53,14 @@ static GDBusInterfaceVTable interface_table = { typedef struct _SoundServiceDbusPrivate SoundServiceDbusPrivate; struct _SoundServiceDbusPrivate { - GDBusConnection* connection; - DbusmenuMenuitem* root_menuitem; - SliderMenuItem* volume_slider_menuitem; - MuteMenuItem* mute_menuitem; - SoundState current_sound_state; + GDBusConnection* connection; + DbusmenuMenuitem* root_menuitem; + ActiveSink* active_sink; }; static GDBusNodeInfo * node_info = NULL; static GDBusInterfaceInfo * interface_info = NULL; -static gboolean b_startup = TRUE; + #define SOUND_SERVICE_DBUS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUND_SERVICE_DBUS_TYPE, SoundServiceDbusPrivate)) static void sound_service_dbus_class_init (SoundServiceDbusClass *klass); @@ -74,17 +68,8 @@ static void sound_service_dbus_init (SoundServiceDbus *self); static void sound_service_dbus_dispose (GObject *object); static void sound_service_dbus_finalize (GObject *object); -static void sound_service_dbus_build_sound_menu ( SoundServiceDbus* root, - gboolean mute_update, - gboolean availability, - gdouble volume ); static void show_sound_settings_dialog (DbusmenuMenuitem *mi, gpointer user_data); -static SoundState sound_service_dbus_get_state_from_volume (SoundServiceDbus* self); -static void sound_service_dbus_determine_state (SoundServiceDbus* self, - gboolean availability, - gboolean mute, - gdouble volume); static gboolean sound_service_dbus_blacklist_player (SoundServiceDbus* self, gchar* player_name, gboolean blacklist); @@ -133,8 +118,6 @@ sound_service_dbus_init (SoundServiceDbus *self) priv->connection = NULL; - priv->current_sound_state = UNAVAILABLE; - /* Fetch the session bus */ priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); @@ -167,32 +150,26 @@ sound_service_dbus_create_root_item (SoundServiceDbus* self) DbusmenuServer *server = dbusmenu_server_new(INDICATOR_SOUND_MENU_DBUS_OBJECT_PATH); dbusmenu_server_set_root (server, priv->root_menuitem); g_object_unref (priv->root_menuitem); - establish_pulse_activities (self); + priv->active_sink = active_sink_new (self); return priv->root_menuitem; } -static void +void sound_service_dbus_build_sound_menu ( SoundServiceDbus* self, - gboolean mute_update, - gboolean availability, - gdouble volume ) + DbusmenuMenuitem* mute_item, + DbusmenuMenuitem* slider_item) { SoundServiceDbusPrivate * priv = SOUND_SERVICE_DBUS_GET_PRIVATE(self); // Mute button - priv->mute_menuitem = mute_menu_item_new ( mute_update, availability); - dbusmenu_menuitem_child_append (priv->root_menuitem, - mute_menu_item_get_button (priv->mute_menuitem)); - - // Slider - priv->volume_slider_menuitem = slider_menu_item_new ( availability, volume ); - dbusmenu_menuitem_child_append (priv->root_menuitem, DBUSMENU_MENUITEM ( priv->volume_slider_menuitem )); + dbusmenu_menuitem_child_append (priv->root_menuitem, mute_item); + g_debug ("just about to add the slider %i", DBUSMENU_IS_MENUITEM(slider_item)); + dbusmenu_menuitem_child_append (priv->root_menuitem, slider_item); // Separator - DbusmenuMenuitem* separator = dbusmenu_menuitem_new(); - dbusmenu_menuitem_property_set( separator, + dbusmenu_menuitem_property_set (separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); dbusmenu_menuitem_child_append(priv->root_menuitem, separator); @@ -207,9 +184,7 @@ sound_service_dbus_build_sound_menu ( SoundServiceDbus* self, dbusmenu_menuitem_child_append(priv->root_menuitem, settings_mi); g_object_unref (settings_mi); g_signal_connect(G_OBJECT(settings_mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, - G_CALLBACK(show_sound_settings_dialog), NULL); - - sound_service_dbus_determine_state (self, availability, mute_update, volume); + G_CALLBACK(show_sound_settings_dialog), NULL); } /** @@ -229,41 +204,11 @@ show_sound_settings_dialog (DbusmenuMenuitem *mi, } } -void -sound_service_dbus_update_pa_state ( SoundServiceDbus* self, - gboolean availability, - gboolean mute_update, - gdouble volume ) -{ - g_debug("update pa state with availability of %i, mute value of %i and a volume percent is %f", availability, mute_update, volume); - SoundServiceDbusPrivate * priv = SOUND_SERVICE_DBUS_GET_PRIVATE(self); - - if (b_startup == TRUE) { - sound_service_dbus_build_sound_menu ( self, - mute_update, - availability, - volume ); - b_startup = FALSE; - return; - } - - mute_menu_item_update ( priv->mute_menuitem, - mute_update ); - slider_menu_item_update ( priv->volume_slider_menuitem, - volume ); - - mute_menu_item_enable ( priv->mute_menuitem, availability); - slider_menu_item_enable ( priv->volume_slider_menuitem, - availability ); - sound_service_dbus_determine_state (self, availability, mute_update, volume); - -} - - static void sound_service_dbus_dispose (GObject *object) { G_OBJECT_CLASS (sound_service_dbus_parent_class)->dispose (object); + //TODO dispose of the active sink instance ! return; } @@ -274,101 +219,19 @@ sound_service_dbus_finalize (GObject *object) return; } -// UNTIL PA-MANAGER IS REFACTORED AND THE ACTIVESINK CLASS IS CREATED LEAVE -// THE UI ELEMENTS SEPARATELY HANDLED LIKE THIS. -void -sound_service_dbus_update_volume (SoundServiceDbus* self, - gdouble volume) -{ - SoundServiceDbusPrivate *priv = SOUND_SERVICE_DBUS_GET_PRIVATE (self); - slider_menu_item_update (priv->volume_slider_menuitem, volume); - sound_service_dbus_update_sound_state (self, - sound_service_dbus_get_state_from_volume (self)); -} - -void -sound_service_dbus_update_sink_mute (SoundServiceDbus* self, - gboolean mute_update) -{ - SoundServiceDbusPrivate *priv = SOUND_SERVICE_DBUS_GET_PRIVATE (self); - mute_menu_item_update (priv->mute_menuitem, mute_update); - SoundState state = sound_service_dbus_get_state_from_volume (self); - if (mute_update == TRUE){ - state = MUTED; - } - sound_service_dbus_update_sound_state (self, state); -} - -/*------- State calculators ------------------*/ -static SoundState -sound_service_dbus_get_state_from_volume (SoundServiceDbus* self) -{ - SoundServiceDbusPrivate *priv = SOUND_SERVICE_DBUS_GET_PRIVATE (self); - GVariant* v = dbusmenu_menuitem_property_get_variant (DBUSMENU_MENUITEM(priv->volume_slider_menuitem), - DBUSMENU_VOLUME_MENUITEM_LEVEL); - gdouble volume_percent = g_variant_get_double (v); - - SoundState state = LOW_LEVEL; - - if (volume_percent < 30.0 && volume_percent > 0) { - state = LOW_LEVEL; - } - else if (volume_percent < 70.0 && volume_percent >= 30.0) { - state = MEDIUM_LEVEL; - } - else if (volume_percent >= 70.0) { - state = HIGH_LEVEL; - } - else if (volume_percent == 0.0) { - state = ZERO_LEVEL; - } - return state; -} - -static void -sound_service_dbus_determine_state (SoundServiceDbus* self, - gboolean availability, - gboolean mute, - gdouble volume) -{ - SoundState update; - if (availability == FALSE) { - update = UNAVAILABLE; - } - else if (mute == TRUE) { - update = MUTED; - } - else{ - update = sound_service_dbus_get_state_from_volume (self); - } - sound_service_dbus_update_sound_state (self, update); -} - // EMIT STATE SIGNAL - -// TODO: this will be a bit messy until the pa_manager is sorted. -// And we figure out all of the edge cases. void sound_service_dbus_update_sound_state (SoundServiceDbus* self, SoundState new_state) { SoundServiceDbusPrivate *priv = SOUND_SERVICE_DBUS_GET_PRIVATE (self); - SoundState update = new_state; - // Ensure that after it has become available update the state with the current volume level - if (new_state == AVAILABLE && - mute_menu_item_is_muted (priv->mute_menuitem) == FALSE){ - update = sound_service_dbus_get_state_from_volume (self); - } - if (update != BLOCKED){ - priv->current_sound_state = update; - } - GVariant* v_output = g_variant_new("(i)", (int)update); + GVariant* v_output = g_variant_new("(i)", (int)new_state); GError * error = NULL; - g_debug ("emitting signal with value %i", (int)update); + g_debug ("emitting state signal with value %i", (int)new_state); g_dbus_connection_emit_signal( priv->connection, NULL, INDICATOR_SOUND_SERVICE_DBUS_OBJECT_PATH, @@ -384,7 +247,6 @@ sound_service_dbus_update_sound_state (SoundServiceDbus* self, } //HANDLE DBUS METHOD CALLS -// TODO we will need to implement the black_list method. static void bus_method_call (GDBusConnection * connection, const gchar * sender, @@ -401,8 +263,8 @@ bus_method_call (GDBusConnection * connection, SoundServiceDbusPrivate *priv = SOUND_SERVICE_DBUS_GET_PRIVATE (service); if (g_strcmp0(method, "GetSoundState") == 0) { - g_debug("Get state - %i", priv->current_sound_state ); - retval = g_variant_new ( "(i)", priv->current_sound_state); + g_debug("Get state - %i", active_sink_get_state (priv->active_sink)); + retval = g_variant_new ( "(i)", active_sink_get_state (priv->active_sink)); } else if (g_strcmp0(method, "BlacklistMediaPlayer") == 0) { gboolean blacklist; @@ -422,6 +284,9 @@ bus_method_call (GDBusConnection * connection, g_dbus_method_invocation_return_value (invocation, retval); } +/** + TODO - Works nicely but refactor into at least two different methods +**/ static gboolean sound_service_dbus_blacklist_player (SoundServiceDbus* self, gchar* player_name, gboolean blacklist) diff --git a/src/sound-service-dbus.h b/src/sound-service-dbus.h index fab3549..cdc4608 100644 --- a/src/sound-service-dbus.h +++ b/src/sound-service-dbus.h @@ -55,12 +55,10 @@ GType sound_service_dbus_get_type (void) G_GNUC_CONST; DbusmenuMenuitem* sound_service_dbus_create_root_item (SoundServiceDbus* self); void sound_service_dbus_update_sound_state (SoundServiceDbus* self, SoundState new_state); -void sound_service_dbus_update_sink_mute(SoundServiceDbus* self, gboolean sink_mute); -void sound_service_dbus_update_volume(SoundServiceDbus* self, gdouble volume); -void sound_service_dbus_update_pa_state ( SoundServiceDbus* root, - gboolean availability, - gboolean mute_update, - gdouble volume ); +void sound_service_dbus_build_sound_menu ( SoundServiceDbus* self, + DbusmenuMenuitem* mute_item, + DbusmenuMenuitem* slider_item); + G_END_DECLS diff --git a/src/sound-service.c b/src/sound-service.c index 2cb33d3..cfc0b7e 100644 --- a/src/sound-service.c +++ b/src/sound-service.c @@ -18,8 +18,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "sound-service.h" - -#include "pulse-manager.h" +#include "pulseaudio-mgr.h" #include "sound-service-dbus.h" #include "music-player-bridge.h" |