aboutsummaryrefslogtreecommitdiff
path: root/src/sound-service.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sound-service.c')
-rw-r--r--src/sound-service.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/src/sound-service.c b/src/sound-service.c
new file mode 100644
index 0000000..dc43d77
--- /dev/null
+++ b/src/sound-service.c
@@ -0,0 +1,255 @@
+/*
+This service primarily controls PulseAudio and is driven by the sound indicator menu on the panel.
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+ Ted Gould <ted@canonical.com>
+ Christoph Korn <c_korn@gmx.de>
+ Cody Russell <crussell@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 "sound-service-dbus.h"
+#include "sound-service.h"
+#include "common-defs.h"
+
+/**********************************************************************************************************************/
+// Pulse-Audio asychronous call-backs
+/**********************************************************************************************************************/
+static void context_get_sink_info_by_index_callback(pa_context *c, const pa_sink_info *sink, int eol, void *userdata){
+ if (eol > 0) {
+ return;
+ }
+ else{
+ g_debug("\n SINK INFO Name : %s \n", sink->name);
+ g_debug("\n SINK INFO Muted : %d \n", sink->mute);
+ if (sink->mute == 1){
+ g_debug("HERE is one for the DBUS - sink input while sink is muted");
+ sound_service_dbus_sink_input_while_muted(dbus_interface, sink->index, TRUE);
+ }
+ else{
+ g_debug("Sink input while the device is unmuted - not interested");
+ sound_service_dbus_sink_input_while_muted(dbus_interface, sink->index, FALSE);
+ }
+ }
+}
+
+static void context_success_callback(pa_context *c, int success, void *userdata){
+ g_debug("Context Success Callback - result = %i", success);
+}
+
+static void retrieve_complete_sink_list(pa_context *c, const pa_sink_info *sink, int eol, void *userdata){
+ if(eol > 0){
+ // TODO apparently never returns 0 sinks - Tested and it appears this assumption/prediction is correct.
+ // i would imagine different behaviour on different machines - watch this space!
+ // Some fuzzy reasoning might be needed.
+ if(sink_list->len == 1){
+ pa_sink_info* only_sink = (pa_sink_info*)g_ptr_array_index(sink_list, 0);
+ //TODO: sink is not null but its module is the null-module-sink!
+ // For now taking the easy route string compare on the name and the active port
+ // needs more testing
+ int value = g_strcasecmp(only_sink->name, " auto_null ");
+ g_debug("comparison outcome with auto_null is %i", value);
+ sink_available = (value != 0 && only_sink->active_port != NULL);
+ g_debug("Available sink is named %s", only_sink->name);
+ g_debug("does Available sink have an active port: %i", only_sink->active_port != NULL);
+ g_debug("sink_available = %i", sink_available);
+ return;
+ }
+ sink_available = TRUE;
+ return;
+ }
+ g_ptr_array_add(sink_list, (gpointer)sink);
+}
+
+
+static void set_global_mute_callback(pa_context *c, const pa_sink_info *sink, int eol, void *userdata){
+ if(eol > 0){
+ g_debug("No more sinks to mute ! \n Everything should now be muted/unmuted ?" );
+ return;
+ }
+ // Otherwise mute/unmute it!
+ pa_context_set_sink_mute_by_index(pulse_context, sink->index, all_muted == TRUE ? 1 : 0, context_success_callback, NULL);
+}
+
+static void context_get_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_debug("\n Sink input info callback : SINK INPUT INFO IS NULL BUT EOL was not POSITIVE!!!");
+ return;
+ }
+ g_debug("\n SINK INPUT INFO CALLBACK about to start asking questions...\n");
+ g_debug("\n SINK INPUT INFO Name : %s \n", info->name);
+ g_debug("\n SINK INPUT INFO sink index : %d \n", info->sink);
+ pa_operation_unref(pa_context_get_sink_info_by_index(c, info->sink, context_get_sink_info_by_index_callback, userdata));
+ }
+}
+
+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:
+ g_debug("Event sink for %i", index);
+ break;
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ // This will be triggered when the sink receives input from a new stream
+ // If a playback client is paused and then resumed this will NOT trigger this event.
+ g_debug("Subscribed_events_callback - type = sink input and index = %i", index);
+ g_debug("Sink input info query just about to happen");
+ pa_operation_unref(pa_context_get_sink_input_info(c, index, context_get_sink_input_info_callback, userdata));
+ g_debug("Sink input info query just happened");
+ 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");
+ 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_debug("FAILED to retrieve context");
+ break;
+ case PA_CONTEXT_TERMINATED:
+ g_debug("context terminated");
+ break;
+ case PA_CONTEXT_READY:
+ g_debug("PA daemon is ready");
+ pa_context_set_subscribe_callback(c, subscribed_events_callback, userdata);
+ pa_operation_unref(pa_context_get_sink_info_list(c, retrieve_complete_sink_list, NULL));
+ pa_operation_unref(pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL));
+ //pa_operation_unref(pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL));
+ break;
+ }
+}
+
+/**********************************************************************************************************************/
+// Init functions (GTK and DBUS)
+/**********************************************************************************************************************/
+/**
+Pass to the g_idle_add method - returning False will ensure that this method is never called again as it is removed as an event source.
+**/
+static gboolean idle_routine (gpointer data)
+{
+ return FALSE;
+}
+
+/**
+Build the DBus menu items. For now Mute all/Unmute is the only available option
+**/
+static void rebuild_sound_menu(DbusmenuMenuitem *root, SoundServiceDbus *service)
+{
+ mute_all_menuitem = dbusmenu_menuitem_new();
+
+ dbusmenu_menuitem_property_set(mute_all_menuitem, DBUSMENU_MENUITEM_PROP_LABEL, _(all_muted == FALSE ? "Mute All" : "Unmute"));
+ g_signal_connect(G_OBJECT(mute_all_menuitem), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(set_global_mute), NULL);
+ //TODO: If no valid sinks are found grey out the item(s)
+ dbusmenu_menuitem_property_set_bool(mute_all_menuitem, DBUSMENU_MENUITEM_PROP_SENSITIVE, sink_available);
+ dbusmenu_menuitem_child_append(root, mute_all_menuitem);
+}
+
+static void set_global_mute()
+{
+ all_muted = !all_muted;
+ g_debug("Mute is now = %i", all_muted);
+ pa_operation_unref(pa_context_get_sink_info_list(pulse_context, set_global_mute_callback, NULL));
+ dbusmenu_menuitem_property_set(mute_all_menuitem, DBUSMENU_MENUITEM_PROP_LABEL, _(all_muted == FALSE ? "Mute All" : "Unmute"));
+}
+
+
+/* When the service interface starts to shutdown, we
+ should follow it. -
+*/
+void
+service_shutdown (IndicatorService *service, gpointer user_data)
+{
+
+ if (mainloop != NULL) {
+
+/* g_debug("Service shutdown");*/
+/* if (pulse_context){*/
+/* pa_context_unref(pulse_context);*/
+/* }*/
+/* g_ptr_array_free(sink_list, TRUE);*/
+/* pa_glib_mainloop_free(pa_main_loop);*/
+/* g_main_loop_quit(mainloop);*/
+ }
+ return;
+}
+
+
+/* Main, is well, main. It brings everything up and throws
+ us into the mainloop of no return. Some refactoring needed.*/
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+
+ IndicatorService * service = indicator_service_new_version(INDICATOR_SOUND_DBUS_NAME,
+ INDICATOR_SOUND_DBUS_VERSION);
+ g_signal_connect(G_OBJECT(service),
+ INDICATOR_SERVICE_SIGNAL_SHUTDOWN,
+ G_CALLBACK(service_shutdown), NULL);
+
+ root_menuitem = dbusmenu_menuitem_new();
+ g_debug("Root ID: %d", dbusmenu_menuitem_get_id(root_menuitem));
+
+ g_idle_add(idle_routine, root_menuitem);
+
+ sink_list = g_ptr_array_new();
+ dbus_interface = g_object_new(SOUND_SERVICE_DBUS_TYPE, NULL);
+
+ DbusmenuServer * server = dbusmenu_server_new(INDICATOR_SOUND_DBUS_OBJECT);
+ dbusmenu_server_set_root(server, root_menuitem);
+
+ 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), "ayatana.indicator.sound");
+ g_assert(pulse_context);
+
+
+ // Establish event callback registration
+ pa_context_set_state_callback(pulse_context, context_state_callback, NULL);
+ pa_context_connect(pulse_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
+
+ rebuild_sound_menu (root_menuitem, dbus_interface);
+
+ // Run the loop
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ return 0;
+}
+
+
+
+