/* * Copyright (C) 2014 Michal Ratajsky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library 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 library; if not, see . */ #include #include #include #include #include #include #include "pulse-connection.h" #include "pulse-enums.h" #include "pulse-enum-types.h" #include "pulse-monitor.h" struct _PulseConnectionPrivate { gchar *server; guint outstanding; pa_context *context; pa_proplist *proplist; pa_glib_mainloop *mainloop; PulseConnectionState state; }; enum { PROP_0, PROP_SERVER, PROP_STATE, N_PROPERTIES }; static GParamSpec *properties[N_PROPERTIES] = { NULL, }; enum { SERVER_INFO, CARD_INFO, CARD_REMOVED, SINK_INFO, SINK_REMOVED, SOURCE_INFO, SOURCE_REMOVED, SINK_INPUT_INFO, SINK_INPUT_REMOVED, SOURCE_OUTPUT_INFO, SOURCE_OUTPUT_REMOVED, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0, }; static void pulse_connection_class_init (PulseConnectionClass *klass); static void pulse_connection_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void pulse_connection_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void pulse_connection_init (PulseConnection *connection); static void pulse_connection_finalize (GObject *object); G_DEFINE_TYPE (PulseConnection, pulse_connection, G_TYPE_OBJECT); static gchar *connection_get_app_name (void); static gboolean connection_load_lists (PulseConnection *connection); static void connection_state_cb (pa_context *c, void *userdata); static void connection_subscribe_cb (pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); static void connection_server_info_cb (pa_context *c, const pa_server_info *info, void *userdata); static void connection_card_info_cb (pa_context *c, const pa_card_info *info, int eol, void *userdata); static void connection_sink_info_cb (pa_context *c, const pa_sink_info *info, int eol, void *userdata); static void connection_source_info_cb (pa_context *c, const pa_source_info *info, int eol, void *userdata); static void connection_sink_input_info_cb (pa_context *c, const pa_sink_input_info *info, int eol, void *userdata); static void connection_source_output_info_cb (pa_context *c, const pa_source_output_info *info, int eol, void *userdata); static void connection_change_state (PulseConnection *connection, PulseConnectionState state); static void connection_list_loaded (PulseConnection *connection); static gboolean connection_process_operation (PulseConnection *connection, pa_operation *op); static void pulse_connection_class_init (PulseConnectionClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = pulse_connection_finalize; object_class->get_property = pulse_connection_get_property; object_class->set_property = pulse_connection_set_property; properties[PROP_SERVER] = g_param_spec_string ("server", "Server", "PulseAudio server to connect to", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_STATE] = g_param_spec_enum ("state", "State", "Connection state", PULSE_TYPE_CONNECTION_STATE, PULSE_CONNECTION_DISCONNECTED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); signals[SERVER_INFO] = g_signal_new ("server-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, server_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[CARD_INFO] = g_signal_new ("card-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, card_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[CARD_REMOVED] = g_signal_new ("card-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, card_removed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SINK_INFO] = g_signal_new ("sink-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, sink_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[SINK_REMOVED] = g_signal_new ("sink-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, sink_removed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SINK_INPUT_INFO] = g_signal_new ("sink-input-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, sink_input_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[SINK_INPUT_REMOVED] = g_signal_new ("sink-input-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, sink_input_removed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SOURCE_INFO] = g_signal_new ("source-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, source_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[SOURCE_REMOVED] = g_signal_new ("source-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, source_removed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SOURCE_OUTPUT_INFO] = g_signal_new ("source-output-info", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, source_output_info), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[SOURCE_OUTPUT_REMOVED] = g_signal_new ("source-output-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PulseConnectionClass, source_output_removed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); g_object_class_install_properties (object_class, N_PROPERTIES, properties); g_type_class_add_private (object_class, sizeof (PulseConnectionPrivate)); } static void pulse_connection_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { PulseConnection *connection; connection = PULSE_CONNECTION (object); switch (param_id) { case PROP_SERVER: g_value_set_string (value, connection->priv->server); break; case PROP_STATE: g_value_set_enum (value, connection->priv->state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void pulse_connection_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { PulseConnection *connection; connection = PULSE_CONNECTION (object); switch (param_id) { case PROP_SERVER: /* Construct-only string */ connection->priv->server = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void pulse_connection_init (PulseConnection *connection) { connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, PULSE_TYPE_CONNECTION, PulseConnectionPrivate); } static void pulse_connection_finalize (GObject *object) { PulseConnection *connection; connection = PULSE_CONNECTION (object); g_free (connection->priv->server); if (connection->priv->context != NULL) pa_context_unref (connection->priv->context); pa_proplist_free (connection->priv->proplist); pa_glib_mainloop_free (connection->priv->mainloop); G_OBJECT_CLASS (pulse_connection_parent_class)->finalize (object); } PulseConnection * pulse_connection_new (const gchar *app_name, const gchar *app_id, const gchar *app_version, const gchar *app_icon, const gchar *server_address) { pa_glib_mainloop *mainloop; pa_proplist *proplist; PulseConnection *connection; mainloop = pa_glib_mainloop_new (g_main_context_get_thread_default ()); if (G_UNLIKELY (mainloop == NULL)) { g_warning ("Failed to create PulseAudio main loop"); return NULL; } /* Create a property list to hold information about the application, * the list will be kept with the connection as it will be reused later * when creating PulseAudio contexts and streams */ proplist = pa_proplist_new (); if (app_name != NULL) { pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, app_name); } else { /* Set a sensible default name when application does not provide one */ gchar *name = connection_get_app_name (); pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, name); g_free (name); } if (app_id != NULL) pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, app_id); if (app_icon != NULL) pa_proplist_sets (proplist, PA_PROP_APPLICATION_ICON_NAME, app_icon); if (app_version != NULL) pa_proplist_sets (proplist, PA_PROP_APPLICATION_VERSION, app_version); connection = g_object_new (PULSE_TYPE_CONNECTION, "server", server_address, NULL); connection->priv->mainloop = mainloop; connection->priv->proplist = proplist; return connection; } gboolean pulse_connection_connect (PulseConnection *connection, gboolean wait_for_daemon) { pa_context *context; pa_context_flags_t flags = PA_CONTEXT_NOFLAGS; pa_mainloop_api *mainloop; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_DISCONNECTED) return TRUE; mainloop = pa_glib_mainloop_get_api (connection->priv->mainloop); context = pa_context_new_with_proplist (mainloop, NULL, connection->priv->proplist); if (G_UNLIKELY (context == NULL)) { g_warning ("Failed to create PulseAudio context"); return FALSE; } /* Set function to monitor status changes */ pa_context_set_state_callback (context, connection_state_cb, connection); if (wait_for_daemon) flags = PA_CONTEXT_NOFAIL; /* Initiate a connection, state changes will be delivered asynchronously */ if (pa_context_connect (context, connection->priv->server, flags, NULL) == 0) { connection->priv->context = context; connection_change_state (connection, PULSE_CONNECTION_CONNECTING); return TRUE; } pa_context_unref (context); return FALSE; } void pulse_connection_disconnect (PulseConnection *connection) { g_return_if_fail (PULSE_IS_CONNECTION (connection)); if (connection->priv->state == PULSE_CONNECTION_DISCONNECTED) return; pa_context_unref (connection->priv->context); connection->priv->context = NULL; connection->priv->outstanding = 0; connection_change_state (connection, PULSE_CONNECTION_DISCONNECTED); } PulseConnectionState pulse_connection_get_state (PulseConnection *connection) { g_return_val_if_fail (PULSE_IS_CONNECTION (connection), PULSE_CONNECTION_DISCONNECTED); return connection->priv->state; } PulseMonitor * pulse_connection_create_monitor (PulseConnection *connection, guint32 index_source, guint32 index_sink_input) { g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return NULL; return pulse_monitor_new (connection->priv->context, connection->priv->proplist, index_source, index_sink_input); } // XXX watch for some operation failures and eventually reload data // to restore the previous state gboolean pulse_connection_set_default_sink (PulseConnection *connection, const gchar *name) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_default_sink (connection->priv->context, name, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_default_source (PulseConnection *connection, const gchar *name) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_default_source (connection->priv->context, name, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_card_profile (PulseConnection *connection, const gchar *card, const gchar *profile) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_card_profile_by_name (connection->priv->context, card, profile, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_sink_mute (PulseConnection *connection, guint32 index, gboolean mute) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_sink_mute_by_index (connection->priv->context, index, (int) mute, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_sink_volume (PulseConnection *connection, guint32 index, const pa_cvolume *volume) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_sink_volume_by_index (connection->priv->context, index, volume, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_sink_port (PulseConnection *connection, guint32 index, const gchar *port) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_sink_port_by_index (connection->priv->context, index, port, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_sink_input_mute (PulseConnection *connection, guint32 index, gboolean mute) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_sink_input_mute (connection->priv->context, index, (int) mute, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_sink_input_volume (PulseConnection *connection, guint32 index, const pa_cvolume *volume) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_sink_input_volume (connection->priv->context, index, volume, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_source_mute (PulseConnection *connection, guint32 index, gboolean mute) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_source_mute_by_index (connection->priv->context, index, (int) mute, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_source_volume (PulseConnection *connection, guint32 index, const pa_cvolume *volume) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_source_volume_by_index (connection->priv->context, index, volume, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_source_port (PulseConnection *connection, guint32 index, const gchar *port) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_source_port_by_index (connection->priv->context, index, port, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_set_source_output_mute (PulseConnection *connection, guint32 index, gboolean mute) { #if PA_CHECK_VERSION(1, 0, 0) pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_source_output_mute (connection->priv->context, index, (int) mute, NULL, NULL); return connection_process_operation (connection, op); #else return FALSE; #endif } gboolean pulse_connection_set_source_output_volume (PulseConnection *connection, guint32 index, const pa_cvolume *volume) { #if PA_CHECK_VERSION(1, 0, 0) pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_set_source_output_volume (connection->priv->context, index, volume, NULL, NULL); return connection_process_operation (connection, op); #else return FALSE; #endif } gboolean pulse_connection_suspend_sink (PulseConnection *connection, guint32 index, gboolean suspend) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_suspend_sink_by_index (connection->priv->context, index, (int) suspend, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_suspend_source (PulseConnection *connection, guint32 index, gboolean suspend) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_suspend_source_by_index (connection->priv->context, index, (int) suspend, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_move_sink_input (PulseConnection *connection, guint32 index, guint32 sink_index) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_move_sink_input_by_index (connection->priv->context, index, sink_index, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_move_source_output (PulseConnection *connection, guint32 index, guint32 source_index) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_move_source_output_by_index (connection->priv->context, index, source_index, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_kill_sink_input (PulseConnection *connection, guint32 index) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_kill_sink_input (connection->priv->context, index, NULL, NULL); return connection_process_operation (connection, op); } gboolean pulse_connection_kill_source_output (PulseConnection *connection, guint32 index) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_CONNECTED) return FALSE; op = pa_context_kill_source_output (connection->priv->context, index, NULL, NULL); return connection_process_operation (connection, op); } static gchar * connection_get_app_name (void) { const char *name_app; char name_buf[256]; /* Inspired by GStreamer's pulse plugin */ name_app = g_get_application_name (); if (name_app != NULL) return g_strdup (name_app); if (pa_get_binary_name (name_buf, sizeof (name_buf)) != NULL) return g_strdup (name_buf); return g_strdup_printf ("libmatemixer-%lu", (gulong) getpid ()); } static gboolean connection_load_lists (PulseConnection *connection) { GList *ops = NULL; pa_operation *op; if (G_UNLIKELY (connection->priv->outstanding > 0)) { g_warn_if_reached (); return FALSE; } op = pa_context_get_card_info_list (connection->priv->context, connection_card_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); op = pa_context_get_sink_info_list (connection->priv->context, connection_sink_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); op = pa_context_get_sink_input_info_list (connection->priv->context, connection_sink_input_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); op = pa_context_get_source_info_list (connection->priv->context, connection_source_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); op = pa_context_get_source_output_info_list (connection->priv->context, connection_source_output_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); g_list_foreach (ops, (GFunc) pa_operation_unref, NULL); g_list_free (ops); connection->priv->outstanding = 5; return TRUE; error: g_list_foreach (ops, (GFunc) pa_operation_cancel, NULL); g_list_foreach (ops, (GFunc) pa_operation_unref, NULL); g_list_free (ops); return FALSE; } static void connection_state_cb (pa_context *c, void *userdata) { PulseConnection *connection; pa_context_state_t state; connection = PULSE_CONNECTION (userdata); state = pa_context_get_state (c); if (state == PA_CONTEXT_READY) { pa_operation *op; if (connection->priv->state == PULSE_CONNECTION_LOADING || connection->priv->state == PULSE_CONNECTION_CONNECTED) { g_warn_if_reached (); return; } /* We are connected, let's subscribe to notifications and load the * initial lists */ op = pa_context_subscribe (connection->priv->context, PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, NULL, NULL); if (op != NULL) { pa_context_set_subscribe_callback (connection->priv->context, connection_subscribe_cb, connection); pa_operation_unref (op); if (connection_load_lists (connection) == TRUE) { connection_change_state (connection, PULSE_CONNECTION_LOADING); return; } /* Treat as a connection failure */ state = PA_CONTEXT_FAILED; } else { g_warning ("Failed to subscribe to PulseAudio notifications: %s", pa_strerror (pa_context_errno (connection->priv->context))); /* Treat as a connection failure */ state = PA_CONTEXT_FAILED; } } if (state == PA_CONTEXT_TERMINATED || state == PA_CONTEXT_FAILED) { /* We do not distinguish between failure and clean connection termination */ pulse_connection_disconnect (connection); return; } if (state == PA_CONTEXT_CONNECTING) connection_change_state (connection, PULSE_CONNECTION_CONNECTING); else if (state == PA_CONTEXT_AUTHORIZING || state == PA_CONTEXT_SETTING_NAME) connection_change_state (connection, PULSE_CONNECTION_AUTHORIZING); } static void connection_subscribe_cb (pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { PulseConnection *connection; pa_operation *op; connection = PULSE_CONNECTION (userdata); switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_CARD: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { g_signal_emit (G_OBJECT (connection), signals[CARD_REMOVED], 0, idx); } else { op = pa_context_get_card_info_by_index (connection->priv->context, idx, connection_card_info_cb, connection); connection_process_operation (connection, op); } break; case PA_SUBSCRIPTION_EVENT_SINK: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { g_signal_emit (G_OBJECT (connection), signals[SINK_REMOVED], 0, idx); } else { op = pa_context_get_sink_info_by_index (connection->priv->context, idx, connection_sink_info_cb, connection); connection_process_operation (connection, op); } break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { g_signal_emit (G_OBJECT (connection), signals[SINK_INPUT_REMOVED], 0, idx); } else { op = pa_context_get_sink_input_info (connection->priv->context, idx, connection_sink_input_info_cb, connection); connection_process_operation (connection, op); } break; case PA_SUBSCRIPTION_EVENT_SOURCE: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { g_signal_emit (G_OBJECT (connection), signals[SOURCE_REMOVED], 0, idx); } else { op = pa_context_get_source_info_by_index (connection->priv->context, idx, connection_source_info_cb, connection); connection_process_operation (connection, op); } break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { g_signal_emit (G_OBJECT (connection), signals[SOURCE_OUTPUT_REMOVED], 0, idx); } else { op = pa_context_get_source_output_info (connection->priv->context, idx, connection_source_output_info_cb, connection); connection_process_operation (connection, op); } break; } } static void connection_server_info_cb (pa_context *c, const pa_server_info *info, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); g_signal_emit (G_OBJECT (connection), signals[SERVER_INFO], 0, info); /* This notification may arrive at any time, but it also finalizes the * connection process */ if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_change_state (connection, PULSE_CONNECTION_CONNECTED); } static void connection_card_info_cb (pa_context *c, const pa_card_info *info, int eol, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_list_loaded (connection); return; } g_signal_emit (G_OBJECT (connection), signals[CARD_INFO], 0, info); } static void connection_sink_info_cb (pa_context *c, const pa_sink_info *info, int eol, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_list_loaded (connection); return; } g_signal_emit (G_OBJECT (connection), signals[SINK_INFO], 0, info); } static void connection_sink_input_info_cb (pa_context *c, const pa_sink_input_info *info, int eol, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_list_loaded (connection); return; } g_signal_emit (G_OBJECT (connection), signals[SINK_INPUT_INFO], 0, info); } static void connection_source_info_cb (pa_context *c, const pa_source_info *info, int eol, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_list_loaded (connection); return; } g_signal_emit (G_OBJECT (connection), signals[SOURCE_INFO], 0, info); } static void connection_source_output_info_cb (pa_context *c, const pa_source_output_info *info, int eol, void *userdata) { PulseConnection *connection; connection = PULSE_CONNECTION (userdata); if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) connection_list_loaded (connection); return; } g_signal_emit (G_OBJECT (connection), signals[SOURCE_OUTPUT_INFO], 0, info); } static void connection_change_state (PulseConnection *connection, PulseConnectionState state) { if (connection->priv->state == state) return; connection->priv->state = state; g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); } static void connection_list_loaded (PulseConnection *connection) { /* Decrement the number of outstanding requests as a list has just been * downloaded; when the number reaches 0, server information is requested * as the final step in the connection process */ connection->priv->outstanding--; if (G_UNLIKELY (connection->priv->outstanding < 0)) { g_warn_if_reached (); connection->priv->outstanding = 0; } if (connection->priv->outstanding == 0) { pa_operation *op; op = pa_context_get_server_info (connection->priv->context, connection_server_info_cb, connection); if (G_UNLIKELY (connection_process_operation (connection, op) == FALSE)) pulse_connection_disconnect (connection); } } static gboolean connection_process_operation (PulseConnection *connection, pa_operation *op) { if (G_UNLIKELY (op == NULL)) { g_warning ("PulseAudio operation failed: %s", pa_strerror (pa_context_errno (connection->priv->context))); return FALSE; } pa_operation_unref (op); return TRUE; }