/* * 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" struct _PulseConnectionPrivate { gchar *server; guint outstanding; gboolean reconnect; gboolean connected_once; pa_context *context; pa_glib_mainloop *mainloop; PulseConnectionState state; }; enum { PROP_0, PROP_SERVER, PROP_RECONNECT, PROP_STATE, N_PROPERTIES }; 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 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_list_loaded (PulseConnection *connection); static gboolean connection_process_operation (PulseConnection *connection, pa_operation *op); G_DEFINE_TYPE (PulseConnection, pulse_connection, G_TYPE_OBJECT); static GParamSpec *properties[N_PROPERTIES] = { NULL, }; static guint signals[N_SIGNALS] = { 0, }; static void pulse_connection_init (PulseConnection *connection) { connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, PULSE_TYPE_CONNECTION, 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_RECONNECT: g_value_set_boolean (value, connection->priv->reconnect); 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: g_free (connection->priv->server); connection->priv->server = g_value_dup_string (value); break; case PROP_RECONNECT: connection->priv->reconnect = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void pulse_connection_finalize (GObject *object) { PulseConnection *connection; connection = PULSE_CONNECTION (object); g_free (connection->priv->server); G_OBJECT_CLASS (pulse_connection_parent_class)->finalize (object); } 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_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_RECONNECT] = g_param_spec_boolean ("reconnect", "Reconnect", "Try to reconnect when connection is lost", TRUE, 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_removed), 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)); } 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_context *context; 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; } proplist = pa_proplist_new (); if (app_name) pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, app_name); if (app_id) pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, app_id); if (app_icon) pa_proplist_sets (proplist, PA_PROP_APPLICATION_ICON_NAME, app_icon); if (app_version) pa_proplist_sets (proplist, PA_PROP_APPLICATION_VERSION, app_version); if (app_name != NULL) { context = pa_context_new_with_proplist (pa_glib_mainloop_get_api (mainloop), app_name, proplist); } else { gchar *name = connection_get_app_name (); context = pa_context_new_with_proplist (pa_glib_mainloop_get_api (mainloop), name, proplist); g_free (name); } pa_proplist_free (proplist); if (G_UNLIKELY (context == NULL)) { g_warning ("Failed to create PulseAudio context"); pa_glib_mainloop_free (mainloop); return NULL; } connection = g_object_new (PULSE_TYPE_CONNECTION, "server", server_address, "reconnect", TRUE, NULL); connection->priv->mainloop = mainloop; connection->priv->context = context; return connection; } gboolean pulse_connection_connect (PulseConnection *connection) { int ret; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_DISCONNECTED) return TRUE; /* Set function to monitor status changes */ pa_context_set_state_callback (connection->priv->context, connection_state_cb, connection); /* Initiate a connection, this call does not guarantee the connection * to be established and usable */ ret = pa_context_connect (connection->priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL); if (ret < 0) { g_warning ("Failed to connect to PulseAudio server: %s", pa_strerror (ret)); return FALSE; } return TRUE; } void pulse_connection_disconnect (PulseConnection *connection) { g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state == PULSE_CONNECTION_DISCONNECTED) return; pa_context_disconnect (connection->priv->context); connection->priv->state = PULSE_CONNECTION_DISCONNECTED; g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); } PulseConnectionState pulse_connection_get_state (PulseConnection *connection) { g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); return connection->priv->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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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_move_sink_input (PulseConnection *connection, guint32 index, guint32 sink_index) { pa_operation *op; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), 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); 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); 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); 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; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (G_UNLIKELY (connection->priv->outstanding)) { /* This can only mean a bug */ g_warn_if_reached (); return FALSE; } op = pa_context_get_server_info (connection->priv->context, connection_server_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; ops = g_list_prepend (ops, op); 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; // XXX check state op = pa_context_subscribe (connection->priv->context, 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) { connection->priv->state = PULSE_CONNECTION_LOADING; connection->priv->connected_once = TRUE; pa_context_set_subscribe_callback (connection->priv->context, connection_subscribe_cb, connection); pa_operation_unref (op); connection_load_lists (connection); g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); } else { /* If we could not subscribe to notifications, we consider it the * same as a connection failture */ g_warning ("Failed to subscribe to PulseAudio notifications: %s", pa_strerror (pa_context_errno (connection->priv->context))); state = PA_CONTEXT_FAILED; } } if (state == PA_CONTEXT_TERMINATED || state == PA_CONTEXT_FAILED) { /* We also handle the case of clean connection termination as it is a state * change which should not normally happen, because the signal subscription * is cancelled before disconnecting */ pulse_connection_disconnect (connection); if (connection->priv->connected_once && connection->priv->reconnect) pulse_connection_connect (connection); } } 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) { g_signal_emit (G_OBJECT (userdata), signals[SERVER_INFO], 0, info); } 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) connection_list_loaded (connection); else 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_list_loaded (PulseConnection *connection) { connection->priv->outstanding--; if (G_UNLIKELY (connection->priv->outstanding < 0)) { g_warn_if_reached (); connection->priv->outstanding = 0; } if (connection->priv->outstanding == 0) { connection->priv->state = PULSE_CONNECTION_CONNECTED; g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); } } 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; }