diff options
Diffstat (limited to 'backends')
82 files changed, 7088 insertions, 4945 deletions
diff --git a/backends/alsa/Makefile.am b/backends/alsa/Makefile.am index 220bb3b..48dcaba 100644 --- a/backends/alsa/Makefile.am +++ b/backends/alsa/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-alsa.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-alsa\" diff --git a/backends/alsa/alsa-backend.c b/backends/alsa/alsa-backend.c index 7a17b85..6bac691 100644 --- a/backends/alsa/alsa-backend.c +++ b/backends/alsa/alsa-backend.c @@ -27,12 +27,22 @@ #include "alsa-stream.h" #define BACKEND_NAME "ALSA" -#define BACKEND_PRIORITY 9 +#define BACKEND_PRIORITY 20 + +#define ALSA_DEVICE_GET_ID(d) \ + (g_object_get_data (G_OBJECT (d), "__matemixer_alsa_device_id")) + +#define ALSA_DEVICE_SET_ID(d,id) \ + (g_object_set_data_full (G_OBJECT (d), \ + "__matemixer_alsa_device_id", \ + g_strdup (id), \ + g_free)) struct _AlsaBackendPrivate { GSource *timeout_source; - GHashTable *devices; + GList *streams; + GList *devices; GHashTable *devices_ids; }; @@ -47,26 +57,38 @@ static void alsa_backend_finalize (GObject *object); G_DEFINE_DYNAMIC_TYPE (AlsaBackend, alsa_backend, MATE_MIXER_TYPE_BACKEND) #pragma clang diagnostic pop -static gboolean alsa_backend_open (MateMixerBackend *backend); -static void alsa_backend_close (MateMixerBackend *backend); -static GList * alsa_backend_list_devices (MateMixerBackend *backend); -static GList * alsa_backend_list_streams (MateMixerBackend *backend); +static gboolean alsa_backend_open (MateMixerBackend *backend); +static void alsa_backend_close (MateMixerBackend *backend); +static const GList *alsa_backend_list_devices (MateMixerBackend *backend); +static const GList *alsa_backend_list_streams (MateMixerBackend *backend); + +static gboolean read_devices (AlsaBackend *alsa); + +static gboolean read_device (AlsaBackend *alsa, + const gchar *card); + +static void add_device (AlsaBackend *alsa, + AlsaDevice *device); -static gboolean read_devices (AlsaBackend *alsa); +static void remove_device (AlsaBackend *alsa, + AlsaDevice *device); +static void remove_device_by_name (AlsaBackend *alsa, + const gchar *name); +static void remove_device_by_list_item (AlsaBackend *alsa, + GList *item); -static gboolean read_device (AlsaBackend *alsa, - const gchar *card); +static void remove_stream (AlsaBackend *alsa, + const gchar *name); -static void add_device (AlsaBackend *alsa, - AlsaDevice *device); +static void select_default_input_stream (AlsaBackend *alsa); +static void select_default_output_stream (AlsaBackend *alsa); -static void remove_device (AlsaBackend *alsa, - AlsaDevice *device); -static void remove_stream (AlsaBackend *alsa, - const gchar *name); +static void free_stream_list (AlsaBackend *alsa); -static void select_default_input_stream (AlsaBackend *alsa); -static void select_default_output_stream (AlsaBackend *alsa); +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_device_name (gconstpointer a, + gconstpointer b); static MateMixerBackendInfo info; @@ -118,11 +140,6 @@ alsa_backend_init (AlsaBackend *alsa) ALSA_TYPE_BACKEND, AlsaBackendPrivate); - alsa->priv->devices = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); - alsa->priv->devices_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -151,7 +168,6 @@ alsa_backend_finalize (GObject *object) alsa = ALSA_BACKEND (object); - g_hash_table_unref (alsa->priv->devices); g_hash_table_unref (alsa->priv->devices_ids); G_OBJECT_CLASS (alsa_backend_parent_class)->finalize (object); @@ -166,9 +182,8 @@ alsa_backend_open (MateMixerBackend *backend) alsa = ALSA_BACKEND (backend); - /* Poll ALSA for changes every 500 milliseconds, this actually only - * discovers added or changed sound cards, sound card related events - * are handled by AlsaDevices */ + /* Poll ALSA for changes every second, this only discovers added or removed + * sound cards, sound card related events are handled by AlsaDevices */ alsa->priv->timeout_source = g_timeout_source_new_seconds (1); g_source_set_callback (alsa->priv->timeout_source, (GSourceFunc) read_devices, @@ -197,61 +212,60 @@ alsa_backend_close (MateMixerBackend *backend) g_source_destroy (alsa->priv->timeout_source); - g_hash_table_remove_all (alsa->priv->devices); + if (alsa->priv->devices != NULL) { + g_list_free_full (alsa->priv->devices, g_object_unref); + alsa->priv->devices = NULL; + } + + free_stream_list (alsa); + g_hash_table_remove_all (alsa->priv->devices_ids); _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } -static GList * +static const GList * alsa_backend_list_devices (MateMixerBackend *backend) { - GList *list; - g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (ALSA_BACKEND (backend)->priv->devices); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return ALSA_BACKEND (backend)->priv->devices; } -static GList * +static const GList * alsa_backend_list_streams (MateMixerBackend *backend) { - AlsaBackend *alsa; - GHashTableIter iter; - gpointer value; - GList *list = NULL; + AlsaBackend *alsa; g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); alsa = ALSA_BACKEND (backend); - /* We don't keep a list or hash table of all streams here, instead walk - * through the list of devices and create the list manually, each device - * has at most one input and one output stream */ - g_hash_table_iter_init (&iter, alsa->priv->devices); - - while (g_hash_table_iter_next (&iter, NULL, &value)) { - AlsaDevice *device = ALSA_DEVICE (value); - AlsaStream *stream; - - stream = alsa_device_get_output_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - stream = alsa_device_get_input_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); + if (alsa->priv->streams == NULL) { + GList *list; + + /* Walk through the list of devices and create the stream list, each + * device has at most one input and one output stream */ + list = alsa->priv->devices; + + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream; + + stream = alsa_device_get_input_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + stream = alsa_device_get_output_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + list = list->next; + } } - - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return alsa->priv->streams; } static gboolean @@ -260,12 +274,12 @@ read_devices (AlsaBackend *alsa) gint num; gint ret; gchar card[16]; - gboolean changed = FALSE; + gboolean added = FALSE; /* Read the default device first, it will be either one of the hardware cards * that will be queried later, or a software mixer */ if (read_device (alsa, "default") == TRUE) - changed = TRUE; + added = TRUE; for (num = -1;;) { /* Read number of the next sound card */ @@ -277,12 +291,12 @@ read_devices (AlsaBackend *alsa) g_snprintf (card, sizeof (card), "hw:%d", num); if (read_device (alsa, card) == TRUE) - changed = TRUE; + added = TRUE; } /* If any card has been added, make sure we have the most suitable default * input and output streams */ - if (changed == TRUE) { + if (added == TRUE) { select_default_input_stream (alsa); select_default_output_stream (alsa); } @@ -300,15 +314,13 @@ read_device (AlsaBackend *alsa, const gchar *card) /* The device may be already known, remove it if it's known and fails * to be read, this happens for example when PulseAudio is killed */ - device = g_hash_table_lookup (alsa->priv->devices, card); - ret = snd_ctl_open (&ctl, card, 0); if (ret < 0) { g_warning ("Failed to open ALSA control for %s: %s", card, snd_strerror (ret)); - if (device != NULL) - remove_device (alsa, device); + + remove_device_by_name (alsa, card); return FALSE; } @@ -317,9 +329,8 @@ read_device (AlsaBackend *alsa, const gchar *card) ret = snd_ctl_card_info (ctl, info); if (ret < 0) { g_warning ("Failed to read card info: %s", snd_strerror (ret)); - if (device != NULL) - remove_device (alsa, device); + remove_device_by_name (alsa, card); snd_ctl_close (ctl); return FALSE; } @@ -342,11 +353,7 @@ read_device (AlsaBackend *alsa, const gchar *card) return FALSE; } - g_object_set_data_full (G_OBJECT (device), - "__matemixer_alsa_device_id", - g_strdup (id), - g_free); - + ALSA_DEVICE_SET_ID (device, id); add_device (alsa, device); snd_ctl_close (ctl); @@ -356,19 +363,13 @@ read_device (AlsaBackend *alsa, const gchar *card) static void add_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; - - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + alsa->priv->devices = g_list_insert_sorted_with_data (alsa->priv->devices, + device, + (GCompareDataFunc) compare_devices, + NULL); - g_hash_table_insert (alsa->priv->devices, - g_strdup (name), - g_object_ref (device)); - - /* Remember the device identifier, use a single string copy as we only free - * the hash table key */ - g_hash_table_add (alsa->priv->devices_ids, - g_strdup (g_object_get_data (G_OBJECT (device), - "__matemixer_alsa_device_id"))); + /* Keep track of device identifiers */ + g_hash_table_add (alsa->priv->devices_ids, g_strdup (ALSA_DEVICE_GET_ID (device))); g_signal_connect_swapped (G_OBJECT (device), "closed", @@ -379,7 +380,22 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) G_CALLBACK (remove_stream), alsa); - g_signal_emit_by_name (G_OBJECT (alsa), "device-added", name); + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-added", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (free_stream_list), + alsa); + + g_signal_emit_by_name (G_OBJECT (alsa), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); /* Load the device elements after emitting device-added, because the load * function will most likely emit stream-added on the device and backend */ @@ -389,27 +405,48 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) static void remove_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; + GList *item; + + item = g_list_find (alsa->priv->devices, device); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_name (AlsaBackend *alsa, const gchar *name) +{ + GList *item; + + item = g_list_find_custom (alsa->priv->devices, name, compare_device_name); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_list_item (AlsaBackend *alsa, GList *item) +{ + AlsaDevice *device; + + device = ALSA_DEVICE (item->data); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + g_signal_handlers_disconnect_by_data (G_OBJECT (device), alsa); - g_signal_handlers_disconnect_by_func (G_OBJECT (device), - G_CALLBACK (remove_device), - alsa); - g_signal_handlers_disconnect_by_func (G_OBJECT (device), - G_CALLBACK (remove_stream), - alsa); + if (alsa_device_is_open (device) == TRUE) + alsa_device_close (device); + + alsa->priv->devices = g_list_delete_link (alsa->priv->devices, item); - /* Remove the device */ g_hash_table_remove (alsa->priv->devices_ids, - g_object_get_data (G_OBJECT (device), - "__matemixer_alsa_device_id")); + ALSA_DEVICE_GET_ID (device)); + + /* The list may and may not have been invalidate by device signals */ + free_stream_list (alsa); - // XXX close the device and make it remove streams - g_hash_table_remove (alsa->priv->devices, name); g_signal_emit_by_name (G_OBJECT (alsa), "device-removed", - name); + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } static void @@ -419,7 +456,6 @@ remove_stream (AlsaBackend *alsa, const gchar *name) stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (alsa)); - // XXX see if the change happens after stream is removed or before if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) select_default_input_stream (alsa); @@ -432,36 +468,19 @@ remove_stream (AlsaBackend *alsa, const gchar *name) static void select_default_input_stream (AlsaBackend *alsa) { - AlsaDevice *device; - AlsaStream *stream; - gchar card[16]; - gint num; - - /* Always prefer stream in the "default" device */ - device = g_hash_table_lookup (alsa->priv->devices, "default"); - if (device != NULL) { - stream = alsa_device_get_input_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), - MATE_MIXER_STREAM (stream)); - return; - } - } + GList *list; - /* If there is no input stream in the default device, search the cards in - * the correct order */ - for (num = 0;; num++) { - g_snprintf (card, sizeof (card), "hw:%d", num); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_input_stream (device); - device = g_hash_table_lookup (alsa->priv->devices, card); - if (device == NULL) - break; - stream = alsa_device_get_input_stream (device); if (stream != NULL) { _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), MATE_MIXER_STREAM (stream)); return; } + list = list->next; } /* In the worst case unset the default stream */ @@ -471,38 +490,50 @@ select_default_input_stream (AlsaBackend *alsa) static void select_default_output_stream (AlsaBackend *alsa) { - AlsaDevice *device; - AlsaStream *stream; - gchar card[16]; - gint num; - - /* Always prefer stream in the "default" device */ - device = g_hash_table_lookup (alsa->priv->devices, "default"); - if (device != NULL) { - stream = alsa_device_get_output_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), - MATE_MIXER_STREAM (stream)); - return; - } - } + GList *list; - /* If there is no input stream in the default device, search the cards in - * the correct order */ - for (num = 0;; num++) { - g_snprintf (card, sizeof (card), "hw:%d", num); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_output_stream (device); - device = g_hash_table_lookup (alsa->priv->devices, card); - if (device == NULL) - break; - stream = alsa_device_get_output_stream (device); if (stream != NULL) { _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), MATE_MIXER_STREAM (stream)); return; } + list = list->next; } /* In the worst case unset the default stream */ _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), NULL); } + +static void +free_stream_list (AlsaBackend *alsa) +{ + if (alsa->priv->streams == NULL) + return; + + g_list_free_full (alsa->priv->streams, g_object_unref); + + alsa->priv->streams = NULL; +} + +static gint +compare_devices (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *d1 = MATE_MIXER_DEVICE (a); + MateMixerDevice *d2 = MATE_MIXER_DEVICE (b); + + return strcmp (mate_mixer_device_get_name (d1), mate_mixer_device_get_name (d2)); +} + +static gint +compare_device_name (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *device = MATE_MIXER_DEVICE (a); + const gchar *name = (const gchar *) b; + + return strcmp (mate_mixer_device_get_name (device), name); +} diff --git a/backends/alsa/alsa-constants.c b/backends/alsa/alsa-constants.c index 2124a2e..7b7021f 100644 --- a/backends/alsa/alsa-constants.c +++ b/backends/alsa/alsa-constants.c @@ -22,15 +22,190 @@ #include "alsa-constants.h" -// XXX add more and probably move them somewhere else +/* + * These lists of ALSA mixer elements are based on PulseAudio's mixer paths and own + * observations. The intention is to provide translatable and in some cases better + * readable labels and role assignments. The controls list is also used for selecting + * the default controls and the selection machanism relies on the order of elements, + * so more useful elements should be placed on the top. The last two boolean values + * indicate whether we prefer the element to be used as a default input or output + * control. + * + * Of course the lists are very incomplete and it would be great if users validated and + * refreshed them from time to time. + */ const AlsaControlInfo alsa_controls[] = { - { "Master", N_("Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, - { "Speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, - { "Capture", N_("Capture"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, - { "PCM", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM }, - { "Line", N_("Line"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT }, - { "Mic", N_("Mic"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT }, + /* Output controls */ + { "Master", N_("Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Hardware Master", N_("Hardware Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "PCM", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, FALSE, TRUE }, + { "Desktop Speaker", N_("Desktop Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Front", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Front Speaker", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker Front", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headphone", N_("Headphone"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headphone2", N_("Headphone 2"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headset", N_("Headset"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Master Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Surround Speaker", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Center", N_("Center Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Center Speaker", N_("Center Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "CLFE", N_("CLFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker CLFE", N_("CLFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Master Mono", N_("Master Mono"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Master Digital", N_("Master Digital"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Digital/SPDIF", N_("Digital"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, FALSE, TRUE }, + { "Speaker Side", N_("Side Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Side", N_("Side Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Rear", N_("Rear Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Wave", N_("Wave"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "Phone", N_("Phone"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "CD", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, FALSE, TRUE }, + { "Music", N_("Music"), MATE_MIXER_STREAM_CONTROL_ROLE_MUSIC, FALSE, TRUE }, + { "AC97", N_("AC97"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "LFE", N_("LFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "LFE Speaker", N_("LFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "Bass Speaker", N_("Bass Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "PC Speaker", N_("PC Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "Synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "MIDI", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "Synth/MIDI", N_("Synth/MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "Bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, FALSE, FALSE }, + { "Treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, FALSE, FALSE }, + + /* Input controls */ + { "Capture", N_("Capture"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, TRUE, FALSE }, + { "Mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Mic/Line", N_("Microphone/Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Internal Mic", N_("Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Int Mic", N_("Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Front Mic", N_("Front Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Rear Mic", N_("Rear Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Dock Mic", N_("Dock Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Headphone Mic", N_("Headphone Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Headset Mic", N_("Headset Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Inverted Internal Mic", N_("Inverted Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Line", N_("Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Aux", N_("Auxiliary"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_VIDEO, TRUE, FALSE }, + { "TV Tuner", N_("TV Tuner"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, TRUE, FALSE }, + { "FM", N_("FM"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, TRUE, FALSE }, + { "Mic Boost", N_("Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Mic Boost (+20dB)", N_("Microphone Boost (+20dB)"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Int Mic Boost", N_("Internal Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Internal Mic Boost", N_("Internal Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Front Mic Boost", N_("Front Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Rear Mic Boost", N_("Rear Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Dock Mic Boost", N_("Dock Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Headphone Mic Boost", N_("Headphone Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Headset Mic Boost", N_("Headset Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Line Boost", N_("Line In Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { NULL } +}; + +/* Switches and toggles */ +const AlsaSwitchInfo alsa_switches[] = +{ + { "Analog Output", N_("Analog Output"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Analog Source", N_("Analog Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Capture Source", N_("Capture Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input Source", N_("Input Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input Source Select", N_("Input Source Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Digital Input Source", N_("Digital Input Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "PCM Capture Source", N_("PCM Capture Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 Playback Source", N_("Digital Playback Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mono Output Select", N_("Mono Output Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Shared Mic/Line in", N_("Shared Mic/Line In"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mic Select", N_("Microphone Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mic Jack Mode", N_("Microphone Jack Mode"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Surround Jack Mode", N_("Surround Jack Mode"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Auto-Mute Mode", N_("Auto-Mute Mode"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + + /* (Probably) toggles */ + { "External Amplifier", N_("External Amplifier"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Bass Boost", N_("Bass Boost"), MATE_MIXER_SWITCH_ROLE_BOOST }, + { "Capture Boost", N_("Capture Boost"), MATE_MIXER_SWITCH_ROLE_BOOST }, + { "IEC958", N_("Digital"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 In", N_("Digital In"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 Optical Raw", N_("Optical"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Auto Gain Control", N_("Auto Gain Control"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mix", N_("Mix"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mix Mono", N_("Mix Mono"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mic Capture", N_("Mic Capture"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input 1", N_("Input 1"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input 2", N_("Input 2"), MATE_MIXER_SWITCH_ROLE_PORT }, + { NULL } +}; + +const AlsaSwitchOptionInfo alsa_switch_options[] = +{ + /* Output options */ + { "Speakers", N_("Speakers"), NULL }, + { "Headphones", N_("Headphones"), NULL }, + { "FP Headphones", N_("Front Panel Headphones"), NULL }, + + /* Microphone options */ + { "Mic", N_("Microphone"), "audio-input-microphone" }, + { "Microphone", N_("Microphone"), "audio-input-microphone" }, + { "Mic1", N_("Microphone 1"), "audio-input-microphone" }, + { "Mic2", N_("Microphone 2"), "audio-input-microphone" }, + { "Mic in", N_("Microphone In"), "audio-input-microphone" }, + { "Mic In", N_("Microphone In"), "audio-input-microphone" }, + { "Front Mic", N_("Front Microphone"), "audio-input-microphone" }, + { "Front Microphone", N_("Front Microphone"), "audio-input-microphone" }, + { "Headphone Mic", N_("Headphone Microphone"), "audio-input-microphone" }, + { "Headset Mic", N_("Headset Microphone"), "audio-input-microphone" }, + { "Dock Mic", N_("Dock Microphone"), "audio-input-microphone" }, + { "Internal Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Int Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Internal Mic 1", N_("Internal Microphone 1"), "audio-input-microphone" }, + { "iMic", N_("Internal Microphone"), "audio-input-microphone" }, + { "i-Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "IntMic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Int DMic", N_("Internal Digital Microphone"), "audio-input-microphone" }, + { "Digital Mic", N_("Digital Microphone"), "audio-input-microphone" }, + { "Digital Mic 1", N_("Digital Microphone 1"), "audio-input-microphone" }, + { "Digital Mic 2", N_("Digital Microphone 2"), "audio-input-microphone" }, + { "D-Mic", N_("Digital Microphone"), "audio-input-microphone" }, + { "ExtMic", N_("External Microphone"), "audio-input-microphone" }, + { "Ext Mic", N_("External Microphone"), "audio-input-microphone" }, + { "E-Mic", N_("External Microphone"), "audio-input-microphone" }, + { "e-Mic", N_("External Microphone"), "audio-input-microphone" }, + { "Rear Mic", N_("Rear Microphone"), "audio-input-microphone" }, + { "Cam Mic", N_("Camera Microphone"), "audio-input-microphone" }, + + /* Other options */ + { "Analog", N_("Analog"), NULL }, + { "Analog In", N_("Analog In"), NULL }, + { "Analog Inputs", N_("Analog Inputs"), NULL }, + { "Line in", N_("Line In"), NULL }, + { "Line In", N_("Line In"), NULL }, + { "Line-In", N_("Line In"), NULL }, + { "Mic/Line", N_("Microphone/Line In"), NULL }, + { "Line/Mic", N_("Line In/Microphone"), NULL }, + { "LineIn", N_("Line In"), NULL }, + { "Line", N_("Line In"), NULL }, + { "Input1", N_("Input 1"), NULL }, + { "Input2", N_("Input 2"), NULL }, + { "IEC958 In", N_("Digital In"), NULL }, + { "TV Tuner", N_("TV Tuner"), NULL }, + { "FM", N_("FM"), NULL }, + { "AUX", N_("Auxiliary"), NULL }, + { "AUX IN", N_("Auxiliary In"), NULL }, + { "Aux In", N_("Auxiliary In"), NULL }, + { "Aux", N_("Auxiliary"), NULL }, + { "Aux0", N_("Auxiliary 0"), NULL }, + { "Aux1", N_("Auxiliary 1"), NULL }, + { "Aux2", N_("Auxiliary 2"), NULL }, + { "Aux3", N_("Auxiliary 3"), NULL }, + { "Docking-Station", N_("Docking Station"), NULL }, + { "Mixer", N_("Mixer"), NULL }, + { "Unknown1", N_("Unknown 1"), NULL }, + { "Unknown2", N_("Unknown 2"), NULL }, { NULL } }; diff --git a/backends/alsa/alsa-constants.h b/backends/alsa/alsa-constants.h index 81257c7..8137289 100644 --- a/backends/alsa/alsa-constants.h +++ b/backends/alsa/alsa-constants.h @@ -22,14 +22,35 @@ #include <alsa/asoundlib.h> #include <libmatemixer/matemixer.h> +G_BEGIN_DECLS + typedef struct { gchar *name; gchar *label; MateMixerStreamControlRole role; + gboolean use_default_input; + gboolean use_default_output; } AlsaControlInfo; +typedef struct { + gchar *name; + gchar *label; + MateMixerSwitchRole role; +} AlsaSwitchInfo; + +typedef struct { + gchar *name; + gchar *label; + gchar *icon; +} AlsaSwitchOptionInfo; + extern const AlsaControlInfo alsa_controls[]; +extern const AlsaSwitchInfo alsa_switches[]; +extern const AlsaSwitchOptionInfo alsa_switch_options[]; + extern const MateMixerChannelPosition alsa_channel_map_from[]; extern const snd_mixer_selem_channel_id_t alsa_channel_map_to[]; +G_END_DECLS + #endif /* ALSA_CONSTANTS_H */ diff --git a/backends/alsa/alsa-device.c b/backends/alsa/alsa-device.c index 5acc6f5..f7f705e 100644 --- a/backends/alsa/alsa-device.c +++ b/backends/alsa/alsa-device.c @@ -15,7 +15,9 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ -#include <strings.h> +#include <string.h> +#include <libintl.h> + #include <glib.h> #include <glib/gi18n.h> #include <glib-object.h> @@ -35,6 +37,18 @@ #define ALSA_DEVICE_ICON "audio-card" +#define ALSA_STREAM_CONTROL_GET_SCORE(c) \ + (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (c), \ + "__matemixer_alsa_control_score"))) + +#define ALSA_STREAM_CONTROL_SET_SCORE(c,score) \ + (g_object_set_data (G_OBJECT (c), \ + "__matemixer_alsa_control_score", \ + GINT_TO_POINTER (score))) + +#define ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE(s) \ + (ALSA_STREAM_CONTROL_GET_SCORE (alsa_stream_get_default_control (ALSA_STREAM (s)))) + struct _AlsaDevicePrivate { snd_mixer_t *handle; @@ -43,7 +57,8 @@ struct _AlsaDevicePrivate GCond cond; AlsaStream *input; AlsaStream *output; - GHashTable *switches; + GList *streams; + GList *switches; gboolean events_pending; }; @@ -61,64 +76,83 @@ static void alsa_device_finalize (GObject *object); G_DEFINE_TYPE (AlsaDevice, alsa_device, MATE_MIXER_TYPE_DEVICE) -static MateMixerSwitch *alsa_device_get_switch (MateMixerDevice *mmd, - const gchar *name); +static const GList * alsa_device_list_streams (MateMixerDevice *mmd); +static const GList * alsa_device_list_switches (MateMixerDevice *mmd); + +static gboolean add_stream_input_control (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_control (AlsaDevice *device, + snd_mixer_elem_t *el); + +static gboolean add_switch (AlsaDevice *device, + AlsaStream *stream, + snd_mixer_elem_t *el); -static GList * alsa_device_list_streams (MateMixerDevice *mmd); -static GList * alsa_device_list_switches (MateMixerDevice *mmd); +static gboolean add_device_switch (AlsaDevice *device, + snd_mixer_elem_t *el); -static gboolean add_stream_input_control (AlsaDevice *device, - snd_mixer_elem_t *el); -static gboolean add_stream_output_control (AlsaDevice *device, - snd_mixer_elem_t *el); +static gboolean add_stream_input_switch (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_switch (AlsaDevice *device, + snd_mixer_elem_t *el); -static gboolean add_switch (AlsaDevice *device, - AlsaStream *stream, - snd_mixer_elem_t *el); +static gboolean add_stream_input_toggle (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_toggle (AlsaDevice *device, + snd_mixer_elem_t *el); -static gboolean add_device_switch (AlsaDevice *device, - snd_mixer_elem_t *el); +static void load_element (AlsaDevice *device, + snd_mixer_elem_t *el); -static gboolean add_stream_input_switch (AlsaDevice *device, - snd_mixer_elem_t *el); -static gboolean add_stream_output_switch (AlsaDevice *device, - snd_mixer_elem_t *el); +static void load_elements_by_name (AlsaDevice *device, + const gchar *name); -static gboolean add_stream_input_toggle (AlsaDevice *device, - snd_mixer_elem_t *el); -static gboolean add_stream_output_toggle (AlsaDevice *device, - snd_mixer_elem_t *el); +static void remove_elements_by_name (AlsaDevice *device, + const gchar *name); -static void load_element (AlsaDevice *device, - snd_mixer_elem_t *el); +static void handle_poll (AlsaDevice *device); -static void load_elements_by_name (AlsaDevice *device, - const gchar *name); +static gboolean handle_process_events (AlsaDevice *device); -static void remove_elements_by_name (AlsaDevice *device, - const gchar *name); +static int handle_callback (snd_mixer_t *handle, + guint mask, + snd_mixer_elem_t *el); +static int handle_element_callback (snd_mixer_elem_t *el, + guint mask); -static void handle_poll (AlsaDevice *device); +static void validate_default_controls (AlsaDevice *device); -static gboolean handle_process_events (AlsaDevice *device); +static AlsaStreamControl *get_best_stream_control (AlsaStream *stream); -static int handle_callback (snd_mixer_t *handle, - guint mask, - snd_mixer_elem_t *el); -static int handle_element_callback (snd_mixer_elem_t *el, - guint mask); +static gchar * get_element_name (snd_mixer_elem_t *el); -static void close_device (AlsaDevice *device); +static void get_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); +static void get_input_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); +static void get_output_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); -static gchar * get_element_name (snd_mixer_elem_t *el); -static void get_control_info (snd_mixer_elem_t *el, - gchar **name, - gchar **label, - MateMixerStreamControlRole *role); +static void get_switch_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerSwitchRole *role); -static void get_switch_info (snd_mixer_elem_t *el, - gchar **name, - gchar **label); +static void close_mixer (AlsaDevice *device); + +static void free_stream_list (AlsaDevice *device); + +static gint compare_switch_name (gconstpointer a, + gconstpointer b); static void alsa_device_class_init (AlsaDeviceClass *klass) @@ -131,14 +165,13 @@ alsa_device_class_init (AlsaDeviceClass *klass) object_class->finalize = alsa_device_finalize; device_class = MATE_MIXER_DEVICE_CLASS (klass); - device_class->get_switch = alsa_device_get_switch; device_class->list_streams = alsa_device_list_streams; device_class->list_switches = alsa_device_list_switches; signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (AlsaDeviceClass, closed), NULL, NULL, @@ -157,11 +190,6 @@ alsa_device_init (AlsaDevice *device) ALSA_TYPE_DEVICE, AlsaDevicePrivate); - device->priv->switches = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); - device->priv->context = g_main_context_ref_thread_default (); g_mutex_init (&device->priv->mutex); @@ -178,7 +206,12 @@ alsa_device_dispose (GObject *object) g_clear_object (&device->priv->input); g_clear_object (&device->priv->output); - g_hash_table_remove_all (device->priv->switches); + if (device->priv->switches != NULL) { + g_list_free_full (device->priv->switches, g_object_unref); + device->priv->switches = NULL; + } + + free_stream_list (device); G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); } @@ -193,11 +226,9 @@ alsa_device_finalize (GObject *object) g_mutex_clear (&device->priv->mutex); g_cond_clear (&device->priv->cond); - g_hash_table_unref (device->priv->switches); g_main_context_unref (device->priv->context); - if (device->priv->handle != NULL) - snd_mixer_close (device->priv->handle); + close_mixer (device); G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); } @@ -208,7 +239,7 @@ alsa_device_new (const gchar *name, const gchar *label) AlsaDevice *device; gchar *stream_name; - g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (label != NULL, NULL); device = g_object_new (ALSA_TYPE_DEVICE, @@ -223,13 +254,13 @@ alsa_device_new (const gchar *name, const gchar *label) stream_name = g_strdup_printf ("alsa-input-%s", name); device->priv->input = alsa_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_INPUT); + MATE_MIXER_DIRECTION_INPUT); g_free (stream_name); stream_name = g_strdup_printf ("alsa-output-%s", name); device->priv->output = alsa_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_OUTPUT); + MATE_MIXER_DIRECTION_OUTPUT); g_free (stream_name); return device; @@ -289,6 +320,72 @@ alsa_device_open (AlsaDevice *device) return TRUE; } +gboolean +alsa_device_is_open (AlsaDevice *device) +{ + g_return_val_if_fail (ALSA_IS_DEVICE (device), FALSE); + + if (device->priv->handle != NULL) + return TRUE; + + return FALSE; +} + +void +alsa_device_close (AlsaDevice *device) +{ + GList *list; + + g_return_if_fail (ALSA_IS_DEVICE (device)); + + if (device->priv->handle == NULL) + return; + + /* Make each stream remove its controls and switches */ + if (alsa_stream_has_controls_or_switches (device->priv->input) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + + alsa_stream_remove_all (device->priv->input); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); + } + + if (alsa_stream_has_controls_or_switches (device->priv->output) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + + alsa_stream_remove_all (device->priv->output); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); + } + + /* Remove device switches */ + list = device->priv->switches; + while (list != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (list->data); + GList *next = list->next; + + device->priv->switches = g_list_delete_link (device->priv->switches, list); + g_signal_emit_by_name (G_OBJECT (device), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + g_object_unref (swtch); + + list = next; + } + + close_mixer (device); + + g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); +} + void alsa_device_load (AlsaDevice *device) { @@ -307,6 +404,9 @@ alsa_device_load (AlsaDevice *device) el = snd_mixer_elem_next (el); } + /* Assign proper default controls */ + validate_default_controls (device); + /* Set callback for ALSA events */ snd_mixer_set_callback (device->priv->handle, handle_callback); snd_mixer_set_callback_private (device->priv->handle, device); @@ -332,7 +432,7 @@ alsa_device_get_input_stream (AlsaDevice *device) /* Normally controlless streams should not exist, here we simulate the * behaviour for the owning instance */ - if (alsa_stream_is_empty (device->priv->input) == FALSE) + if (alsa_stream_has_controls_or_switches (device->priv->input) == TRUE) return device->priv->input; return NULL; @@ -345,7 +445,7 @@ alsa_device_get_output_stream (AlsaDevice *device) /* Normally controlless streams should not exist, here we simulate the * behaviour for the owning instance */ - if (alsa_stream_is_empty (device->priv->output) == FALSE) + if (alsa_stream_has_controls_or_switches (device->priv->output) == TRUE) return device->priv->output; return NULL; @@ -360,44 +460,39 @@ add_element (AlsaDevice *device, AlsaStream *stream, AlsaElement *element) return FALSE; if (stream != NULL) { - gboolean empty = FALSE; - - if (alsa_stream_is_empty (stream) == TRUE) { + if (alsa_stream_has_controls_or_switches (stream) == FALSE) { const gchar *name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + free_stream_list (device); + /* Pretend the stream has just been created now that we are adding * the first control */ g_signal_emit_by_name (G_OBJECT (device), "stream-added", name); - empty = TRUE; } if (ALSA_IS_STREAM_CONTROL (element)) { + /* Stream control */ alsa_stream_add_control (stream, ALSA_STREAM_CONTROL (element)); - - /* If this is the first control, set it as the default one. - * The controls often seem to come in the order of importance, but this is - * driver specific, so we may later see if there is another control which - * better matches the default. */ - if (empty == TRUE) - alsa_stream_set_default_control (stream, ALSA_STREAM_CONTROL (element)); - added = TRUE; } else if (ALSA_IS_SWITCH (element)) { /* Switch belonging to a stream */ alsa_stream_add_switch (stream, ALSA_SWITCH (element)); added = TRUE; + } else if (ALSA_IS_TOGGLE (element)) { + /* Toggle belonging to a stream */ + alsa_stream_add_toggle (stream, ALSA_TOGGLE (element)); + added = TRUE; } - } else if (ALSA_IS_SWITCH (element)) { + } else if (ALSA_IS_SWITCH (element) || ALSA_IS_TOGGLE (element)) { /* Switch belonging to the device */ const gchar *name = mate_mixer_switch_get_name (MATE_MIXER_SWITCH (element)); - g_hash_table_insert (device->priv->switches, - g_strdup (name), - g_object_ref (element)); + device->priv->switches = + g_list_append (device->priv->switches, g_object_ref (element)); g_signal_emit_by_name (G_OBJECT (device), "switch-added", @@ -414,47 +509,32 @@ add_element (AlsaDevice *device, AlsaStream *stream, AlsaElement *element) return added; } -static MateMixerSwitch * -alsa_device_get_switch (MateMixerDevice *mmd, const gchar *name) -{ - g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); - g_return_val_if_fail (name != NULL, NULL); - - return g_hash_table_lookup (ALSA_DEVICE (mmd)->priv->switches, name); -} - -static GList * +static const GList * alsa_device_list_streams (MateMixerDevice *mmd) { AlsaDevice *device; - GList *list = NULL; g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); device = ALSA_DEVICE (mmd); - if (device->priv->output != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->output)); - if (device->priv->input != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->input)); - - return list; + if (device->priv->streams == NULL) { + if (device->priv->output != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->input)); + } + return device->priv->streams; } -static GList * +static const GList * alsa_device_list_switches (MateMixerDevice *mmd) { - GList *list; - g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (ALSA_DEVICE (mmd)->priv->switches); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return ALSA_DEVICE (mmd)->priv->switches; } static gboolean @@ -463,25 +543,28 @@ add_stream_input_control (AlsaDevice *device, snd_mixer_elem_t *el) AlsaStreamControl *control; gchar *name; gchar *label; + gboolean ret; + gint score; MateMixerStreamControlRole role; - get_control_info (el, &name, &label, &role); + get_input_control_info (el, &name, &label, &role, &score); - g_debug ("Found device %s input control %s", + g_debug ("Reading device %s input control %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), - label); + name); - control = alsa_stream_input_control_new (name, label, role); + control = alsa_stream_input_control_new (name, label, role, device->priv->input); g_free (name); g_free (label); + ALSA_STREAM_CONTROL_SET_SCORE (control, score); + alsa_element_set_snd_element (ALSA_ELEMENT (control), el); - if (add_element (device, device->priv->input, ALSA_ELEMENT (control)) == FALSE) { - g_object_unref (control); - return FALSE; - } - return TRUE; + ret = add_element (device, device->priv->input, ALSA_ELEMENT (control)); + + g_object_unref (control); + return ret; } static gboolean @@ -490,49 +573,51 @@ add_stream_output_control (AlsaDevice *device, snd_mixer_elem_t *el) AlsaStreamControl *control; gchar *label; gchar *name; + gboolean ret; + gint score; MateMixerStreamControlRole role; - get_control_info (el, &name, &label, &role); + get_output_control_info (el, &name, &label, &role, &score); - g_debug ("Found device %s output control %s", + g_debug ("Reading device %s output control %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), - label); + name); - control = alsa_stream_output_control_new (name, label, role); + control = alsa_stream_output_control_new (name, label, role, device->priv->output); g_free (name); g_free (label); + ALSA_STREAM_CONTROL_SET_SCORE (control, score); + alsa_element_set_snd_element (ALSA_ELEMENT (control), el); - if (add_element (device, device->priv->output, ALSA_ELEMENT (control)) == FALSE) { - g_object_unref (control); - return FALSE; - } - return TRUE; + ret = add_element (device, device->priv->output, ALSA_ELEMENT (control)); + + g_object_unref (control); + return ret; } static AlsaToggle * create_toggle (AlsaDevice *device, snd_mixer_elem_t *el, AlsaToggleType type) { - AlsaToggle *toggle; - AlsaSwitchOption *on; - AlsaSwitchOption *off; - gchar *name; - gchar *label; + AlsaToggle *toggle; + AlsaSwitchOption *on; + AlsaSwitchOption *off; + gchar *name; + gchar *label; + MateMixerSwitchRole role; on = alsa_switch_option_new ("On", _("On"), NULL, 1); off = alsa_switch_option_new ("Off", _("Off"), NULL, 0); - get_switch_info (el, &name, &label); - toggle = alsa_toggle_new (name, - label, - type, - on, off); - g_free (name); - g_free (label); + get_switch_info (el, &name, &label, &role); + + toggle = alsa_toggle_new (name, label, role, type, on, off); alsa_element_set_snd_element (ALSA_ELEMENT (toggle), el); + g_free (name); + g_free (label); g_object_unref (on); g_object_unref (off); @@ -542,14 +627,15 @@ create_toggle (AlsaDevice *device, snd_mixer_elem_t *el, AlsaToggleType type) static gboolean add_switch (AlsaDevice *device, AlsaStream *stream, snd_mixer_elem_t *el) { - AlsaElement *element = NULL; - GList *options = NULL; - gchar *name; - gchar *label; - gchar item[128]; - guint i; - gint count; - gint ret; + AlsaElement *element = NULL; + GList *options = NULL; + gchar *name; + gchar *label; + gchar item[128]; + guint i; + gint count; + gboolean ret; + MateMixerSwitchRole role; count = snd_mixer_selem_get_enum_items (el); if G_UNLIKELY (count <= 0) { @@ -560,30 +646,44 @@ add_switch (AlsaDevice *device, AlsaStream *stream, snd_mixer_elem_t *el) } for (i = 0; i < count; i++) { - ret = snd_mixer_selem_get_enum_item_name (el, i, - sizeof (item), - item); - if G_LIKELY (ret == 0) - options = g_list_prepend (options, - alsa_switch_option_new (item, item, NULL, i)); - else + gint ret = snd_mixer_selem_get_enum_item_name (el, i, sizeof (item), item); + + if G_LIKELY (ret == 0) { + gint j; + AlsaSwitchOption *option = NULL; + + for (j = 0; alsa_switch_options[j].name != NULL; j++) + if (strcmp (item, alsa_switch_options[j].name) == 0) { + option = alsa_switch_option_new (item, + gettext (alsa_switch_options[j].label), + alsa_switch_options[j].icon, + i); + break; + } + + if (option == NULL) + option = alsa_switch_option_new (item, item, NULL, i); + + options = g_list_prepend (options, option); + } else g_warning ("Failed to read switch item name: %s", snd_strerror (ret)); } - get_switch_info (el, &name, &label); + get_switch_info (el, &name, &label, &role); /* Takes ownership of options */ - element = ALSA_ELEMENT (alsa_switch_new (name, label, g_list_reverse (options))); + element = ALSA_ELEMENT (alsa_switch_new (name, label, + role, + g_list_reverse (options))); g_free (name); g_free (label); alsa_element_set_snd_element (element, el); - if (add_element (device, stream, element) == FALSE) { - g_object_unref (element); - return FALSE; - } - return TRUE; + ret = add_element (device, stream, element); + + g_object_unref (element); + return ret; } static gboolean @@ -623,6 +723,7 @@ static gboolean add_stream_input_toggle (AlsaDevice *device, snd_mixer_elem_t *el) { AlsaToggle *toggle; + gboolean ret; g_debug ("Reading device %s input toggle %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), @@ -630,17 +731,17 @@ add_stream_input_toggle (AlsaDevice *device, snd_mixer_elem_t *el) toggle = create_toggle (device, el, ALSA_TOGGLE_CAPTURE); - if (add_element (device, device->priv->input, ALSA_ELEMENT (toggle)) == FALSE) { - g_object_unref (toggle); - return FALSE; - } - return TRUE; + ret = add_element (device, device->priv->input, ALSA_ELEMENT (toggle)); + + g_object_unref (toggle); + return ret; } static gboolean add_stream_output_toggle (AlsaDevice *device, snd_mixer_elem_t *el) { AlsaToggle *toggle; + gboolean ret; g_debug ("Reading device %s output toggle %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), @@ -648,11 +749,10 @@ add_stream_output_toggle (AlsaDevice *device, snd_mixer_elem_t *el) toggle = create_toggle (device, el, ALSA_TOGGLE_PLAYBACK); - if (add_element (device, device->priv->output, ALSA_ELEMENT (toggle)) == FALSE) { - g_object_unref (toggle); - return FALSE; - } - return TRUE; + ret = add_element (device, device->priv->output, ALSA_ELEMENT (toggle)); + + g_object_unref (toggle); + return ret; } static void @@ -671,8 +771,7 @@ load_element (AlsaDevice *device, snd_mixer_elem_t *el) penum = TRUE; /* Enumerated controls which are not marked as capture or playback - * are considered to be a part of the whole device, although sometimes - * this is incorrectly reported by the driver */ + * are considered to be a part of the whole device */ if (cenum == FALSE && penum == FALSE) { add_device_switch (device, el); } @@ -707,25 +806,28 @@ load_element (AlsaDevice *device, snd_mixer_elem_t *el) static void load_elements_by_name (AlsaDevice *device, const gchar *name) { - AlsaElement *element; + GList *item; alsa_stream_load_elements (device->priv->input, name); alsa_stream_load_elements (device->priv->output, name); - element = g_hash_table_lookup (device->priv->switches, name); - if (element != NULL) - alsa_element_load (element); + item = g_list_find_custom (device->priv->switches, name, compare_switch_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); } static void remove_elements_by_name (AlsaDevice *device, const gchar *name) { + GList *item; + if (alsa_stream_remove_elements (device->priv->input, name) == TRUE) { /* Removing last stream element "removes" the stream */ - if (alsa_stream_is_empty (device->priv->input) == TRUE) { + if (alsa_stream_has_controls_or_switches (device->priv->input) == FALSE) { const gchar *stream_name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + free_stream_list (device); g_signal_emit_by_name (G_OBJECT (device), "stream-removed", stream_name); @@ -734,20 +836,28 @@ remove_elements_by_name (AlsaDevice *device, const gchar *name) if (alsa_stream_remove_elements (device->priv->output, name) == TRUE) { /* Removing last stream element "removes" the stream */ - if (alsa_stream_is_empty (device->priv->output) == TRUE) { + if (alsa_stream_has_controls_or_switches (device->priv->output) == FALSE) { const gchar *stream_name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + free_stream_list (device); g_signal_emit_by_name (G_OBJECT (device), "stream-removed", stream_name); } } - if (g_hash_table_remove (device->priv->switches, name) == TRUE) + item = g_list_find_custom (device->priv->switches, name, compare_switch_name); + if (item != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (item->data); + + device->priv->switches = g_list_delete_link (device->priv->switches, item); g_signal_emit_by_name (G_OBJECT (device), "switch-removed", - name); + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); + } } static void @@ -806,7 +916,7 @@ handle_process_events (AlsaDevice *device) if (device->priv->handle != NULL) { gint ret = snd_mixer_handle_events (device->priv->handle); if (ret < 0) - close_device (device); + alsa_device_close (device); } device->priv->events_pending = FALSE; @@ -826,7 +936,15 @@ handle_callback (snd_mixer_t *handle, guint mask, snd_mixer_elem_t *el) if (mask & SND_CTL_EVENT_MASK_ADD) { AlsaDevice *device = snd_mixer_get_callback_private (handle); + if (device->priv->handle == NULL) { + /* The mixer is already closed */ + return 0; + } + load_element (device, el); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } return 0; } @@ -838,6 +956,11 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) gchar *name; device = snd_mixer_elem_get_callback_private (el); + if (device->priv->handle == NULL) { + /* The mixer is already closed */ + return 0; + } + name = get_element_name (el); if (mask == SND_CTL_EVENT_MASK_REMOVE) { @@ -846,10 +969,16 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) snd_mixer_elem_set_callback (el, NULL); remove_elements_by_name (device, name); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } else { if (mask & SND_CTL_EVENT_MASK_INFO) { remove_elements_by_name (device, name); load_element (device, el); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } if (mask & SND_CTL_EVENT_MASK_VALUE) load_elements_by_name (device, name); @@ -860,16 +989,86 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) } static void -close_device (AlsaDevice *device) +validate_default_controls (AlsaDevice *device) { - if (device->priv->handle != NULL) { - snd_mixer_close (device->priv->handle); - device->priv->handle = NULL; + AlsaStreamControl *best; + gint best_score; + gint current_score; + + /* + * Select the most suitable default control. Don't try too hard here because + * our list of known elements is incomplete and most drivers seem to provide + * the list in a reasonable order with the best element at the start. Each + * element in our list has a value (or score) which is simply its position + * in the list. Better elements are on the top, so smaller value represents + * a better element. + * + * Two cases are handled here: + * 1) The current default control is in our list, but the list also includes + * a better element. + * 2) The current default control is not in our list, but the list includes + * an element which is reasonably good. + * + * In other cases just keep the first control as the default. + */ + if (alsa_stream_has_controls (device->priv->input) == TRUE) { + best = get_best_stream_control (device->priv->input); + + best_score = ALSA_STREAM_CONTROL_GET_SCORE (best); + current_score = ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE (device->priv->input); + + /* See if the best element would make a good default one */ + if (best_score > -1) { + g_debug ("Found usable default input element %s (score %d)", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (best)), + best_score); + + if (current_score == -1 || best_score < current_score) + alsa_stream_set_default_control (device->priv->input, best); + } } - /* This signal tells the owner that the device has been closed voluntarily - * from within the instance */ - g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); + if (alsa_stream_has_controls (device->priv->output) == TRUE) { + best = get_best_stream_control (device->priv->output); + + best_score = ALSA_STREAM_CONTROL_GET_SCORE (best); + current_score = ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE (device->priv->output); + + /* See if the best element would make a good default one */ + if (best_score > -1) { + g_debug ("Found usable default output element %s (score %d)", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (best)), + best_score); + + if (current_score == -1 || best_score < current_score) + alsa_stream_set_default_control (device->priv->output, best); + } + } +} + +static AlsaStreamControl * +get_best_stream_control (AlsaStream *stream) +{ + const GList *list; + AlsaStreamControl *best = NULL; + guint best_score = -1; + + list = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (stream)); + while (list != NULL) { + AlsaStreamControl *current; + guint current_score; + + current = ALSA_STREAM_CONTROL (list->data); + current_score = ALSA_STREAM_CONTROL_GET_SCORE (current); + + if (best == NULL || (current_score != -1 && + (best_score == -1 || current_score < best_score))) { + best = current; + best_score = current_score; + } + list = list->next; + } + return best; } static gchar * @@ -884,7 +1083,8 @@ static void get_control_info (snd_mixer_elem_t *el, gchar **name, gchar **label, - MateMixerStreamControlRole *role) + MateMixerStreamControlRole *role, + gint *score) { MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; const gchar *n; @@ -893,49 +1093,119 @@ get_control_info (snd_mixer_elem_t *el, n = snd_mixer_selem_get_name (el); - for (i = 0; alsa_controls[i].name != NULL; i++) - if (strcmp (n, alsa_controls[i].name) == 0) { - l = alsa_controls[i].label; - r = alsa_controls[i].role; - break; - } + for (i = 0; alsa_controls[i].name != NULL; i++) { + if (strcmp (n, alsa_controls[i].name) != 0) + continue; + + l = gettext (alsa_controls[i].label); + r = alsa_controls[i].role; + break; + } *name = get_element_name (el); - if (l != NULL) + if (l != NULL) { *label = g_strdup (l); - else + *score = i; + } else { *label = g_strdup (n); + *score = -1; + } *role = r; } static void -get_switch_info (snd_mixer_elem_t *el, - gchar **name, - gchar **label) +get_input_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score) { - // MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; - const gchar *n; - const gchar *l = NULL; - // gint i; + get_control_info (el, name, label, role, score); + + if (*score > -1 && alsa_controls[*score].use_default_input == FALSE) + *score = -1; +} + +static void +get_output_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score) +{ + get_control_info (el, name, label, role, score); + + if (*score > -1 && alsa_controls[*score].use_default_output == FALSE) + *score = -1; +} + +static void +get_switch_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerSwitchRole *role) +{ + MateMixerSwitchRole r = MATE_MIXER_SWITCH_ROLE_UNKNOWN; + const gchar *n; + const gchar *l = NULL; + gint i; n = snd_mixer_selem_get_name (el); - // TODO provide translated label and flags + for (i = 0; alsa_switches[i].name != NULL; i++) { + if (strcmp (n, alsa_switches[i].name) != 0) + continue; + + l = gettext (alsa_switches[i].label); + r = alsa_switches[i].role; + break; + } -/* - for (i = 0; alsa_controls[i].name != NULL; i++) - if (strcmp (n, alsa_controls[i].name) == 0) { - l = alsa_controls[i].label; - r = alsa_controls[i].role; - break; - } -*/ *name = get_element_name (el); if (l != NULL) *label = g_strdup (l); else *label = g_strdup (n); - // *role = r; + *role = r; +} + +static void +close_mixer (AlsaDevice *device) +{ + snd_mixer_t *handle; + + if (device->priv->handle == NULL) + return; + + /* Closing the mixer may fire up remove callbacks, prevent this by unsetting + * the handle before closing it and checking it in the callback. + * Ideally, we should unset callbacks from all the elements, but this seems + * to do the job. */ + handle = device->priv->handle; + + device->priv->handle = NULL; + snd_mixer_close (handle); +} + +static void +free_stream_list (AlsaDevice *device) +{ + /* This function is called each time the stream list changes */ + if (device->priv->streams == NULL) + return; + + g_list_free_full (device->priv->streams, g_object_unref); + + device->priv->streams = NULL; +} + +static gint +compare_switch_name (gconstpointer a, gconstpointer b) +{ + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (a); + const gchar *name = (const gchar *) b; + + return strcmp (mate_mixer_switch_get_name (swtch), name); } diff --git a/backends/alsa/alsa-device.h b/backends/alsa/alsa-device.h index 3b3c970..9e908cf 100644 --- a/backends/alsa/alsa-device.h +++ b/backends/alsa/alsa-device.h @@ -20,6 +20,7 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer.h> #include "alsa-stream.h" @@ -64,6 +65,9 @@ AlsaDevice *alsa_device_new (const gchar *name, const gchar *label); gboolean alsa_device_open (AlsaDevice *device); +gboolean alsa_device_is_open (AlsaDevice *device); +void alsa_device_close (AlsaDevice *device); + void alsa_device_load (AlsaDevice *device); AlsaStream *alsa_device_get_input_stream (AlsaDevice *device); diff --git a/backends/alsa/alsa-element.c b/backends/alsa/alsa-element.c index f925064..d837965 100644 --- a/backends/alsa/alsa-element.c +++ b/backends/alsa/alsa-element.c @@ -51,3 +51,19 @@ alsa_element_load (AlsaElement *element) return ALSA_ELEMENT_GET_INTERFACE (element)->load (element); } + +void +alsa_element_close (AlsaElement *element) +{ + AlsaElementInterface *iface; + + g_return_if_fail (ALSA_IS_ELEMENT (element)); + + /* Close the element by unsetting the ALSA element and optionally calling + * a closing function */ + alsa_element_set_snd_element (element, NULL); + + iface = ALSA_ELEMENT_GET_INTERFACE (element); + if (iface->close != NULL) + iface->close (element); +} diff --git a/backends/alsa/alsa-element.h b/backends/alsa/alsa-element.h index 01d30f1..1c30f68 100644 --- a/backends/alsa/alsa-element.h +++ b/backends/alsa/alsa-element.h @@ -46,6 +46,7 @@ struct _AlsaElementInterface snd_mixer_elem_t *el); gboolean (*load) (AlsaElement *element); + void (*close) (AlsaElement *element); }; GType alsa_element_get_type (void) G_GNUC_CONST; @@ -56,6 +57,8 @@ void alsa_element_set_snd_element (AlsaElement *element, gboolean alsa_element_load (AlsaElement *element); +void alsa_element_close (AlsaElement *element); + G_END_DECLS #endif /* ALSA_ELEMENT_H */ diff --git a/backends/alsa/alsa-stream-control.c b/backends/alsa/alsa-stream-control.c index bc7a937..97a0f8b 100644 --- a/backends/alsa/alsa-stream-control.c +++ b/backends/alsa/alsa-stream-control.c @@ -18,6 +18,7 @@ #include <glib.h> #include <glib-object.h> #include <alsa/asoundlib.h> + #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> @@ -159,64 +160,67 @@ alsa_stream_control_set_data (AlsaStreamControl *control, AlsaControlData *data) { MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_NO_FLAGS; MateMixerStreamControl *mmsc; + gboolean mute = FALSE; g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); g_return_if_fail (data != NULL); mmsc = MATE_MIXER_STREAM_CONTROL (control); + control->priv->data = *data; + g_object_freeze_notify (G_OBJECT (control)); if (data->channels > 0) { if (data->switch_usable == TRUE) { - flags |= MATE_MIXER_STREAM_CONTROL_HAS_MUTE; + /* If the mute switch is joined, all the channels get the same value, + * otherwise the element has per-channel mute, which we don't support. + * In that case, treat the control as unmuted if any channel is + * unmuted. */ + if (data->channels == 1 || data->switch_joined == TRUE) { + mute = data->m[0]; + } else { + gint i; + mute = TRUE; + for (i = 0; i < data->channels; i++) + if (data->m[i] == FALSE) { + mute = FALSE; + break; + } + } + + flags |= MATE_MIXER_STREAM_CONTROL_MUTE_READABLE; if (data->active == TRUE) - flags |= MATE_MIXER_STREAM_CONTROL_CAN_SET_MUTE; + flags |= MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE; } - flags |= MATE_MIXER_STREAM_CONTROL_HAS_VOLUME; + + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE; if (data->active == TRUE) - flags |= MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME; - } - if (data->max_decibel > -MATE_MIXER_INFINITY) - flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; - control->priv->data = *data; - control->priv->channel_mask = _mate_mixer_create_channel_mask (data->c, data->channels); + if (data->max_decibel > -MATE_MIXER_INFINITY) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; - if (data->volume_joined == FALSE) { - if (MATE_MIXER_CHANNEL_MASK_HAS_LEFT (control->priv->channel_mask) && - MATE_MIXER_CHANNEL_MASK_HAS_RIGHT (control->priv->channel_mask)) - flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + control->priv->channel_mask = _mate_mixer_create_channel_mask (data->c, data->channels); + + if (data->volume_joined == FALSE) { + if (MATE_MIXER_CHANNEL_MASK_HAS_LEFT (control->priv->channel_mask) && + MATE_MIXER_CHANNEL_MASK_HAS_RIGHT (control->priv->channel_mask)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + + if (MATE_MIXER_CHANNEL_MASK_HAS_FRONT (control->priv->channel_mask) && + MATE_MIXER_CHANNEL_MASK_HAS_BACK (control->priv->channel_mask)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_FADE; + } - if (MATE_MIXER_CHANNEL_MASK_HAS_FRONT (control->priv->channel_mask) && - MATE_MIXER_CHANNEL_MASK_HAS_BACK (control->priv->channel_mask)) - flags |= MATE_MIXER_STREAM_CONTROL_CAN_FADE; + g_object_notify (G_OBJECT (control), "volume"); + } else { + control->priv->channel_mask = 0; } + _mate_mixer_stream_control_set_mute (mmsc, mute); _mate_mixer_stream_control_set_flags (mmsc, flags); - if (data->switch_usable == TRUE) { - gboolean mute; - - /* If the mute switch is joined, all the channels get the same value, - * otherwise the element has per-channel mute, which we don't support. - * In that case, treat the control as unmuted if any channel is - * unmuted. */ - if (data->channels == 1 || data->switch_joined == TRUE) { - mute = data->m[0]; - } else { - gint i; - mute = TRUE; - for (i = 0; i < data->channels; i++) - if (data->m[i] == FALSE) { - mute = FALSE; - break; - } - } - _mate_mixer_stream_control_set_mute (mmsc, mute); - } else - _mate_mixer_stream_control_set_mute (mmsc, FALSE); - if (flags & MATE_MIXER_STREAM_CONTROL_CAN_BALANCE) _mate_mixer_stream_control_set_balance (mmsc, control_data_get_balance (data)); if (flags & MATE_MIXER_STREAM_CONTROL_CAN_FADE) @@ -237,7 +241,6 @@ static void alsa_stream_control_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) { g_return_if_fail (ALSA_IS_STREAM_CONTROL (element)); - g_return_if_fail (el != NULL); ALSA_STREAM_CONTROL (element)->priv->element = el; } @@ -288,6 +291,8 @@ alsa_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) for (i = 0; i < control->priv->data.channels; i++) control->priv->data.m[i] = mute; + + _mate_mixer_stream_control_set_mute (mmsc, mute); } return TRUE; } @@ -344,6 +349,8 @@ alsa_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) for (i = 0; i < control->priv->data.channels; i++) control->priv->data.v[i] = volume; + control->priv->data.volume = volume; + g_object_notify (G_OBJECT (control), "volume"); } return TRUE; @@ -364,7 +371,7 @@ alsa_stream_control_get_decibel (MateMixerStreamControl *mmsc) volume = alsa_stream_control_get_volume (mmsc); if (klass->get_decibel_from_volume (control, volume, &decibel) == FALSE) - return FALSE; + return -MATE_MIXER_INFINITY; return decibel; } @@ -428,7 +435,7 @@ alsa_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint chan control = ALSA_STREAM_CONTROL (mmsc); if (channel >= control->priv->data.channels) - return FALSE; + return 0; return control->priv->data.v[channel]; } @@ -463,6 +470,7 @@ alsa_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint chan if (klass->set_channel_volume (control, c, volume) == FALSE) return FALSE; + // XXX recalc total volume control->priv->data.v[channel] = volume; g_object_notify (G_OBJECT (control), "volume"); @@ -483,13 +491,13 @@ alsa_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint cha control = ALSA_STREAM_CONTROL (mmsc); if (channel >= control->priv->data.channels) - return FALSE; + return -MATE_MIXER_INFINITY; klass = ALSA_STREAM_CONTROL_GET_CLASS (control); volume = control->priv->data.v[channel]; if (klass->get_decibel_from_volume (control, volume, &decibel) == FALSE) - return FALSE; + return -MATE_MIXER_INFINITY; return decibel; } @@ -637,27 +645,27 @@ alsa_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade) } static guint -alsa_stream_control_get_min_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_min_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.min; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.min; } static guint -alsa_stream_control_get_max_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_max_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static guint -alsa_stream_control_get_normal_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_normal_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static guint -alsa_stream_control_get_base_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_base_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static void diff --git a/backends/alsa/alsa-stream-control.h b/backends/alsa/alsa-stream-control.h index f9ac6b6..acd02bd 100644 --- a/backends/alsa/alsa-stream-control.h +++ b/backends/alsa/alsa-stream-control.h @@ -41,9 +41,6 @@ typedef struct { guint channels; } AlsaControlData; -extern const MateMixerChannelPosition alsa_channel_map_from[SND_MIXER_SCHN_LAST]; -extern const snd_mixer_selem_channel_id_t alsa_channel_map_to[MATE_MIXER_CHANNEL_MAX]; - #define ALSA_TYPE_STREAM_CONTROL \ (alsa_stream_control_get_type ()) #define ALSA_STREAM_CONTROL(o) \ @@ -103,9 +100,6 @@ AlsaControlData * alsa_stream_control_get_data (AlsaStreamControl void alsa_stream_control_set_data (AlsaStreamControl *control, AlsaControlData *data); -gboolean alsa_stream_control_set_role (AlsaStreamControl *control, - MateMixerStreamControlRole role); - G_END_DECLS #endif /* ALSA_STREAM_CONTROL_H */ diff --git a/backends/alsa/alsa-stream-input-control.c b/backends/alsa/alsa-stream-input-control.c index 2ef0c42..2e3f46d 100644 --- a/backends/alsa/alsa-stream-input-control.c +++ b/backends/alsa/alsa-stream-input-control.c @@ -22,6 +22,7 @@ #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> +#include "alsa-constants.h" #include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-stream-input-control.h" @@ -51,8 +52,7 @@ static gboolean alsa_stream_input_control_get_decibel_from_volume (AlsaStreamCon guint volume, gdouble *decibel); -static void read_volume_data (snd_mixer_elem_t *el, - AlsaControlData *data); +static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data); static void alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass) @@ -77,12 +77,14 @@ alsa_stream_input_control_init (AlsaStreamInputControl *control) AlsaStreamControl * alsa_stream_input_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role) + MateMixerStreamControlRole role, + AlsaStream *stream) { return g_object_new (ALSA_TYPE_STREAM_INPUT_CONTROL, "name", name, "label", label, "role", role, + "stream", stream, NULL); } @@ -98,7 +100,6 @@ alsa_stream_input_control_load (AlsaStreamControl *control) if G_UNLIKELY (el == NULL) return FALSE; - /* Expect that the element has a volume control */ if G_UNLIKELY (snd_mixer_selem_has_capture_volume (el) == 0 && snd_mixer_selem_has_common_volume (el) == 0) { g_warn_if_reached (); @@ -180,8 +181,7 @@ alsa_stream_input_control_set_channel_volume (AlsaStreamControl *contr if G_UNLIKELY (el == NULL) return FALSE; - /* Set the volume for a single channels, the volume may still be "joined" and - * set all the channels by itself */ + /* Set the volume for a single channel */ ret = snd_mixer_selem_set_capture_volume (el, channel, volume); if (ret < 0) { g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); diff --git a/backends/alsa/alsa-stream-input-control.h b/backends/alsa/alsa-stream-input-control.h index c427e3c..0ce885a 100644 --- a/backends/alsa/alsa-stream-input-control.h +++ b/backends/alsa/alsa-stream-input-control.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include "alsa-stream.h" #include "alsa-stream-control.h" G_BEGIN_DECLS @@ -57,7 +58,8 @@ GType alsa_stream_input_control_get_type (void) G_GNUC_CONST; AlsaStreamControl *alsa_stream_input_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role); + MateMixerStreamControlRole role, + AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-stream-output-control.c b/backends/alsa/alsa-stream-output-control.c index 5a3e6b3..1f4faf8 100644 --- a/backends/alsa/alsa-stream-output-control.c +++ b/backends/alsa/alsa-stream-output-control.c @@ -22,6 +22,7 @@ #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> +#include "alsa-constants.h" #include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-stream-output-control.h" @@ -51,8 +52,7 @@ static gboolean alsa_stream_output_control_get_decibel_from_volume (AlsaStreamCo guint volume, gdouble *decibel); -static void read_volume_data (snd_mixer_elem_t *el, - AlsaControlData *data); +static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data); static void alsa_stream_output_control_class_init (AlsaStreamOutputControlClass *klass) @@ -77,12 +77,14 @@ alsa_stream_output_control_init (AlsaStreamOutputControl *control) AlsaStreamControl * alsa_stream_output_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role) + MateMixerStreamControlRole role, + AlsaStream *stream) { return g_object_new (ALSA_TYPE_STREAM_OUTPUT_CONTROL, "name", name, "label", label, "role", role, + "stream", stream, NULL); } @@ -98,7 +100,6 @@ alsa_stream_output_control_load (AlsaStreamControl *control) if G_UNLIKELY (el == NULL) return FALSE; - /* Expect that the element has a volume control */ if G_UNLIKELY (snd_mixer_selem_has_playback_volume (el) == 0 && snd_mixer_selem_has_common_volume (el) == 0) { g_warn_if_reached (); @@ -180,8 +181,7 @@ alsa_stream_output_control_set_channel_volume (AlsaStreamControl *cont if G_UNLIKELY (el == NULL) return FALSE; - /* Set the volume for a single channels, the volume may still be "joined" and - * set all the channels by itself */ + /* Set the volume for a single channel */ ret = snd_mixer_selem_set_playback_volume (el, channel, volume); if (ret < 0) { g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); diff --git a/backends/alsa/alsa-stream-output-control.h b/backends/alsa/alsa-stream-output-control.h index 845eaae..9a5b708 100644 --- a/backends/alsa/alsa-stream-output-control.h +++ b/backends/alsa/alsa-stream-output-control.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include "alsa-stream.h" #include "alsa-stream-control.h" G_BEGIN_DECLS @@ -57,7 +58,8 @@ GType alsa_stream_output_control_get_type (void) G_GNUC_CONST; AlsaStreamControl *alsa_stream_output_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role); + MateMixerStreamControlRole role, + AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-stream.c b/backends/alsa/alsa-stream.c index d2f68d4..bc9c1b5 100644 --- a/backends/alsa/alsa-stream.c +++ b/backends/alsa/alsa-stream.c @@ -18,6 +18,7 @@ #include <glib.h> #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include "alsa-element.h" #include "alsa-stream.h" @@ -26,27 +27,23 @@ struct _AlsaStreamPrivate { - GHashTable *switches; - GHashTable *controls; - MateMixerStreamControl *control; + GList *switches; + GList *controls; }; static void alsa_stream_class_init (AlsaStreamClass *klass); static void alsa_stream_init (AlsaStream *stream); static void alsa_stream_dispose (GObject *object); -static void alsa_stream_finalize (GObject *object); G_DEFINE_TYPE (AlsaStream, alsa_stream, MATE_MIXER_TYPE_STREAM) -static MateMixerStreamControl *alsa_stream_get_control (MateMixerStream *mms, - const gchar *name); -static MateMixerStreamControl *alsa_stream_get_default_control (MateMixerStream *mms); - -static MateMixerSwitch * alsa_stream_get_switch (MateMixerStream *mms, - const gchar *name); +static const GList *alsa_stream_list_controls (MateMixerStream *mms); +static const GList *alsa_stream_list_switches (MateMixerStream *mms); -static GList * alsa_stream_list_controls (MateMixerStream *mms); -static GList * alsa_stream_list_switches (MateMixerStream *mms); +static gint compare_control_name (gconstpointer a, + gconstpointer b); +static gint compare_switch_name (gconstpointer a, + gconstpointer b); static void alsa_stream_class_init (AlsaStreamClass *klass) @@ -55,15 +52,11 @@ alsa_stream_class_init (AlsaStreamClass *klass) MateMixerStreamClass *stream_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = alsa_stream_dispose; - object_class->finalize = alsa_stream_finalize; + object_class->dispose = alsa_stream_dispose; stream_class = MATE_MIXER_STREAM_CLASS (klass); - stream_class->get_control = alsa_stream_get_control; - stream_class->get_default_control = alsa_stream_get_default_control; - stream_class->get_switch = alsa_stream_get_switch; - stream_class->list_controls = alsa_stream_list_controls; - stream_class->list_switches = alsa_stream_list_switches; + stream_class->list_controls = alsa_stream_list_controls; + stream_class->list_switches = alsa_stream_list_switches; g_type_class_add_private (object_class, sizeof (AlsaStreamPrivate)); } @@ -74,16 +67,6 @@ alsa_stream_init (AlsaStream *stream) stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, ALSA_TYPE_STREAM, AlsaStreamPrivate); - - stream->priv->controls = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); - - stream->priv->switches = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); } static void @@ -93,36 +76,30 @@ alsa_stream_dispose (GObject *object) stream = ALSA_STREAM (object); - g_hash_table_remove_all (stream->priv->controls); - g_hash_table_remove_all (stream->priv->switches); - - g_clear_object (&stream->priv->control); + if (stream->priv->controls != NULL) { + g_list_free_full (stream->priv->controls, g_object_unref); + stream->priv->controls = NULL; + } + if (stream->priv->switches != NULL) { + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + } G_OBJECT_CLASS (alsa_stream_parent_class)->dispose (object); } -static void -alsa_stream_finalize (GObject *object) -{ - AlsaStream *stream; - - stream = ALSA_STREAM (object); - - g_hash_table_destroy (stream->priv->controls); - g_hash_table_destroy (stream->priv->switches); - - G_OBJECT_CLASS (alsa_stream_parent_class)->finalize (object); -} - AlsaStream * -alsa_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags) +alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction) { + const gchar *label = mate_mixer_device_get_label (device); + return g_object_new (ALSA_TYPE_STREAM, "name", name, + "label", label, "device", device, - "flags", flags, + "direction", direction, NULL); } @@ -135,9 +112,16 @@ alsa_stream_add_control (AlsaStream *stream, AlsaStreamControl *control) g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); - g_hash_table_insert (stream->priv->controls, - g_strdup (name), - g_object_ref (control)); + + stream->priv->controls = + g_list_append (stream->priv->controls, g_object_ref (control)); + + g_signal_emit_by_name (G_OBJECT (stream), + "control-added", + name); + + if (alsa_stream_has_default_control (stream) == FALSE) + alsa_stream_set_default_control (stream, control); } void @@ -149,125 +133,250 @@ alsa_stream_add_switch (AlsaStream *stream, AlsaSwitch *swtch) g_return_if_fail (ALSA_IS_SWITCH (swtch)); name = mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch)); - g_hash_table_insert (stream->priv->switches, - g_strdup (name), - g_object_ref (swtch)); + + stream->priv->switches = + g_list_append (stream->priv->switches, g_object_ref (swtch)); + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + name); +} + +void +alsa_stream_add_toggle (AlsaStream *stream, AlsaToggle *toggle) +{ + const gchar *name; + + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (ALSA_IS_TOGGLE (toggle)); + + name = mate_mixer_switch_get_name (MATE_MIXER_SWITCH (toggle)); + + /* Toggle is MateMixerSwitch, but not AlsaSwitch */ + stream->priv->switches = + g_list_append (stream->priv->switches, g_object_ref (toggle)); + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + name); +} + +gboolean +alsa_stream_has_controls (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (stream->priv->controls != NULL) + return TRUE; + + return FALSE; } gboolean -alsa_stream_is_empty (AlsaStream *stream) +alsa_stream_has_switches (AlsaStream *stream) { g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); - if (g_hash_table_size (stream->priv->controls) > 0 || - g_hash_table_size (stream->priv->switches) > 0) - return FALSE; + if (stream->priv->switches != NULL) + return TRUE; - return TRUE; + return FALSE; +} + +gboolean +alsa_stream_has_controls_or_switches (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (stream->priv->controls != NULL || + stream->priv->switches != NULL) + return TRUE; + + return FALSE; +} + +gboolean +alsa_stream_has_default_control (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)) != NULL) + return TRUE; + + return FALSE; +} + +AlsaStreamControl * +alsa_stream_get_default_control (AlsaStream *stream) +{ + MateMixerStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM (stream), NULL); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); + if (control != NULL) + return ALSA_STREAM_CONTROL (control); + + return NULL; } void alsa_stream_set_default_control (AlsaStream *stream, AlsaStreamControl *control) { g_return_if_fail (ALSA_IS_STREAM (stream)); - g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); - - /* This function is only used internally so avoid validating that the control - * belongs to this stream */ - if (stream->priv->control != NULL) - g_object_unref (stream->priv->control); + g_return_if_fail (control == NULL || ALSA_IS_STREAM_CONTROL (control)); - if (control != NULL) - stream->priv->control = MATE_MIXER_STREAM_CONTROL (g_object_ref (control)); + if (control == NULL) + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), NULL); else - stream->priv->control = NULL; + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), + MATE_MIXER_STREAM_CONTROL (control)); } void alsa_stream_load_elements (AlsaStream *stream, const gchar *name) { - AlsaElement *element; + GList *item; g_return_if_fail (ALSA_IS_STREAM (stream)); g_return_if_fail (name != NULL); - element = g_hash_table_lookup (stream->priv->controls, name); - if (element != NULL) - alsa_element_load (element); + item = g_list_find_custom (stream->priv->controls, name, compare_control_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); - element = g_hash_table_lookup (stream->priv->switches, name); - if (element != NULL) - alsa_element_load (element); + item = g_list_find_custom (stream->priv->switches, name, compare_switch_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); } gboolean alsa_stream_remove_elements (AlsaStream *stream, const gchar *name) { + GList *item; gboolean removed = FALSE; g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); g_return_val_if_fail (name != NULL, FALSE); - if (g_hash_table_remove (stream->priv->controls, name) == TRUE) + item = g_list_find_custom (stream->priv->controls, name, compare_control_name); + if (item != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (item->data); + + alsa_element_close (ALSA_ELEMENT (control)); + stream->priv->controls = g_list_delete_link (stream->priv->controls, item); + + /* Change the default control if we have just removed it */ + if (control == mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream))) { + AlsaStreamControl *first = NULL; + + if (stream->priv->controls != NULL) + first = ALSA_STREAM_CONTROL (stream->priv->controls->data); + + alsa_stream_set_default_control (stream, first); + } + + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); removed = TRUE; - if (g_hash_table_remove (stream->priv->switches, name) == TRUE) + } + + item = g_list_find_custom (stream->priv->switches, name, compare_switch_name); + if (item != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (item->data); + + alsa_element_close (ALSA_ELEMENT (swtch)); + + stream->priv->switches = g_list_delete_link (stream->priv->switches, item); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); removed = TRUE; + } return removed; } -static MateMixerStreamControl * -alsa_stream_get_control (MateMixerStream *mms, const gchar *name) +void +alsa_stream_remove_all (AlsaStream *stream) { - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + GList *list; - return g_hash_table_lookup (ALSA_STREAM (mms)->priv->controls, name); -} + g_return_if_fail (ALSA_IS_STREAM (stream)); -static MateMixerStreamControl * -alsa_stream_get_default_control (MateMixerStream *mms) -{ - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + /* Remove all stream controls */ + list = stream->priv->controls; + while (list != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data); + GList *next = list->next; + + alsa_element_close (ALSA_ELEMENT (control)); + + stream->priv->controls = g_list_delete_link (stream->priv->controls, list); + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); + list = next; + } + + /* Unset the default stream control */ + alsa_stream_set_default_control (stream, NULL); + + /* Remove all stream switches */ + list = stream->priv->switches; + while (list != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (list->data); + GList *next = list->next; + + alsa_element_close (ALSA_ELEMENT (swtch)); - return ALSA_STREAM (mms)->priv->control; + stream->priv->switches = g_list_delete_link (stream->priv->switches, list); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); + list = next; + } } -static MateMixerSwitch * -alsa_stream_get_switch (MateMixerStream *mms, const gchar *name) +static const GList * +alsa_stream_list_controls (MateMixerStream *mms) { g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); - return g_hash_table_lookup (ALSA_STREAM (mms)->priv->switches, name); + return ALSA_STREAM (mms)->priv->controls; } -static GList * -alsa_stream_list_controls (MateMixerStream *mms) +static const GList * +alsa_stream_list_switches (MateMixerStream *mms) { - GList *list; - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (ALSA_STREAM (mms)->priv->controls); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return ALSA_STREAM (mms)->priv->switches; } -static GList * -alsa_stream_list_switches (MateMixerStream *mms) +static gint +compare_control_name (gconstpointer a, gconstpointer b) { - GList *list; + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (a); + const gchar *name = (const gchar *) b; - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + return strcmp (mate_mixer_stream_control_get_name (control), name); +} - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (ALSA_STREAM (mms)->priv->switches); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); +static gint +compare_switch_name (gconstpointer a, gconstpointer b) +{ + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (a); + const gchar *name = (const gchar *) b; - return list; + return strcmp (mate_mixer_switch_get_name (swtch), name); } diff --git a/backends/alsa/alsa-stream.h b/backends/alsa/alsa-stream.h index f26a643..5aa3095 100644 --- a/backends/alsa/alsa-stream.h +++ b/backends/alsa/alsa-stream.h @@ -22,9 +22,9 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> -#include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-switch.h" +#include "alsa-toggle.h" G_BEGIN_DECLS @@ -58,30 +58,35 @@ struct _AlsaStreamClass MateMixerStreamClass parent_class; }; -GType alsa_stream_get_type (void) G_GNUC_CONST; +GType alsa_stream_get_type (void) G_GNUC_CONST; -AlsaStream *alsa_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags); +AlsaStream * alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction); -void alsa_stream_add_control (AlsaStream *stream, - AlsaStreamControl *control); +void alsa_stream_add_control (AlsaStream *stream, + AlsaStreamControl *control); +void alsa_stream_add_switch (AlsaStream *stream, + AlsaSwitch *swtch); +void alsa_stream_add_toggle (AlsaStream *stream, + AlsaToggle *toggle); -void alsa_stream_add_switch (AlsaStream *stream, - AlsaSwitch *swtch); +gboolean alsa_stream_has_controls (AlsaStream *stream); +gboolean alsa_stream_has_switches (AlsaStream *stream); +gboolean alsa_stream_has_controls_or_switches (AlsaStream *stream); +gboolean alsa_stream_has_default_control (AlsaStream *stream); -gboolean alsa_stream_is_empty (AlsaStream *stream); +AlsaStreamControl *alsa_stream_get_default_control (AlsaStream *stream); +void alsa_stream_set_default_control (AlsaStream *stream, + AlsaStreamControl *control); -void alsa_stream_set_default_control (AlsaStream *stream, - AlsaStreamControl *control); +void alsa_stream_load_elements (AlsaStream *stream, + const gchar *name); -void alsa_stream_load_elements (AlsaStream *stream, - const gchar *name); +gboolean alsa_stream_remove_elements (AlsaStream *stream, + const gchar *name); -gboolean alsa_stream_remove_elements (AlsaStream *stream, - const gchar *name); - -void alsa_stream_remove_all (AlsaStream *stream); +void alsa_stream_remove_all (AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-switch-option.c b/backends/alsa/alsa-switch-option.c index 2173113..1800df2 100644 --- a/backends/alsa/alsa-switch-option.c +++ b/backends/alsa/alsa-switch-option.c @@ -18,9 +18,7 @@ #include <glib.h> #include <glib-object.h> #include <alsa/asoundlib.h> - #include <libmatemixer/matemixer.h> -#include <libmatemixer/matemixer-private.h> #include "alsa-switch-option.h" @@ -59,6 +57,7 @@ alsa_switch_option_new (const gchar *name, option = g_object_new (ALSA_TYPE_SWITCH_OPTION, "name", name, "label", label, + "icon", icon, NULL); option->priv->id = id; diff --git a/backends/alsa/alsa-switch-option.h b/backends/alsa/alsa-switch-option.h index c2dda87..98d4f57 100644 --- a/backends/alsa/alsa-switch-option.h +++ b/backends/alsa/alsa-switch-option.h @@ -24,17 +24,17 @@ G_BEGIN_DECLS -#define ALSA_TYPE_SWITCH_OPTION \ +#define ALSA_TYPE_SWITCH_OPTION \ (alsa_switch_option_get_type ()) -#define ALSA_SWITCH_OPTION(o) \ +#define ALSA_SWITCH_OPTION(o) \ (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOption)) -#define ALSA_IS_SWITCH_OPTION(o) \ +#define ALSA_IS_SWITCH_OPTION(o) \ (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_SWITCH_OPTION)) -#define ALSA_SWITCH_OPTION_CLASS(k) \ +#define ALSA_SWITCH_OPTION_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) -#define ALSA_IS_SWITCH_OPTION_CLASS(k) \ +#define ALSA_IS_SWITCH_OPTION_CLASS(k) \ (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_SWITCH_OPTION)) -#define ALSA_SWITCH_OPTION_GET_CLASS(o) \ +#define ALSA_SWITCH_OPTION_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) typedef struct _AlsaSwitchOption AlsaSwitchOption; diff --git a/backends/alsa/alsa-switch.c b/backends/alsa/alsa-switch.c index 15151ae..6a0f1f4 100644 --- a/backends/alsa/alsa-switch.c +++ b/backends/alsa/alsa-switch.c @@ -37,6 +37,7 @@ static void alsa_element_interface_init (AlsaElementInterface *iface); static void alsa_switch_class_init (AlsaSwitchClass *klass); static void alsa_switch_init (AlsaSwitch *swtch); +static void alsa_switch_dispose (GObject *object); G_DEFINE_TYPE_WITH_CODE (AlsaSwitch, alsa_switch, MATE_MIXER_TYPE_SWITCH, @@ -46,7 +47,7 @@ G_DEFINE_TYPE_WITH_CODE (AlsaSwitch, alsa_switch, static gboolean alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso); -static GList * alsa_switch_list_options (MateMixerSwitch *mms); +static const GList * alsa_switch_list_options (MateMixerSwitch *mms); static snd_mixer_elem_t * alsa_switch_get_snd_element (AlsaElement *element); static void alsa_switch_set_snd_element (AlsaElement *element, @@ -64,8 +65,12 @@ alsa_element_interface_init (AlsaElementInterface *iface) static void alsa_switch_class_init (AlsaSwitchClass *klass) { + GObjectClass *object_class; MateMixerSwitchClass *switch_class; + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = alsa_switch_dispose; + switch_class = MATE_MIXER_SWITCH_CLASS (klass); switch_class->set_active_option = alsa_switch_set_active_option; switch_class->list_options = alsa_switch_list_options; @@ -74,6 +79,21 @@ alsa_switch_class_init (AlsaSwitchClass *klass) } static void +alsa_switch_dispose (GObject *object) +{ + AlsaSwitch *swtch; + + swtch = ALSA_SWITCH (object); + + if (swtch->priv->options != NULL) { + g_list_free_full (swtch->priv->options, g_object_unref); + swtch->priv->options = NULL; + } + + G_OBJECT_CLASS (alsa_switch_parent_class)->dispose (object); +} + +static void alsa_switch_init (AlsaSwitch *swtch) { swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, @@ -82,13 +102,17 @@ alsa_switch_init (AlsaSwitch *swtch) } AlsaSwitch * -alsa_switch_new (const gchar *name, const gchar *label, GList *options) +alsa_switch_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + GList *options) { AlsaSwitch *swtch; swtch = g_object_new (ALSA_TYPE_SWITCH, "name", name, "label", label, + "role", role, NULL); /* Takes ownership of options */ @@ -109,6 +133,9 @@ alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso swtch = ALSA_SWITCH (mms); + if G_UNLIKELY (swtch->priv->element == NULL) + return FALSE; + /* The channel mask is created when reading the active option the first * time, so a successful load must be done before changing the option */ if G_UNLIKELY (swtch->priv->channel_mask == 0) { @@ -136,12 +163,12 @@ alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso return set_item; } -static GList * -alsa_switch_list_options (MateMixerSwitch *swtch) +static const GList * +alsa_switch_list_options (MateMixerSwitch *mms) { - g_return_val_if_fail (ALSA_IS_SWITCH (swtch), NULL); + g_return_val_if_fail (ALSA_IS_SWITCH (mms), NULL); - return ALSA_SWITCH (swtch)->priv->options; + return ALSA_SWITCH (mms)->priv->options; } static snd_mixer_elem_t * @@ -156,7 +183,6 @@ static void alsa_switch_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) { g_return_if_fail (ALSA_IS_SWITCH (element)); - g_return_if_fail (el != NULL); ALSA_SWITCH (element)->priv->element = el; } @@ -170,8 +196,13 @@ alsa_switch_load (AlsaElement *element) gint ret; snd_mixer_selem_channel_id_t c; + g_return_val_if_fail (ALSA_IS_SWITCH (element), FALSE); + swtch = ALSA_SWITCH (element); + if G_UNLIKELY (swtch->priv->element == NULL) + return FALSE; + /* When reading the first time we try all the channels, otherwise only the * ones which returned success before */ if (swtch->priv->channel_mask == 0) { @@ -220,7 +251,7 @@ alsa_switch_load (AlsaElement *element) } g_warning ("Unknown active option of switch %s: %d", - snd_mixer_selem_get_name (swtch->priv->element), + mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch)), item); return FALSE; diff --git a/backends/alsa/alsa-switch.h b/backends/alsa/alsa-switch.h index fdcfb87..b7f5931 100644 --- a/backends/alsa/alsa-switch.h +++ b/backends/alsa/alsa-switch.h @@ -56,9 +56,10 @@ struct _AlsaSwitchClass GType alsa_switch_get_type (void) G_GNUC_CONST; -AlsaSwitch *alsa_switch_new (const gchar *name, - const gchar *label, - GList *options); +AlsaSwitch *alsa_switch_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + GList *options); G_END_DECLS diff --git a/backends/alsa/alsa-toggle.c b/backends/alsa/alsa-toggle.c index efa3460..a7958c9 100644 --- a/backends/alsa/alsa-toggle.c +++ b/backends/alsa/alsa-toggle.c @@ -42,13 +42,13 @@ G_DEFINE_TYPE_WITH_CODE (AlsaToggle, alsa_toggle, MATE_MIXER_TYPE_TOGGLE, G_IMPLEMENT_INTERFACE (ALSA_TYPE_ELEMENT, alsa_element_interface_init)) -static gboolean alsa_toggle_set_active_option (MateMixerSwitch *mms, - MateMixerSwitchOption *mmso); +static gboolean alsa_toggle_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); -static snd_mixer_elem_t * alsa_toggle_get_snd_element (AlsaElement *element); -static void alsa_toggle_set_snd_element (AlsaElement *element, - snd_mixer_elem_t *el); -static gboolean alsa_toggle_load (AlsaElement *element); +static snd_mixer_elem_t *alsa_toggle_get_snd_element (AlsaElement *element); +static void alsa_toggle_set_snd_element (AlsaElement *element, + snd_mixer_elem_t *el); +static gboolean alsa_toggle_load (AlsaElement *element); static void alsa_element_interface_init (AlsaElementInterface *iface) @@ -78,17 +78,20 @@ alsa_toggle_init (AlsaToggle *toggle) } AlsaToggle * -alsa_toggle_new (const gchar *name, - const gchar *label, - AlsaToggleType type, - AlsaSwitchOption *on, - AlsaSwitchOption *off) +alsa_toggle_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off) { AlsaToggle *toggle; toggle = g_object_new (ALSA_TYPE_TOGGLE, "name", name, "label", label, + "flags", MATE_MIXER_SWITCH_TOGGLE, + "role", role, "state-option-on", on, "state-option-off", off, NULL); @@ -109,7 +112,12 @@ alsa_toggle_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso toggle = ALSA_TOGGLE (mms); - /* For toggles the 0/1 value is stored as the switch option id */ + if G_UNLIKELY (toggle->priv->element == NULL) + return FALSE; + + /* For toggles the 0/1 value is stored as the switch option id, there is not really + * a need to validate that the option belong to the switch, just make sure it + * contains the value 0 or 1 */ value = alsa_switch_option_get_id (ALSA_SWITCH_OPTION (mmso)); if G_UNLIKELY (value != 0 && value != 1) { g_warn_if_reached (); @@ -143,7 +151,6 @@ static void alsa_toggle_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) { g_return_if_fail (ALSA_IS_TOGGLE (element)); - g_return_if_fail (el != NULL); ALSA_TOGGLE (element)->priv->element = el; } @@ -158,6 +165,9 @@ alsa_toggle_load (AlsaElement *element) toggle = ALSA_TOGGLE (element); + if G_UNLIKELY (toggle->priv->element == NULL) + return FALSE; + /* When reading the first time we try all the channels, otherwise only the * ones which returned success before */ if (toggle->priv->channel_mask == 0) { @@ -207,7 +217,6 @@ alsa_toggle_load (AlsaElement *element) active = mate_mixer_toggle_get_state_option (MATE_MIXER_TOGGLE (toggle), FALSE); _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (toggle), active); - return TRUE; } diff --git a/backends/alsa/alsa-toggle.h b/backends/alsa/alsa-toggle.h index d9c083b..1e1993c 100644 --- a/backends/alsa/alsa-toggle.h +++ b/backends/alsa/alsa-toggle.h @@ -63,11 +63,12 @@ struct _AlsaToggleClass GType alsa_toggle_get_type (void) G_GNUC_CONST; -AlsaToggle *alsa_toggle_new (const gchar *name, - const gchar *label, - AlsaToggleType type, - AlsaSwitchOption *on, - AlsaSwitchOption *off); +AlsaToggle *alsa_toggle_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off); G_END_DECLS diff --git a/backends/null/Makefile.am b/backends/null/Makefile.am index 8ce28d1..08005e4 100644 --- a/backends/null/Makefile.am +++ b/backends/null/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-null.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-null\" diff --git a/backends/null/null-backend.c b/backends/null/null-backend.c index 49038b1..ee0ad2e 100644 --- a/backends/null/null-backend.c +++ b/backends/null/null-backend.c @@ -29,7 +29,10 @@ static void null_backend_class_init (NullBackendClass *klass); static void null_backend_class_finalize (NullBackendClass *klass); static void null_backend_init (NullBackend *null); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_DYNAMIC_TYPE (NullBackend, null_backend, MATE_MIXER_TYPE_BACKEND) +#pragma clang diagnostic pop static gboolean null_backend_open (MateMixerBackend *backend); diff --git a/backends/oss/Makefile.am b/backends/oss/Makefile.am index 44caeb8..f535a37 100644 --- a/backends/oss/Makefile.am +++ b/backends/oss/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-oss.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-oss\" @@ -19,7 +20,12 @@ libmatemixer_oss_la_SOURCES = \ oss-stream.c \ oss-stream.h \ oss-stream-control.c \ - oss-stream-control.h + oss-stream-control.h \ + oss-switch.c \ + oss-switch.h \ + oss-switch-option.c \ + oss-switch-option.h \ + oss-types.h libmatemixer_oss_la_LIBADD = \ $(GLIB_LIBS) \ diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c index 2b5eca7..23d265b 100644 --- a/backends/oss/oss-backend.c +++ b/backends/oss/oss-backend.c @@ -32,7 +32,7 @@ #include "oss-stream.h" #define BACKEND_NAME "OSS" -#define BACKEND_PRIORITY 9 +#define BACKEND_PRIORITY 10 #if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) /* At least on systems based on FreeBSD we will need to read device names @@ -47,7 +47,9 @@ struct _OssBackendPrivate { gchar *default_device; GSource *timeout_source; - GHashTable *devices; + GList *streams; + GList *devices; + GHashTable *devices_paths; }; static void oss_backend_class_init (OssBackendClass *klass); @@ -61,36 +63,49 @@ static void oss_backend_finalize (GObject *object); G_DEFINE_DYNAMIC_TYPE (OssBackend, oss_backend, MATE_MIXER_TYPE_BACKEND) #pragma clang diagnostic pop -static gboolean oss_backend_open (MateMixerBackend *backend); -static void oss_backend_close (MateMixerBackend *backend); -static GList * oss_backend_list_devices (MateMixerBackend *backend); -static GList * oss_backend_list_streams (MateMixerBackend *backend); +static gboolean oss_backend_open (MateMixerBackend *backend); +static void oss_backend_close (MateMixerBackend *backend); +static const GList *oss_backend_list_devices (MateMixerBackend *backend); +static const GList *oss_backend_list_streams (MateMixerBackend *backend); -static gboolean read_devices (OssBackend *oss); +static gboolean read_devices (OssBackend *oss); -static gboolean read_device (OssBackend *oss, - const gchar *path, - gboolean *added); +static gboolean read_device (OssBackend *oss, + const gchar *path, + gboolean *added); -static gchar * read_device_label (OssBackend *oss, - const gchar *path, - gint fd); +static gchar * read_device_label (OssBackend *oss, + const gchar *path, + gint fd); -static gchar * read_device_label_sndstat (OssBackend *oss, - const gchar *sndstat, - const gchar *path, - guint index) G_GNUC_UNUSED; +static gchar * read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) G_GNUC_UNUSED; -static void add_device (OssBackend *oss, - OssDevice *device); -static void remove_device (OssBackend *oss, - OssDevice *device); +static void add_device (OssBackend *oss, + OssDevice *device); +static void remove_device (OssBackend *oss, + OssDevice *device); -static void remove_stream (OssBackend *oss, - const gchar *name); +static void remove_device_by_path (OssBackend *oss, + const gchar *path); -static void select_default_input_stream (OssBackend *oss); -static void select_default_output_stream (OssBackend *oss); +static void remove_device_by_list_item (OssBackend *oss, + GList *item); + +static void remove_stream (OssBackend *oss, + const gchar *name); + +static void select_default_input_stream (OssBackend *oss); +static void select_default_output_stream (OssBackend *oss); + +static void free_stream_list (OssBackend *oss); + +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_device_path (gconstpointer a, + gconstpointer b); static MateMixerBackendInfo info; @@ -142,10 +157,10 @@ oss_backend_init (OssBackend *oss) OSS_TYPE_BACKEND, OssBackendPrivate); - oss->priv->devices = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); + oss->priv->devices_paths = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); } static void @@ -170,7 +185,7 @@ oss_backend_finalize (GObject *object) oss = OSS_BACKEND (object); - g_hash_table_destroy (oss->priv->devices); + g_hash_table_unref (oss->priv->devices_paths); G_OBJECT_CLASS (oss_backend_parent_class)->finalize (object); } @@ -212,63 +227,65 @@ oss_backend_close (MateMixerBackend *backend) oss = OSS_BACKEND (backend); g_source_destroy (oss->priv->timeout_source); - g_hash_table_remove_all (oss->priv->devices); - g_free (oss->priv->default_device); - oss->priv->default_device = NULL; + if (oss->priv->devices != NULL) { + g_list_free_full (oss->priv->devices, g_object_unref); + oss->priv->devices = NULL; + } + if (oss->priv->default_device != NULL) { + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + } + + free_stream_list (oss); + + g_hash_table_remove_all (oss->priv->devices_paths); _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } -static GList * +static const GList * oss_backend_list_devices (MateMixerBackend *backend) { - GList *list; - g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (OSS_BACKEND (backend)->priv->devices); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return OSS_BACKEND (backend)->priv->devices; } -static GList * +static const GList * oss_backend_list_streams (MateMixerBackend *backend) { - OssBackend *oss; - GHashTableIter iter; - gpointer value; - GList *list = NULL; + OssBackend *oss; g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); oss = OSS_BACKEND (backend); - /* We don't keep a list or hash table of all streams here, instead walk - * through the list of devices and create the list manually, each device - * has at most one input and one output stream */ - g_hash_table_iter_init (&iter, oss->priv->devices); + if (oss->priv->streams == NULL) { + GList *list; - while (g_hash_table_iter_next (&iter, NULL, &value)) { - OssDevice *device = OSS_DEVICE (value); - OssStream *stream; + /* Walk through the list of devices and create the stream list, each + * device has at most one input and one output stream */ + list = oss->priv->devices; - stream = oss_device_get_output_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - stream = oss_device_get_input_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - } + while (list != NULL) { + OssDevice *device = OSS_DEVICE (list->data); + OssStream *stream; - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + oss->priv->streams = + g_list_append (oss->priv->streams, g_object_ref (stream)); + } + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + oss->priv->streams = + g_list_append (oss->priv->streams, g_object_ref (stream)); + } + list = list->next; + } + } + return oss->priv->streams; } static gboolean @@ -278,8 +295,10 @@ read_devices (OssBackend *oss) gboolean added = FALSE; for (i = 0; i < OSS_MAX_DEVICES; i++) { - gboolean added_current; - gchar *path = g_strdup_printf ("/dev/mixer%i", i); + gchar *path; + gboolean added_current = FALSE; + + path = g_strdup_printf ("/dev/mixer%i", i); /* On recent FreeBSD both /dev/mixer and /dev/mixer0 point to the same * mixer device, on NetBSD and OpenBSD /dev/mixer is a symlink to one @@ -289,7 +308,7 @@ read_devices (OssBackend *oss) if (read_device (oss, path, &added_current) == FALSE && i == 0) read_device (oss, "/dev/mixer", &added_current); - if (added_current) + if (added_current == TRUE) added = TRUE; g_free (path); @@ -312,16 +331,12 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) gchar *bname; gchar *label; - device = g_hash_table_lookup (oss->priv->devices, path); - *added = FALSE; - fd = g_open (path, O_RDWR, 0); if (fd == -1) { if (errno != ENOENT && errno != ENXIO) g_debug ("%s: %s", path, g_strerror (errno)); - if (device != NULL) - remove_device (oss, device); + remove_device_by_path (oss, path); return FALSE; } @@ -329,7 +344,7 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) * still tested to be absolutely sure that the device is removed it case * it has disappeared, but normally the device's polling facility should * handle this by itself */ - if (device != NULL) { + if (g_hash_table_contains (oss->priv->devices_paths, path) == TRUE) { close (fd); return TRUE; } @@ -345,8 +360,9 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) if ((*added = oss_device_open (device)) == TRUE) add_device (oss, device); + else + g_object_unref (device); - g_object_unref (device); return *added; } @@ -445,44 +461,104 @@ read_device_label_sndstat (OssBackend *oss, static void add_device (OssBackend *oss, OssDevice *device) { - const gchar *name; + oss->priv->devices = + g_list_insert_sorted_with_data (oss->priv->devices, + device, + (GCompareDataFunc) compare_devices, + NULL); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + /* Keep track of added device paths */ + g_hash_table_add (oss->priv->devices_paths, + g_strdup (oss_device_get_path (device))); - g_hash_table_insert (oss->priv->devices, - g_strdup (oss_device_get_path (device)), - g_object_ref (device)); - - // XXX make device emit it when closed + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (remove_device), + oss); g_signal_connect_swapped (G_OBJECT (device), "stream-removed", G_CALLBACK (remove_stream), oss); - g_signal_emit_by_name (G_OBJECT (oss), "device-added", name); + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (free_stream_list), + oss); + g_signal_connect_swapped (G_OBJECT (device), + "stream-added", + G_CALLBACK (free_stream_list), + oss); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (free_stream_list), + oss); + g_signal_emit_by_name (G_OBJECT (oss), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + /* Load the device elements after emitting device-added, because the load + * function will most likely emit stream-added on the device and backend */ oss_device_load (device); } static void remove_device (OssBackend *oss, OssDevice *device) { - const gchar *name; + GList *item; + + item = g_list_find (oss->priv->devices, device); + if (item != NULL) + remove_device_by_list_item (oss, item); +} + +static void +remove_device_by_path (OssBackend *oss, const gchar *path) +{ + GList *item; + + item = g_list_find_custom (oss->priv->devices, path, compare_device_path); + if (item != NULL) + remove_device_by_list_item (oss, item); +} + +static void +remove_device_by_list_item (OssBackend *oss, GList *item) +{ + OssDevice *device; const gchar *path; - path = oss_device_get_path (device); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + device = OSS_DEVICE (item->data); + + g_signal_handlers_disconnect_by_func (G_OBJECT (device), + G_CALLBACK (remove_device), + oss); + + if (oss_device_is_open (device) == TRUE) + oss_device_close (device); g_signal_handlers_disconnect_by_func (G_OBJECT (device), G_CALLBACK (remove_stream), oss); - // XXX close the device and make it remove streams - g_hash_table_remove (oss->priv->devices, path); + oss->priv->devices = g_list_delete_link (oss->priv->devices, item); + + path = oss_device_get_path (device); + g_hash_table_remove (oss->priv->devices_paths, path); + + if (g_strcmp0 (oss->priv->default_device, path) == 0) { + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + } + + /* The list may and may not have been invalidate by device signals */ + free_stream_list (oss); g_signal_emit_by_name (G_OBJECT (oss), "device-removed", - name); + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } static void @@ -492,7 +568,6 @@ remove_stream (OssBackend *oss, const gchar *name) stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (oss)); - // XXX see if the change happens after stream is removed or before if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) select_default_input_stream (oss); @@ -502,16 +577,31 @@ remove_stream (OssBackend *oss, const gchar *name) select_default_output_stream (oss); } +static OssDevice * +get_default_device (OssBackend *oss) +{ + GList *item; + + if (oss->priv->default_device == NULL) + return NULL; + + item = g_list_find_custom (oss->priv->devices, + oss->priv->default_device, + compare_device_path); + if G_LIKELY (item != NULL) + return OSS_DEVICE (item->data); + + return NULL; +} + static void select_default_input_stream (OssBackend *oss) { - OssDevice *device = NULL; + OssDevice *device; OssStream *stream; - gint i; + GList *list; - /* Always prefer stream in the "default" device */ - if (oss->priv->default_device != NULL) - device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + device = get_default_device (oss); if (device != NULL) { stream = oss_device_get_input_stream (device); if (stream != NULL) { @@ -521,23 +611,17 @@ select_default_input_stream (OssBackend *oss) } } - for (i = 0; i < OSS_MAX_DEVICES; i++) { - gchar *path = g_strdup_printf ("/dev/mixer%i", i); - - device = g_hash_table_lookup (oss->priv->devices, path); - if (device == NULL && i == 0) - device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + list = oss->priv->devices; + while (list != NULL) { + device = OSS_DEVICE (list->data); + stream = oss_device_get_input_stream (device); - if (device != NULL) { - stream = oss_device_get_input_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), - MATE_MIXER_STREAM (stream)); - g_free (path); - return; - } + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; } - g_free (path); + list = list->next; } /* In the worst case unset the default stream */ @@ -547,13 +631,11 @@ select_default_input_stream (OssBackend *oss) static void select_default_output_stream (OssBackend *oss) { - OssDevice *device = NULL; + OssDevice *device; OssStream *stream; - gint i; + GList *list; - /* Always prefer stream in the "default" device */ - if (oss->priv->default_device != NULL) - device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + device = get_default_device (oss); if (device != NULL) { stream = oss_device_get_output_stream (device); if (stream != NULL) { @@ -563,25 +645,48 @@ select_default_output_stream (OssBackend *oss) } } - for (i = 0; i < OSS_MAX_DEVICES; i++) { - gchar *path = g_strdup_printf ("/dev/mixer%i", i); - - device = g_hash_table_lookup (oss->priv->devices, path); - if (device == NULL && i == 0) - device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + list = oss->priv->devices; + while (list != NULL) { + device = OSS_DEVICE (list->data); + stream = oss_device_get_output_stream (device); - if (device != NULL) { - stream = oss_device_get_output_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), - MATE_MIXER_STREAM (stream)); - g_free (path); - return; - } + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; } - g_free (path); + list = list->next; } /* In the worst case unset the default stream */ _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), NULL); } + +static void +free_stream_list (OssBackend *oss) +{ + if (oss->priv->streams == NULL) + return; + + g_list_free_full (oss->priv->streams, g_object_unref); + + oss->priv->streams = NULL; +} + +static gint +compare_devices (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *d1 = MATE_MIXER_DEVICE (a); + MateMixerDevice *d2 = MATE_MIXER_DEVICE (b); + + return strcmp (mate_mixer_device_get_name (d1), mate_mixer_device_get_name (d2)); +} + +static gint +compare_device_path (gconstpointer a, gconstpointer b) +{ + OssDevice *device = OSS_DEVICE (a); + const gchar *path = (const gchar *) b; + + return strcmp (oss_device_get_path (device), path); +} diff --git a/backends/oss/oss-backend.h b/backends/oss/oss-backend.h index 325b61c..9617e79 100644 --- a/backends/oss/oss-backend.h +++ b/backends/oss/oss-backend.h @@ -20,9 +20,10 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> -#include <libmatemixer/matemixer-backend.h> -#include <libmatemixer/matemixer-backend-module.h> +#include "oss-types.h" #define OSS_TYPE_BACKEND \ (oss_backend_get_type ()) @@ -37,7 +38,6 @@ #define OSS_BACKEND_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_BACKEND, OssBackendClass)) -typedef struct _OssBackend OssBackend; typedef struct _OssBackendClass OssBackendClass; typedef struct _OssBackendPrivate OssBackendPrivate; @@ -54,7 +54,7 @@ struct _OssBackendClass MateMixerBackendClass parent_class; }; -GType oss_backend_get_type (void) G_GNUC_CONST; +GType oss_backend_get_type (void) G_GNUC_CONST; /* Support function for dynamic loading of the backend module */ void backend_module_init (GTypeModule *module); diff --git a/backends/oss/oss-device.c b/backends/oss/oss-device.c index cf51705..44ed18f 100644 --- a/backends/oss/oss-device.c +++ b/backends/oss/oss-device.c @@ -16,7 +16,6 @@ */ #include <errno.h> -#include <unistd.h> #include <glib.h> #include <glib/gi18n.h> #include <glib/gstdio.h> @@ -29,87 +28,184 @@ #include "oss-device.h" #include "oss-stream.h" #include "oss-stream-control.h" +#include "oss-switch-option.h" + +/* + * NOTES: + * + * OSS has a predefined list of channels (or "devices"), which historically used + * to be mapped to individual sound card pins. At this time, the channels are + * chosen somehow arbitrarily by drivers. + * + * Each of the channels may have a record switch, which toggles between playback + * and capture direction. OSS doesn't have mute switches and we can't really use + * the record switch as one. For this reason all channels are modelled as + * muteless stream controls and the record switch is modelled as a port switch. + * + * Also, we avoid modelling capturable channels as both input and output channels, + * because the ones which allow capture are normally capture-only channels + * (OSS just doesn't have the ability to make the distinction), so each channel in + * the list contains a flag of whether it can be used as a capture source, given + * that it's reported as capturable. Capturable channels are therefore modelled + * as input controls and this approach avoids for example putting PCM in an input + * stream (which makes no sense). + * + * OSS also has an indicator of whether the record switch is exclusive (only + * allows one capture source at a time), to simplify the lives of applications + * we always create a port switch and therefore assume the exclusivity is always + * true. Ideally, we should probably model a bunch of toggles, one for each channel + * with capture capability if they are known not to be exclusive. + */ #define OSS_DEVICE_ICON "audio-card" -typedef enum -{ +#define OSS_POLL_TIMEOUT_NORMAL 500 +#define OSS_POLL_TIMEOUT_RAPID 50 +#define OSS_POLL_TIMEOUT_RESTORE 3000 + +typedef enum { + OSS_POLL_NORMAL, + OSS_POLL_RAPID +} OssPollMode; + +typedef enum { OSS_DEV_ANY, OSS_DEV_INPUT, OSS_DEV_OUTPUT -} OssDevType; +} OssDevChannelType; -typedef struct -{ +typedef struct { gchar *name; gchar *label; MateMixerStreamControlRole role; - OssDevType type; -} OssDev; - -static const OssDev oss_devices[] = { - { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT }, - { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT }, - { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT }, - { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, - { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + OssDevChannelType type; + gchar *icon; +} OssDevChannel; + +/* Index of a channel in the array corresponds to the channel number passed to ioctl()s, + * device names are takes from soundcard.h */ +static const OssDevChannel oss_devices[] = { + { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT, NULL }, + { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT, NULL }, + { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT, NULL }, + { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT, NULL }, + { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT, NULL }, /* OSS manual says this should be the beeper, but Linux OSS seems to assign it to * regular volume control */ - { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT }, - { "line", N_("Line-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT }, + { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT, NULL }, + { "line", N_("Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, OSS_DEV_INPUT, "audio-input-microphone" }, + { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT, NULL }, /* Recording monitor */ - { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "pcm2", N_("PCM-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT, NULL }, + { "pcm2", N_("PCM 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT, NULL }, /* Recording level (master input) */ - { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT }, - { "igain", N_("In-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, - { "ogain", N_("Out-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "line1", N_("Line-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "line2", N_("Line-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "line3", N_("Line-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "dig1", N_("Digital-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "dig2", N_("Digital-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "dig3", N_("Digital-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "phin", N_("Phone-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "phout", N_("Phone-out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT }, - { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "monitor", N_("Monitor"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "depth", N_("3D-depth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "center", N_("3D-center"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "midi", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT } + { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT, NULL }, + { "igain", N_("Input Gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT, NULL }, + { "ogain", N_("Output Gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT, NULL }, + { "line1", N_("Line In 1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "line2", N_("Line In 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "line3", N_("Line In 3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + /* These 3 can be attached to either digital input or output */ + { "dig1", N_("Digital 1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "dig2", N_("Digital 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "dig3", N_("Digital 3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "phin", N_("Phone In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "phout", N_("Phone Out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT, NULL }, + { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_VIDEO, OSS_DEV_INPUT, NULL }, + { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + + /* soundcard.h on some systems include more channels, but different files provide + * different meanings for the remaining ones and the value is doubtful */ }; #define OSS_N_DEVICES MIN (G_N_ELEMENTS (oss_devices), SOUND_MIXER_NRDEVICES) +/* Priorities for selecting default controls */ +static const gint oss_input_priority[] = { + 11, /* rec */ + 12, /* igain */ + 7, /* mic */ + 6, /* line */ + 14, /* line1 */ + 15, /* line2 */ + 16, /* line3 */ + 17, /* dig1 */ + 18, /* dig2 */ + 19, /* dig3 */ + 20, /* phin */ + 8, /* cd */ + 22, /* video */ + 23, /* radio */ + 3, /* synth */ + 27 /* midi */ +}; + +static const gint oss_output_priority[] = { + 0, /* vol */ + 4, /* pcm */ + 10, /* pcm2 */ + 5, /* speaker */ + 17, /* dig1 */ + 18, /* dig2 */ + 19, /* dig3 */ + 25, /* depth */ + 26, /* center */ + 21, /* phone out */ + 13, /* ogain */ + 9, /* mix */ + 24, /* monitor */ + 1, /* bass */ + 2 /* treble */ +}; + struct _OssDevicePrivate { - gint fd; - gchar *path; - gint devmask; - gint stereodevs; - gint recmask; - gint recsrc; - OssStream *input; - OssStream *output; + gint fd; + gchar *path; + gint devmask; + gint stereodevs; + gint recmask; + guint poll_tag; + guint poll_tag_restore; + guint poll_counter; + gboolean poll_use_counter; + OssPollMode poll_mode; + GList *streams; + OssStream *input; + OssStream *output; +}; + +enum { + CLOSED, + N_SIGNALS }; -static void oss_device_class_init (OssDeviceClass *klass); -static void oss_device_init (OssDevice *device); -static void oss_device_dispose (GObject *object); -static void oss_device_finalize (GObject *object); +static guint signals[N_SIGNALS] = { 0, }; + +static void oss_device_class_init (OssDeviceClass *klass); +static void oss_device_init (OssDevice *device); +static void oss_device_dispose (GObject *object); +static void oss_device_finalize (GObject *object); G_DEFINE_TYPE (OssDevice, oss_device, MATE_MIXER_TYPE_DEVICE) -static GList * oss_device_list_streams (MateMixerDevice *device); +static const GList *oss_device_list_streams (MateMixerDevice *mmd); + +static gboolean poll_mixer (OssDevice *device); +static gboolean poll_mixer_restore (OssDevice *device); + +static void read_mixer_devices (OssDevice *device); +static void read_mixer_switch (OssDevice *device); -static gboolean read_mixer_devices (OssDevice *device); +static guint create_poll_source (OssDevice *device, + OssPollMode mode); +static guint create_poll_restore_source (OssDevice *device); -static gboolean set_stream_default_control (OssStream *stream, - OssStreamControl *control, - gboolean force); +static void free_stream_list (OssDevice *device); + +static gint compare_stream_control_devnum (gconstpointer a, + gconstpointer b); static void oss_device_class_init (OssDeviceClass *klass) @@ -124,6 +220,18 @@ oss_device_class_init (OssDeviceClass *klass) device_class = MATE_MIXER_DEVICE_CLASS (klass); device_class->list_streams = oss_device_list_streams; + signals[CLOSED] = + g_signal_new ("closed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (OssDeviceClass, closed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + g_type_class_add_private (object_class, sizeof (OssDevicePrivate)); } @@ -145,6 +253,8 @@ oss_device_dispose (GObject *object) g_clear_object (&device->priv->input); g_clear_object (&device->priv->output); + free_stream_list (device); + G_OBJECT_CLASS (oss_device_parent_class)->dispose (object); } @@ -153,13 +263,19 @@ oss_device_finalize (GObject *object) { OssDevice *device = OSS_DEVICE (object); - close (device->priv->fd); + if (device->priv->fd != -1) + close (device->priv->fd); + + g_free (device->priv->path); G_OBJECT_CLASS (oss_device_parent_class)->finalize (object); } OssDevice * -oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint fd) +oss_device_new (const gchar *name, + const gchar *label, + const gchar *path, + gint fd) { OssDevice *device; gchar *stream_name; @@ -180,13 +296,13 @@ oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint f stream_name = g_strdup_printf ("oss-input-%s", name); device->priv->input = oss_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_INPUT); + MATE_MIXER_DIRECTION_INPUT); g_free (stream_name); stream_name = g_strdup_printf ("oss-output-%s", name); device->priv->output = oss_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_OUTPUT); + MATE_MIXER_DIRECTION_OUTPUT); g_free (stream_name); return device; @@ -205,30 +321,19 @@ oss_device_open (OssDevice *device) /* Read the essential information about the device, these values are not * expected to change and will not be queried */ - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_DEVMASK), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_DEVMASK), &device->priv->devmask); - if (ret != 0) + if (ret == -1) goto fail; - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_STEREODEVS), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_STEREODEVS), &device->priv->stereodevs); - if (ret < 0) + if (ret == -1) goto fail; - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_RECMASK), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_RECMASK), &device->priv->recmask); - if (ret < 0) - goto fail; - - /* The recording source mask may change at any time, here we just read - * the initial value */ - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_RECSRC), - &device->priv->recsrc); - if (ret < 0) + if (ret == -1) goto fail; /* NOTE: Linux also supports SOUND_MIXER_OUTSRC and SOUND_MIXER_OUTMASK which @@ -246,41 +351,135 @@ fail: } gboolean -oss_device_load (OssDevice *device) +oss_device_is_open (OssDevice *device) { - MateMixerStreamControl *control; - g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE); - read_mixer_devices (device); + if (device->priv->fd != -1) + return TRUE; + + return FALSE; +} + +void +oss_device_close (OssDevice *device) +{ + g_return_if_fail (OSS_IS_DEVICE (device)); - control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->input)); - if (control == NULL) { - // XXX pick something + if (device->priv->fd == -1) + return; + + /* Make each stream remove its controls and switch */ + if (oss_stream_has_controls (device->priv->input) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + + oss_stream_remove_all (device->priv->input); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); } - if (control != NULL) - g_debug ("Default input stream control is %s", - mate_mixer_stream_control_get_label (control)); + if (oss_stream_has_controls (device->priv->output) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + + oss_stream_remove_all (device->priv->output); + free_stream_list (device); - control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->output)); - if (control == NULL) { - // XXX pick something + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); } - if (control != NULL) - g_debug ("Default output stream control is %s", - mate_mixer_stream_control_get_label (control)); + if (device->priv->poll_tag != 0) + g_source_remove (device->priv->poll_tag); - return TRUE; + if (device->priv->poll_tag_restore != 0) + g_source_remove (device->priv->poll_tag_restore); + + close (device->priv->fd); + device->priv->fd = -1; + + g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); } -gint -oss_device_get_fd (OssDevice *device) +void +oss_device_load (OssDevice *device) { - g_return_val_if_fail (OSS_IS_DEVICE (device), -1); + const GList *controls; + guint i; + + g_return_if_fail (OSS_IS_DEVICE (device)); + + read_mixer_devices (device); - return device->priv->fd; + /* Set default input control */ + if (oss_stream_has_controls (device->priv->input) == TRUE) { + controls = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (device->priv->input)); + + for (i = 0; i < G_N_ELEMENTS (oss_input_priority); i++) { + GList *item = g_list_find_custom ((GList *) controls, + GINT_TO_POINTER (oss_input_priority[i]), + compare_stream_control_devnum); + if (item == NULL) + continue; + + oss_stream_set_default_control (device->priv->input, + OSS_STREAM_CONTROL (item->data)); + break; + } + } + + /* Set default output control */ + if (oss_stream_has_controls (device->priv->output) == TRUE) { + controls = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (device->priv->output)); + + for (i = 0; i < G_N_ELEMENTS (oss_output_priority); i++) { + GList *item = g_list_find_custom ((GList *) controls, + GINT_TO_POINTER (oss_output_priority[i]), + compare_stream_control_devnum); + if (item == NULL) + continue; + + oss_stream_set_default_control (device->priv->output, + OSS_STREAM_CONTROL (item->data)); + break; + } + } + + if (device->priv->recmask != 0) + read_mixer_switch (device); + + /* See if we can use the modify_counter field to optimize polling */ +#ifdef SOUND_MIXER_INFO + do { + struct mixer_info info; + gint ret; + + ret = ioctl (device->priv->fd, SOUND_MIXER_INFO, &info); + if (ret == 0) { + device->priv->poll_counter = info.modify_counter; + device->priv->poll_use_counter = TRUE; + } + } while (0); +#endif + + /* + * Use a polling strategy inspired by KMix: + * + * Poll for changes with the OSS_POLL_TIMEOUT_NORMAL interval, when we + * encounter a change in modify_counter, decrease the interval to + * OSS_POLL_TIMEOUT_RAPID for a few seconds to allow for smoother + * adjustments, for example when user drags a slider. + * + * This way is not used on systems which don't support the modify_counter + * field, because there is no way to find out whether anything has + * changed and therefore when to start the rapid polling. + */ + device->priv->poll_tag = create_poll_source (device, OSS_POLL_NORMAL); } const gchar * @@ -307,98 +506,254 @@ oss_device_get_output_stream (OssDevice *device) return device->priv->output; } -static GList * +static const GList * oss_device_list_streams (MateMixerDevice *mmd) { OssDevice *device; - GList *list = NULL; g_return_val_if_fail (OSS_IS_DEVICE (mmd), NULL); device = OSS_DEVICE (mmd); - if (device->priv->output != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->output)); - if (device->priv->input != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->input)); - - return list; + if (device->priv->streams == NULL) { + if (device->priv->output != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->input)); + } + return device->priv->streams; } #define OSS_MASK_HAS_DEVICE(mask,i) ((gboolean) (((mask) & (1 << (i))) > 0)) -static gboolean +static void read_mixer_devices (OssDevice *device) { - gint i; + OssStreamControl *control; + const gchar *name; + guint i; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); for (i = 0; i < OSS_N_DEVICES; i++) { - OssStreamControl *control; - gboolean input = FALSE; + OssStream *stream; + gboolean stereo; /* Skip unavailable controls */ if (OSS_MASK_HAS_DEVICE (device->priv->devmask, i) == FALSE) continue; - if (oss_devices[i].type == OSS_DEV_ANY) { - input = OSS_MASK_HAS_DEVICE (device->priv->recmask, i); - } - else if (oss_devices[i].type == OSS_DEV_INPUT) { - input = TRUE; - } + if (OSS_MASK_HAS_DEVICE (device->priv->recmask, i) == TRUE) + stream = device->priv->input; + else + stream = device->priv->output; - control = oss_stream_control_new (oss_devices[i].name, - oss_devices[i].label, + stereo = OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i); + control = oss_stream_control_new (oss_devices[i].name, gettext (oss_devices[i].label), oss_devices[i].role, + stream, device->priv->fd, i, - OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i)); - - if (input == TRUE) { - oss_stream_add_control (OSS_STREAM (device->priv->input), control); - - if (i == SOUND_MIXER_RECLEV || i == SOUND_MIXER_IGAIN) { - if (i == SOUND_MIXER_RECLEV) - set_stream_default_control (OSS_STREAM (device->priv->input), - control, - TRUE); - else - set_stream_default_control (OSS_STREAM (device->priv->input), - control, - FALSE); - } + stereo); + + if (oss_stream_has_controls (stream) == FALSE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + + free_stream_list (device); + + /* Pretend the stream has just been created now that we are adding + * the first control */ + g_signal_emit_by_name (G_OBJECT (device), + "stream-added", + name); + } + + g_debug ("Adding device %s control %s", + name, + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control))); + + oss_stream_add_control (stream, control); + oss_stream_control_load (control); + + g_object_unref (control); + } +} + +static void +read_mixer_switch (OssDevice *device) +{ + GList *options = NULL; + guint i; + + for (i = 0; i < OSS_N_DEVICES; i++) { + OssSwitchOption *option; + + if (OSS_MASK_HAS_DEVICE (device->priv->recmask, i) == FALSE) + continue; + + option = oss_switch_option_new (oss_devices[i].name, + gettext (oss_devices[i].label), + oss_devices[i].icon, + i); + options = g_list_prepend (options, option); + } + + if G_LIKELY (options != NULL) + oss_stream_set_switch_data (device->priv->input, + device->priv->fd, + options); +} + +static gboolean +poll_mixer (OssDevice *device) +{ + gboolean load = TRUE; + + if G_UNLIKELY (device->priv->fd == -1) + return G_SOURCE_REMOVE; + +#ifdef SOUND_MIXER_INFO + if (device->priv->poll_use_counter == TRUE) { + gint ret; + struct mixer_info info; + + /* + * The modify_counter field increases each time a change happens on + * the device. + * + * If this ioctl() works, we use the field to only poll the controls + * if a change actually occured and we can also adjust the poll interval. + * + * This works well at least on Linux, NetBSD and OpenBSD. This call is + * not supported on FreeBSD as of version 10. + * + * The call is also used to detect unplugged devices early, when not + * supported, the unplug is still caught in the backend. + */ + ret = ioctl (device->priv->fd, SOUND_MIXER_INFO, &info); + if (ret == -1) { + if (errno == EINTR) + return G_SOURCE_CONTINUE; + + oss_device_close (device); + return G_SOURCE_REMOVE; + } + + if (device->priv->poll_counter < info.modify_counter) { + device->priv->poll_counter = info.modify_counter; } else { - oss_stream_add_control (OSS_STREAM (device->priv->output), control); - - if (i == SOUND_MIXER_VOLUME || i == SOUND_MIXER_PCM) { - if (i == SOUND_MIXER_VOLUME) - set_stream_default_control (OSS_STREAM (device->priv->output), - control, - TRUE); - else - set_stream_default_control (OSS_STREAM (device->priv->output), - control, - FALSE); - } + load = FALSE; } + } +#endif + + if (load == TRUE) { + oss_stream_load (device->priv->input); + oss_stream_load (device->priv->output); + + if (device->priv->poll_use_counter == TRUE && + device->priv->poll_mode == OSS_POLL_NORMAL) { + /* Create a new rapid source */ + device->priv->poll_tag = create_poll_source (device, OSS_POLL_RAPID); - g_debug ("Added control %s", - mate_mixer_stream_control_get_label (MATE_MIXER_STREAM_CONTROL (control))); + /* Also create another source to restore the poll interval to the + * original state */ + device->priv->poll_tag_restore = create_poll_restore_source (device); - oss_stream_control_update (control); + device->priv->poll_mode = OSS_POLL_RAPID; + return G_SOURCE_REMOVE; + } } - return TRUE; + return G_SOURCE_CONTINUE; } static gboolean -set_stream_default_control (OssStream *stream, OssStreamControl *control, gboolean force) +poll_mixer_restore (OssDevice *device) { - MateMixerStreamControl *current; + if G_LIKELY (device->priv->poll_mode == OSS_POLL_RAPID) { + /* Remove the current rapid source */ + g_source_remove (device->priv->poll_tag); - current = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); - if (current == NULL || force == TRUE) { - oss_stream_set_default_control (stream, OSS_STREAM_CONTROL (control)); - return TRUE; + device->priv->poll_tag = create_poll_source (device, OSS_POLL_NORMAL); + device->priv->poll_mode = OSS_POLL_NORMAL; } - return FALSE; + + /* Remove the tag for this function as it is only called once, the tag + * is only kept so we can remove it in the case the device is closed */ + device->priv->poll_tag_restore = 0; + + return G_SOURCE_REMOVE; +} + +static guint +create_poll_source (OssDevice *device, OssPollMode mode) +{ + GSource *source; + guint timeout; + guint tag; + + switch (mode) { + case OSS_POLL_NORMAL: + timeout = OSS_POLL_TIMEOUT_NORMAL; + break; + case OSS_POLL_RAPID: + timeout = OSS_POLL_TIMEOUT_RAPID; + break; + default: + g_warn_if_reached (); + return 0; + } + + source = g_timeout_source_new (timeout); + g_source_set_callback (source, + (GSourceFunc) poll_mixer, + device, + NULL); + + tag = g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + + return tag; +} + +static guint +create_poll_restore_source (OssDevice *device) +{ + GSource *source; + guint tag; + + source = g_timeout_source_new (OSS_POLL_TIMEOUT_RESTORE); + g_source_set_callback (source, + (GSourceFunc) poll_mixer_restore, + device, + NULL); + + tag = g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + + return tag; +} + +static void +free_stream_list (OssDevice *device) +{ + /* This function is called each time the stream list changes */ + if (device->priv->streams == NULL) + return; + + g_list_free_full (device->priv->streams, g_object_unref); + + device->priv->streams = NULL; +} + +static gint +compare_stream_control_devnum (gconstpointer a, gconstpointer b) +{ + OssStreamControl *control = OSS_STREAM_CONTROL (a); + guint devnum = GPOINTER_TO_INT (b); + + return !(oss_stream_control_get_devnum (control) == devnum); } diff --git a/backends/oss/oss-device.h b/backends/oss/oss-device.h index 261a884..a723f41 100644 --- a/backends/oss/oss-device.h +++ b/backends/oss/oss-device.h @@ -22,7 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> -#include "oss-stream.h" +#include "oss-types.h" G_BEGIN_DECLS @@ -39,7 +39,6 @@ G_BEGIN_DECLS #define OSS_DEVICE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_DEVICE, OssDeviceClass)) -typedef struct _OssDevice OssDevice; typedef struct _OssDeviceClass OssDeviceClass; typedef struct _OssDevicePrivate OssDevicePrivate; @@ -54,6 +53,9 @@ struct _OssDevice struct _OssDeviceClass { MateMixerDeviceClass parent; + + /*< private >*/ + void (*closed) (OssDevice *device); }; GType oss_device_get_type (void) G_GNUC_CONST; @@ -64,9 +66,11 @@ OssDevice * oss_device_new (const gchar *name, gint fd); gboolean oss_device_open (OssDevice *device); -gboolean oss_device_load (OssDevice *device); +gboolean oss_device_is_open (OssDevice *device); +void oss_device_close (OssDevice *device); + +void oss_device_load (OssDevice *device); -gint oss_device_get_fd (OssDevice *device); const gchar *oss_device_get_path (OssDevice *device); OssStream * oss_device_get_input_stream (OssDevice *device); diff --git a/backends/oss/oss-stream-control.c b/backends/oss/oss-stream-control.c index 5161528..0307fc7 100644 --- a/backends/oss/oss-stream-control.c +++ b/backends/oss/oss-stream-control.c @@ -26,27 +26,28 @@ #include "oss-common.h" #include "oss-stream-control.h" +#define OSS_VOLUME_JOIN(left,right) (((left) & 0xFF) | (((right) & 0xFF) << 8)) + +#define OSS_VOLUME_JOIN_SAME(volume) (OSS_VOLUME_JOIN ((volume), (volume))) +#define OSS_VOLUME_JOIN_ARRAY(volume) (OSS_VOLUME_JOIN ((volume[0]), (volume[1]))) + +#define OSS_VOLUME_TAKE_LEFT(volume) ((volume) & 0xFF) +#define OSS_VOLUME_TAKE_RIGHT(volume) (((volume) >> 8) & 0xFF) + struct _OssStreamControlPrivate { - gint fd; - gint devnum; - guint volume[2]; - gfloat balance; - gboolean stereo; - MateMixerStreamControlRole role; - MateMixerStreamControlFlags flags; + gint fd; + gint devnum; + guint8 volume[2]; + gboolean stereo; }; static void oss_stream_control_class_init (OssStreamControlClass *klass); - static void oss_stream_control_init (OssStreamControl *control); static void oss_stream_control_finalize (GObject *object); G_DEFINE_TYPE (OssStreamControl, oss_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL) -static gboolean oss_stream_control_set_mute (MateMixerStreamControl *mmsc, - gboolean mute); - static guint oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc); static guint oss_stream_control_get_volume (MateMixerStreamControl *mmsc); @@ -73,7 +74,9 @@ static guint oss_stream_control_get_max_volume (MateMix static guint oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); static guint oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc); -static gboolean write_volume (OssStreamControl *control, +static void read_balance (OssStreamControl *control); + +static gboolean write_and_set_volume (OssStreamControl *control, gint volume); static void @@ -86,14 +89,13 @@ oss_stream_control_class_init (OssStreamControlClass *klass) object_class->finalize = oss_stream_control_finalize; control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); - control_class->set_mute = oss_stream_control_set_mute; control_class->get_num_channels = oss_stream_control_get_num_channels; control_class->get_volume = oss_stream_control_get_volume; control_class->set_volume = oss_stream_control_set_volume; control_class->get_channel_volume = oss_stream_control_get_channel_volume; control_class->set_channel_volume = oss_stream_control_set_channel_volume; - control_class->get_channel_position = oss_stream_control_get_channel_position; control_class->has_channel_position = oss_stream_control_has_channel_position; + control_class->get_channel_position = oss_stream_control_get_channel_position; control_class->set_balance = oss_stream_control_set_balance; control_class->get_min_volume = oss_stream_control_get_min_volume; control_class->get_max_volume = oss_stream_control_get_max_volume; @@ -118,7 +120,8 @@ oss_stream_control_finalize (GObject *object) control = OSS_STREAM_CONTROL (object); - close (control->priv->fd); + if (control->priv->fd != -1) + close (control->priv->fd); G_OBJECT_CLASS (oss_stream_control_parent_class)->finalize (object); } @@ -127,6 +130,7 @@ OssStreamControl * oss_stream_control_new (const gchar *name, const gchar *label, MateMixerStreamControlRole role, + OssStream *stream, gint fd, gint devnum, gboolean stereo) @@ -134,8 +138,11 @@ oss_stream_control_new (const gchar *name, OssStreamControl *control; MateMixerStreamControlFlags flags; - flags = MATE_MIXER_STREAM_CONTROL_HAS_VOLUME | - MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME; + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + + flags = MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; if (stereo == TRUE) flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; @@ -143,6 +150,8 @@ oss_stream_control_new (const gchar *name, "name", name, "label", label, "flags", flags, + "role", role, + "stream", stream, NULL); control->priv->fd = fd; @@ -151,52 +160,50 @@ oss_stream_control_new (const gchar *name, return control; } -gboolean -oss_stream_control_update (OssStreamControl *control) +gint +oss_stream_control_get_devnum (OssStreamControl *control) { - gint v; - gint ret; + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), 0); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); + return control->priv->devnum; +} - ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v); - if (ret < 0) { - g_warning ("Failed to read volume: %s", g_strerror (errno)); - return FALSE; - } +void +oss_stream_control_load (OssStreamControl *control) +{ + gint v, ret; - control->priv->volume[0] = v & 0xFF; + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - if (control->priv->stereo == TRUE) { - gfloat balance; - guint left; - guint right; - - control->priv->volume[1] = (v >> 8) & 0xFF; - - /* Calculate balance */ - left = control->priv->volume[0]; - right = control->priv->volume[1]; - if (left == right) - balance = 0.0f; - else if (left > right) - balance = -1.0f + ((gfloat) right / (gfloat) left); - else - balance = +1.0f - ((gfloat) left / (gfloat) right); + if G_UNLIKELY (control->priv->fd == -1) + return; + + ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v); + if (ret == -1) + return; + + if (v != OSS_VOLUME_JOIN_ARRAY (control->priv->volume)) { + control->priv->volume[0] = OSS_VOLUME_TAKE_LEFT (v); + if (control->priv->stereo == TRUE) + control->priv->volume[1] = OSS_VOLUME_TAKE_RIGHT (v); - _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), - balance); + g_object_notify (G_OBJECT (control), "volume"); } - return TRUE; + + if (control->priv->stereo == TRUE) + read_balance (control); } -static gboolean -oss_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) +void +oss_stream_control_close (OssStreamControl *control) { - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - // TODO - return TRUE; + if (control->priv->fd == -1) + return; + + close (control->priv->fd); + control->priv->fd = -1; } static guint @@ -226,17 +233,15 @@ static gboolean oss_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) { OssStreamControl *control; - gint v; g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); - v = CLAMP (volume, 0, 100); - if (control->priv->stereo == TRUE) - v |= (volume & 0xFF) << 8; + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; - return write_volume (control, v); + return write_and_set_volume (control, OSS_VOLUME_JOIN_SAME (CLAMP (volume, 0, 100))); } static guint @@ -269,27 +274,26 @@ oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); - volume = CLAMP (volume, 0, 100); - if (control->priv->stereo == TRUE) { - if (channel > 1) - return FALSE; + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; + if (channel > 1 || (control->priv->stereo == FALSE && channel > 0)) + return FALSE; - /* Stereo channel volume - left channel is in the lowest 8 bits and + volume = CLAMP (volume, 0, 100); + + if (control->priv->stereo == TRUE) { + /* Stereo channel volume - left channel is in the lower 8 bits and * right channel is in the higher 8 bits */ if (channel == 0) - v = volume | (control->priv->volume[1] << 8); + v = OSS_VOLUME_JOIN (volume, control->priv->volume[1]); else - v = control->priv->volume[0] | (volume << 8); + v = OSS_VOLUME_JOIN (control->priv->volume[0], volume); } else { - if (channel > 0) - return FALSE; - - /* Single channel volume - only lowest 8 bits are used */ + /* Single channel volume - only lower 8 bits are used */ v = volume; } - - return write_volume (control, v); + return write_and_set_volume (control, v); } static MateMixerChannelPosition @@ -334,24 +338,24 @@ oss_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) { OssStreamControl *control; guint max; - gint v; + gint volume[2]; g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; + max = MAX (control->priv->volume[0], control->priv->volume[1]); if (balance <= 0) { - control->priv->volume[1] = (balance + 1.0f) * max; - control->priv->volume[0] = max; + volume[1] = (balance + 1.0f) * max; + volume[0] = max; } else { - control->priv->volume[0] = (1.0f - balance) * max; - control->priv->volume[1] = max; + volume[0] = (1.0f - balance) * max; + volume[1] = max; } - - v = control->priv->volume[0] | (control->priv->volume[1] << 8); - - return write_volume (control, v); + return write_and_set_volume (control, OSS_VOLUME_JOIN_ARRAY (volume)); } static guint @@ -378,15 +382,45 @@ oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc) return 100; } +static void +read_balance (OssStreamControl *control) +{ + gfloat balance; + guint left = control->priv->volume[0]; + guint right = control->priv->volume[1]; + + if (left == right) + balance = 0.0f; + else if (left > right) + balance = -1.0f + ((gfloat) right / (gfloat) left); + else + balance = +1.0f - ((gfloat) left / (gfloat) right); + + _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), + balance); +} + static gboolean -write_volume (OssStreamControl *control, gint volume) +write_and_set_volume (OssStreamControl *control, gint volume) { gint ret; + /* Nothing to do? */ + if (volume == OSS_VOLUME_JOIN_ARRAY (control->priv->volume)) + return TRUE; + ret = ioctl (control->priv->fd, MIXER_WRITE (control->priv->devnum), &volume); - if (ret < 0) { - g_warning ("Failed to set volume: %s", g_strerror (errno)); + if (ret == -1) return FALSE; - } + + oss_stream_control_load (control); + return TRUE; + + /* The ioctl may make adjustments to the passed volume, so make sure we have + * the correct value saved */ + control->priv->volume[0] = OSS_VOLUME_TAKE_LEFT (volume); + control->priv->volume[1] = OSS_VOLUME_TAKE_RIGHT (volume); + + g_object_notify (G_OBJECT (control), "volume"); return TRUE; } diff --git a/backends/oss/oss-stream-control.h b/backends/oss/oss-stream-control.h index c839faf..1957088 100644 --- a/backends/oss/oss-stream-control.h +++ b/backends/oss/oss-stream-control.h @@ -22,6 +22,8 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include "oss-types.h" + G_BEGIN_DECLS #define OSS_TYPE_STREAM_CONTROL \ @@ -37,7 +39,6 @@ G_BEGIN_DECLS #define OSS_STREAM_CONTROL_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM_CONTROL, OssStreamControlClass)) -typedef struct _OssStreamControl OssStreamControl; typedef struct _OssStreamControlClass OssStreamControlClass; typedef struct _OssStreamControlPrivate OssStreamControlPrivate; @@ -54,16 +55,20 @@ struct _OssStreamControlClass MateMixerStreamControlClass parent; }; -GType oss_stream_control_get_type (void) G_GNUC_CONST; +GType oss_stream_control_get_type (void) G_GNUC_CONST; + +OssStreamControl *oss_stream_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role, + OssStream *stream, + gint fd, + gint devnum, + gboolean stereo); -OssStreamControl *oss_stream_control_new (const gchar *name, - const gchar *label, - MateMixerStreamControlRole role, - gint fd, - gint devnum, - gboolean stereo); +gint oss_stream_control_get_devnum (OssStreamControl *control); -gboolean oss_stream_control_update (OssStreamControl *control); +void oss_stream_control_load (OssStreamControl *control); +void oss_stream_control_close (OssStreamControl *control); G_END_DECLS diff --git a/backends/oss/oss-stream.c b/backends/oss/oss-stream.c index 5f0c629..227c88b 100644 --- a/backends/oss/oss-stream.c +++ b/backends/oss/oss-stream.c @@ -16,32 +16,32 @@ */ #include <glib.h> +#include <glib/gi18n.h> #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> -#include "oss-device.h" #include "oss-stream.h" #include "oss-stream-control.h" +#include "oss-switch.h" + +#define OSS_STREAM_SWITCH_NAME "port" struct _OssStreamPrivate { - GHashTable *controls; - OssStreamControl *control; + OssSwitch *swtch; + GList *switches; + GList *controls; }; -static void oss_stream_class_init (OssStreamClass *klass); - -static void oss_stream_init (OssStream *ostream); -static void oss_stream_dispose (GObject *object); -static void oss_stream_finalize (GObject *object); +static void oss_stream_class_init (OssStreamClass *klass); +static void oss_stream_init (OssStream *stream); +static void oss_stream_dispose (GObject *object); G_DEFINE_TYPE (OssStream, oss_stream, MATE_MIXER_TYPE_STREAM) -static MateMixerStreamControl *oss_stream_get_control (MateMixerStream *stream, - const gchar *name); -static MateMixerStreamControl *oss_stream_get_default_control (MateMixerStream *stream); - -static GList * oss_stream_list_controls (MateMixerStream *stream); +static const GList *oss_stream_list_controls (MateMixerStream *mms); +static const GList *oss_stream_list_switches (MateMixerStream *mms); static void oss_stream_class_init (OssStreamClass *klass) @@ -50,13 +50,11 @@ oss_stream_class_init (OssStreamClass *klass) MateMixerStreamClass *stream_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = oss_stream_dispose; - object_class->finalize = oss_stream_finalize; + object_class->dispose = oss_stream_dispose; stream_class = MATE_MIXER_STREAM_CLASS (klass); - stream_class->get_control = oss_stream_get_control; - stream_class->get_default_control = oss_stream_get_default_control; - stream_class->list_controls = oss_stream_list_controls; + stream_class->list_controls = oss_stream_list_controls; + stream_class->list_switches = oss_stream_list_switches; g_type_class_add_private (object_class, sizeof (OssStreamPrivate)); } @@ -67,11 +65,6 @@ oss_stream_init (OssStream *stream) stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, OSS_TYPE_STREAM, OssStreamPrivate); - - stream->priv->controls = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); } static void @@ -81,103 +74,201 @@ oss_stream_dispose (GObject *object) stream = OSS_STREAM (object); - g_clear_object (&stream->priv->control); - g_hash_table_remove_all (stream->priv->controls); + if (stream->priv->controls != NULL) { + g_list_free_full (stream->priv->controls, g_object_unref); + stream->priv->controls = NULL; + } + if (stream->priv->switches != NULL) { + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + } + + g_clear_object (&stream->priv->swtch); G_OBJECT_CLASS (oss_stream_parent_class)->dispose (object); } -static void -oss_stream_finalize (GObject *object) +OssStream * +oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction) { - OssStream *stream; + const gchar *label = mate_mixer_device_get_label (device); + + return g_object_new (OSS_TYPE_STREAM, + "name", name, + "label", label, + "device", device, + "direction", direction, + NULL); +} - stream = OSS_STREAM (object); +void +oss_stream_add_control (OssStream *stream, OssStreamControl *control) +{ + const gchar *name; + + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - g_hash_table_destroy (stream->priv->controls); + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + + stream->priv->controls = + g_list_append (stream->priv->controls, g_object_ref (control)); - G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object); + g_signal_emit_by_name (G_OBJECT (stream), + "control-added", + name); } -OssStream * -oss_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags) +void +oss_stream_load (OssStream *stream) { - OssStream *stream; + GList *list; + + g_return_if_fail (OSS_IS_STREAM (stream)); + + list = stream->priv->controls; + while (list != NULL) { + OssStreamControl *control = OSS_STREAM_CONTROL (list->data); - stream = g_object_new (OSS_TYPE_STREAM, - "name", name, - "device", device, - "flags", flags, - NULL); - return stream; + oss_stream_control_load (control); + list = list->next; + } + + if (stream->priv->swtch != NULL) + oss_switch_load (stream->priv->swtch); } gboolean -oss_stream_add_control (OssStream *stream, OssStreamControl *control) +oss_stream_has_controls (OssStream *stream) { - const gchar *name; - g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); - name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + if (stream->priv->controls != NULL) + return TRUE; - g_hash_table_insert (stream->priv->controls, - g_strdup (name), - control); - return TRUE; + return FALSE; } gboolean -oss_stream_set_default_control (OssStream *stream, OssStreamControl *control) +oss_stream_has_default_control (OssStream *stream) { g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); - - /* This function is only used internally so avoid validating that the control - * belongs to this stream */ - if (stream->priv->control != NULL) - g_object_unref (stream->priv->control); - if G_LIKELY (control != NULL) - stream->priv->control = g_object_ref (control); - else - stream->priv->control = NULL; + if (mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)) != NULL) + return TRUE; - return TRUE; + return FALSE; } -static MateMixerStreamControl * -oss_stream_get_control (MateMixerStream *mms, const gchar *name) +OssStreamControl * +oss_stream_get_default_control (OssStream *stream) { - g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - g_return_val_if_fail (name != NULL, NULL); + MateMixerStreamControl *control; + + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); + if (control != NULL) + return OSS_STREAM_CONTROL (control); - return g_hash_table_lookup (OSS_STREAM (mms)->priv->controls, name); + return NULL; } -static MateMixerStreamControl * -oss_stream_get_default_control (MateMixerStream *mms) +void +oss_stream_set_default_control (OssStream *stream, OssStreamControl *control) { - g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (control == NULL || OSS_IS_STREAM_CONTROL (control)); + + if (control == NULL) + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), NULL); + else + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), + MATE_MIXER_STREAM_CONTROL (control)); +} - return MATE_MIXER_STREAM_CONTROL (OSS_STREAM (mms)->priv->control); +void +oss_stream_set_switch_data (OssStream *stream, gint fd, GList *options) +{ + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (fd != -1); + g_return_if_fail (options != NULL); + + /* Function may only be called once */ + if G_UNLIKELY (stream->priv->swtch != NULL) { + g_warn_if_reached (); + return; + } + + /* Takes ownership of options */ + stream->priv->swtch = oss_switch_new (OSS_STREAM_SWITCH_NAME, + _("Capture Source"), + fd, + options); + + /* Read the active selection */ + oss_switch_load (stream->priv->swtch); + + stream->priv->switches = g_list_prepend (NULL, g_object_ref (stream->priv->swtch)); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + OSS_STREAM_SWITCH_NAME); } -static GList * -oss_stream_list_controls (MateMixerStream *mms) +void +oss_stream_remove_all (OssStream *stream) { GList *list; + g_return_if_fail (OSS_IS_STREAM (stream)); + + list = stream->priv->controls; + while (list != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data); + GList *next = list->next; + + oss_stream_control_close (OSS_STREAM_CONTROL (control)); + + stream->priv->controls = g_list_delete_link (stream->priv->controls, list); + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); + list = next; + } + + /* Unset the default stream control */ + oss_stream_set_default_control (stream, NULL); + + if (stream->priv->swtch != NULL) { + oss_switch_close (stream->priv->swtch); + + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + OSS_STREAM_SWITCH_NAME); + + g_clear_object (&stream->priv->swtch); + } +} + +static const GList * +oss_stream_list_controls (MateMixerStream *mms) +{ g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (OSS_STREAM (mms)->priv->controls); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); + return OSS_STREAM (mms)->priv->controls; +} + +static const GList * +oss_stream_list_switches (MateMixerStream *mms) +{ + g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - return list; + return OSS_STREAM (mms)->priv->switches; } diff --git a/backends/oss/oss-stream.h b/backends/oss/oss-stream.h index 0470eb7..2ade0d8 100644 --- a/backends/oss/oss-stream.h +++ b/backends/oss/oss-stream.h @@ -22,8 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> -#include "oss-device.h" -#include "oss-stream-control.h" +#include "oss-types.h" G_BEGIN_DECLS @@ -40,7 +39,6 @@ G_BEGIN_DECLS #define OSS_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM, OssStreamClass)) -typedef struct _OssStream OssStream; typedef struct _OssStreamClass OssStreamClass; typedef struct _OssStreamPrivate OssStreamPrivate; @@ -57,17 +55,29 @@ struct _OssStreamClass MateMixerStreamClass parent; }; -GType oss_stream_get_type (void) G_GNUC_CONST; +GType oss_stream_get_type (void) G_GNUC_CONST; -OssStream *oss_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags); +OssStream * oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction); -gboolean oss_stream_add_control (OssStream *stream, - OssStreamControl *control); +void oss_stream_add_control (OssStream *stream, + OssStreamControl *control); -gboolean oss_stream_set_default_control (OssStream *stream, - OssStreamControl *control); +void oss_stream_load (OssStream *stream); + +gboolean oss_stream_has_controls (OssStream *stream); +gboolean oss_stream_has_default_control (OssStream *stream); + +OssStreamControl *oss_stream_get_default_control (OssStream *stream); +void oss_stream_set_default_control (OssStream *stream, + OssStreamControl *control); + +void oss_stream_set_switch_data (OssStream *stream, + gint fd, + GList *options); + +void oss_stream_remove_all (OssStream *stream); G_END_DECLS diff --git a/backends/oss/oss-switch-option.c b/backends/oss/oss-switch-option.c new file mode 100644 index 0000000..862133d --- /dev/null +++ b/backends/oss/oss-switch-option.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-switch-option.h" + +struct _OssSwitchOptionPrivate +{ + guint devnum; +}; + +static void oss_switch_option_class_init (OssSwitchOptionClass *klass); +static void oss_switch_option_init (OssSwitchOption *option); + +G_DEFINE_TYPE (OssSwitchOption, oss_switch_option, MATE_MIXER_TYPE_SWITCH_OPTION) + +static void +oss_switch_option_class_init (OssSwitchOptionClass *klass) +{ + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (OssSwitchOptionPrivate)); +} + +static void +oss_switch_option_init (OssSwitchOption *option) +{ + option->priv = G_TYPE_INSTANCE_GET_PRIVATE (option, + OSS_TYPE_SWITCH_OPTION, + OssSwitchOptionPrivate); +} + +OssSwitchOption * +oss_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint devnum) +{ + OssSwitchOption *option; + + option = g_object_new (OSS_TYPE_SWITCH_OPTION, + "name", name, + "label", label, + "icon", icon, + NULL); + + option->priv->devnum = devnum; + return option; +} + +guint +oss_switch_option_get_devnum (OssSwitchOption *option) +{ + g_return_val_if_fail (OSS_IS_SWITCH_OPTION (option), 0); + + return option->priv->devnum; +} diff --git a/backends/oss/oss-switch-option.h b/backends/oss/oss-switch-option.h new file mode 100644 index 0000000..91c21e3 --- /dev/null +++ b/backends/oss/oss-switch-option.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_SWITCH_OPTION_H +#define OSS_SWITCH_OPTION_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-types.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_SWITCH_OPTION \ + (oss_switch_option_get_type ()) +#define OSS_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_SWITCH_OPTION, OssSwitchOption)) +#define OSS_IS_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_SWITCH_OPTION)) +#define OSS_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_SWITCH_OPTION, OssSwitchOptionClass)) +#define OSS_IS_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_SWITCH_OPTION)) +#define OSS_SWITCH_OPTION_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_SWITCH_OPTION, OssSwitchOptionClass)) + +typedef struct _OssSwitchOptionClass OssSwitchOptionClass; +typedef struct _OssSwitchOptionPrivate OssSwitchOptionPrivate; + +struct _OssSwitchOption +{ + MateMixerSwitchOption parent; + + /*< private >*/ + OssSwitchOptionPrivate *priv; +}; + +struct _OssSwitchOptionClass +{ + MateMixerSwitchOptionClass parent_class; +}; + +GType oss_switch_option_get_type (void) G_GNUC_CONST; + +OssSwitchOption *oss_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint devnum); + +guint oss_switch_option_get_devnum (OssSwitchOption *option); + +G_END_DECLS + +#endif /* OSS_SWITCH_OPTION_H */ diff --git a/backends/oss/oss-switch.c b/backends/oss/oss-switch.c new file mode 100644 index 0000000..b138051 --- /dev/null +++ b/backends/oss/oss-switch.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "oss-common.h" +#include "oss-switch.h" +#include "oss-switch-option.h" + +struct _OssSwitchPrivate +{ + gint fd; + GList *options; +}; + +static void oss_switch_class_init (OssSwitchClass *klass); +static void oss_switch_init (OssSwitch *swtch); +static void oss_switch_dispose (GObject *object); +static void oss_switch_finalize (GObject *object); + +G_DEFINE_TYPE (OssSwitch, oss_switch, MATE_MIXER_TYPE_SWITCH) + +static gboolean oss_switch_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static const GList *oss_switch_list_options (MateMixerSwitch *mms); + +static void +oss_switch_class_init (OssSwitchClass *klass) +{ + GObjectClass *object_class; + MateMixerSwitchClass *switch_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_switch_dispose; + object_class->finalize = oss_switch_finalize; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = oss_switch_set_active_option; + switch_class->list_options = oss_switch_list_options; + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (OssSwitchPrivate)); +} + +static void +oss_switch_init (OssSwitch *swtch) +{ + swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, + OSS_TYPE_SWITCH, + OssSwitchPrivate); +} + +static void +oss_switch_dispose (GObject *object) +{ + OssSwitch *swtch; + + swtch = OSS_SWITCH (object); + + if (swtch->priv->options != NULL) { + g_list_free_full (swtch->priv->options, g_object_unref); + swtch->priv->options = NULL; + } + + G_OBJECT_CLASS (oss_switch_parent_class)->dispose (object); +} + +static void +oss_switch_finalize (GObject *object) +{ + OssSwitch *swtch; + + swtch = OSS_SWITCH (object); + + if (swtch->priv->fd != -1) + close (swtch->priv->fd); + + G_OBJECT_CLASS (oss_switch_parent_class)->finalize (object); +} + +OssSwitch * +oss_switch_new (const gchar *name, + const gchar *label, + gint fd, + GList *options) +{ + OssSwitch *swtch; + + swtch = g_object_new (OSS_TYPE_SWITCH, + "name", name, + "label", label, + "flags", MATE_MIXER_SWITCH_ALLOWS_NO_ACTIVE_OPTION, + "role", MATE_MIXER_SWITCH_ROLE_PORT, + NULL); + + /* Takes ownership of options */ + swtch->priv->fd = dup (fd); + swtch->priv->options = options; + + return swtch; +} + +void +oss_switch_load (OssSwitch *swtch) +{ + GList *list; + gint recsrc; + gint ret; + + g_return_if_fail (OSS_IS_SWITCH (swtch)); + + if G_UNLIKELY (swtch->priv->fd == -1) + return; + + ret = ioctl (swtch->priv->fd, MIXER_READ (SOUND_MIXER_RECSRC), &recsrc); + if (ret == -1) + return; + if (recsrc == 0) { + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), NULL); + return; + } + + list = swtch->priv->options; + while (list != NULL) { + OssSwitchOption *option = OSS_SWITCH_OPTION (list->data); + gint devnum = oss_switch_option_get_devnum (option); + + /* Mark the selected option when we find it */ + if (recsrc & (1 << devnum)) { + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), + MATE_MIXER_SWITCH_OPTION (option)); + return; + } + list = list->next; + } + + g_warning ("Unknown active option of switch %s", + mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch))); +} + +void +oss_switch_close (OssSwitch *swtch) +{ + g_return_if_fail (OSS_IS_SWITCH (swtch)); + + if (swtch->priv->fd == -1) + return; + + close (swtch->priv->fd); + swtch->priv->fd = -1; +} + +static gboolean +oss_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + OssSwitch *swtch; + gint ret; + gint recsrc; + gint devnum; + + g_return_val_if_fail (OSS_IS_SWITCH (mms), FALSE); + g_return_val_if_fail (OSS_IS_SWITCH_OPTION (mmso), FALSE); + + swtch = OSS_SWITCH (mms); + + if G_UNLIKELY (swtch->priv->fd == -1) + return FALSE; + + devnum = oss_switch_option_get_devnum (OSS_SWITCH_OPTION (mmso)); + recsrc = 1 << devnum; + + ret = ioctl (swtch->priv->fd, MIXER_WRITE (SOUND_MIXER_RECSRC), &recsrc); + if (ret == -1) + return FALSE; + + return TRUE; +} + +static const GList * +oss_switch_list_options (MateMixerSwitch *mms) +{ + g_return_val_if_fail (OSS_IS_SWITCH (mms), NULL); + + return OSS_SWITCH (mms)->priv->options; +} diff --git a/backends/oss/oss-switch.h b/backends/oss/oss-switch.h new file mode 100644 index 0000000..dc91758 --- /dev/null +++ b/backends/oss/oss-switch.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_SWITCH_H +#define OSS_SWITCH_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-types.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_SWITCH \ + (oss_switch_get_type ()) +#define OSS_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_SWITCH, OssSwitch)) +#define OSS_IS_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_SWITCH)) +#define OSS_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_SWITCH, OssSwitchClass)) +#define OSS_IS_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_SWITCH)) +#define OSS_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_SWITCH, OssSwitchClass)) + +typedef struct _OssSwitchClass OssSwitchClass; +typedef struct _OssSwitchPrivate OssSwitchPrivate; + +struct _OssSwitch +{ + MateMixerSwitch parent; + + /*< private >*/ + OssSwitchPrivate *priv; +}; + +struct _OssSwitchClass +{ + MateMixerSwitchClass parent_class; +}; + +GType oss_switch_get_type (void) G_GNUC_CONST; + +OssSwitch *oss_switch_new (const gchar *name, + const gchar *label, + gint fd, + GList *options); + +void oss_switch_load (OssSwitch *swtch); +void oss_switch_close (OssSwitch *swtch); + +G_END_DECLS + +#endif /* OSS_SWITCH_H */ diff --git a/backends/oss/oss-types.h b/backends/oss/oss-types.h new file mode 100644 index 0000000..afd4f58 --- /dev/null +++ b/backends/oss/oss-types.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_TYPES_H +#define OSS_TYPES_H + +G_BEGIN_DECLS + +typedef struct _OssBackend OssBackend; +typedef struct _OssDevice OssDevice; +typedef struct _OssStream OssStream; +typedef struct _OssStreamControl OssStreamControl; +typedef struct _OssSwitch OssSwitch; +typedef struct _OssSwitchOption OssSwitchOption; + +G_END_DECLS + +#endif /* OSS_TYPES_H */ diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am index 0e5a4d6..4c851bf 100644 --- a/backends/pulse/Makefile.am +++ b/backends/pulse/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-pulse.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-pulse\" @@ -13,12 +14,14 @@ libmatemixer_pulse_la_CFLAGS = \ libmatemixer_pulse_la_SOURCES = \ pulse-backend.c \ pulse-backend.h \ - pulse-client-stream.c \ - pulse-client-stream.h \ pulse-connection.c \ pulse-connection.h \ pulse-device.c \ pulse-device.h \ + pulse-device-profile.c \ + pulse-device-profile.h \ + pulse-device-switch.c \ + pulse-device-switch.h \ pulse-enums.h \ pulse-enum-types.c \ pulse-enum-types.h \ @@ -28,16 +31,31 @@ libmatemixer_pulse_la_SOURCES = \ pulse-helpers.h \ pulse-monitor.c \ pulse-monitor.h \ + pulse-port.c \ + pulse-port.h \ + pulse-port-switch.c \ + pulse-port-switch.h \ pulse-stream.c \ pulse-stream.h \ + pulse-stream-control.c \ + pulse-stream-control.h \ pulse-sink.c \ pulse-sink.h \ + pulse-sink-control.c \ + pulse-sink-control.h \ pulse-sink-input.c \ pulse-sink-input.h \ + pulse-sink-switch.c \ + pulse-sink-switch.h \ pulse-source.c \ pulse-source.h \ + pulse-source-control.c \ + pulse-source-control.h \ pulse-source-output.c \ - pulse-source-output.h + pulse-source-output.h \ + pulse-source-switch.c \ + pulse-source-switch.h \ + pulse-types.h libmatemixer_pulse_la_LIBADD = \ $(GLIB_LIBS) \ diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c index 0494545..e1b7ed5 100644 --- a/backends/pulse/pulse-backend.c +++ b/backends/pulse/pulse-backend.c @@ -19,9 +19,8 @@ #include <glib.h> #include <glib-object.h> -#include <libmatemixer/matemixer-backend.h> -#include <libmatemixer/matemixer-backend-module.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include <pulse/ext-stream-restore.h> @@ -38,72 +37,110 @@ #include "pulse-source-output.h" #define BACKEND_NAME "PulseAudio" -#define BACKEND_PRIORITY 10 +#define BACKEND_PRIORITY 100 struct _PulseBackendPrivate { - gchar *app_name; - gchar *app_id; - gchar *app_version; - gchar *app_icon; - gchar *server_address; - gboolean connected_once; - GSource *connect_source; - MateMixerStream *default_sink; - MateMixerStream *default_source; - GHashTable *devices; - GHashTable *streams; - GHashTable *ext_streams; - MateMixerState state; - PulseConnection *connection; -}; - -enum { - PROP_0, - PROP_STATE, - PROP_DEFAULT_INPUT, - PROP_DEFAULT_OUTPUT, - N_PROPERTIES + guint connect_tag; + gboolean connected_once; + GHashTable *devices; + GHashTable *sinks; + GHashTable *sources; + GHashTable *sink_inputs; + GHashTable *source_outputs; + GHashTable *ext_streams; + GList *devices_list; + GList *streams_list; + GList *ext_streams_list; + MateMixerAppInfo *app_info; + gchar *server_address; + PulseConnection *connection; }; -static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +#define PULSE_CHANGE_STATE(p, s) \ + (_mate_mixer_backend_set_state (MATE_MIXER_BACKEND (p), (s))) +#define PULSE_GET_DEFAULT_SINK(p) \ + (mate_mixer_backend_get_default_output_stream (MATE_MIXER_BACKEND (p))) +#define PULSE_GET_DEFAULT_SOURCE(p) \ + (mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (p))) +#define PULSE_SET_DEFAULT_SINK(p, s) \ + (_mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (p), MATE_MIXER_STREAM (s))) +#define PULSE_SET_DEFAULT_SOURCE(p, s) \ + (_mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (p), MATE_MIXER_STREAM (s))) + +#define PULSE_GET_PENDING_SINK(p) \ + (g_object_get_data (G_OBJECT (p), \ + "__matemixer_pulse_pending_sink")) \ + +#define PULSE_SET_PENDING_SINK(p,name) \ + (g_object_set_data_full (G_OBJECT (p), \ + "__matemixer_pulse_pending_sink", \ + g_strdup (name), \ + g_free)) + +#define PULSE_SET_PENDING_SINK_NULL(p) \ + (g_object_set_data (G_OBJECT (p), \ + "__matemixer_pulse_pending_sink", \ + NULL)) + +#define PULSE_GET_PENDING_SOURCE(p) \ + (g_object_get_data (G_OBJECT (p), \ + "__matemixer_pulse_pending_source")) \ + +#define PULSE_SET_PENDING_SOURCE(p,name) \ + (g_object_set_data_full (G_OBJECT (p), \ + "__matemixer_pulse_pending_source", \ + g_strdup (name), \ + g_free)) + +#define PULSE_SET_PENDING_SOURCE_NULL(p) \ + (g_object_set_data (G_OBJECT (p), \ + "__matemixer_pulse_pending_source", \ + NULL)) + +#define PULSE_GET_HANGING(o) \ + ((gboolean) g_object_get_data (G_OBJECT (o), \ + "__matemixer_pulse_hanging")) + +#define PULSE_SET_HANGING(o) \ + (g_object_set_data (G_OBJECT (o), \ + "__matemixer_pulse_hanging", \ + GUINT_TO_POINTER (1))) + +#define PULSE_UNSET_HANGING(o) \ + (g_object_steal_data (G_OBJECT (o), \ + "__matemixer_pulse_hanging")) static void pulse_backend_class_init (PulseBackendClass *klass); static void pulse_backend_class_finalize (PulseBackendClass *klass); -static void pulse_backend_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec); - static void pulse_backend_init (PulseBackend *pulse); static void pulse_backend_dispose (GObject *object); static void pulse_backend_finalize (GObject *object); -G_DEFINE_DYNAMIC_TYPE_EXTENDED (PulseBackend, pulse_backend, - G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, - mate_mixer_backend_interface_init)) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +G_DEFINE_DYNAMIC_TYPE (PulseBackend, pulse_backend, MATE_MIXER_TYPE_BACKEND) +#pragma clang diagnostic pop -static gboolean pulse_backend_open (MateMixerBackend *backend); -static void pulse_backend_close (MateMixerBackend *backend); +static gboolean pulse_backend_open (MateMixerBackend *backend); +static void pulse_backend_close (MateMixerBackend *backend); -static MateMixerState pulse_backend_get_state (MateMixerBackend *backend); +static void pulse_backend_set_app_info (MateMixerBackend *backend, + MateMixerAppInfo *info); -static void pulse_backend_set_data (MateMixerBackend *backend, - const MateMixerBackendData *data); +static void pulse_backend_set_server_address (MateMixerBackend *backend, + const gchar *address); -static GList * pulse_backend_list_devices (MateMixerBackend *backend); -static GList * pulse_backend_list_streams (MateMixerBackend *backend); -static GList * pulse_backend_list_cached_streams (MateMixerBackend *backend); +static const GList * pulse_backend_list_devices (MateMixerBackend *backend); +static const GList * pulse_backend_list_streams (MateMixerBackend *backend); +static const GList * pulse_backend_list_stored_controls (MateMixerBackend *backend); -static MateMixerStream *pulse_backend_get_default_input_stream (MateMixerBackend *backend); -static gboolean pulse_backend_set_default_input_stream (MateMixerBackend *backend, - MateMixerStream *stream); +static gboolean pulse_backend_set_default_input_stream (MateMixerBackend *backend, + MateMixerStream *stream); -static MateMixerStream *pulse_backend_get_default_output_stream (MateMixerBackend *backend); -static gboolean pulse_backend_set_default_output_stream (MateMixerBackend *backend, - MateMixerStream *stream); +static gboolean pulse_backend_set_default_output_stream (MateMixerBackend *backend, + MateMixerStream *stream); static void on_connection_state_notify (PulseConnection *connection, GParamSpec *pspec, @@ -152,32 +189,16 @@ static void on_connection_ext_stream_info (PulseConnection PulseBackend *pulse); static gboolean connect_source_reconnect (PulseBackend *pulse); -static void connect_source_remove (PulseBackend *pulse); static void check_pending_sink (PulseBackend *pulse, PulseStream *stream); static void check_pending_source (PulseBackend *pulse, PulseStream *stream); -static void mark_hanging (PulseBackend *pulse); -static void mark_hanging_hash (GHashTable *hash); - -static void unmark_hanging (PulseBackend *pulse, - GObject *object); - -static void remove_hanging (PulseBackend *pulse); -static void remove_device (PulseBackend *pulse, - PulseDevice *device); -static void remove_stream (PulseBackend *pulse, - PulseStream *stream); - -static void change_state (PulseBackend *backend, - MateMixerState state); +static void free_list_devices (PulseBackend *pulse); +static void free_list_streams (PulseBackend *pulse); +static void free_list_ext_streams (PulseBackend *pulse); -static gint compare_devices (gconstpointer a, - gconstpointer b); -static gint compare_streams (gconstpointer a, - gconstpointer b); static gboolean compare_stream_names (gpointer key, gpointer value, gpointer user_data); @@ -195,78 +216,42 @@ backend_module_init (GTypeModule *module) info.backend_type = MATE_MIXER_BACKEND_PULSEAUDIO; } -const MateMixerBackendInfo * -backend_module_get_info (void) +const MateMixerBackendInfo *backend_module_get_info (void) { return &info; } static void -mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) -{ - iface->open = pulse_backend_open; - iface->close = pulse_backend_close; - iface->get_state = pulse_backend_get_state; - iface->set_data = pulse_backend_set_data; - iface->list_devices = pulse_backend_list_devices; - iface->list_streams = pulse_backend_list_streams; - iface->list_cached_streams = pulse_backend_list_cached_streams; - iface->get_default_input_stream = pulse_backend_get_default_input_stream; - iface->set_default_input_stream = pulse_backend_set_default_input_stream; - iface->get_default_output_stream = pulse_backend_get_default_output_stream; - iface->set_default_output_stream = pulse_backend_set_default_output_stream; -} - -static void pulse_backend_class_init (PulseBackendClass *klass) { - GObjectClass *object_class; + GObjectClass *object_class; + MateMixerBackendClass *backend_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_backend_dispose; - object_class->finalize = pulse_backend_finalize; - object_class->get_property = pulse_backend_get_property; - - g_object_class_override_property (object_class, PROP_STATE, "state"); - g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); - g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); + object_class->dispose = pulse_backend_dispose; + object_class->finalize = pulse_backend_finalize; + + backend_class = MATE_MIXER_BACKEND_CLASS (klass); + backend_class->set_app_info = pulse_backend_set_app_info; + backend_class->set_server_address = pulse_backend_set_server_address; + backend_class->open = pulse_backend_open; + backend_class->close = pulse_backend_close; + backend_class->list_devices = pulse_backend_list_devices; + backend_class->list_streams = pulse_backend_list_streams; + backend_class->list_stored_controls = pulse_backend_list_stored_controls; + backend_class->set_default_input_stream = pulse_backend_set_default_input_stream; + backend_class->set_default_output_stream = pulse_backend_set_default_output_stream; g_type_class_add_private (object_class, sizeof (PulseBackendPrivate)); } -/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE() */ static void pulse_backend_class_finalize (PulseBackendClass *klass) { } static void -pulse_backend_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) -{ - PulseBackend *pulse; - - pulse = PULSE_BACKEND (object); - - switch (param_id) { - case PROP_STATE: - g_value_set_enum (value, pulse->priv->state); - break; - case PROP_DEFAULT_INPUT: - g_value_set_object (value, pulse->priv->default_source); - break; - case PROP_DEFAULT_OUTPUT: - g_value_set_object (value, pulse->priv->default_sink); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - break; - } -} - -static void pulse_backend_init (PulseBackend *pulse) { pulse->priv = G_TYPE_INSTANCE_GET_PRIVATE (pulse, @@ -279,22 +264,46 @@ pulse_backend_init (PulseBackend *pulse) g_direct_equal, NULL, g_object_unref); - pulse->priv->streams = - g_hash_table_new_full (g_int64_hash, - g_int64_equal, - g_free, + pulse->priv->sinks = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, g_object_unref); + pulse->priv->sources = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->ext_streams = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + pulse->priv->sink_inputs = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->source_outputs = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); } static void pulse_backend_dispose (GObject *object) { - pulse_backend_close (MATE_MIXER_BACKEND (object)); + MateMixerBackend *backend; + MateMixerState state; + + backend = MATE_MIXER_BACKEND (object); + + state = mate_mixer_backend_get_state (backend); + if (state != MATE_MIXER_STATE_IDLE) + pulse_backend_close (backend); G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object); } @@ -306,19 +315,24 @@ pulse_backend_finalize (GObject *object) pulse = PULSE_BACKEND (object); - g_free (pulse->priv->app_name); - g_free (pulse->priv->app_id); - g_free (pulse->priv->app_version); - g_free (pulse->priv->app_icon); - g_free (pulse->priv->server_address); + if (pulse->priv->app_info != NULL) + _mate_mixer_app_info_free (pulse->priv->app_info); - g_hash_table_destroy (pulse->priv->devices); - g_hash_table_destroy (pulse->priv->streams); - g_hash_table_destroy (pulse->priv->ext_streams); + g_hash_table_unref (pulse->priv->devices); + g_hash_table_unref (pulse->priv->sinks); + g_hash_table_unref (pulse->priv->sources); + g_hash_table_unref (pulse->priv->ext_streams); + g_hash_table_unref (pulse->priv->sink_inputs); + g_hash_table_unref (pulse->priv->source_outputs); G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object); } +#define PULSE_APP_NAME(p) (mate_mixer_app_info_get_name (p->priv->app_info)) +#define PULSE_APP_ID(p) (mate_mixer_app_info_get_id (p->priv->app_info)) +#define PULSE_APP_VERSION(p) (mate_mixer_app_info_get_version (p->priv->app_info)) +#define PULSE_APP_ICON(p) (mate_mixer_app_info_get_icon (p->priv->app_info)) + static gboolean pulse_backend_open (MateMixerBackend *backend) { @@ -329,22 +343,22 @@ pulse_backend_open (MateMixerBackend *backend) pulse = PULSE_BACKEND (backend); - if (G_UNLIKELY (pulse->priv->connection != NULL)) { + if G_UNLIKELY (pulse->priv->connection != NULL) { g_warn_if_reached (); return TRUE; } - connection = pulse_connection_new (pulse->priv->app_name, - pulse->priv->app_id, - pulse->priv->app_version, - pulse->priv->app_icon, + connection = pulse_connection_new (PULSE_APP_NAME (pulse), + PULSE_APP_ID (pulse), + PULSE_APP_VERSION (pulse), + PULSE_APP_ICON (pulse), pulse->priv->server_address); /* No connection attempt is made during the construction of the connection, * but it sets up the PulseAudio structures, which might fail in an * unlikely case */ - if (G_UNLIKELY (connection == NULL)) { - change_state (pulse, MATE_MIXER_STATE_FAILED); + if G_UNLIKELY (connection == NULL) { + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_FAILED); return FALSE; } @@ -409,16 +423,21 @@ pulse_backend_open (MateMixerBackend *backend) G_CALLBACK (on_connection_ext_stream_info), pulse); - change_state (pulse, MATE_MIXER_STATE_CONNECTING); + PULSE_CHANGE_STATE (backend, MATE_MIXER_STATE_CONNECTING); /* Connect to the PulseAudio server, this might fail either instantly or * asynchronously, for example when remote connection timeouts */ if (pulse_connection_connect (connection, FALSE) == FALSE) { g_object_unref (connection); - change_state (pulse, MATE_MIXER_STATE_FAILED); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_FAILED); return FALSE; } + _mate_mixer_backend_set_flags (backend, + MATE_MIXER_BACKEND_HAS_APPLICATION_CONTROLS | + MATE_MIXER_BACKEND_CAN_SET_DEFAULT_INPUT_STREAM | + MATE_MIXER_BACKEND_CAN_SET_DEFAULT_OUTPUT_STREAM); + pulse->priv->connection = connection; return TRUE; } @@ -432,7 +451,10 @@ pulse_backend_close (MateMixerBackend *backend) pulse = PULSE_BACKEND (backend); - connect_source_remove (pulse); + if (pulse->priv->connect_tag != 0) { + g_source_remove (pulse->priv->connect_tag); + pulse->priv->connect_tag = 0; + } if (pulse->priv->connection != NULL) { g_signal_handlers_disconnect_by_data (G_OBJECT (pulse->priv->connection), @@ -441,109 +463,100 @@ pulse_backend_close (MateMixerBackend *backend) g_clear_object (&pulse->priv->connection); } - g_clear_object (&pulse->priv->default_sink); - g_clear_object (&pulse->priv->default_source); - g_hash_table_remove_all (pulse->priv->devices); - g_hash_table_remove_all (pulse->priv->streams); + g_hash_table_remove_all (pulse->priv->sinks); + g_hash_table_remove_all (pulse->priv->sources); g_hash_table_remove_all (pulse->priv->ext_streams); pulse->priv->connected_once = FALSE; - change_state (pulse, MATE_MIXER_STATE_IDLE); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_IDLE); } -static MateMixerState -pulse_backend_get_state (MateMixerBackend *backend) +static void +pulse_backend_set_app_info (MateMixerBackend *backend, MateMixerAppInfo *info) { - g_return_val_if_fail (PULSE_IS_BACKEND (backend), MATE_MIXER_STATE_UNKNOWN); + PulseBackend *pulse; + + g_return_if_fail (PULSE_IS_BACKEND (backend)); + g_return_if_fail (info != NULL); + + pulse = PULSE_BACKEND (backend); - return PULSE_BACKEND (backend)->priv->state; + if (pulse->priv->app_info != NULL) + _mate_mixer_app_info_free (pulse->priv->app_info); + + pulse->priv->app_info = _mate_mixer_app_info_copy (info); } static void -pulse_backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) +pulse_backend_set_server_address (MateMixerBackend *backend, const gchar *address) { - PulseBackend *pulse; - g_return_if_fail (PULSE_IS_BACKEND (backend)); - g_return_if_fail (data != NULL); - pulse = PULSE_BACKEND (backend); + g_free (PULSE_BACKEND (backend)->priv->server_address); - g_free (pulse->priv->app_name); - g_free (pulse->priv->app_id); - g_free (pulse->priv->app_version); - g_free (pulse->priv->app_icon); - g_free (pulse->priv->server_address); - - pulse->priv->app_name = g_strdup (data->app_name); - pulse->priv->app_id = g_strdup (data->app_id); - pulse->priv->app_version = g_strdup (data->app_version); - pulse->priv->app_icon = g_strdup (data->app_icon); - pulse->priv->server_address = g_strdup (data->server_address); + PULSE_BACKEND (backend)->priv->server_address = g_strdup (address); } -static GList * +static const GList * pulse_backend_list_devices (MateMixerBackend *backend) { - GList *list; + PulseBackend *pulse; g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - /* Convert the hash table to a sorted linked list, this list is expected - * to be cached in the main library */ - list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->devices); - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); + pulse = PULSE_BACKEND (backend); - return g_list_sort (list, compare_devices); + if (pulse->priv->devices_list == NULL) { + pulse->priv->devices_list = g_hash_table_get_values (pulse->priv->devices); + if (pulse->priv->devices_list != NULL) + g_list_foreach (pulse->priv->devices_list, (GFunc) g_object_ref, NULL); } - return NULL; + return pulse->priv->devices_list; } -static GList * +static const GList * pulse_backend_list_streams (MateMixerBackend *backend) { - GList *list; + PulseBackend *pulse; g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - /* Convert the hash table to a sorted linked list, this list is expected - * to be cached in the main library */ - list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->streams); - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return g_list_sort (list, compare_streams); - } - return NULL; -} + pulse = PULSE_BACKEND (backend); -static GList * -pulse_backend_list_cached_streams (MateMixerBackend *backend) -{ - GList *list; + if (pulse->priv->streams_list == NULL) { + GList *sinks; + GList *sources; - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + sinks = g_hash_table_get_values (pulse->priv->sinks); + if (sinks != NULL) + g_list_foreach (sinks, (GFunc) g_object_ref, NULL); - /* Convert the hash table to a sorted linked list, this list is expected - * to be cached in the main library */ - list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->ext_streams); - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); + sources = g_hash_table_get_values (pulse->priv->sources); + if (sources != NULL) + g_list_foreach (sources, (GFunc) g_object_ref, NULL); - return g_list_sort (list, compare_streams); + pulse->priv->streams_list = g_list_concat (sinks, sources); } - return NULL; + return pulse->priv->streams_list; } -static MateMixerStream * -pulse_backend_get_default_input_stream (MateMixerBackend *backend) +static const GList * +pulse_backend_list_stored_controls (MateMixerBackend *backend) { + PulseBackend *pulse; + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - return PULSE_BACKEND (backend)->priv->default_source; + pulse = PULSE_BACKEND (backend); + + if (pulse->priv->ext_streams_list == NULL) { + pulse->priv->ext_streams_list = g_hash_table_get_values (pulse->priv->ext_streams); + if (pulse->priv->ext_streams_list != NULL) + g_list_foreach (pulse->priv->ext_streams_list, (GFunc) g_object_ref, NULL); + } + return pulse->priv->ext_streams_list; } static gboolean @@ -562,29 +575,13 @@ pulse_backend_set_default_input_stream (MateMixerBackend *backend, if (pulse_connection_set_default_source (pulse->priv->connection, name) == FALSE) return FALSE; - if (pulse->priv->default_source != NULL) - g_object_unref (pulse->priv->default_source); - - pulse->priv->default_source = g_object_ref (stream); - /* We might be in the process of setting a default source for which the details * are not yet known, make sure the change does not happen */ - g_object_set_data (G_OBJECT (pulse), - "backend-pending-source", - NULL); - - g_object_notify (G_OBJECT (pulse), "default-input"); + PULSE_SET_PENDING_SOURCE_NULL (pulse); + PULSE_SET_DEFAULT_SOURCE (pulse, stream); return TRUE; } -static MateMixerStream * -pulse_backend_get_default_output_stream (MateMixerBackend *backend) -{ - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - - return PULSE_BACKEND (backend)->priv->default_sink; -} - static gboolean pulse_backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *stream) @@ -601,18 +598,10 @@ pulse_backend_set_default_output_stream (MateMixerBackend *backend, if (pulse_connection_set_default_sink (pulse->priv->connection, name) == FALSE) return FALSE; - if (pulse->priv->default_sink != NULL) - g_object_unref (pulse->priv->default_sink); - - pulse->priv->default_sink = g_object_ref (stream); - /* We might be in the process of setting a default sink for which the details * are not yet known, make sure the change does not happen */ - g_object_set_data (G_OBJECT (pulse), - "backend-pending-sink", - NULL); - - g_object_notify (G_OBJECT (pulse), "default-output"); + PULSE_SET_PENDING_SINK_NULL (pulse); + PULSE_SET_DEFAULT_SINK (pulse, stream); return TRUE; } @@ -633,41 +622,41 @@ on_connection_state_notify (PulseConnection *connection, * Stream callbacks will unmark available streams and remaining * unavailable streams will be removed when the CONNECTED state * is reached. */ - mark_hanging (pulse); - change_state (pulse, MATE_MIXER_STATE_CONNECTING); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_CONNECTING); - if (pulse->priv->connect_source == NULL && - pulse_connection_connect (connection, TRUE) == FALSE) { - pulse->priv->connect_source = g_timeout_source_new (200); + if G_UNLIKELY (pulse->priv->connect_tag != 0) + break; - g_source_set_callback (pulse->priv->connect_source, + if (pulse_connection_connect (connection, TRUE) == FALSE) { + GSource *source; + + source = g_timeout_source_new (200); + g_source_set_callback (source, (GSourceFunc) connect_source_reconnect, pulse, - (GDestroyNotify) connect_source_remove); + NULL); + pulse->priv->connect_tag = + g_source_attach (source, g_main_context_get_thread_default ()); - g_source_attach (pulse->priv->connect_source, - g_main_context_get_thread_default ()); + g_source_unref (source); } break; } /* First connection attempt has failed */ - change_state (pulse, MATE_MIXER_STATE_FAILED); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_FAILED); break; case PULSE_CONNECTION_CONNECTING: case PULSE_CONNECTION_AUTHORIZING: case PULSE_CONNECTION_LOADING: - change_state (pulse, MATE_MIXER_STATE_CONNECTING); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_CONNECTING); break; case PULSE_CONNECTION_CONNECTED: - if (pulse->priv->connected_once == TRUE) - remove_hanging (pulse); - else - pulse->priv->connected_once = TRUE; + pulse->priv->connected_once = TRUE; - change_state (pulse, MATE_MIXER_STATE_READY); + PULSE_CHANGE_STATE (pulse, MATE_MIXER_STATE_READY); break; } } @@ -677,18 +666,17 @@ on_connection_server_info (PulseConnection *connection, const pa_server_info *info, PulseBackend *pulse) { - const gchar *name_source = NULL; - const gchar *name_sink = NULL; + MateMixerStream *stream; + const gchar *name_source = NULL; + const gchar *name_sink = NULL; - if (pulse->priv->default_source != NULL) - name_source = mate_mixer_stream_get_name (pulse->priv->default_source); + stream = PULSE_GET_DEFAULT_SOURCE (pulse); + if (stream != NULL) + name_source = mate_mixer_stream_get_name (stream); if (g_strcmp0 (name_source, info->default_source_name) != 0) { - if (pulse->priv->default_source != NULL) - g_clear_object (&pulse->priv->default_source); - if (info->default_source_name != NULL) { - MateMixerStream *stream = g_hash_table_find (pulse->priv->streams, + MateMixerStream *stream = g_hash_table_find (pulse->priv->sources, compare_stream_names, (gpointer) info->default_source_name); @@ -698,20 +686,14 @@ on_connection_server_info (PulseConnection *connection, * When this happens, remember the name of the stream and wait for the * stream info callback. */ if (stream != NULL) { - pulse->priv->default_source = g_object_ref (stream); - g_object_set_data (G_OBJECT (pulse), - "backend-pending-source", - NULL); - - g_debug ("Default input stream changed to %s", info->default_source_name); + PULSE_SET_DEFAULT_SOURCE (pulse, stream); + PULSE_SET_PENDING_SOURCE_NULL (pulse); } else { g_debug ("Default input stream changed to unknown stream %s", info->default_source_name); - g_object_set_data_full (G_OBJECT (pulse), - "backend-pending-source", - g_strdup (info->default_source_name), - g_free); + PULSE_SET_PENDING_SOURCE (pulse, info->default_source_name); + PULSE_SET_DEFAULT_SOURCE (pulse, NULL); /* In most cases (for example changing profile) the stream info * arrives by itself, but do not rely on it and request it explicitely. @@ -721,20 +703,16 @@ on_connection_server_info (PulseConnection *connection, info->default_source_name); } } else - g_debug ("Default input stream unset"); - - g_object_notify (G_OBJECT (pulse), "default-input"); + PULSE_SET_DEFAULT_SOURCE (pulse, NULL); } - if (pulse->priv->default_sink != NULL) - name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink); + stream = PULSE_GET_DEFAULT_SINK (pulse); + if (stream != NULL) + name_sink = mate_mixer_stream_get_name (stream); if (g_strcmp0 (name_sink, info->default_sink_name) != 0) { - if (pulse->priv->default_sink != NULL) - g_clear_object (&pulse->priv->default_sink); - if (info->default_sink_name != NULL) { - MateMixerStream *stream = g_hash_table_find (pulse->priv->streams, + MateMixerStream *stream = g_hash_table_find (pulse->priv->sinks, compare_stream_names, (gpointer) info->default_sink_name); @@ -744,21 +722,14 @@ on_connection_server_info (PulseConnection *connection, * When this happens, remember the name of the stream and wait for the * stream info callback. */ if (stream != NULL) { - pulse->priv->default_sink = g_object_ref (stream); - g_object_set_data (G_OBJECT (pulse), - "backend-pending-sink", - NULL); - - g_debug ("Default output stream changed to %s", info->default_sink_name); - + PULSE_SET_DEFAULT_SINK (pulse, stream); + PULSE_SET_PENDING_SINK_NULL (pulse); } else { g_debug ("Default output stream changed to unknown stream %s", info->default_sink_name); - g_object_set_data_full (G_OBJECT (pulse), - "backend-pending-sink", - g_strdup (info->default_sink_name), - g_free); + PULSE_SET_PENDING_SINK (pulse, info->default_sink_name); + PULSE_SET_DEFAULT_SINK (pulse, NULL); /* In most cases (for example changing profile) the stream info * arrives by itself, but do not rely on it and request it explicitely. @@ -768,12 +739,10 @@ on_connection_server_info (PulseConnection *connection, info->default_sink_name); } } else - g_debug ("Default output stream unset"); - - g_object_notify (G_OBJECT (pulse), "default-output"); + PULSE_SET_DEFAULT_SINK (pulse, NULL); } - if (pulse->priv->state != MATE_MIXER_STATE_READY) + if (mate_mixer_backend_get_state (MATE_MIXER_BACKEND (pulse)) != MATE_MIXER_STATE_READY) g_debug ("Sound server is %s version %s, running on %s", info->server_name, info->server_version, @@ -790,21 +759,17 @@ on_connection_card_info (PulseConnection *connection, device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->index)); if (device == NULL) { device = pulse_device_new (connection, info); - if (G_UNLIKELY (device == NULL)) - return; - g_hash_table_insert (pulse->priv->devices, GUINT_TO_POINTER (info->index), device); + g_hash_table_insert (pulse->priv->devices, + GUINT_TO_POINTER (info->index), + device); + free_list_devices (pulse); g_signal_emit_by_name (G_OBJECT (pulse), "device-added", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); - } else { + } else pulse_device_update (device, info); - - /* The object might be hanging if reconnecting is in progress, remove the - * hanging flag to prevent it from being removed when connected */ - unmark_hanging (pulse, G_OBJECT (device)); - } } static void @@ -813,29 +778,22 @@ on_connection_card_removed (PulseConnection *connection, PulseBackend *pulse) { PulseDevice *device; + gchar *name; device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (index)); - if (G_UNLIKELY (device == NULL)) + if G_UNLIKELY (device == NULL) return; - remove_device (pulse, device); -} + name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); -/* PulseAudio uses 32-bit integers as indices for sinks, sink inputs, sources and - * source inputs, these indices are not unique among the different kinds of streams, - * but we want to keep all of them in a single hash table. Allow this by using 64-bit - * hash table keys, use the lower 32 bits for the PulseAudio index and some of the - * higher bits to indicate what kind of stream it is. */ -enum { - HASH_BIT_SINK = (1ULL << 63), - HASH_BIT_SINK_INPUT = (1ULL << 62), - HASH_BIT_SOURCE = (1ULL << 61), - HASH_BIT_SOURCE_OUTPUT = (1ULL << 60) -}; -#define HASH_ID_SINK(idx) (((idx) & 0xffffffff) | HASH_BIT_SINK) -#define HASH_ID_SINK_INPUT(idx) (((idx) & 0xffffffff) | HASH_BIT_SINK_INPUT) -#define HASH_ID_SOURCE(idx) (((idx) & 0xffffffff) | HASH_BIT_SOURCE) -#define HASH_ID_SOURCE_OUTPUT(idx) (((idx) & 0xffffffff) | HASH_BIT_SOURCE_OUTPUT) + g_hash_table_remove (pulse->priv->devices, GUINT_TO_POINTER (index)); + + free_list_devices (pulse); + g_signal_emit_by_name (G_OBJECT (pulse), + "device-removed", + name); + g_free (name); +} static void on_connection_sink_info (PulseConnection *connection, @@ -844,32 +802,36 @@ on_connection_sink_info (PulseConnection *connection, { PulseDevice *device = NULL; PulseStream *stream; - gint64 index = HASH_ID_SINK (info->index); if (info->card != PA_INVALID_INDEX) - device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->card)); + device = g_hash_table_lookup (pulse->priv->devices, + GUINT_TO_POINTER (info->card)); - stream = g_hash_table_lookup (pulse->priv->streams, &index); + stream = g_hash_table_lookup (pulse->priv->sinks, GUINT_TO_POINTER (info->index)); if (stream == NULL) { - stream = pulse_sink_new (connection, info, device); - if (G_UNLIKELY (stream == NULL)) - return; + stream = PULSE_STREAM (pulse_sink_new (connection, info, device)); - g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + free_list_streams (pulse); + g_hash_table_insert (pulse->priv->sinks, + GUINT_TO_POINTER (info->index), + stream); - g_signal_emit_by_name (G_OBJECT (pulse), - "stream-added", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + if (device != NULL) { + pulse_device_add_stream (device, stream); + } else { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + /* Only emit when not a part of the device, otherwise emitted by + * the main library */ + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + name); + } /* We might be waiting for this sink to set it as the default */ check_pending_sink (pulse, stream); - } else { - pulse_sink_update (stream, info, device); - - /* The object might be hanging if reconnecting is in progress, remove the - * hanging flag to prevent it from being removed when connected */ - unmark_hanging (pulse, G_OBJECT (stream)); - } + } else + pulse_sink_update (PULSE_SINK (stream), info); } static void @@ -878,13 +840,38 @@ on_connection_sink_removed (PulseConnection *connection, PulseBackend *pulse) { PulseStream *stream; - gint64 index = HASH_ID_SINK (idx); + PulseDevice *device; - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (G_UNLIKELY (stream == NULL)) + stream = g_hash_table_lookup (pulse->priv->sinks, GUINT_TO_POINTER (idx)); + if G_UNLIKELY (stream == NULL) return; - remove_stream (pulse, stream); + g_object_ref (stream); + + free_list_streams (pulse); + g_hash_table_remove (pulse->priv->sinks, GUINT_TO_POINTER (idx)); + + device = pulse_stream_get_device (stream); + if (device != NULL) { + pulse_device_remove_stream (device, stream); + } else { + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } + + /* The removed stream might be one of the default streams, this happens + * especially when switching profiles, after which PulseAudio removes the + * old streams and creates new ones with different names */ + if (MATE_MIXER_STREAM (stream) == PULSE_GET_DEFAULT_SINK (pulse)) { + PULSE_SET_DEFAULT_SINK (pulse, NULL); + + /* PulseAudio usually sends a server info update by itself when default + * stream changes, but there is at least one case when it does not - setting + * a card profile to off, so to be sure request an update explicitely */ + pulse_connection_load_server_info (pulse->priv->connection); + } + g_object_unref (stream); } static void @@ -892,40 +879,20 @@ on_connection_sink_input_info (PulseConnection *connection, const pa_sink_input_info *info, PulseBackend *pulse) { - PulseStream *stream; - PulseStream *parent = NULL; - gint64 index; + PulseSink *sink; - if (G_LIKELY (info->sink != PA_INVALID_INDEX)) { - index = HASH_ID_SINK (info->sink); - - parent = g_hash_table_lookup (pulse->priv->streams, &index); - if (G_UNLIKELY (parent == NULL)) - g_debug ("Unknown parent %d of PulseAudio sink input %s", - info->sink, - info->name); - } - - index = HASH_ID_SINK_INPUT (info->index); - - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (stream == NULL) { - stream = pulse_sink_input_new (connection, info, parent); - if (G_UNLIKELY (stream == NULL)) - return; + if G_UNLIKELY (info->sink == PA_INVALID_INDEX) + return; - g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + sink = g_hash_table_lookup (pulse->priv->sinks, GUINT_TO_POINTER (info->sink)); + if G_UNLIKELY (sink == NULL) + return; - g_signal_emit_by_name (G_OBJECT (pulse), - "stream-added", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); - } else { - pulse_sink_input_update (stream, info, parent); + pulse_sink_add_input (sink, info); - /* The object might be hanging if reconnecting is in progress, remove the - * hanging flag to prevent it from being removed when connected */ - unmark_hanging (pulse, G_OBJECT (stream)); - } + g_hash_table_insert (pulse->priv->sink_inputs, + GUINT_TO_POINTER (info->index), + g_object_ref (sink)); } static void @@ -933,14 +900,13 @@ on_connection_sink_input_removed (PulseConnection *connection, guint idx, PulseBackend *pulse) { - PulseStream *stream; - gint64 index = HASH_ID_SINK_INPUT (idx); + PulseSink *sink; - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (G_UNLIKELY (stream == NULL)) + sink = g_hash_table_lookup (pulse->priv->sink_inputs, GUINT_TO_POINTER (idx)); + if G_UNLIKELY (sink == NULL) return; - remove_stream (pulse, stream); + pulse_sink_remove_input (sink, idx); } static void @@ -950,36 +916,40 @@ on_connection_source_info (PulseConnection *connection, { PulseDevice *device = NULL; PulseStream *stream; - gint64 index = HASH_ID_SOURCE (info->index); /* Skip monitor streams */ if (info->monitor_of_sink != PA_INVALID_INDEX) return; if (info->card != PA_INVALID_INDEX) - device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->card)); + device = g_hash_table_lookup (pulse->priv->devices, + GUINT_TO_POINTER (info->card)); - stream = g_hash_table_lookup (pulse->priv->streams, &index); + stream = g_hash_table_lookup (pulse->priv->sources, GUINT_TO_POINTER (info->index)); if (stream == NULL) { - stream = pulse_source_new (connection, info, device); - if (G_UNLIKELY (stream == NULL)) - return; + stream = PULSE_STREAM (pulse_source_new (connection, info, device)); - g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + free_list_streams (pulse); + g_hash_table_insert (pulse->priv->sources, + GUINT_TO_POINTER (info->index), + stream); - g_signal_emit_by_name (G_OBJECT (pulse), - "stream-added", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + if (device != NULL) { + pulse_device_add_stream (device, stream); + } else { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + /* Only emit when not a part of the device, otherwise emitted by + * the main library */ + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + name); + } /* We might be waiting for this source to set it as the default */ check_pending_source (pulse, stream); - } else { - pulse_source_update (stream, info, device); - - /* The object might be hanging if reconnecting is in progress, remove the - * hanging flag to prevent it from being removed when connected */ - unmark_hanging (pulse, G_OBJECT (stream)); - } + } else + pulse_source_update (PULSE_SOURCE (stream), info); } static void @@ -987,14 +957,39 @@ on_connection_source_removed (PulseConnection *connection, guint idx, PulseBackend *pulse) { + PulseDevice *device; PulseStream *stream; - gint64 index = HASH_ID_SOURCE (idx); - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (G_UNLIKELY (stream == NULL)) + stream = g_hash_table_lookup (pulse->priv->sources, GUINT_TO_POINTER (idx)); + if G_UNLIKELY (stream == NULL) return; - remove_stream (pulse, stream); + g_object_ref (stream); + + free_list_streams (pulse); + g_hash_table_remove (pulse->priv->sources, GUINT_TO_POINTER (idx)); + + device = pulse_stream_get_device (stream); + if (device != NULL) { + pulse_device_remove_stream (device, stream); + } else { + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } + + /* The removed stream might be one of the default streams, this happens + * especially when switching profiles, after which PulseAudio removes the + * old streams and creates new ones with different names */ + if (MATE_MIXER_STREAM (stream) == PULSE_GET_DEFAULT_SOURCE (pulse)) { + PULSE_SET_DEFAULT_SOURCE (pulse, NULL); + + /* PulseAudio usually sends a server info update by itself when default + * stream changes, but there is at least one case when it does not - setting + * a card profile to off, so to be sure request an update explicitely */ + pulse_connection_load_server_info (pulse->priv->connection); + } + g_object_unref (stream); } static void @@ -1002,39 +997,20 @@ on_connection_source_output_info (PulseConnection *connection, const pa_source_output_info *info, PulseBackend *pulse) { - PulseStream *stream; - PulseStream *parent = NULL; - gint64 index; - - if (G_LIKELY (info->source != PA_INVALID_INDEX)) { - index = HASH_ID_SOURCE (info->source); - - /* Most likely a monitor source that we have skipped */ - parent = g_hash_table_lookup (pulse->priv->streams, &index); - if (parent == NULL) - return; - } + PulseSource *source; - index = HASH_ID_SOURCE_OUTPUT (info->index); - - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (stream == NULL) { - stream = pulse_source_output_new (connection, info, parent); - if (G_UNLIKELY (stream == NULL)) - return; + if G_UNLIKELY (info->source == PA_INVALID_INDEX) + return; - g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + source = g_hash_table_lookup (pulse->priv->sources, GUINT_TO_POINTER (info->source)); + if G_UNLIKELY (source == NULL) + return; - g_signal_emit_by_name (G_OBJECT (pulse), - "stream-added", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); - } else { - pulse_source_output_update (stream, info, parent); + pulse_source_add_output (source, info); - /* The object might be hanging if reconnecting is in progress, remove the - * hanging flag to prevent it from being removed when connected */ - unmark_hanging (pulse, G_OBJECT (stream)); - } + g_hash_table_insert (pulse->priv->source_outputs, + GUINT_TO_POINTER (info->index), + g_object_ref (source)); } static void @@ -1042,14 +1018,13 @@ on_connection_source_output_removed (PulseConnection *connection, guint idx, PulseBackend *pulse) { - PulseStream *stream; - gint64 index = HASH_ID_SOURCE_OUTPUT (idx); + PulseSource *source; - stream = g_hash_table_lookup (pulse->priv->streams, &index); - if (G_UNLIKELY (stream == NULL)) + source = g_hash_table_lookup (pulse->priv->source_outputs, GUINT_TO_POINTER (idx)); + if G_UNLIKELY (source == NULL) return; - remove_stream (pulse, stream); + pulse_source_remove_output (source, idx); } static void @@ -1057,63 +1032,75 @@ on_connection_ext_stream_info (PulseConnection *connection, const pa_ext_stream_restore_info *info, PulseBackend *pulse) { - PulseStream *stream; - PulseStream *parent = NULL; + PulseExtStream *ext; + PulseStream *parent = NULL; - if (G_LIKELY (info->device != NULL)) - parent = g_hash_table_find (pulse->priv->streams, compare_stream_names, + if (info->device != NULL) { + parent = g_hash_table_find (pulse->priv->sinks, compare_stream_names, (gpointer) info->device); - stream = g_hash_table_lookup (pulse->priv->ext_streams, info->name); - if (stream == NULL) { - stream = pulse_ext_stream_new (connection, info, parent); - if (G_UNLIKELY (stream == NULL)) - return; + if (parent == NULL) + parent = g_hash_table_find (pulse->priv->sources, compare_stream_names, + (gpointer) info->device); + } + + ext = g_hash_table_lookup (pulse->priv->ext_streams, info->name); + if (ext == NULL) { + ext = pulse_ext_stream_new (connection, info, parent); - g_hash_table_insert (pulse->priv->ext_streams, g_strdup (info->name), stream); + free_list_ext_streams (pulse); + g_hash_table_insert (pulse->priv->ext_streams, + g_strdup (info->name), + ext); g_signal_emit_by_name (G_OBJECT (pulse), - "cached-stream-added", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + "stored-control-added", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (ext))); } else { - pulse_ext_stream_update (stream, info, parent); + pulse_ext_stream_update (ext, info, parent); /* The object might be hanging if ext-streams are being loaded, remove * the hanging flag to prevent it from being removed */ - unmark_hanging (pulse, G_OBJECT (stream)); + PULSE_UNSET_HANGING (ext); } } static void on_connection_ext_stream_loading (PulseConnection *connection, PulseBackend *pulse) { - mark_hanging_hash (pulse->priv->ext_streams); + GHashTableIter iter; + gpointer ext; + + g_hash_table_iter_init (&iter, pulse->priv->ext_streams); + + while (g_hash_table_iter_next (&iter, NULL, &ext) == TRUE) + PULSE_SET_HANGING (ext); } static void on_connection_ext_stream_loaded (PulseConnection *connection, PulseBackend *pulse) { GHashTableIter iter; - gpointer value; + gpointer name; + gpointer ext; g_hash_table_iter_init (&iter, pulse->priv->ext_streams); - while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { - guint hanging = - GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); + while (g_hash_table_iter_next (&iter, &name, &ext) == TRUE) { + if (PULSE_GET_HANGING (ext) == FALSE) + continue; - if (hanging == 1) { - gchar *name = g_strdup ((const gchar *) value); + free_list_ext_streams (pulse); + g_hash_table_remove (pulse->priv->ext_streams, (gconstpointer) name); - g_hash_table_remove (pulse->priv->ext_streams, (gconstpointer) name); - g_signal_emit_by_name (G_OBJECT (pulse), - "cached-stream-removed", - name); - g_free (name); - } + g_signal_emit_by_name (G_OBJECT (pulse), + "stored-control-removed", + name); } + g_debug ("Ext-streams refreshed"); } +// XXX rename static gboolean connect_source_reconnect (PulseBackend *pulse) { @@ -1121,16 +1108,10 @@ connect_source_reconnect (PulseBackend *pulse) * and wait for the connection state notifications, otherwise this function * will be called again */ if (pulse_connection_connect (pulse->priv->connection, TRUE) == TRUE) { - connect_source_remove (pulse); - return FALSE; + pulse->priv->connect_tag = 0; + return G_SOURCE_REMOVE; } - return TRUE; -} - -static void -connect_source_remove (PulseBackend *pulse) -{ - g_clear_pointer (&pulse->priv->connect_source, g_source_unref); + return G_SOURCE_CONTINUE; } static void @@ -1141,7 +1122,7 @@ check_pending_sink (PulseBackend *pulse, PulseStream *stream) /* See if the currently added sream matches the default input stream * we are waiting for */ - pending = g_object_get_data (G_OBJECT (pulse), "backend-pending-sink"); + pending = PULSE_GET_PENDING_SINK (pulse); if (pending == NULL) return; @@ -1149,14 +1130,10 @@ check_pending_sink (PulseBackend *pulse, PulseStream *stream) if (g_strcmp0 (pending, name) != 0) return; - pulse->priv->default_sink = g_object_ref (stream); - g_object_set_data (G_OBJECT (pulse), - "backend-pending-sink", - NULL); + g_debug ("Setting default output stream to pending stream %s", name); - g_debug ("Default output stream changed to pending stream %s", name); - - g_object_notify (G_OBJECT (pulse), "default-output"); + PULSE_SET_PENDING_SINK_NULL (pulse); + PULSE_SET_DEFAULT_SINK (pulse, stream); } static void @@ -1167,7 +1144,7 @@ check_pending_source (PulseBackend *pulse, PulseStream *stream) /* See if the currently added sream matches the default input stream * we are waiting for */ - pending = g_object_get_data (G_OBJECT (pulse), "backend-pending-source"); + pending = PULSE_GET_PENDING_SOURCE (pulse); if (pending == NULL) return; @@ -1175,160 +1152,43 @@ check_pending_source (PulseBackend *pulse, PulseStream *stream) if (g_strcmp0 (pending, name) != 0) return; - pulse->priv->default_source = g_object_ref (stream); - g_object_set_data (G_OBJECT (pulse), - "backend-pending-source", - NULL); - - g_debug ("Default input stream changed to pending stream %s", name); + g_debug ("Setting default input stream to pending stream %s", name); - g_object_notify (G_OBJECT (pulse), "default-input"); + PULSE_SET_PENDING_SOURCE_NULL (pulse); + PULSE_SET_DEFAULT_SOURCE (pulse, stream); } static void -mark_hanging (PulseBackend *pulse) +free_list_devices (PulseBackend *pulse) { - /* Mark devices and streams as hanging, ext-streams are handled separately */ - mark_hanging_hash (pulse->priv->devices); - mark_hanging_hash (pulse->priv->streams); -} - -static void -mark_hanging_hash (GHashTable *hash) -{ - GHashTableIter iter; - gpointer value; - - g_hash_table_iter_init (&iter, hash); - - while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) - g_object_set_data (G_OBJECT (value), "backend-hanging", GUINT_TO_POINTER (1)); -} - -static void -unmark_hanging (PulseBackend *pulse, GObject *object) -{ - if (pulse->priv->connected_once == FALSE) - return; - if (pulse->priv->state == MATE_MIXER_STATE_READY) + if (pulse->priv->devices_list == NULL) return; - g_object_steal_data (object, "backend-hanging"); -} - -static void -remove_hanging (PulseBackend *pulse) -{ - GHashTableIter iter; - gpointer value; - - g_hash_table_iter_init (&iter, pulse->priv->devices); - - while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { - guint hanging = - GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); - - if (hanging == 1) - remove_device (pulse, PULSE_DEVICE (value)); - } - - g_hash_table_iter_init (&iter, pulse->priv->streams); - - while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { - guint hanging = - GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); + g_list_free_full (pulse->priv->devices_list, g_object_unref); - if (hanging == 1) - remove_stream (pulse, PULSE_STREAM (value)); - } -} - -static void -remove_device (PulseBackend *pulse, PulseDevice *device) -{ - gchar *name; - - name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); - - g_hash_table_remove (pulse->priv->devices, - GUINT_TO_POINTER (pulse_device_get_index (device))); - - g_signal_emit_by_name (G_OBJECT (pulse), "device-removed", name); - g_free (name); + pulse->priv->devices_list = NULL; } static void -remove_stream (PulseBackend *pulse, PulseStream *stream) +free_list_streams (PulseBackend *pulse) { - gchar *name; - guint32 idx; - gint64 index; - gboolean reload = FALSE; - - /* The removed stream might be one of the default streams, this happens - * especially when switching profiles, after which PulseAudio removes the - * old streams and creates new ones with different names */ - if (MATE_MIXER_STREAM (stream) == pulse->priv->default_sink) { - g_clear_object (&pulse->priv->default_sink); - - g_object_notify (G_OBJECT (pulse), "default-output"); - reload = TRUE; - } - else if (MATE_MIXER_STREAM (stream) == pulse->priv->default_source) { - g_clear_object (&pulse->priv->default_source); - - g_object_notify (G_OBJECT (pulse), "default-input"); - reload = TRUE; - } - - idx = pulse_stream_get_index (stream); - - if (PULSE_IS_SINK (stream)) - index = HASH_ID_SINK (idx); - else if (PULSE_IS_SINK_INPUT (stream)) - index = HASH_ID_SINK_INPUT (idx); - else if (PULSE_IS_SOURCE (stream)) - index = HASH_ID_SOURCE (idx); - else - index = HASH_ID_SOURCE_OUTPUT (idx); - - name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); - - g_hash_table_remove (pulse->priv->streams, &index); + if (pulse->priv->streams_list == NULL) + return; - /* PulseAudio usually sends a server info update by itself when default - * stream changes, but there is at least one case when it does not - setting - * a card profile to off, so to be sure request an update explicitely */ - if (reload == TRUE) - pulse_connection_load_server_info (pulse->priv->connection); + g_list_free_full (pulse->priv->streams_list, g_object_unref); - g_signal_emit_by_name (G_OBJECT (pulse), "stream-removed", name); - g_free (name); + pulse->priv->streams_list = NULL; } static void -change_state (PulseBackend *backend, MateMixerState state) +free_list_ext_streams (PulseBackend *pulse) { - if (backend->priv->state == state) + if (pulse->priv->ext_streams_list == NULL) return; - backend->priv->state = state; - - g_object_notify (G_OBJECT (backend), "state"); -} + g_list_free_full (pulse->priv->ext_streams_list, g_object_unref); -static gint -compare_devices (gconstpointer a, gconstpointer b) -{ - return strcmp (mate_mixer_device_get_name (MATE_MIXER_DEVICE (a)), - mate_mixer_device_get_name (MATE_MIXER_DEVICE (b))); -} - -static gint -compare_streams (gconstpointer a, gconstpointer b) -{ - return strcmp (mate_mixer_stream_get_name (MATE_MIXER_STREAM (a)), - mate_mixer_stream_get_name (MATE_MIXER_STREAM (b))); + pulse->priv->ext_streams_list = NULL; } static gboolean @@ -1336,5 +1196,5 @@ compare_stream_names (gpointer key, gpointer value, gpointer user_data) { MateMixerStream *stream = MATE_MIXER_STREAM (value); - return !strcmp (mate_mixer_stream_get_name (stream), (const gchar *) user_data); + return strcmp (mate_mixer_stream_get_name (stream), (const gchar *) user_data) == 0; } diff --git a/backends/pulse/pulse-backend.h b/backends/pulse/pulse-backend.h index bd1face..2c4b8a8 100644 --- a/backends/pulse/pulse-backend.h +++ b/backends/pulse/pulse-backend.h @@ -20,8 +20,10 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> -#include <libmatemixer/matemixer-backend-module.h> +#include "pulse-types.h" #define PULSE_TYPE_BACKEND \ (pulse_backend_get_type ()) @@ -36,13 +38,12 @@ #define PULSE_BACKEND_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_BACKEND, PulseBackendClass)) -typedef struct _PulseBackend PulseBackend; typedef struct _PulseBackendClass PulseBackendClass; typedef struct _PulseBackendPrivate PulseBackendPrivate; struct _PulseBackend { - GObject parent; + MateMixerBackend parent; /*< private >*/ PulseBackendPrivate *priv; @@ -50,7 +51,7 @@ struct _PulseBackend struct _PulseBackendClass { - GObjectClass parent_class; + MateMixerBackendClass parent_class; }; GType pulse_backend_get_type (void) G_GNUC_CONST; diff --git a/backends/pulse/pulse-client-stream.c b/backends/pulse/pulse-client-stream.c deleted file mode 100644 index b99c498..0000000 --- a/backends/pulse/pulse-client-stream.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2014 Michal Ratajsky <[email protected]> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#include <string.h> -#include <glib.h> -#include <glib-object.h> - -#include <libmatemixer/matemixer-client-stream.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-stream.h> - -#include <pulse/pulseaudio.h> - -#include "pulse-client-stream.h" -#include "pulse-sink.h" -#include "pulse-source.h" -#include "pulse-stream.h" - -struct _PulseClientStreamPrivate -{ - gchar *app_name; - gchar *app_id; - gchar *app_version; - gchar *app_icon; - MateMixerStream *parent; - MateMixerClientStreamFlags flags; - MateMixerClientStreamRole role; -}; - -enum { - PROP_0, - PROP_CLIENT_FLAGS, - PROP_ROLE, - PROP_PARENT, - PROP_APP_NAME, - PROP_APP_ID, - PROP_APP_VERSION, - PROP_APP_ICON -}; - -enum { - REMOVED, - N_SIGNALS -}; - -static guint signals[N_SIGNALS] = { 0, }; - -static void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface); - -static void pulse_client_stream_class_init (PulseClientStreamClass *klass); - -static void pulse_client_stream_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec); - -static void pulse_client_stream_init (PulseClientStream *client); -static void pulse_client_stream_dispose (GObject *object); -static void pulse_client_stream_finalize (GObject *object); - -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseClientStream, pulse_client_stream, PULSE_TYPE_STREAM, - G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_CLIENT_STREAM, - mate_mixer_client_stream_interface_init)) - -static MateMixerClientStreamFlags pulse_client_stream_get_flags (MateMixerClientStream *client); -static MateMixerClientStreamRole pulse_client_stream_get_role (MateMixerClientStream *client); - -static MateMixerStream * pulse_client_stream_get_parent (MateMixerClientStream *client); -static gboolean pulse_client_stream_set_parent (MateMixerClientStream *client, - MateMixerStream *parent); - -static gboolean pulse_client_stream_remove (MateMixerClientStream *client); - -static const gchar * pulse_client_stream_get_app_name (MateMixerClientStream *client); -static const gchar * pulse_client_stream_get_app_id (MateMixerClientStream *client); -static const gchar * pulse_client_stream_get_app_version (MateMixerClientStream *client); -static const gchar * pulse_client_stream_get_app_icon (MateMixerClientStream *client); - -static void -mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface) -{ - iface->get_flags = pulse_client_stream_get_flags; - iface->get_role = pulse_client_stream_get_role; - iface->get_parent = pulse_client_stream_get_parent; - iface->set_parent = pulse_client_stream_set_parent; - iface->remove = pulse_client_stream_remove; - iface->get_app_name = pulse_client_stream_get_app_name; - iface->get_app_id = pulse_client_stream_get_app_id; - iface->get_app_version = pulse_client_stream_get_app_version; - iface->get_app_icon = pulse_client_stream_get_app_icon; -} - -static void -pulse_client_stream_class_init (PulseClientStreamClass *klass) -{ - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_client_stream_dispose; - object_class->finalize = pulse_client_stream_finalize; - object_class->get_property = pulse_client_stream_get_property; - - signals[REMOVED] = - g_signal_new ("removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseClientStreamClass, removed), - NULL, - NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0, - G_TYPE_NONE); - - g_object_class_override_property (object_class, PROP_CLIENT_FLAGS, "client-flags"); - g_object_class_override_property (object_class, PROP_ROLE, "role"); - g_object_class_override_property (object_class, PROP_PARENT, "parent"); - g_object_class_override_property (object_class, PROP_APP_NAME, "app-name"); - g_object_class_override_property (object_class, PROP_APP_ID, "app-id"); - g_object_class_override_property (object_class, PROP_APP_VERSION, "app-version"); - g_object_class_override_property (object_class, PROP_APP_ICON, "app-icon"); - - g_type_class_add_private (object_class, sizeof (PulseClientStreamPrivate)); -} - -static void -pulse_client_stream_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) -{ - PulseClientStream *client; - - client = PULSE_CLIENT_STREAM (object); - - switch (param_id) { - case PROP_CLIENT_FLAGS: - g_value_set_flags (value, client->priv->flags); - break; - case PROP_ROLE: - g_value_set_enum (value, client->priv->role); - break; - case PROP_PARENT: - g_value_set_object (value, client->priv->parent); - break; - case PROP_APP_NAME: - g_value_set_string (value, client->priv->app_name); - break; - case PROP_APP_ID: - g_value_set_string (value, client->priv->app_id); - break; - case PROP_APP_VERSION: - g_value_set_string (value, client->priv->app_version); - break; - case PROP_APP_ICON: - g_value_set_string (value, client->priv->app_icon); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - break; - } -} - -static void -pulse_client_stream_init (PulseClientStream *client) -{ - client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client, - PULSE_TYPE_CLIENT_STREAM, - PulseClientStreamPrivate); -} - -static void -pulse_client_stream_dispose (GObject *object) -{ - PulseClientStream *client; - - client = PULSE_CLIENT_STREAM (object); - - g_clear_object (&client->priv->parent); - - G_OBJECT_CLASS (pulse_client_stream_parent_class)->dispose (object); -} - -static void -pulse_client_stream_finalize (GObject *object) -{ - PulseClientStream *client; - - client = PULSE_CLIENT_STREAM (object); - - g_free (client->priv->app_name); - g_free (client->priv->app_id); - g_free (client->priv->app_version); - g_free (client->priv->app_icon); - - G_OBJECT_CLASS (pulse_client_stream_parent_class)->finalize (object); -} - -gboolean -pulse_client_stream_update_flags (PulseClientStream *pclient, - MateMixerClientStreamFlags flags) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (pclient->priv->flags != flags) { - pclient->priv->flags = flags; - - g_object_notify (G_OBJECT (pclient), "client-flags"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_parent (PulseClientStream *pclient, MateMixerStream *parent) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (pclient->priv->parent != parent) { - g_clear_object (&pclient->priv->parent); - - if (G_LIKELY (parent != NULL)) - pclient->priv->parent = g_object_ref (parent); - - g_object_notify (G_OBJECT (pclient), "parent"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_role (PulseClientStream *pclient, - MateMixerClientStreamRole role) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (pclient->priv->role != role) { - pclient->priv->role = role; - - g_object_notify (G_OBJECT (pclient), "role"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_app_name (PulseClientStream *pclient, const gchar *app_name) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (g_strcmp0 (pclient->priv->app_name, app_name) != 0) { - g_free (pclient->priv->app_name); - pclient->priv->app_name = g_strdup (app_name); - - g_object_notify (G_OBJECT (pclient), "app-name"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_app_id (PulseClientStream *pclient, const gchar *app_id) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (g_strcmp0 (pclient->priv->app_id, app_id) != 0) { - g_free (pclient->priv->app_id); - pclient->priv->app_id = g_strdup (app_id); - - g_object_notify (G_OBJECT (pclient), "app-id"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_app_version (PulseClientStream *pclient, const gchar *app_version) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (g_strcmp0 (pclient->priv->app_version, app_version) != 0) { - g_free (pclient->priv->app_version); - pclient->priv->app_version = g_strdup (app_version); - - g_object_notify (G_OBJECT (pclient), "app-version"); - } - return TRUE; -} - -gboolean -pulse_client_stream_update_app_icon (PulseClientStream *pclient, const gchar *app_icon) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - - if (g_strcmp0 (pclient->priv->app_icon, app_icon) != 0) { - g_free (pclient->priv->app_icon); - pclient->priv->app_icon = g_strdup (app_icon); - - g_object_notify (G_OBJECT (pclient), "app-icon"); - } - return TRUE; -} - -static MateMixerClientStreamFlags -pulse_client_stream_get_flags (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), MATE_MIXER_CLIENT_STREAM_NO_FLAGS); - - return PULSE_CLIENT_STREAM (client)->priv->flags; -} - -static MateMixerClientStreamRole -pulse_client_stream_get_role (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), MATE_MIXER_CLIENT_STREAM_ROLE_NONE); - - return PULSE_CLIENT_STREAM (client)->priv->role; -} - -static MateMixerStream * -pulse_client_stream_get_parent (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); - - return PULSE_CLIENT_STREAM (client)->priv->parent; -} - -static gboolean -pulse_client_stream_set_parent (MateMixerClientStream *client, MateMixerStream *parent) -{ - MateMixerStreamFlags flags; - PulseClientStream *pclient; - PulseClientStreamClass *klass; - - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); - g_return_val_if_fail (PULSE_IS_STREAM (parent), FALSE); - - pclient = PULSE_CLIENT_STREAM (client); - klass = PULSE_CLIENT_STREAM_GET_CLASS (pclient); - - if (pclient->priv->parent == parent) - return TRUE; - - flags = mate_mixer_stream_get_flags (MATE_MIXER_STREAM (pclient)); - - /* Validate the parent stream */ - if (flags & MATE_MIXER_STREAM_INPUT && !PULSE_IS_SOURCE (parent)) { - g_warning ("Could not change stream parent to %s: not a parent input stream", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); - return FALSE; - } else if (flags & MATE_MIXER_STREAM_OUTPUT && !PULSE_IS_SINK (parent)) { - g_warning ("Could not change stream parent to %s: not a parent output stream", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); - return FALSE; - } - - /* Set the parent */ - if (klass->set_parent (pclient, PULSE_STREAM (parent)) == FALSE) - return FALSE; - - if (pclient->priv->parent != NULL) - g_object_unref (pclient->priv->parent); - - /* It is allowed for the parent to be NULL when the instance is created, but - * changing the parent requires a valid parent stream */ - pclient->priv->parent = g_object_ref (parent); - - g_object_notify (G_OBJECT (client), "parent"); - return TRUE; -} - -static gboolean -pulse_client_stream_remove (MateMixerClientStream *client) -{ - PulseClientStream *pclient; - PulseClientStreamClass *klass; - - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); - - pclient = PULSE_CLIENT_STREAM (client); - klass = PULSE_CLIENT_STREAM_GET_CLASS (pclient); - - if (klass->remove (pclient) == FALSE) - return FALSE; - - // XXX handle this in the backend - g_signal_emit (G_OBJECT (client), - signals[REMOVED], - 0); - - return TRUE; -} - -static const gchar * -pulse_client_stream_get_app_name (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); - - return PULSE_CLIENT_STREAM (client)->priv->app_name; -} - -static const gchar * -pulse_client_stream_get_app_id (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); - - return PULSE_CLIENT_STREAM (client)->priv->app_id; -} - -static const gchar * -pulse_client_stream_get_app_version (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); - - return PULSE_CLIENT_STREAM (client)->priv->app_version; -} - -static const gchar * -pulse_client_stream_get_app_icon (MateMixerClientStream *client) -{ - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); - - return PULSE_CLIENT_STREAM (client)->priv->app_icon; -} diff --git a/backends/pulse/pulse-client-stream.h b/backends/pulse/pulse-client-stream.h deleted file mode 100644 index fe24dc3..0000000 --- a/backends/pulse/pulse-client-stream.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2014 Michal Ratajsky <[email protected]> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#ifndef PULSE_CLIENT_STREAM_H -#define PULSE_CLIENT_STREAM_H - -#include <glib.h> -#include <glib-object.h> - -#include <libmatemixer/matemixer-client-stream.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-stream.h> - -#include "pulse-stream.h" - -G_BEGIN_DECLS - -#define PULSE_TYPE_CLIENT_STREAM \ - (pulse_client_stream_get_type ()) -#define PULSE_CLIENT_STREAM(o) \ - (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_CLIENT_STREAM, PulseClientStream)) -#define PULSE_IS_CLIENT_STREAM(o) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_CLIENT_STREAM)) -#define PULSE_CLIENT_STREAM_CLASS(k) \ - (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_CLIENT_STREAM, PulseClientStreamClass)) -#define PULSE_IS_CLIENT_STREAM_CLASS(k) \ - (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_CLIENT_STREAM)) -#define PULSE_CLIENT_STREAM_GET_CLASS(o) \ - (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_CLIENT_STREAM, PulseClientStreamClass)) - -typedef struct _PulseClientStream PulseClientStream; -typedef struct _PulseClientStreamClass PulseClientStreamClass; -typedef struct _PulseClientStreamPrivate PulseClientStreamPrivate; - -struct _PulseClientStream -{ - PulseStream parent; - - /*< private >*/ - PulseClientStreamPrivate *priv; -}; - -struct _PulseClientStreamClass -{ - PulseStreamClass parent_class; - - /*< private >*/ - /* Virtual table */ - gboolean (*set_parent) (PulseClientStream *pclient, - PulseStream *pstream); - - gboolean (*remove) (PulseClientStream *pclient); - - /* Signals */ - void (*removed) (PulseClientStream *pclient); -}; - -GType pulse_client_stream_get_type (void) G_GNUC_CONST; - -gboolean pulse_client_stream_update_flags (PulseClientStream *pclient, - MateMixerClientStreamFlags flags); - -gboolean pulse_client_stream_update_role (PulseClientStream *pclient, - MateMixerClientStreamRole role); - -gboolean pulse_client_stream_update_parent (PulseClientStream *pclient, - MateMixerStream *parent); - -gboolean pulse_client_stream_update_app_name (PulseClientStream *pclient, - const gchar *app_name); -gboolean pulse_client_stream_update_app_id (PulseClientStream *pclient, - const gchar *app_id); -gboolean pulse_client_stream_update_app_version (PulseClientStream *pclient, - const gchar *app_version); -gboolean pulse_client_stream_update_app_icon (PulseClientStream *pclient, - const gchar *app_icon); - -G_END_DECLS - -#endif /* PULSE_CLIENT_STREAM_H */ diff --git a/backends/pulse/pulse-connection.c b/backends/pulse/pulse-connection.c index cc39caf..7344d2e 100644 --- a/backends/pulse/pulse-connection.c +++ b/backends/pulse/pulse-connection.c @@ -31,14 +31,14 @@ struct _PulseConnectionPrivate { - gchar *server; - guint outstanding; - pa_context *context; - pa_proplist *proplist; - pa_glib_mainloop *mainloop; - gboolean ext_streams_loading; - gboolean ext_streams_dirty; - PulseConnectionState state; + gchar *server; + guint outstanding; + pa_context *context; + pa_proplist *proplist; + pa_glib_mainloop *mainloop; + gboolean ext_streams_loading; + gboolean ext_streams_dirty; + PulseConnectionState state; }; enum { @@ -417,7 +417,7 @@ pulse_connection_new (const gchar *app_name, PulseConnection *connection; mainloop = pa_glib_mainloop_new (g_main_context_get_thread_default ()); - if (G_UNLIKELY (mainloop == NULL)) { + if G_UNLIKELY (mainloop == NULL) { g_warning ("Failed to create PulseAudio main loop"); return NULL; } @@ -468,7 +468,7 @@ pulse_connection_connect (PulseConnection *connection, gboolean wait_for_daemon) context = pa_context_new_with_proplist (mainloop, NULL, connection->priv->proplist); - if (G_UNLIKELY (context == NULL)) { + if G_UNLIKELY (context == NULL) { g_warning ("Failed to create PulseAudio context"); return FALSE; } @@ -774,7 +774,6 @@ pulse_connection_create_monitor (PulseConnection *connection, return pulse_monitor_new (connection->priv->context, connection->priv->proplist, - NULL, index_source, index_sink_input); } @@ -1227,7 +1226,7 @@ load_lists (PulseConnection *connection) GSList *ops = NULL; pa_operation *op; - if (G_UNLIKELY (connection->priv->outstanding > 0)) { + if G_UNLIKELY (connection->priv->outstanding > 0) { g_warn_if_reached (); return FALSE; } @@ -1235,7 +1234,7 @@ load_lists (PulseConnection *connection) op = pa_context_get_card_info_list (connection->priv->context, pulse_card_info_cb, connection); - if (G_UNLIKELY (op == NULL)) + if G_UNLIKELY (op == NULL) goto error; ops = g_slist_prepend (ops, op); @@ -1243,7 +1242,7 @@ load_lists (PulseConnection *connection) op = pa_context_get_sink_info_list (connection->priv->context, pulse_sink_info_cb, connection); - if (G_UNLIKELY (op == NULL)) + if G_UNLIKELY (op == NULL) goto error; ops = g_slist_prepend (ops, op); @@ -1251,7 +1250,7 @@ load_lists (PulseConnection *connection) op = pa_context_get_sink_input_info_list (connection->priv->context, pulse_sink_input_info_cb, connection); - if (G_UNLIKELY (op == NULL)) + if G_UNLIKELY (op == NULL) goto error; ops = g_slist_prepend (ops, op); @@ -1259,7 +1258,7 @@ load_lists (PulseConnection *connection) op = pa_context_get_source_info_list (connection->priv->context, pulse_source_info_cb, connection); - if (G_UNLIKELY (op == NULL)) + if G_UNLIKELY (op == NULL) goto error; ops = g_slist_prepend (ops, op); @@ -1267,7 +1266,7 @@ load_lists (PulseConnection *connection) op = pa_context_get_source_output_info_list (connection->priv->context, pulse_source_output_info_cb, connection); - if (G_UNLIKELY (op == NULL)) + if G_UNLIKELY (op == NULL) goto error; ops = g_slist_prepend (ops, op); @@ -1303,7 +1302,7 @@ load_list_finished (PulseConnection *connection) * as the final step in the connection process */ connection->priv->outstanding--; - if (G_UNLIKELY (connection->priv->outstanding < 0)) { + if G_UNLIKELY (connection->priv->outstanding < 0) { g_warn_if_reached (); connection->priv->outstanding = 0; } @@ -1311,7 +1310,7 @@ load_list_finished (PulseConnection *connection) if (connection->priv->outstanding == 0) { gboolean ret = pulse_connection_load_server_info (connection); - if (G_UNLIKELY (ret == FALSE)) { + if G_UNLIKELY (ret == FALSE) { pulse_connection_disconnect (connection); return FALSE; } @@ -1640,7 +1639,7 @@ change_state (PulseConnection *connection, PulseConnectionState state) static gboolean process_pulse_operation (PulseConnection *connection, pa_operation *op) { - if (G_UNLIKELY (op == NULL)) { + if G_UNLIKELY (op == NULL) { g_warning ("PulseAudio operation failed: %s", pa_strerror (pa_context_errno (connection->priv->context))); return FALSE; diff --git a/backends/pulse/pulse-connection.h b/backends/pulse/pulse-connection.h index b9119fd..7dc0bfb 100644 --- a/backends/pulse/pulse-connection.h +++ b/backends/pulse/pulse-connection.h @@ -25,7 +25,7 @@ #include <pulse/ext-stream-restore.h> #include "pulse-enums.h" -#include "pulse-monitor.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -42,7 +42,6 @@ G_BEGIN_DECLS #define PULSE_CONNECTION_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_CONNECTION, PulseConnectionClass)) -typedef struct _PulseConnection PulseConnection; typedef struct _PulseConnectionClass PulseConnectionClass; typedef struct _PulseConnectionPrivate PulseConnectionPrivate; @@ -59,7 +58,6 @@ struct _PulseConnectionClass GObjectClass parent_class; /*< private >*/ - /* Signals */ void (*server_info) (PulseConnection *connection, const pa_server_info *info); @@ -201,7 +199,6 @@ gboolean pulse_connection_kill_source_output (PulseConnection gboolean pulse_connection_write_ext_stream (PulseConnection *connection, const pa_ext_stream_restore_info *info); - gboolean pulse_connection_delete_ext_stream (PulseConnection *connection, const gchar *name); diff --git a/backends/pulse/pulse-device-profile.c b/backends/pulse/pulse-device-profile.c new file mode 100644 index 0000000..5487841 --- /dev/null +++ b/backends/pulse/pulse-device-profile.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-device.h" +#include "pulse-device-profile.h" + +struct _PulseDeviceProfilePrivate +{ + guint priority; +}; + +static void pulse_device_profile_class_init (PulseDeviceProfileClass *klass); +static void pulse_device_profile_init (PulseDeviceProfile *profile); + +G_DEFINE_TYPE (PulseDeviceProfile, pulse_device_profile, MATE_MIXER_TYPE_SWITCH_OPTION) + +static void +pulse_device_profile_class_init (PulseDeviceProfileClass *klass) +{ + g_type_class_add_private (klass, sizeof (PulseDeviceProfilePrivate)); +} + +static void +pulse_device_profile_init (PulseDeviceProfile *profile) +{ + profile->priv = G_TYPE_INSTANCE_GET_PRIVATE (profile, + PULSE_TYPE_DEVICE_PROFILE, + PulseDeviceProfilePrivate); +} + +PulseDeviceProfile * +pulse_device_profile_new (const gchar *name, + const gchar *label, + guint priority) +{ + PulseDeviceProfile *profile; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + + profile = g_object_new (PULSE_TYPE_DEVICE_PROFILE, + "name", name, + "label", label, + NULL); + + profile->priv->priority = priority; + return profile; +} + +const gchar * +pulse_device_profile_get_name (PulseDeviceProfile *profile) +{ + g_return_val_if_fail (PULSE_IS_DEVICE_PROFILE (profile), NULL); + + return mate_mixer_switch_option_get_name (MATE_MIXER_SWITCH_OPTION (profile)); +} + +guint +pulse_device_profile_get_priority (PulseDeviceProfile *profile) +{ + g_return_val_if_fail (PULSE_IS_DEVICE_PROFILE (profile), 0); + + return profile->priv->priority; +} diff --git a/backends/pulse/pulse-device-profile.h b/backends/pulse/pulse-device-profile.h new file mode 100644 index 0000000..0a9c3f4 --- /dev/null +++ b/backends/pulse/pulse-device-profile.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_DEVICE_PROFILE_H +#define PULSE_DEVICE_PROFILE_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_DEVICE_PROFILE \ + (pulse_device_profile_get_type ()) +#define PULSE_DEVICE_PROFILE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_DEVICE_PROFILE, PulseDeviceProfile)) +#define PULSE_IS_DEVICE_PROFILE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_DEVICE_PROFILE)) +#define PULSE_DEVICE_PROFILE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_DEVICE_PROFILE, PulseDeviceProfileClass)) +#define PULSE_IS_DEVICE_PROFILE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_DEVICE_PROFILE)) +#define PULSE_DEVICE_PROFILE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_IS_DEVICE_PROFILE, PulseDeviceProfileClass)) + +typedef struct _PulseDeviceProfileClass PulseDeviceProfileClass; +typedef struct _PulseDeviceProfilePrivate PulseDeviceProfilePrivate; + +struct _PulseDeviceProfile +{ + MateMixerSwitchOption parent; + + /*< private >*/ + PulseDeviceProfilePrivate *priv; +}; + +struct _PulseDeviceProfileClass +{ + MateMixerSwitchOptionClass parent; +}; + +GType pulse_device_profile_get_type (void) G_GNUC_CONST; + +PulseDeviceProfile *pulse_device_profile_new (const gchar *name, + const gchar *label, + guint priority); + +const gchar * pulse_device_profile_get_name (PulseDeviceProfile *profile); +guint pulse_device_profile_get_priority (PulseDeviceProfile *profile); + +G_END_DECLS + +#endif /* PULSE_DEVICE_PROFILE_H */ diff --git a/backends/pulse/pulse-device-switch.c b/backends/pulse/pulse-device-switch.c new file mode 100644 index 0000000..7a43d0a --- /dev/null +++ b/backends/pulse/pulse-device-switch.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "pulse-connection.h" +#include "pulse-device.h" +#include "pulse-device-profile.h" +#include "pulse-device-switch.h" + +struct _PulseDeviceSwitchPrivate +{ + GList *profiles; + PulseDevice *device; +}; + +enum { + PROP_0, + PROP_DEVICE, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +static void pulse_device_switch_class_init (PulseDeviceSwitchClass *klass); + +static void pulse_device_switch_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_device_switch_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void pulse_device_switch_init (PulseDeviceSwitch *swtch); +static void pulse_device_switch_dispose (GObject *object); + +G_DEFINE_TYPE (PulseDeviceSwitch, pulse_device_switch, MATE_MIXER_TYPE_SWITCH) + +static gboolean pulse_device_switch_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static const GList *pulse_device_switch_list_options (MateMixerSwitch *mms); + +static gint compare_profiles (gconstpointer a, + gconstpointer b); +static gint compare_profile_name (gconstpointer a, + gconstpointer b); + +static void +pulse_device_switch_class_init (PulseDeviceSwitchClass *klass) +{ + GObjectClass *object_class; + MateMixerSwitchClass *switch_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_device_switch_dispose; + object_class->get_property = pulse_device_switch_get_property; + object_class->set_property = pulse_device_switch_set_property; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = pulse_device_switch_set_active_option; + switch_class->list_options = pulse_device_switch_list_options; + + properties[PROP_DEVICE] = + g_param_spec_object ("device", + "Device", + "PulseAudio device", + PULSE_TYPE_DEVICE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (PulseDeviceSwitchPrivate)); +} + +static void +pulse_device_switch_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + PulseDeviceSwitch *swtch; + + swtch = PULSE_DEVICE_SWITCH (object); + + switch (param_id) { + case PROP_DEVICE: + g_value_set_object (value, swtch->priv->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_device_switch_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + PulseDeviceSwitch *swtch; + + swtch = PULSE_DEVICE_SWITCH (object); + + switch (param_id) { + case PROP_DEVICE: + /* Construct-only object */ + swtch->priv->device = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_device_switch_init (PulseDeviceSwitch *swtch) +{ + swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, + PULSE_TYPE_DEVICE_SWITCH, + PulseDeviceSwitchPrivate); +} + +static void +pulse_device_switch_dispose (GObject *object) +{ + PulseDeviceSwitch *swtch; + + swtch = PULSE_DEVICE_SWITCH (object); + + g_clear_object (&swtch->priv->device); + + G_OBJECT_CLASS (pulse_device_switch_parent_class)->dispose (object); +} + +PulseDeviceSwitch * +pulse_device_switch_new (const gchar *name, const gchar *label, PulseDevice *device) +{ + return g_object_new (PULSE_TYPE_DEVICE_SWITCH, + "name", name, + "label", label, + "role", MATE_MIXER_SWITCH_ROLE_DEVICE_PROFILE, + "device", device, + NULL); +} + +PulseDevice * +pulse_device_switch_get_device (PulseDeviceSwitch *swtch) +{ + g_return_val_if_fail (PULSE_IS_DEVICE_SWITCH (swtch), NULL); + + return swtch->priv->device; +} + +void +pulse_device_switch_add_profile (PulseDeviceSwitch *swtch, PulseDeviceProfile *profile) +{ + g_return_if_fail (PULSE_IS_DEVICE_SWITCH (swtch)); + g_return_if_fail (PULSE_IS_DEVICE_PROFILE (profile)); + + swtch->priv->profiles = g_list_insert_sorted (swtch->priv->profiles, + profile, + compare_profiles); +} + +void +pulse_device_switch_set_active_profile (PulseDeviceSwitch *swtch, + PulseDeviceProfile *profile) +{ + g_return_if_fail (PULSE_IS_DEVICE_SWITCH (swtch)); + g_return_if_fail (PULSE_IS_DEVICE_PROFILE (profile)); + + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), + MATE_MIXER_SWITCH_OPTION (profile)); +} + +void +pulse_device_switch_set_active_profile_by_name (PulseDeviceSwitch *swtch, const gchar *name) +{ + GList *item; + + g_return_if_fail (PULSE_IS_DEVICE_SWITCH (swtch)); + g_return_if_fail (name != NULL); + + item = g_list_find_custom (swtch->priv->profiles, name, compare_profile_name); + if G_UNLIKELY (item == NULL) { + g_debug ("Invalid device switch profile name %s", name); + return; + } + pulse_device_switch_set_active_profile (swtch, PULSE_DEVICE_PROFILE (item->data)); +} + +static gboolean +pulse_device_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + PulseDevice *device; + const gchar *device_name; + const gchar *profile_name; + + g_return_val_if_fail (PULSE_IS_DEVICE_SWITCH (mms), FALSE); + g_return_val_if_fail (PULSE_IS_DEVICE_PROFILE (mmso), FALSE); + + device = pulse_device_switch_get_device (PULSE_DEVICE_SWITCH (mms)); + if G_UNLIKELY (device == NULL) + return FALSE; + + device_name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + profile_name = mate_mixer_switch_option_get_name (mmso); + + return pulse_connection_set_card_profile (pulse_device_get_connection (device), + device_name, + profile_name); +} + +static const GList * +pulse_device_switch_list_options (MateMixerSwitch *swtch) +{ + g_return_val_if_fail (PULSE_IS_DEVICE_SWITCH (swtch), NULL); + + return PULSE_DEVICE_SWITCH (swtch)->priv->profiles; +} + +static gint +compare_profiles (gconstpointer a, gconstpointer b) +{ + return pulse_device_profile_get_priority (PULSE_DEVICE_PROFILE (b)) - + pulse_device_profile_get_priority (PULSE_DEVICE_PROFILE (a)); +} + +static gint +compare_profile_name (gconstpointer a, gconstpointer b) +{ + PulseDeviceProfile *profile = PULSE_DEVICE_PROFILE (a); + const gchar *name = (const gchar *) b; + + return strcmp (pulse_device_profile_get_name (profile), name); +} diff --git a/backends/pulse/pulse-device-switch.h b/backends/pulse/pulse-device-switch.h new file mode 100644 index 0000000..50f4a68 --- /dev/null +++ b/backends/pulse/pulse-device-switch.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_DEVICE_SWITCH_H +#define PULSE_DEVICE_SWITCH_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_DEVICE_SWITCH \ + (pulse_device_switch_get_type ()) +#define PULSE_DEVICE_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_DEVICE_SWITCH, PulseDeviceSwitch)) +#define PULSE_IS_DEVICE_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_DEVICE_SWITCH)) +#define PULSE_DEVICE_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_DEVICE_SWITCH, PulseDeviceSwitchClass)) +#define PULSE_IS_DEVICE_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_DEVICE_SWITCH)) +#define PULSE_DEVICE_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_DEVICE_SWITCH, PulseDeviceSwitchClass)) + +typedef struct _PulseDeviceSwitchClass PulseDeviceSwitchClass; +typedef struct _PulseDeviceSwitchPrivate PulseDeviceSwitchPrivate; + +struct _PulseDeviceSwitch +{ + MateMixerSwitch parent; + + /*< private >*/ + PulseDeviceSwitchPrivate *priv; +}; + +struct _PulseDeviceSwitchClass +{ + MateMixerSwitchClass parent_class; +}; + +GType pulse_device_switch_get_type (void) G_GNUC_CONST; + +PulseDeviceSwitch *pulse_device_switch_new (const gchar *name, + const gchar *label, + PulseDevice *device); + +PulseDevice * pulse_device_switch_get_device (PulseDeviceSwitch *swtch); + +void pulse_device_switch_add_profile (PulseDeviceSwitch *swtch, + PulseDeviceProfile *profile); + +void pulse_device_switch_set_active_profile (PulseDeviceSwitch *swtch, + PulseDeviceProfile *profile); + +void pulse_device_switch_set_active_profile_by_name (PulseDeviceSwitch *swtch, + const gchar *name); + +G_END_DECLS + +#endif /* PULSE_DEVICE_SWITCH_H */ diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c index 96e06c8..5403712 100644 --- a/backends/pulse/pulse-device.c +++ b/backends/pulse/pulse-device.c @@ -17,45 +17,40 @@ #include <string.h> #include <glib.h> +#include <glib/gi18n.h> #include <glib-object.h> -#include <libmatemixer/matemixer-device.h> -#include <libmatemixer/matemixer-device-profile.h> -#include <libmatemixer/matemixer-device-profile-private.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-port.h> -#include <libmatemixer/matemixer-port-private.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" #include "pulse-device.h" +#include "pulse-device-profile.h" +#include "pulse-device-switch.h" +#include "pulse-port.h" +#include "pulse-stream.h" struct _PulseDevicePrivate { - guint32 index; - gchar *name; - gchar *description; - gchar *icon; - GHashTable *ports; - GList *ports_list; - GHashTable *profiles; - GList *profiles_list; - PulseConnection *connection; - MateMixerDeviceProfile *profile; + guint32 index; + GHashTable *ports; + GHashTable *streams; + GList *streams_list; + GList *switches_list; + PulseConnection *connection; + PulseDeviceSwitch *pswitch; }; enum { PROP_0, - PROP_NAME, - PROP_DESCRIPTION, - PROP_ICON, - PROP_ACTIVE_PROFILE, PROP_INDEX, - PROP_CONNECTION + PROP_CONNECTION, + N_PROPERTIES }; -static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface); +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; static void pulse_device_class_init (PulseDeviceClass *klass); @@ -72,60 +67,25 @@ static void pulse_device_init (PulseDevice *device); static void pulse_device_dispose (GObject *object); static void pulse_device_finalize (GObject *object); -G_DEFINE_TYPE_WITH_CODE (PulseDevice, pulse_device, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_DEVICE, - mate_mixer_device_interface_init)) +G_DEFINE_TYPE (PulseDevice, pulse_device, MATE_MIXER_TYPE_DEVICE) -#if PA_CHECK_VERSION (5, 0, 0) -typedef pa_card_profile_info2 _pa_card_profile_info; -#else -typedef pa_card_profile_info _pa_card_profile_info; -#endif - -static const gchar * pulse_device_get_name (MateMixerDevice *device); -static const gchar * pulse_device_get_description (MateMixerDevice *device); -static const gchar * pulse_device_get_icon (MateMixerDevice *device); +static MateMixerStream *pulse_device_get_stream (MateMixerDevice *mmd, + const gchar *name); -static MateMixerPort * pulse_device_get_port (MateMixerDevice *device, - const gchar *name); -static MateMixerDeviceProfile *pulse_device_get_profile (MateMixerDevice *device, - const gchar *name); +static const GList * pulse_device_list_streams (MateMixerDevice *mmd); +static const GList * pulse_device_list_switches (MateMixerDevice *mmd); -static const GList * pulse_device_list_ports (MateMixerDevice *device); -static const GList * pulse_device_list_profiles (MateMixerDevice *device); +static void pulse_device_load (PulseDevice *device, + const pa_card_info *info); -static MateMixerDeviceProfile *pulse_device_get_active_profile (MateMixerDevice *device); -static gboolean pulse_device_set_active_profile (MateMixerDevice *device, - MateMixerDeviceProfile *profile); - -static void update_port (PulseDevice *device, - pa_card_port_info *p_info); -static void update_profile (PulseDevice *device, - _pa_card_profile_info *p_info); - -static gint compare_ports (gconstpointer a, - gconstpointer b); -static gint compare_profiles (gconstpointer a, - gconstpointer b); - -static void -mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) -{ - iface->get_name = pulse_device_get_name; - iface->get_description = pulse_device_get_description; - iface->get_icon = pulse_device_get_icon; - iface->get_port = pulse_device_get_port; - iface->get_profile = pulse_device_get_profile; - iface->list_ports = pulse_device_list_ports; - iface->list_profiles = pulse_device_list_profiles; - iface->get_active_profile = pulse_device_get_active_profile; - iface->set_active_profile = pulse_device_set_active_profile; -} +static void free_list_streams (PulseDevice *device); +static void free_list_switches (PulseDevice *device); static void pulse_device_class_init (PulseDeviceClass *klass) { - GObjectClass *object_class; + GObjectClass *object_class; + MateMixerDeviceClass *device_class; object_class = G_OBJECT_CLASS (klass); object_class->dispose = pulse_device_dispose; @@ -133,31 +93,32 @@ pulse_device_class_init (PulseDeviceClass *klass) object_class->get_property = pulse_device_get_property; object_class->set_property = pulse_device_set_property; - g_object_class_install_property (object_class, - PROP_INDEX, - g_param_spec_uint ("index", - "Index", - "Device index", - 0, - G_MAXUINT, - 0, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (object_class, - PROP_CONNECTION, - g_param_spec_object ("connection", - "Connection", - "PulseAudio connection", - PULSE_TYPE_CONNECTION, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); - - g_object_class_override_property (object_class, PROP_NAME, "name"); - g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); - g_object_class_override_property (object_class, PROP_ICON, "icon"); - g_object_class_override_property (object_class, PROP_ACTIVE_PROFILE, "active-profile"); + device_class = MATE_MIXER_DEVICE_CLASS (klass); + device_class->get_stream = pulse_device_get_stream; + device_class->list_streams = pulse_device_list_streams; + device_class->list_switches = pulse_device_list_switches; + + properties[PROP_INDEX] = + g_param_spec_uint ("index", + "Index", + "Index of the device", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_CONNECTION] = + g_param_spec_object ("connection", + "Connection", + "PulseAudio connection", + PULSE_TYPE_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); g_type_class_add_private (object_class, sizeof (PulseDevicePrivate)); } @@ -173,18 +134,6 @@ pulse_device_get_property (GObject *object, device = PULSE_DEVICE (object); switch (param_id) { - case PROP_NAME: - g_value_set_string (value, device->priv->name); - break; - case PROP_DESCRIPTION: - g_value_set_string (value, device->priv->description); - break; - case PROP_ICON: - g_value_set_string (value, device->priv->icon); - break; - case PROP_ACTIVE_PROFILE: - g_value_set_object (value, device->priv->profile); - break; case PROP_INDEX: g_value_set_uint (value, device->priv->index); break; @@ -212,7 +161,6 @@ pulse_device_set_property (GObject *object, device->priv->index = g_value_get_uint (value); break; case PROP_CONNECTION: - /* Construct-only object */ device->priv->connection = g_value_dup_object (value); break; default: @@ -233,10 +181,10 @@ pulse_device_init (PulseDevice *device) g_free, g_object_unref); - device->priv->profiles = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); + device->priv->streams = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); } static void @@ -246,20 +194,14 @@ pulse_device_dispose (GObject *object) device = PULSE_DEVICE (object); - if (device->priv->ports_list != NULL) { - g_list_free_full (device->priv->ports_list, g_object_unref); - device->priv->ports_list = NULL; - } g_hash_table_remove_all (device->priv->ports); + g_hash_table_remove_all (device->priv->streams); - if (device->priv->profiles_list != NULL) { - g_list_free_full (device->priv->profiles_list, g_object_unref); - device->priv->profiles_list = NULL; - } - g_hash_table_remove_all (device->priv->profiles); - - g_clear_object (&device->priv->profile); g_clear_object (&device->priv->connection); + g_clear_object (&device->priv->pswitch); + + free_list_streams (device); + free_list_switches (device); G_OBJECT_CLASS (pulse_device_parent_class)->dispose (object); } @@ -271,12 +213,8 @@ pulse_device_finalize (GObject *object) device = PULSE_DEVICE (object); - g_free (device->priv->name); - g_free (device->priv->description); - g_free (device->priv->icon); - - g_hash_table_destroy (device->priv->ports); - g_hash_table_destroy (device->priv->profiles); + g_hash_table_unref (device->priv->ports); + g_hash_table_unref (device->priv->streams); G_OBJECT_CLASS (pulse_device_parent_class)->finalize (object); } @@ -285,123 +223,86 @@ PulseDevice * pulse_device_new (PulseConnection *connection, const pa_card_info *info) { PulseDevice *device; + const gchar *label; + const gchar *icon; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); g_return_val_if_fail (info != NULL, NULL); + label = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_DESCRIPTION); + if G_UNLIKELY (label == NULL) + label = info->name; + + icon = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_ICON_NAME); + if G_UNLIKELY (icon == NULL) + icon = "audio-card"; + /* Consider the device index as unchanging parameter */ device = g_object_new (PULSE_TYPE_DEVICE, - "connection", connection, "index", info->index, + "connection", connection, + "name", info->name, + "label", label, + "icon", icon, NULL); - /* Other data may change at any time, so let's make a use of our update function */ + pulse_device_load (device, info); pulse_device_update (device, info); return device; } -gboolean +void pulse_device_update (PulseDevice *device, const pa_card_info *info) { - MateMixerDeviceProfile *profile = NULL; - const gchar *prop; - guint32 i; - - g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); - g_return_val_if_fail (info != NULL, FALSE); - - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (device)); - - /* Name */ - if (g_strcmp0 (device->priv->name, info->name) != 0) { - g_free (device->priv->name); - device->priv->name = g_strdup (info->name); - - g_object_notify (G_OBJECT (device), "name"); - } - - /* Description */ - prop = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_DESCRIPTION); - - if (G_UNLIKELY (prop == NULL)) - prop = info->name; - - if (g_strcmp0 (device->priv->description, prop) != 0) { - g_free (device->priv->description); - device->priv->description = g_strdup (prop); - - g_object_notify (G_OBJECT (device), "description"); - } - - /* Icon */ - prop = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_ICON_NAME); - - if (G_UNLIKELY (prop == NULL)) - prop = "audio-card"; - - if (g_strcmp0 (device->priv->icon, prop) != 0) { - g_free (device->priv->icon); - device->priv->icon = g_strdup (prop); - - g_object_notify (G_OBJECT (device), "icon"); - } - -#if PA_CHECK_VERSION (2, 0, 0) - /* List of ports */ - for (i = 0; i < info->n_ports; i++) { - update_port (device, info->ports[i]); - } -#endif + g_return_if_fail (PULSE_IS_DEVICE (device)); + g_return_if_fail (info != NULL); - /* List of profiles */ - for (i = 0; i < info->n_profiles; i++) { #if PA_CHECK_VERSION (5, 0, 0) - pa_card_profile_info2 *p_info = info->profiles2[i]; - - /* PulseAudio 5.0 includes a new pa_card_profile_info2 which only - * differs in the new available flag, we use it not to include profiles - * which are unavailable */ - if (p_info->available == 0) - continue; + if G_LIKELY (info->active_profile2 != NULL) + pulse_device_switch_set_active_profile_by_name (device->priv->pswitch, + info->active_profile2->name); #else - /* The old profile list is an array of structs, not pointers */ - pa_card_profile_info *p_info = &info->profiles[i]; + if G_LIKELY (info->active_profile != NULL) + pulse_device_switch_set_active_profile_by_name (device->priv->pswitch, + info->active_profile->name); #endif - update_profile (device, p_info); - } - - /* Figure out whether the currently active profile has changed */ - profile = NULL; +} -#if PA_CHECK_VERSION (5, 0, 0) - if (G_LIKELY (info->active_profile2 != NULL)) - profile = g_hash_table_lookup (device->priv->profiles, info->active_profile2->name); -#else - if (G_LIKELY (info->active_profile != NULL)) - profile = g_hash_table_lookup (device->priv->profiles, info->active_profile->name); -#endif +void +pulse_device_add_stream (PulseDevice *device, PulseStream *stream) +{ + const gchar *name; - if (profile != device->priv->profile) { - g_clear_object (&device->priv->profile); + g_return_if_fail (PULSE_IS_DEVICE (device)); + g_return_if_fail (PULSE_IS_STREAM (stream)); - if (G_LIKELY (profile != NULL)) - device->priv->profile = g_object_ref (profile); + name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); - g_object_notify (G_OBJECT (device), "active-profile"); - } + free_list_streams (device); - g_object_thaw_notify (G_OBJECT (device)); - return TRUE; + g_hash_table_insert (device->priv->streams, g_strdup (name), stream); + g_signal_emit_by_name (G_OBJECT (device), + "stream-added", + name); } -PulseConnection * -pulse_device_get_connection (PulseDevice *device) +void +pulse_device_remove_stream (PulseDevice *device, PulseStream *stream) { - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); + const gchar *name; - return device->priv->connection; + g_return_if_fail (PULSE_IS_DEVICE (device)); + g_return_if_fail (PULSE_IS_STREAM (stream)); + + name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + + free_list_streams (device); + + g_hash_table_remove (device->priv->streams, name); + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); } guint32 @@ -412,222 +313,130 @@ pulse_device_get_index (PulseDevice *device) return device->priv->index; } -static const gchar * -pulse_device_get_name (MateMixerDevice *device) -{ - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - - return PULSE_DEVICE (device)->priv->name; -} - -static const gchar * -pulse_device_get_description (MateMixerDevice *device) -{ - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - - return PULSE_DEVICE (device)->priv->description; -} - -static const gchar * -pulse_device_get_icon (MateMixerDevice *device) +PulseConnection * +pulse_device_get_connection (PulseDevice *device) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - return PULSE_DEVICE (device)->priv->icon; + return device->priv->connection; } -static MateMixerPort * -pulse_device_get_port (MateMixerDevice *device, const gchar *name) +PulsePort * +pulse_device_get_port (PulseDevice *device, const gchar *name) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - g_return_val_if_fail (name != NULL, NULL); - return g_hash_table_lookup (PULSE_DEVICE (device)->priv->ports, name); + return g_hash_table_lookup (device->priv->ports, name); } -static MateMixerDeviceProfile * -pulse_device_get_profile (MateMixerDevice *device, const gchar *name) +static MateMixerStream * +pulse_device_get_stream (MateMixerDevice *mmd, const gchar *name) { - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); + g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL); g_return_val_if_fail (name != NULL, NULL); - return g_hash_table_lookup (PULSE_DEVICE (device)->priv->profiles, name); + return g_hash_table_lookup (PULSE_DEVICE (mmd)->priv->streams, name); } static const GList * -pulse_device_list_ports (MateMixerDevice *device) +pulse_device_list_streams (MateMixerDevice *mmd) { - PulseDevice *pulse; - - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - - pulse = PULSE_DEVICE (device); + PulseDevice *device; - if (pulse->priv->ports_list == NULL) { - GList *list = g_hash_table_get_values (pulse->priv->ports); + g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL); - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); + device = PULSE_DEVICE (mmd); - pulse->priv->ports_list = g_list_sort (list, compare_ports); - } + if (device->priv->streams_list == NULL) { + device->priv->streams_list = g_hash_table_get_values (device->priv->streams); + if (device->priv->streams_list != NULL) + g_list_foreach (device->priv->streams_list, (GFunc) g_object_ref, NULL); } - - return (const GList *) pulse->priv->ports_list; + return device->priv->streams_list; } static const GList * -pulse_device_list_profiles (MateMixerDevice *device) +pulse_device_list_switches (MateMixerDevice *mmd) { - PulseDevice *pulse; - - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - - pulse = PULSE_DEVICE (device); - - if (pulse->priv->profiles_list == NULL) { - GList *list = g_hash_table_get_values (pulse->priv->profiles); - - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - pulse->priv->profiles_list = g_list_sort (list, compare_profiles); - } - } + g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL); - return (const GList *) pulse->priv->profiles_list; + return PULSE_DEVICE (mmd)->priv->switches_list; } -static MateMixerDeviceProfile * -pulse_device_get_active_profile (MateMixerDevice *device) +static void +pulse_device_load (PulseDevice *device, const pa_card_info *info) { - g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); + guint i; - return PULSE_DEVICE (device)->priv->profile; -} +#if PA_CHECK_VERSION (2, 0, 0) + for (i = 0; i < info->n_ports; i++) { + PulsePort *port; + const gchar *name; + const gchar *icon; -static gboolean -pulse_device_set_active_profile (MateMixerDevice *device, MateMixerDeviceProfile *profile) -{ - PulseDevice *pulse; - const gchar *name; - gboolean ret; + name = info->ports[i]->name; + icon = pa_proplist_gets (info->ports[i]->proplist, "device.icon_name"); - g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_DEVICE_PROFILE (profile), FALSE); + port = pulse_port_new (name, + info->ports[i]->description, + icon, + info->ports[i]->priority); - pulse = PULSE_DEVICE (device); + g_hash_table_insert (device->priv->ports, g_strdup (name), port); + } +#endif - name = mate_mixer_device_profile_get_name (profile); + /* Create the device profile switch */ + if (info->n_profiles > 0) { + device->priv->pswitch = pulse_device_switch_new ("profile", + _("Profile"), + device); - /* Make sure the profile belongs to the device */ - if (g_hash_table_lookup (pulse->priv->profiles, name) == NULL) { - g_warning ("Profile %s does not belong to device %s", name, pulse->priv->name); - return FALSE; + device->priv->switches_list = g_list_prepend (NULL, g_object_ref (device->priv->pswitch)); } - ret = pulse_connection_set_card_profile (pulse->priv->connection, - pulse->priv->name, - name); - if (ret == TRUE) { - if (pulse->priv->profile != NULL) - g_object_unref (pulse->priv->profile); + for (i = 0; i < info->n_profiles; i++) { + PulseDeviceProfile *profile; + +#if PA_CHECK_VERSION (5, 0, 0) + pa_card_profile_info2 *p_info = info->profiles2[i]; + + /* PulseAudio 5.0 includes a new pa_card_profile_info2 which only + * differs in the new available flag, we use it not to include profiles + * which are unavailable */ + if (p_info->available == 0) + continue; +#else + /* The old profile list is an array of structs, not pointers */ + pa_card_profile_info *p_info = &info->profiles[i]; +#endif - pulse->priv->profile = g_object_ref (profile); + profile = pulse_device_profile_new (p_info->name, + p_info->description, + p_info->priority); - g_object_notify (G_OBJECT (device), "active-profile"); + pulse_device_switch_add_profile (device->priv->pswitch, profile); } - return ret; } static void -update_port (PulseDevice *device, pa_card_port_info *p_info) +free_list_streams (PulseDevice *device) { - MateMixerPort *port; - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - const gchar *icon; - - icon = pa_proplist_gets (p_info->proplist, "device.icon_name"); - - if (p_info->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; - - if (p_info->direction & PA_DIRECTION_INPUT) - flags |= MATE_MIXER_PORT_INPUT; - if (p_info->direction & PA_DIRECTION_OUTPUT) - flags |= MATE_MIXER_PORT_OUTPUT; - - port = g_hash_table_lookup (device->priv->ports, p_info->name); - - if (port != NULL) { - /* Update existing port */ - _mate_mixer_port_update_description (port, p_info->description); - _mate_mixer_port_update_icon (port, icon); - _mate_mixer_port_update_priority (port, p_info->priority); - _mate_mixer_port_update_flags (port, flags); - } else { - /* Add previously unknown port to the hash table */ - port = _mate_mixer_port_new (p_info->name, - p_info->description, - icon, - p_info->priority, - flags); - - g_hash_table_insert (device->priv->ports, g_strdup (p_info->name), port); - } + if (device->priv->streams_list == NULL) + return; + + g_list_free_full (device->priv->streams_list, g_object_unref); + + device->priv->streams_list = NULL; } static void -update_profile (PulseDevice *device, _pa_card_profile_info *p_info) +free_list_switches (PulseDevice *device) { - MateMixerDeviceProfile *profile; - - profile = g_hash_table_lookup (device->priv->profiles, p_info->name); - - if (profile != NULL) { - /* Update existing profile */ - _mate_mixer_device_profile_update_description (profile, p_info->description); - _mate_mixer_device_profile_update_priority (profile, p_info->priority); - _mate_mixer_device_profile_update_num_input_streams (profile, p_info->n_sources); - _mate_mixer_device_profile_update_num_output_streams (profile, p_info->n_sinks); - } else { - /* Add previously unknown profile to the hash table */ - profile = _mate_mixer_device_profile_new (p_info->name, - p_info->description, - p_info->priority, - p_info->n_sources, - p_info->n_sinks); - - g_hash_table_insert (device->priv->profiles, g_strdup (p_info->name), profile); - } -} + if (device->priv->switches_list == NULL) + return; -static gint -compare_ports (gconstpointer a, gconstpointer b) -{ - MateMixerPort *p1 = MATE_MIXER_PORT (a); - MateMixerPort *p2 = MATE_MIXER_PORT (b); - - gint ret = (gint) (mate_mixer_port_get_priority (p2) - - mate_mixer_port_get_priority (p1)); - if (ret != 0) - return ret; - else - return strcmp (mate_mixer_port_get_name (p1), - mate_mixer_port_get_name (p2)); -} + g_list_free_full (device->priv->switches_list, g_object_unref); -static gint -compare_profiles (gconstpointer a, gconstpointer b) -{ - MateMixerDeviceProfile *p1 = MATE_MIXER_DEVICE_PROFILE (a); - MateMixerDeviceProfile *p2 = MATE_MIXER_DEVICE_PROFILE (b); - - gint ret = (gint) (mate_mixer_device_profile_get_priority (p2) - - mate_mixer_device_profile_get_priority (p1)); - if (ret != 0) - return ret; - else - return strcmp (mate_mixer_device_profile_get_name (p1), - mate_mixer_device_profile_get_name (p2)); + device->priv->switches_list = NULL; } diff --git a/backends/pulse/pulse-device.h b/backends/pulse/pulse-device.h index 94c331f..863330f 100644 --- a/backends/pulse/pulse-device.h +++ b/backends/pulse/pulse-device.h @@ -20,33 +20,33 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer.h> #include <pulse/pulseaudio.h> -#include "pulse-connection.h" +#include "pulse-types.h" G_BEGIN_DECLS -#define PULSE_TYPE_DEVICE \ +#define PULSE_TYPE_DEVICE \ (pulse_device_get_type ()) -#define PULSE_DEVICE(o) \ +#define PULSE_DEVICE(o) \ (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_DEVICE, PulseDevice)) -#define PULSE_IS_DEVICE(o) \ +#define PULSE_IS_DEVICE(o) \ (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_DEVICE)) -#define PULSE_DEVICE_CLASS(k) \ +#define PULSE_DEVICE_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_DEVICE, PulseDeviceClass)) -#define PULSE_IS_DEVICE_CLASS(k) \ +#define PULSE_IS_DEVICE_CLASS(k) \ (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_DEVICE)) -#define PULSE_DEVICE_GET_CLASS(o) \ +#define PULSE_DEVICE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_IS_DEVICE, PulseDeviceClass)) -typedef struct _PulseDevice PulseDevice; typedef struct _PulseDeviceClass PulseDeviceClass; typedef struct _PulseDevicePrivate PulseDevicePrivate; struct _PulseDevice { - GObject parent; + MateMixerDevice parent; /*< private >*/ PulseDevicePrivate *priv; @@ -54,20 +54,29 @@ struct _PulseDevice struct _PulseDeviceClass { - GObjectClass parent; + MateMixerDeviceClass parent; }; GType pulse_device_get_type (void) G_GNUC_CONST; -PulseDevice *pulse_device_new (PulseConnection *connection, +PulseDevice * pulse_device_new (PulseConnection *connection, const pa_card_info *info); -gboolean pulse_device_update (PulseDevice *device, +void pulse_device_update (PulseDevice *device, const pa_card_info *info); +void pulse_device_add_stream (PulseDevice *device, + PulseStream *stream); + +void pulse_device_remove_stream (PulseDevice *device, + PulseStream *stream); + guint32 pulse_device_get_index (PulseDevice *device); PulseConnection *pulse_device_get_connection (PulseDevice *device); +PulsePort * pulse_device_get_port (PulseDevice *device, + const gchar *name); + G_END_DECLS #endif /* PULSE_DEVICE_H */ diff --git a/backends/pulse/pulse-ext-stream.c b/backends/pulse/pulse-ext-stream.c index b00e967..3e7490a 100644 --- a/backends/pulse/pulse-ext-stream.c +++ b/backends/pulse/pulse-ext-stream.c @@ -19,252 +19,326 @@ #include <glib.h> #include <glib-object.h> -#include <libmatemixer/matemixer-client-stream.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include <pulse/ext-stream-restore.h> #include "pulse-connection.h" -#include "pulse-client-stream.h" #include "pulse-ext-stream.h" #include "pulse-helpers.h" #include "pulse-stream.h" +#include "pulse-stream-control.h" -static void pulse_ext_stream_class_init (PulseExtStreamClass *klass); -static void pulse_ext_stream_init (PulseExtStream *ext); +struct _PulseExtStreamPrivate +{ + MateMixerAppInfo *app_info; + MateMixerDirection direction; +}; -G_DEFINE_TYPE (PulseExtStream, pulse_ext_stream, PULSE_TYPE_CLIENT_STREAM); +enum { + PROP_0, + PROP_DIRECTION +}; -static void pulse_ext_stream_reload (PulseStream *pstream); +static void mate_mixer_stored_control_interface_init (MateMixerStoredControlInterface *iface); -static gboolean pulse_ext_stream_set_mute (PulseStream *pstream, - gboolean mute); -static gboolean pulse_ext_stream_set_volume (PulseStream *pstream, - pa_cvolume *cvolume); -static gboolean pulse_ext_stream_set_parent (PulseClientStream *pclient, - PulseStream *parent); -static gboolean pulse_ext_stream_remove (PulseClientStream *pclient); +static void pulse_ext_stream_class_init (PulseExtStreamClass *klass); -static void -pulse_ext_stream_class_init (PulseExtStreamClass *klass) -{ - PulseStreamClass *stream_class; - PulseClientStreamClass *client_class; +static void pulse_ext_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_ext_stream_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); - stream_class = PULSE_STREAM_CLASS (klass); +static void pulse_ext_stream_init (PulseExtStream *ext); - stream_class->reload = pulse_ext_stream_reload; - stream_class->set_mute = pulse_ext_stream_set_mute; - stream_class->set_volume = pulse_ext_stream_set_volume; +G_DEFINE_TYPE_WITH_CODE (PulseExtStream, pulse_ext_stream, PULSE_TYPE_STREAM_CONTROL, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STORED_CONTROL, + mate_mixer_stored_control_interface_init)) - client_class = PULSE_CLIENT_STREAM_CLASS (klass); +static MateMixerDirection pulse_ext_stream_get_direction (MateMixerStoredControl *mmsc); - client_class->set_parent = pulse_ext_stream_set_parent; - client_class->remove = pulse_ext_stream_remove; -} +static MateMixerAppInfo * pulse_ext_stream_get_app_info (MateMixerStreamControl *mmsc); + +static gboolean pulse_ext_stream_set_stream (MateMixerStreamControl *mmsc, + MateMixerStream *mms); + +static gboolean pulse_ext_stream_set_mute (PulseStreamControl *control, + gboolean mute); +static gboolean pulse_ext_stream_set_volume (PulseStreamControl *control, + pa_cvolume *cvolume); + +static void fill_ext_stream_restore_info (PulseStreamControl *control, + pa_ext_stream_restore_info *info); static void -pulse_ext_stream_init (PulseExtStream *ext) +mate_mixer_stored_control_interface_init (MateMixerStoredControlInterface *iface) { + iface->get_direction = pulse_ext_stream_get_direction; } -PulseStream * -pulse_ext_stream_new (PulseConnection *connection, - const pa_ext_stream_restore_info *info, - PulseStream *parent) +static void +pulse_ext_stream_class_init (PulseExtStreamClass *klass) { - PulseStream *ext; + GObjectClass *object_class; + MateMixerStreamControlClass *control_class; + PulseStreamControlClass *pulse_class; - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); - g_return_val_if_fail (info != NULL, NULL); + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = pulse_ext_stream_get_property; + object_class->set_property = pulse_ext_stream_set_property; - ext = g_object_new (PULSE_TYPE_EXT_STREAM, - "connection", connection, - NULL); + control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); + control_class->get_app_info = pulse_ext_stream_get_app_info; + control_class->set_stream = pulse_ext_stream_set_stream; - /* Consider the stream name as unchanging parameter */ - pulse_stream_update_name (ext, info->name); + pulse_class = PULSE_STREAM_CONTROL_CLASS (klass); + pulse_class->set_mute = pulse_ext_stream_set_mute; + pulse_class->set_volume = pulse_ext_stream_set_volume; - /* Other data may change at any time, so let's make a use of our update function */ - pulse_ext_stream_update (ext, info, parent); + g_object_class_override_property (object_class, PROP_DIRECTION, "direction"); - return ext; + g_type_class_add_private (object_class, sizeof (PulseExtStreamPrivate)); } -gboolean -pulse_ext_stream_update (PulseStream *pstream, - const pa_ext_stream_restore_info *info, - PulseStream *parent) +static void +pulse_ext_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) { - MateMixerClientStreamRole role = MATE_MIXER_CLIENT_STREAM_ROLE_NONE; - MateMixerStreamFlags flags = MATE_MIXER_STREAM_CLIENT | - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_CAN_SET_VOLUME; - MateMixerClientStreamFlags client_flags = - MATE_MIXER_CLIENT_STREAM_CACHED; + PulseExtStream *ext; - PulseClientStream *pclient; - gchar *suffix; + ext = PULSE_EXT_STREAM (object); - g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); - g_return_val_if_fail (info != NULL, FALSE); + switch (param_id) { + case PROP_DIRECTION: + g_value_set_enum (value, ext->priv->direction); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} - pclient = PULSE_CLIENT_STREAM (pstream); +static void +pulse_ext_stream_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + PulseExtStream *ext; - suffix = strchr (info->name, ':'); - if (suffix != NULL) - suffix++; + ext = PULSE_EXT_STREAM (object); - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (pstream)); + switch (param_id) { + case PROP_DIRECTION: + ext->priv->direction = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_ext_stream_init (PulseExtStream *ext) +{ + ext->priv = G_TYPE_INSTANCE_GET_PRIVATE (ext, + PULSE_TYPE_EXT_STREAM, + PulseExtStreamPrivate); +} + +PulseExtStream * +pulse_ext_stream_new (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseStream *parent) +{ + PulseExtStream *ext; + gchar *suffix; + MateMixerDirection direction; + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_READABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE; + MateMixerStreamControlRole role = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; + MateMixerAppInfo *app_info; + + MateMixerStreamControlMediaRole media_role = MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_UNKNOWN; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); if (g_str_has_prefix (info->name, "sink-input")) - flags |= MATE_MIXER_STREAM_OUTPUT; + direction = MATE_MIXER_DIRECTION_OUTPUT; else if (g_str_has_prefix (info->name, "source-output")) - flags |= MATE_MIXER_STREAM_INPUT; + direction = MATE_MIXER_DIRECTION_INPUT; else - g_debug ("Unknown ext-stream %s", info->name); + direction = MATE_MIXER_DIRECTION_UNKNOWN; + + app_info = _mate_mixer_app_info_new (); + + suffix = strchr (info->name, ':'); + if (suffix != NULL) + suffix++; if (strstr (info->name, "-by-media-role:")) { if (G_LIKELY (suffix != NULL)) - role = pulse_convert_media_role_name (suffix); + media_role = pulse_convert_media_role_name (suffix); } else if (strstr (info->name, "-by-application-name:")) { - client_flags |= MATE_MIXER_CLIENT_STREAM_APPLICATION; + role = MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION; if (G_LIKELY (suffix != NULL)) - pulse_client_stream_update_app_name (pclient, suffix); + _mate_mixer_app_info_set_name (app_info, suffix); } else if (strstr (info->name, "-by-application-id:")) { - client_flags |= MATE_MIXER_CLIENT_STREAM_APPLICATION; + role = MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION; if (G_LIKELY (suffix != NULL)) - pulse_client_stream_update_app_id (pclient, suffix); + _mate_mixer_app_info_set_id (app_info, suffix); } - /* Flags needed before volume */ - pulse_stream_update_flags (pstream, flags); + ext = g_object_new (PULSE_TYPE_EXT_STREAM, + "flags", flags, + "role", role, + "media-role", media_role, + "name", info->name, + "connection", connection, + "direction", direction, + "stream", parent, + NULL); - pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_volume (pstream, &info->volume, 0); + if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION) + ext->priv->app_info = app_info; + else + _mate_mixer_app_info_free (app_info); - pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); + pulse_ext_stream_update (ext, info, parent); + return ext; +} - pulse_client_stream_update_flags (pclient, client_flags); - pulse_client_stream_update_role (pclient, role); +void +pulse_ext_stream_update (PulseExtStream *ext, + const pa_ext_stream_restore_info *info, + PulseStream *parent) +{ + g_return_if_fail (PULSE_IS_EXT_STREAM (ext)); + g_return_if_fail (info != NULL); - if (parent != NULL) - pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); - else - pulse_client_stream_update_parent (pclient, NULL); + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (ext)); + + _mate_mixer_stream_control_set_mute (MATE_MIXER_STREAM_CONTROL (ext), + info->mute ? TRUE : FALSE); - g_object_thaw_notify (G_OBJECT (pstream)); - return TRUE; + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (ext), + &info->channel_map); + + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (ext), + &info->volume, + 0); + + _mate_mixer_stream_control_set_stream (MATE_MIXER_STREAM_CONTROL (ext), + MATE_MIXER_STREAM (parent)); + + g_object_thaw_notify (G_OBJECT (ext)); } -static void -pulse_ext_stream_reload (PulseStream *pstream) +static MateMixerDirection +pulse_ext_stream_get_direction (MateMixerStoredControl *mmsc) { - g_return_if_fail (PULSE_IS_EXT_STREAM (pstream)); + g_return_val_if_fail (PULSE_IS_EXT_STREAM (mmsc), MATE_MIXER_DIRECTION_UNKNOWN); - pulse_connection_load_ext_stream_info (pulse_stream_get_connection (pstream)); + return PULSE_EXT_STREAM (mmsc)->priv->direction; } -static gboolean -pulse_ext_stream_set_mute (PulseStream *pstream, gboolean mute) +static MateMixerAppInfo * +pulse_ext_stream_get_app_info (MateMixerStreamControl *mmsc) { - MateMixerStream *parent; - const pa_channel_map *map; - const pa_cvolume *cvolume; - pa_ext_stream_restore_info info; + g_return_val_if_fail (PULSE_IS_EXT_STREAM (mmsc), NULL); - g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); + return PULSE_EXT_STREAM (mmsc)->priv->app_info; +} - info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); - info.mute = mute; +static gboolean +pulse_ext_stream_set_stream (MateMixerStreamControl *mmsc, MateMixerStream *mms) +{ + pa_ext_stream_restore_info info; - map = pulse_stream_get_channel_map (pstream); - info.channel_map = *map; + g_return_val_if_fail (PULSE_IS_EXT_STREAM (mmsc), FALSE); + g_return_val_if_fail (mms == NULL || PULSE_IS_STREAM (mms), FALSE); - cvolume = pulse_stream_get_cvolume (pstream); - info.volume = *cvolume; + fill_ext_stream_restore_info (PULSE_STREAM_CONTROL (mms), &info); - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); - if (parent != NULL) - info.device = mate_mixer_stream_get_name (parent); + if (mms != NULL) + info.device = mate_mixer_stream_get_name (mms); else info.device = NULL; - return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); + return pulse_connection_write_ext_stream (pulse_stream_control_get_connection (PULSE_STREAM_CONTROL (mmsc)), + &info); } static gboolean -pulse_ext_stream_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +pulse_ext_stream_set_mute (PulseStreamControl *control, gboolean mute) { - MateMixerStream *parent; - const pa_channel_map *map; pa_ext_stream_restore_info info; - g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); - g_return_val_if_fail (cvolume != NULL, FALSE); - - info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); - info.mute = mate_mixer_stream_get_mute (MATE_MIXER_STREAM (pstream)); + g_return_val_if_fail (PULSE_IS_EXT_STREAM (control), FALSE); - map = pulse_stream_get_channel_map (pstream); - info.channel_map = *map; - - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); - if (parent != NULL) - info.device = mate_mixer_stream_get_name (parent); - else - info.device = NULL; + fill_ext_stream_restore_info (control, &info); - info.volume = *cvolume; + info.mute = mute; - return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); + return pulse_connection_write_ext_stream (pulse_stream_control_get_connection (control), + &info); } static gboolean -pulse_ext_stream_set_parent (PulseClientStream *pclient, PulseStream *parent) +pulse_ext_stream_set_volume (PulseStreamControl *control, pa_cvolume *cvolume) { - PulseStream *pstream; - const pa_channel_map *map; - const pa_cvolume *cvolume; pa_ext_stream_restore_info info; - g_return_val_if_fail (PULSE_IS_EXT_STREAM (pclient), FALSE); - g_return_val_if_fail (PULSE_IS_STREAM (parent), FALSE); - - pstream = PULSE_STREAM (pclient); - - info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); - info.mute = mate_mixer_stream_get_mute (MATE_MIXER_STREAM (pstream)); + g_return_val_if_fail (PULSE_IS_EXT_STREAM (control), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - map = pulse_stream_get_channel_map (pstream); - info.channel_map = *map; + fill_ext_stream_restore_info (control, &info); - cvolume = pulse_stream_get_cvolume (pstream); info.volume = *cvolume; - info.device = mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent)); - - return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); + return pulse_connection_write_ext_stream (pulse_stream_control_get_connection (control), + &info); } -static gboolean -pulse_ext_stream_remove (PulseClientStream *pclient) +static void +fill_ext_stream_restore_info (PulseStreamControl *control, + pa_ext_stream_restore_info *info) { - PulseStream *pstream; - const gchar *name; + MateMixerStream *stream; + MateMixerStreamControl *mmsc; + const pa_channel_map *map; + const pa_cvolume *cvolume; + + mmsc = MATE_MIXER_STREAM_CONTROL (control); - g_return_val_if_fail (PULSE_IS_EXT_STREAM (pclient), FALSE); + info->name = mate_mixer_stream_control_get_name (mmsc); + info->mute = mate_mixer_stream_control_get_mute (mmsc); - pstream = PULSE_STREAM (pclient); - name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); + map = pulse_stream_control_get_channel_map (control); + info->channel_map = *map; - return pulse_connection_delete_ext_stream (pulse_stream_get_connection (pstream), name); + cvolume = pulse_stream_control_get_cvolume (control); + info->volume = *cvolume; + + stream = mate_mixer_stream_control_get_stream (mmsc); + if (stream != NULL) + info->device = mate_mixer_stream_get_name (stream); + else + info->device = NULL; } diff --git a/backends/pulse/pulse-ext-stream.h b/backends/pulse/pulse-ext-stream.h index e8dabb6..b667dc7 100644 --- a/backends/pulse/pulse-ext-stream.h +++ b/backends/pulse/pulse-ext-stream.h @@ -24,9 +24,8 @@ #include <pulse/pulseaudio.h> #include <pulse/ext-stream-restore.h> -#include "pulse-client-stream.h" -#include "pulse-connection.h" -#include "pulse-stream.h" +#include "pulse-stream-control.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -43,28 +42,31 @@ G_BEGIN_DECLS #define PULSE_EXT_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_EXT_STREAM, PulseExtStreamClass)) -typedef struct _PulseExtStream PulseExtStream; -typedef struct _PulseExtStreamClass PulseExtStreamClass; +typedef struct _PulseExtStreamClass PulseExtStreamClass; +typedef struct _PulseExtStreamPrivate PulseExtStreamPrivate; struct _PulseExtStream { - PulseClientStream parent; + PulseStreamControl parent; + + /*< private >*/ + PulseExtStreamPrivate *priv; }; struct _PulseExtStreamClass { - PulseClientStreamClass parent_class; + PulseStreamControlClass parent_class; }; -GType pulse_ext_stream_get_type (void) G_GNUC_CONST; +GType pulse_ext_stream_get_type (void) G_GNUC_CONST; -PulseStream *pulse_ext_stream_new (PulseConnection *connection, - const pa_ext_stream_restore_info *info, - PulseStream *parent); +PulseExtStream *pulse_ext_stream_new (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseStream *parent); -gboolean pulse_ext_stream_update (PulseStream *pstream, - const pa_ext_stream_restore_info *info, - PulseStream *parent); +void pulse_ext_stream_update (PulseExtStream *ext, + const pa_ext_stream_restore_info *info, + PulseStream *parent); G_END_DECLS diff --git a/backends/pulse/pulse-helpers.c b/backends/pulse/pulse-helpers.c index 577f2c6..73f8cdb 100644 --- a/backends/pulse/pulse-helpers.c +++ b/backends/pulse/pulse-helpers.c @@ -29,6 +29,7 @@ typedef struct { pa_channel_position_t pa_position; } PositionMap; +// XXX optimize static PositionMap const position_map[] = { { MATE_MIXER_CHANNEL_UNKNOWN, PA_CHANNEL_POSITION_INVALID }, { MATE_MIXER_CHANNEL_MONO, PA_CHANNEL_POSITION_MONO }, @@ -76,42 +77,42 @@ pulse_convert_position_to_pulse (MateMixerChannelPosition position) return PA_CHANNEL_POSITION_INVALID; } -MateMixerClientStreamRole +MateMixerStreamControlMediaRole pulse_convert_media_role_name (const gchar *name) { if (!strcmp (name, "video")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_VIDEO; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_VIDEO; } else if (!strcmp (name, "music")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_MUSIC; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_MUSIC; } else if (!strcmp (name, "game")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_GAME; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_GAME; } else if (!strcmp (name, "event")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_EVENT; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT; } else if (!strcmp (name, "phone")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_PHONE; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_PHONE; } else if (!strcmp (name, "animation")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_ANIMATION; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ANIMATION; } else if (!strcmp (name, "production")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_PRODUCTION; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_PRODUCTION; } else if (!strcmp (name, "a11y")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_A11Y; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_A11Y; } else if (!strcmp (name, "test")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_TEST; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_TEST; } else if (!strcmp (name, "abstract")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_ABSTRACT; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ABSTRACT; } else if (!strcmp (name, "filter")) { - return MATE_MIXER_CLIENT_STREAM_ROLE_FILTER; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_FILTER; } - return MATE_MIXER_CLIENT_STREAM_ROLE_NONE; + return MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_UNKNOWN; } diff --git a/backends/pulse/pulse-helpers.h b/backends/pulse/pulse-helpers.h index 7ccd753..667fc3c 100644 --- a/backends/pulse/pulse-helpers.h +++ b/backends/pulse/pulse-helpers.h @@ -19,17 +19,16 @@ #define PULSE_HELPERS_H #include <glib.h> - -#include <libmatemixer/matemixer-enums.h> +#include <libmatemixer/matemixer.h> #include <pulse/pulseaudio.h> G_BEGIN_DECLS -MateMixerChannelPosition pulse_convert_position_from_pulse (pa_channel_position_t position); -pa_channel_position_t pulse_convert_position_to_pulse (MateMixerChannelPosition position); +MateMixerChannelPosition pulse_convert_position_from_pulse (pa_channel_position_t position); +pa_channel_position_t pulse_convert_position_to_pulse (MateMixerChannelPosition position); -MateMixerClientStreamRole pulse_convert_media_role_name (const gchar *name); +MateMixerStreamControlMediaRole pulse_convert_media_role_name (const gchar *name); G_END_DECLS diff --git a/backends/pulse/pulse-monitor.c b/backends/pulse/pulse-monitor.c index 3d5b4a8..915b71b 100644 --- a/backends/pulse/pulse-monitor.c +++ b/backends/pulse/pulse-monitor.c @@ -16,6 +16,7 @@ */ #include <glib.h> +#include <glib/gi18n.h> #include <glib-object.h> #include <pulse/pulseaudio.h> @@ -27,7 +28,6 @@ struct _PulseMonitorPrivate pa_context *context; pa_proplist *proplist; pa_stream *stream; - gchar *name; guint32 index_source; guint32 index_sink_input; gboolean enabled; @@ -36,7 +36,6 @@ struct _PulseMonitorPrivate enum { PROP_0, PROP_ENABLED, - PROP_NAME, PROP_INDEX_SOURCE, PROP_INDEX_SINK_INPUT, N_PROPERTIES @@ -91,15 +90,6 @@ pulse_monitor_class_init (PulseMonitorClass *klass) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - properties[PROP_NAME] = - g_param_spec_string ("name", - "Name", - "Name of the monitor", - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT | - G_PARAM_STATIC_STRINGS); - properties[PROP_INDEX_SOURCE] = g_param_spec_uint ("index-source", "Index of source", @@ -153,9 +143,6 @@ pulse_monitor_get_property (GObject *object, case PROP_ENABLED: g_value_set_boolean (value, monitor->priv->enabled); break; - case PROP_NAME: - g_value_set_string (value, monitor->priv->name); - break; case PROP_INDEX_SOURCE: g_value_set_uint (value, monitor->priv->index_source); break; @@ -179,9 +166,6 @@ pulse_monitor_set_property (GObject *object, monitor = PULSE_MONITOR (object); switch (param_id) { - case PROP_NAME: - pulse_monitor_set_name (monitor, g_value_get_string (value)); - break; case PROP_INDEX_SOURCE: monitor->priv->index_source = g_value_get_uint (value); break; @@ -218,15 +202,12 @@ pulse_monitor_finalize (GObject *object) pa_context_unref (monitor->priv->context); pa_proplist_free (monitor->priv->proplist); - g_free (monitor->priv->name); - G_OBJECT_CLASS (pulse_monitor_parent_class)->finalize (object); } PulseMonitor * pulse_monitor_new (pa_context *context, pa_proplist *proplist, - const gchar *name, guint32 index_source, guint32 index_sink_input) { @@ -236,7 +217,6 @@ pulse_monitor_new (pa_context *context, g_return_val_if_fail (proplist != NULL, NULL); monitor = g_object_new (PULSE_TYPE_MONITOR, - "name", name, "index-source", index_source, "index-sink-input", index_sink_input, NULL); @@ -280,34 +260,11 @@ pulse_monitor_set_enabled (PulseMonitor *monitor, gboolean enabled) return TRUE; } -const gchar * -pulse_monitor_get_name (PulseMonitor *monitor) -{ - g_return_val_if_fail (PULSE_IS_MONITOR (monitor), NULL); - - return monitor->priv->name; -} - -gboolean -pulse_monitor_set_name (PulseMonitor *monitor, const gchar *name) -{ - g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); - - if (g_strcmp0 (name, monitor->priv->name) != 0) { - g_free (monitor->priv->name); - monitor->priv->name = g_strdup (name); - - g_object_notify_by_pspec (G_OBJECT (monitor), properties[PROP_NAME]); - } - return TRUE; -} - static gboolean stream_connect (PulseMonitor *monitor) { pa_sample_spec spec; pa_buffer_attr attr; - const gchar *name; gchar *idx; int ret; @@ -320,14 +277,9 @@ stream_connect (PulseMonitor *monitor) spec.format = PA_SAMPLE_FLOAT32; spec.rate = 25; - if (monitor->priv->name != NULL) - name = monitor->priv->name; - else - name = "Peak detect"; - monitor->priv->stream = pa_stream_new_with_proplist (monitor->priv->context, - name, + _("Peak detect"), &spec, NULL, monitor->priv->proplist); diff --git a/backends/pulse/pulse-monitor.h b/backends/pulse/pulse-monitor.h index 41147f5..e371ec3 100644 --- a/backends/pulse/pulse-monitor.h +++ b/backends/pulse/pulse-monitor.h @@ -23,6 +23,8 @@ #include <pulse/pulseaudio.h> +#include "pulse-types.h" + G_BEGIN_DECLS #define PULSE_TYPE_MONITOR \ @@ -38,7 +40,6 @@ G_BEGIN_DECLS #define PULSE_MONITOR_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_MONITOR, PulseMonitorClass)) -typedef struct _PulseMonitor PulseMonitor; typedef struct _PulseMonitorClass PulseMonitorClass; typedef struct _PulseMonitorPrivate PulseMonitorPrivate; @@ -54,7 +55,7 @@ struct _PulseMonitorClass { GObjectClass parent_class; - /* Signals */ + /*< private >*/ void (*value) (PulseMonitor *monitor, gdouble value); }; @@ -63,7 +64,6 @@ GType pulse_monitor_get_type (void) G_GNUC_CONST; PulseMonitor *pulse_monitor_new (pa_context *context, pa_proplist *proplist, - const gchar *name, guint32 index_source, guint32 index_sink_input); @@ -71,10 +71,6 @@ gboolean pulse_monitor_get_enabled (PulseMonitor *monitor); gboolean pulse_monitor_set_enabled (PulseMonitor *monitor, gboolean enabled); -const gchar * pulse_monitor_get_name (PulseMonitor *monitor); -gboolean pulse_monitor_set_name (PulseMonitor *monitor, - const gchar *name); - G_END_DECLS #endif /* PULSE_MONITOR_H */ diff --git a/backends/pulse/pulse-port-switch.c b/backends/pulse/pulse-port-switch.c new file mode 100644 index 0000000..08f1543 --- /dev/null +++ b/backends/pulse/pulse-port-switch.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "pulse-connection.h" +#include "pulse-port.h" +#include "pulse-port-switch.h" +#include "pulse-stream.h" + +struct _PulsePortSwitchPrivate +{ + GList *ports; + PulseStream *stream; +}; + +enum { + PROP_0, + PROP_STREAM, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +static void pulse_port_switch_class_init (PulsePortSwitchClass *klass); + +static void pulse_port_switch_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_port_switch_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void pulse_port_switch_init (PulsePortSwitch *swtch); +static void pulse_port_switch_dispose (GObject *object); + +G_DEFINE_ABSTRACT_TYPE (PulsePortSwitch, pulse_port_switch, MATE_MIXER_TYPE_SWITCH) + +static gboolean pulse_port_switch_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static const GList *pulse_port_switch_list_options (MateMixerSwitch *mms); + +static gint compare_ports (gconstpointer a, + gconstpointer b); +static gint compare_port_name (gconstpointer a, + gconstpointer b); + +static void +pulse_port_switch_class_init (PulsePortSwitchClass *klass) +{ + GObjectClass *object_class; + MateMixerSwitchClass *switch_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_port_switch_dispose; + object_class->get_property = pulse_port_switch_get_property; + object_class->set_property = pulse_port_switch_set_property; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = pulse_port_switch_set_active_option; + switch_class->list_options = pulse_port_switch_list_options; + + properties[PROP_STREAM] = + g_param_spec_object ("stream", + "Stream", + "PulseAudio stream", + PULSE_TYPE_STREAM, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (PulsePortSwitchPrivate)); +} + +static void +pulse_port_switch_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + PulsePortSwitch *swtch; + + swtch = PULSE_PORT_SWITCH (object); + + switch (param_id) { + case PROP_STREAM: + g_value_set_object (value, swtch->priv->stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_port_switch_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + PulsePortSwitch *swtch; + + swtch = PULSE_PORT_SWITCH (object); + + switch (param_id) { + case PROP_STREAM: + /* Construct-only object */ + swtch->priv->stream = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_port_switch_init (PulsePortSwitch *swtch) +{ + swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, + PULSE_TYPE_PORT_SWITCH, + PulsePortSwitchPrivate); +} + +static void +pulse_port_switch_dispose (GObject *object) +{ + PulsePortSwitch *swtch; + + swtch = PULSE_PORT_SWITCH (object); + + g_clear_object (&swtch->priv->stream); + + G_OBJECT_CLASS (pulse_port_switch_parent_class)->dispose (object); +} + +PulseStream * +pulse_port_switch_get_stream (PulsePortSwitch *swtch) +{ + g_return_val_if_fail (PULSE_IS_PORT_SWITCH (swtch), NULL); + + return swtch->priv->stream; +} + +void +pulse_port_switch_add_port (PulsePortSwitch *swtch, PulsePort *port) +{ + g_return_if_fail (PULSE_IS_PORT_SWITCH (swtch)); + g_return_if_fail (PULSE_IS_PORT (port)); + + swtch->priv->ports = g_list_insert_sorted (swtch->priv->ports, + port, + compare_ports); +} + +void +pulse_port_switch_set_active_port (PulsePortSwitch *swtch, PulsePort *port) +{ + g_return_if_fail (PULSE_IS_PORT_SWITCH (swtch)); + g_return_if_fail (PULSE_IS_PORT (port)); + + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), + MATE_MIXER_SWITCH_OPTION (port)); +} + +void +pulse_port_switch_set_active_port_by_name (PulsePortSwitch *swtch, const gchar *name) +{ + GList *item; + + g_return_if_fail (PULSE_IS_PORT_SWITCH (swtch)); + g_return_if_fail (name != NULL); + + item = g_list_find_custom (swtch->priv->ports, name, compare_port_name); + if G_UNLIKELY (item == NULL) { + g_debug ("Invalid switch port name %s", name); + return; + } + pulse_port_switch_set_active_port (swtch, PULSE_PORT (item->data)); +} + +static gboolean +pulse_port_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + PulsePortSwitchClass *klass; + + g_return_val_if_fail (PULSE_IS_PORT_SWITCH (mms), FALSE); + g_return_val_if_fail (PULSE_IS_PORT (mmso), FALSE); + + klass = PULSE_PORT_SWITCH_GET_CLASS (PULSE_PORT_SWITCH (mms)); + + return klass->set_active_port (PULSE_PORT_SWITCH (mms), + PULSE_PORT (mmso)); +} + +static const GList * +pulse_port_switch_list_options (MateMixerSwitch *swtch) +{ + g_return_val_if_fail (PULSE_IS_PORT_SWITCH (swtch), NULL); + + return PULSE_PORT_SWITCH (swtch)->priv->ports; +} + +static gint +compare_ports (gconstpointer a, gconstpointer b) +{ + return pulse_port_get_priority (PULSE_PORT (b)) - + pulse_port_get_priority (PULSE_PORT (a)); +} + +static gint +compare_port_name (gconstpointer a, gconstpointer b) +{ + PulsePort *port = PULSE_PORT (a); + const gchar *name = (const gchar *) b; + + return strcmp (pulse_port_get_name (port), name); +} diff --git a/backends/pulse/pulse-port-switch.h b/backends/pulse/pulse-port-switch.h new file mode 100644 index 0000000..6ccef38 --- /dev/null +++ b/backends/pulse/pulse-port-switch.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_PORT_SWITCH_H +#define PULSE_PORT_SWITCH_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_PORT_SWITCH \ + (pulse_port_switch_get_type ()) +#define PULSE_PORT_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_PORT_SWITCH, PulsePortSwitch)) +#define PULSE_IS_PORT_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_PORT_SWITCH)) +#define PULSE_PORT_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_PORT_SWITCH, PulsePortSwitchClass)) +#define PULSE_IS_PORT_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_PORT_SWITCH)) +#define PULSE_PORT_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_PORT_SWITCH, PulsePortSwitchClass)) + +typedef struct _PulsePortSwitchClass PulsePortSwitchClass; +typedef struct _PulsePortSwitchPrivate PulsePortSwitchPrivate; + +struct _PulsePortSwitch +{ + MateMixerSwitch parent; + + /*< private >*/ + PulsePortSwitchPrivate *priv; +}; + +struct _PulsePortSwitchClass +{ + MateMixerSwitchClass parent_class; + + /*< private >*/ + gboolean (*set_active_port) (PulsePortSwitch *swtch, + PulsePort *port); +}; + +GType pulse_port_switch_get_type (void) G_GNUC_CONST; + +PulseStream *pulse_port_switch_get_stream (PulsePortSwitch *swtch); + +void pulse_port_switch_add_port (PulsePortSwitch *swtch, + PulsePort *port); + +void pulse_port_switch_set_active_port (PulsePortSwitch *swtch, + PulsePort *port); + +void pulse_port_switch_set_active_port_by_name (PulsePortSwitch *swtch, + const gchar *name); + +G_END_DECLS + +#endif /* PULSE_PORT_SWITCH_H */ diff --git a/backends/pulse/pulse-port.c b/backends/pulse/pulse-port.c new file mode 100644 index 0000000..f427448 --- /dev/null +++ b/backends/pulse/pulse-port.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-port.h" + +struct _PulsePortPrivate +{ + guint priority; +}; + +static void pulse_port_class_init (PulsePortClass *klass); +static void pulse_port_init (PulsePort *port); + +G_DEFINE_TYPE (PulsePort, pulse_port, MATE_MIXER_TYPE_SWITCH_OPTION) + +static void +pulse_port_class_init (PulsePortClass *klass) +{ + g_type_class_add_private (klass, sizeof (PulsePortPrivate)); +} + +static void +pulse_port_init (PulsePort *port) +{ + port->priv = G_TYPE_INSTANCE_GET_PRIVATE (port, + PULSE_TYPE_PORT, + PulsePortPrivate); +} + +PulsePort * +pulse_port_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint priority) +{ + PulsePort *port; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + + port = g_object_new (PULSE_TYPE_PORT, + "name", name, + "label", label, + "icon", icon, + NULL); + + port->priv->priority = priority; + return port; +} + +const gchar * +pulse_port_get_name (PulsePort *port) +{ + g_return_val_if_fail (PULSE_IS_PORT (port), NULL); + + return mate_mixer_switch_option_get_name (MATE_MIXER_SWITCH_OPTION (port)); +} + +guint +pulse_port_get_priority (PulsePort *port) +{ + g_return_val_if_fail (PULSE_IS_PORT (port), 0); + + return port->priv->priority; +} diff --git a/backends/pulse/pulse-port.h b/backends/pulse/pulse-port.h new file mode 100644 index 0000000..241fa2d --- /dev/null +++ b/backends/pulse/pulse-port.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_PORT_H +#define PULSE_PORT_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_PORT \ + (pulse_port_get_type ()) +#define PULSE_PORT(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_PORT, PulsePort)) +#define PULSE_IS_PORT(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_PORT)) +#define PULSE_PORT_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_PORT, PulsePortClass)) +#define PULSE_IS_PORT_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_PORT)) +#define PULSE_PORT_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_IS_PORT, PulsePortClass)) + +typedef struct _PulsePortClass PulsePortClass; +typedef struct _PulsePortPrivate PulsePortPrivate; + +struct _PulsePort +{ + MateMixerSwitchOption parent; + + /*< private >*/ + PulsePortPrivate *priv; +}; + +struct _PulsePortClass +{ + MateMixerSwitchOptionClass parent; +}; + +GType pulse_port_get_type (void) G_GNUC_CONST; + +PulsePort * pulse_port_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint priority); + +const gchar *pulse_port_get_name (PulsePort *port); +guint pulse_port_get_priority (PulsePort *port); + +G_END_DECLS + +#endif /* PULSE_PORT_H */ diff --git a/backends/pulse/pulse-sink-control.c b/backends/pulse/pulse-sink-control.c new file mode 100644 index 0000000..500cef0 --- /dev/null +++ b/backends/pulse/pulse-sink-control.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-monitor.h" +#include "pulse-stream-control.h" +#include "pulse-sink.h" +#include "pulse-sink-control.h" + +static void pulse_sink_control_class_init (PulseSinkControlClass *klass); +static void pulse_sink_control_init (PulseSinkControl *control); + +G_DEFINE_TYPE (PulseSinkControl, pulse_sink_control, PULSE_TYPE_STREAM_CONTROL); + +static gboolean pulse_sink_control_set_mute (PulseStreamControl *psc, + gboolean mute); +static gboolean pulse_sink_control_set_volume (PulseStreamControl *psc, + pa_cvolume *cvolume); +static PulseMonitor *pulse_sink_control_create_monitor (PulseStreamControl *psc); + +static void +pulse_sink_control_class_init (PulseSinkControlClass *klass) +{ + PulseStreamControlClass *control_class; + + control_class = PULSE_STREAM_CONTROL_CLASS (klass); + control_class->set_mute = pulse_sink_control_set_mute; + control_class->set_volume = pulse_sink_control_set_volume; + control_class->create_monitor = pulse_sink_control_create_monitor; +} + +static void +pulse_sink_control_init (PulseSinkControl *control) +{ +} + +PulseSinkControl * +pulse_sink_control_new (PulseSink *sink, + const pa_sink_info *info) +{ + PulseSinkControl *control; + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_MUTE_READABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; + MateMixerStreamControlRole role; + guint32 index; + + g_return_val_if_fail (PULSE_IS_SINK (sink), NULL); + g_return_val_if_fail (info != NULL, NULL); + + if (info->active_port != NULL) + role = MATE_MIXER_STREAM_CONTROL_ROLE_PORT; + else + role = MATE_MIXER_STREAM_CONTROL_ROLE_MASTER; + + /* Build the flag list */ + if (info->flags & PA_SINK_DECIBEL_VOLUME) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; + + index = pulse_sink_get_index_monitor (sink); + if (index != PA_INVALID_INDEX) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_MONITOR; + + control = g_object_new (PULSE_TYPE_SINK_CONTROL, + "name", info->name, + "label", info->description, + "flags", flags, + "role", role, + "stream", sink, + NULL); + + pulse_sink_control_update (control, info); + return control; +} + +void +pulse_sink_control_update (PulseSinkControl *control, const pa_sink_info *info) +{ + g_return_if_fail (PULSE_IS_SINK_CONTROL (control)); + g_return_if_fail (info != NULL); + + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (control)); + + _mate_mixer_stream_control_set_mute (MATE_MIXER_STREAM_CONTROL (control), + info->mute ? TRUE : FALSE); + + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (control), + &info->channel_map); + + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (control), + &info->volume, + info->base_volume); + + g_object_thaw_notify (G_OBJECT (control)); +} + +static gboolean +pulse_sink_control_set_mute (PulseStreamControl *psc, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SINK_CONTROL (psc), FALSE); + + return pulse_connection_set_sink_mute (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc), + mute); +} + +static gboolean +pulse_sink_control_set_volume (PulseStreamControl *psc, pa_cvolume *cvolume) +{ + g_return_val_if_fail (PULSE_IS_SINK_CONTROL (psc), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); + + return pulse_connection_set_sink_volume (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc), + cvolume); +} + +static PulseMonitor * +pulse_sink_control_create_monitor (PulseStreamControl *psc) +{ + PulseSink *sink; + guint32 index; + + g_return_val_if_fail (PULSE_IS_SINK_CONTROL (psc), NULL); + + sink = PULSE_SINK (mate_mixer_stream_control_get_stream (MATE_MIXER_STREAM_CONTROL (psc))); + + index = pulse_sink_get_index_monitor (sink); + if G_UNLIKELY (index == PA_INVALID_INDEX) { + g_debug ("Monitor of stream control %s is not available", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (psc))); + return NULL; + } + + return pulse_connection_create_monitor (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + index, + PA_INVALID_INDEX); +} diff --git a/backends/pulse/pulse-sink-control.h b/backends/pulse/pulse-sink-control.h new file mode 100644 index 0000000..e9570f4 --- /dev/null +++ b/backends/pulse/pulse-sink-control.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_SINK_CONTROL_H +#define PULSE_SINK_CONTROL_H + +#include <glib.h> +#include <glib-object.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-stream-control.h" +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_SINK_CONTROL \ + (pulse_sink_control_get_type ()) +#define PULSE_SINK_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_SINK_CONTROL, PulseSinkControl)) +#define PULSE_IS_SINK_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_SINK_CONTROL)) +#define PULSE_SINK_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SINK_CONTROL, PulseSinkControlClass)) +#define PULSE_IS_SINK_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK_CONTROL)) +#define PULSE_SINK_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK_CONTROL, PulseSinkControlClass)) + +typedef struct _PulseSinkControlClass PulseSinkControlClass; +typedef struct _PulseSinkControlPrivate PulseSinkControlPrivate; + +struct _PulseSinkControl +{ + PulseStreamControl parent; +}; + +struct _PulseSinkControlClass +{ + PulseStreamControlClass parent_class; +}; + +GType pulse_sink_control_get_type (void) G_GNUC_CONST; + +PulseSinkControl *pulse_sink_control_new (PulseSink *sink, + const pa_sink_info *info); + +void pulse_sink_control_update (PulseSinkControl *control, + const pa_sink_info *info); + +G_END_DECLS + +#endif /* PULSE_SINK_CONTROL_H */ diff --git a/backends/pulse/pulse-sink-input.c b/backends/pulse/pulse-sink-input.c index 1d5f9c2..eab85b8 100644 --- a/backends/pulse/pulse-sink-input.c +++ b/backends/pulse/pulse-sink-input.c @@ -15,57 +15,41 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ -#include <string.h> #include <glib.h> #include <glib-object.h> - -#include <libmatemixer/matemixer-client-stream.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" -#include "pulse-client-stream.h" #include "pulse-helpers.h" #include "pulse-monitor.h" #include "pulse-sink.h" #include "pulse-sink-input.h" #include "pulse-stream.h" +#include "pulse-stream-control.h" static void pulse_sink_input_class_init (PulseSinkInputClass *klass); static void pulse_sink_input_init (PulseSinkInput *input); -G_DEFINE_TYPE (PulseSinkInput, pulse_sink_input, PULSE_TYPE_CLIENT_STREAM); - -static void pulse_sink_input_reload (PulseStream *pstream); +G_DEFINE_TYPE (PulseSinkInput, pulse_sink_input, PULSE_TYPE_STREAM_CONTROL); -static gboolean pulse_sink_input_set_mute (PulseStream *pstream, - gboolean mute); -static gboolean pulse_sink_input_set_volume (PulseStream *pstream, - pa_cvolume *cvolume); -static gboolean pulse_sink_input_set_parent (PulseClientStream *pclient, - PulseStream *parent); -static gboolean pulse_sink_input_remove (PulseClientStream *pclient); -static PulseMonitor *pulse_sink_input_create_monitor (PulseStream *pstream); +static gboolean pulse_sink_input_set_mute (PulseStreamControl *psc, + gboolean mute); +static gboolean pulse_sink_input_set_volume (PulseStreamControl *psc, + pa_cvolume *cvolume); +static PulseMonitor *pulse_sink_input_create_monitor (PulseStreamControl *psc); static void pulse_sink_input_class_init (PulseSinkInputClass *klass) { - PulseStreamClass *stream_class; - PulseClientStreamClass *client_class; - - stream_class = PULSE_STREAM_CLASS (klass); + PulseStreamControlClass *control_class; - stream_class->reload = pulse_sink_input_reload; - stream_class->set_mute = pulse_sink_input_set_mute; - stream_class->set_volume = pulse_sink_input_set_volume; - stream_class->create_monitor = pulse_sink_input_create_monitor; - - client_class = PULSE_CLIENT_STREAM_CLASS (klass); - - client_class->set_parent = pulse_sink_input_set_parent; - client_class->remove = pulse_sink_input_remove; + control_class = PULSE_STREAM_CONTROL_CLASS (klass); + control_class->set_mute = pulse_sink_input_set_mute; + control_class->set_volume = pulse_sink_input_set_volume; + control_class->create_monitor = pulse_sink_input_create_monitor; } static void @@ -73,230 +57,171 @@ pulse_sink_input_init (PulseSinkInput *input) { } -PulseStream * -pulse_sink_input_new (PulseConnection *connection, - const pa_sink_input_info *info, - PulseStream *parent) +PulseSinkInput * +pulse_sink_input_new (PulseSink *sink, const pa_sink_input_info *info) { - PulseSinkInput *input; + PulseSinkInput *input; + gchar *name; + const gchar *prop; + const gchar *label = NULL; + MateMixerAppInfo *app_info = NULL; - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); - g_return_val_if_fail (info != NULL, NULL); + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_MUTE_READABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_MONITOR; + MateMixerStreamControlRole role = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; - /* Consider the sink input index as unchanging parameter */ - input = g_object_new (PULSE_TYPE_SINK_INPUT, - "connection", connection, - "index", info->index, - NULL); + MateMixerStreamControlMediaRole media_role = MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_UNKNOWN; - /* Other data may change at any time, so let's make a use of our update function */ - pulse_sink_input_update (PULSE_STREAM (input), info, parent); - - return PULSE_STREAM (input); -} - -gboolean -pulse_sink_input_update (PulseStream *pstream, - const pa_sink_input_info *info, - PulseStream *parent) -{ - MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | - MATE_MIXER_STREAM_CLIENT | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_HAS_MONITOR; - PulseClientStream *pclient; - const gchar *prop; - const gchar *description = NULL; - gchar *name; - - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); - g_return_val_if_fail (info != NULL, FALSE); - - pclient = PULSE_CLIENT_STREAM (pstream); - - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (pstream)); + g_return_val_if_fail (PULSE_IS_SINK (sink), NULL); + g_return_val_if_fail (info != NULL, NULL); /* Many mixer applications query the Pulse client list and use the client * name here, but we use the name only as an identifier, so let's avoid * this unnecessary overhead and use a custom name. * Also make sure to make the name unique by including the PulseAudio index. */ - name = g_strdup_printf ("pulse-stream-client-output-%lu", (gulong) info->index); - - pulse_stream_update_name (pstream, name); - g_free (name); - - prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); - if (prop != NULL) { - MateMixerClientStreamRole role = pulse_convert_media_role_name (prop); - - if (role == MATE_MIXER_CLIENT_STREAM_ROLE_EVENT) { - /* The event description seems to provide much better readable - * description for event streams */ - prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); - - if (G_LIKELY (prop != NULL)) - description = prop; - } - pulse_client_stream_update_role (pclient, role); - } else - pulse_client_stream_update_role (pclient, MATE_MIXER_CLIENT_STREAM_ROLE_NONE); - - if (description == NULL) - description = info->name; - - pulse_stream_update_description (pstream, description); - pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); - - if (info->client != PA_INVALID_INDEX) - pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_APPLICATION); - else - pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_NO_FLAGS); - - if (G_LIKELY (parent != NULL)) { - if (pulse_sink_get_monitor_index (parent) != PA_INVALID_INDEX) - flags |= MATE_MIXER_STREAM_HAS_MONITOR; - - pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); - } else - pulse_client_stream_update_parent (pclient, NULL); + name = g_strdup_printf ("pulse-output-control-%lu", (gulong) info->index); #if PA_CHECK_VERSION(1, 0, 0) if (info->has_volume) { flags |= - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME; + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; if (info->volume_writable) - flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; } - - /* Flags needed before volume */ - pulse_stream_update_flags (pstream, flags); - pulse_stream_update_channel_map (pstream, &info->channel_map); - - if (info->has_volume) - pulse_stream_update_volume (pstream, &info->volume, 0); - else - pulse_stream_update_volume (pstream, NULL, 0); #else /* Pre-1.0 PulseAudio does not include the has_volume and volume_writable * fields, but does include the volume info, so let's give it a try */ flags |= - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME; + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; +#endif - /* Flags needed before volume */ - pulse_stream_update_flags (pstream, flags); - pulse_stream_update_channel_map (pstream, &info->channel_map); + if (info->client != PA_INVALID_INDEX) { + app_info = _mate_mixer_app_info_new (); - pulse_stream_update_volume (pstream, &info->volume, 0); -#endif + role = MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION; - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); - if (prop != NULL) - pulse_client_stream_update_app_name (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); + if (prop != NULL) + _mate_mixer_app_info_set_name (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); - if (prop != NULL) - pulse_client_stream_update_app_id (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); + if (prop != NULL) + _mate_mixer_app_info_set_id (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); - if (prop != NULL) - pulse_client_stream_update_app_version (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); + if (prop != NULL) + _mate_mixer_app_info_set_version (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); - if (prop != NULL) - pulse_client_stream_update_app_icon (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); + if (prop != NULL) + _mate_mixer_app_info_set_icon (app_info, prop); + } - // XXX needs to fix monitor if parent changes + prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); + if (prop != NULL) { + media_role = pulse_convert_media_role_name (prop); - g_object_thaw_notify (G_OBJECT (pstream)); - return TRUE; -} + if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT) { + /* The event description seems to provide much better readable + * description for event streams */ + prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); + if (prop != NULL) + label = prop; + } + } -static void -pulse_sink_input_reload (PulseStream *pstream) -{ - g_return_if_fail (PULSE_IS_SINK_INPUT (pstream)); + if (label == NULL) + label = info->name; - pulse_connection_load_sink_input_info (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); -} + input = g_object_new (PULSE_TYPE_SINK_INPUT, + "name", name, + "label", label, + "flags", flags, + "role", role, + "media-role", media_role, + "index", info->index, + "stream", sink, + NULL); + g_free (name); -static gboolean -pulse_sink_input_set_mute (PulseStream *pstream, gboolean mute) -{ - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); + if (app_info != NULL) + pulse_stream_control_set_app_info (PULSE_STREAM_CONTROL (input), app_info); - return pulse_connection_set_sink_input_mute (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - mute); + pulse_sink_input_update (input, info); + return input; } -static gboolean -pulse_sink_input_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +void +pulse_sink_input_update (PulseSinkInput *input, const pa_sink_input_info *info) { - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); - g_return_val_if_fail (cvolume != NULL, FALSE); + g_return_if_fail (PULSE_IS_SINK_INPUT (input)); + g_return_if_fail (info != NULL); - return pulse_connection_set_sink_input_volume (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - cvolume); -} + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (input)); -static gboolean -pulse_sink_input_set_parent (PulseClientStream *pclient, PulseStream *parent) -{ - PulseStream *pstream; + _mate_mixer_stream_control_set_mute (MATE_MIXER_STREAM_CONTROL (input), + info->mute ? TRUE : FALSE); - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pclient), FALSE); +#if PA_CHECK_VERSION(1, 0, 0) + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (input), &info->channel_map); - pstream = PULSE_STREAM (pclient); + if (info->has_volume) + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (input), &info->volume, 0); + else + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (input), NULL, 0); +#else + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (input), &info->channel_map); + pulse_stream_control_set_volume (PULSE_STREAM_CONTROL (input), &info->volume, 0); +#endif - return pulse_connection_move_sink_input (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - pulse_stream_get_index (parent)); + g_object_thaw_notify (G_OBJECT (input)); } static gboolean -pulse_sink_input_remove (PulseClientStream *pclient) +pulse_sink_input_set_mute (PulseStreamControl *psc, gboolean mute) { - PulseStream *pstream; + g_return_val_if_fail (PULSE_IS_SINK_INPUT (psc), FALSE); - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pclient), FALSE); + return pulse_connection_set_sink_input_mute (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + pulse_stream_control_get_index (psc), + mute); +} - pstream = PULSE_STREAM (pclient); +static gboolean +pulse_sink_input_set_volume (PulseStreamControl *psc, pa_cvolume *cvolume) +{ + g_return_val_if_fail (PULSE_IS_SINK_INPUT (psc), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - return pulse_connection_kill_sink_input (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); + return pulse_connection_set_sink_input_volume (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + pulse_stream_control_get_index (psc), + cvolume); } static PulseMonitor * -pulse_sink_input_create_monitor (PulseStream *pstream) +pulse_sink_input_create_monitor (PulseStreamControl *psc) { - MateMixerStream *parent; - guint32 index; - - g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), NULL); + PulseSink *sink; + guint32 index; - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); - if (G_UNLIKELY (parent == NULL)) { - g_debug ("Not creating monitor for client stream %s as it is not available", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); - return NULL; - } + g_return_val_if_fail (PULSE_IS_SINK_INPUT (psc), NULL); - index = pulse_sink_get_monitor_index (PULSE_STREAM (parent)); + sink = PULSE_SINK (mate_mixer_stream_control_get_stream (MATE_MIXER_STREAM_CONTROL (psc))); - if (G_UNLIKELY (index == PA_INVALID_INDEX)) { - g_debug ("Not creating monitor for client stream %s as it is not available", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); + index = pulse_sink_get_index_monitor (sink); + if G_UNLIKELY (index == PA_INVALID_INDEX) { + g_debug ("Monitor of stream control %s is not available", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (psc))); return NULL; } - return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), + return pulse_connection_create_monitor (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), index, - pulse_stream_get_index (pstream)); + pulse_stream_control_get_index (psc)); } diff --git a/backends/pulse/pulse-sink-input.h b/backends/pulse/pulse-sink-input.h index 1e5004b..127eab6 100644 --- a/backends/pulse/pulse-sink-input.h +++ b/backends/pulse/pulse-sink-input.h @@ -23,9 +23,8 @@ #include <pulse/pulseaudio.h> -#include "pulse-client-stream.h" -#include "pulse-connection.h" -#include "pulse-stream.h" +#include "pulse-stream-control.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -42,28 +41,25 @@ G_BEGIN_DECLS #define PULSE_SINK_INPUT_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK_INPUT, PulseSinkInputClass)) -typedef struct _PulseSinkInput PulseSinkInput; typedef struct _PulseSinkInputClass PulseSinkInputClass; struct _PulseSinkInput { - PulseClientStream parent; + PulseStreamControl parent; }; struct _PulseSinkInputClass { - PulseClientStreamClass parent_class; + PulseStreamControlClass parent_class; }; -GType pulse_sink_input_get_type (void) G_GNUC_CONST; +GType pulse_sink_input_get_type (void) G_GNUC_CONST; -PulseStream *pulse_sink_input_new (PulseConnection *connection, - const pa_sink_input_info *info, - PulseStream *parent); +PulseSinkInput *pulse_sink_input_new (PulseSink *sink, + const pa_sink_input_info *info); -gboolean pulse_sink_input_update (PulseStream *pstream, - const pa_sink_input_info *info, - PulseStream *parent); +void pulse_sink_input_update (PulseSinkInput *input, + const pa_sink_input_info *info); G_END_DECLS diff --git a/backends/pulse/pulse-sink-switch.c b/backends/pulse/pulse-sink-switch.c new file mode 100644 index 0000000..0e08dac --- /dev/null +++ b/backends/pulse/pulse-sink-switch.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "pulse-connection.h" +#include "pulse-port.h" +#include "pulse-port-switch.h" +#include "pulse-sink-switch.h" +#include "pulse-stream.h" + +static void pulse_sink_switch_class_init (PulseSinkSwitchClass *klass); +static void pulse_sink_switch_init (PulseSinkSwitch *swtch); + +G_DEFINE_TYPE (PulseSinkSwitch, pulse_sink_switch, PULSE_TYPE_PORT_SWITCH) + +static gboolean pulse_sink_switch_set_active_port (PulsePortSwitch *swtch, + PulsePort *port); + +static void +pulse_sink_switch_class_init (PulseSinkSwitchClass *klass) +{ + PulsePortSwitchClass *switch_class; + + switch_class = PULSE_PORT_SWITCH_CLASS (klass); + switch_class->set_active_port = pulse_sink_switch_set_active_port; +} + +static void +pulse_sink_switch_init (PulseSinkSwitch *swtch) +{ +} + +PulsePortSwitch * +pulse_sink_switch_new (const gchar *name, const gchar *label, PulseSink *sink) +{ + return g_object_new (PULSE_TYPE_SINK_SWITCH, + "name", name, + "label", label, + "role", MATE_MIXER_SWITCH_ROLE_PORT, + "stream", sink, + NULL); +} + +static gboolean +pulse_sink_switch_set_active_port (PulsePortSwitch *swtch, PulsePort *port) +{ + PulseStream *stream; + + g_return_val_if_fail (PULSE_IS_SINK_SWITCH (swtch), FALSE); + g_return_val_if_fail (PULSE_IS_PORT (port), FALSE); + + stream = pulse_port_switch_get_stream (swtch); + + return pulse_connection_set_sink_port (pulse_stream_get_connection (stream), + pulse_stream_get_index (stream), + pulse_port_get_name (port)); +} diff --git a/backends/pulse/pulse-sink-switch.h b/backends/pulse/pulse-sink-switch.h new file mode 100644 index 0000000..71ed4c9 --- /dev/null +++ b/backends/pulse/pulse-sink-switch.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_SINK_SWITCH_H +#define PULSE_SINK_SWITCH_H + +#include <glib.h> +#include <glib-object.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_SINK_SWITCH \ + (pulse_sink_switch_get_type ()) +#define PULSE_SINK_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_SINK_SWITCH, PulseSinkSwitch)) +#define PULSE_IS_SINK_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_SINK_SWITCH)) +#define PULSE_SINK_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SINK_SWITCH, PulseSinkSwitchClass)) +#define PULSE_IS_SINK_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK_SWITCH)) +#define PULSE_SINK_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK_SWITCH, PulseSinkSwitchClass)) + +typedef struct _PulseSinkSwitchClass PulseSinkSwitchClass; +typedef struct _PulseSinkSwitchPrivate PulseSinkSwitchPrivate; + +struct _PulseSinkSwitch +{ + PulsePortSwitch parent; +}; + +struct _PulseSinkSwitchClass +{ + PulsePortSwitchClass parent_class; +}; + +GType pulse_sink_switch_get_type (void) G_GNUC_CONST; + +PulsePortSwitch *pulse_sink_switch_new (const gchar *name, + const gchar *label, + PulseSink *sink); + +G_END_DECLS + +#endif /* PULSE_SINK_SWITCH_H */ diff --git a/backends/pulse/pulse-sink.c b/backends/pulse/pulse-sink.c index 0f828b1..d2f0280 100644 --- a/backends/pulse/pulse-sink.c +++ b/backends/pulse/pulse-sink.c @@ -16,62 +16,57 @@ */ #include <glib.h> +#include <glib/gi18n.h> #include <glib-object.h> - -#include <libmatemixer/matemixer-port.h> -#include <libmatemixer/matemixer-port-private.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" #include "pulse-device.h" #include "pulse-monitor.h" +#include "pulse-port.h" +#include "pulse-port-switch.h" #include "pulse-stream.h" #include "pulse-sink.h" +#include "pulse-sink-control.h" +#include "pulse-sink-input.h" +#include "pulse-sink-switch.h" struct _PulseSinkPrivate { - guint32 index_monitor; + guint32 monitor; + GHashTable *inputs; + PulsePortSwitch *pswitch; + GList *streams_list; + GList *switches_list; + PulseSinkControl *control; }; static void pulse_sink_class_init (PulseSinkClass *klass); static void pulse_sink_init (PulseSink *sink); +static void pulse_sink_dispose (GObject *object); +static void pulse_sink_finalize (GObject *object); G_DEFINE_TYPE (PulseSink, pulse_sink, PULSE_TYPE_STREAM); -static void pulse_sink_reload (PulseStream *pstream); - -static gboolean pulse_sink_set_mute (PulseStream *pstream, - gboolean mute); -static gboolean pulse_sink_set_volume (PulseStream *pstream, - pa_cvolume *cvolume); -static gboolean pulse_sink_set_active_port (PulseStream *pstream, - MateMixerPort *port); - -static gboolean pulse_sink_suspend (PulseStream *pstream); -static gboolean pulse_sink_resume (PulseStream *pstream); - -static PulseMonitor *pulse_sink_create_monitor (PulseStream *pstream); - -static void update_ports (PulseStream *pstream, - pa_sink_port_info **ports, - pa_sink_port_info *active); +static const GList *pulse_sink_list_controls (MateMixerStream *mms); +static const GList *pulse_sink_list_switches (MateMixerStream *mms); static void pulse_sink_class_init (PulseSinkClass *klass) { - PulseStreamClass *stream_class; + GObjectClass *object_class; + MateMixerStreamClass *stream_class; - stream_class = PULSE_STREAM_CLASS (klass); + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_sink_dispose; + object_class->finalize = pulse_sink_finalize; - stream_class->reload = pulse_sink_reload; - stream_class->set_mute = pulse_sink_set_mute; - stream_class->set_volume = pulse_sink_set_volume; - stream_class->set_active_port = pulse_sink_set_active_port; - stream_class->suspend = pulse_sink_suspend; - stream_class->resume = pulse_sink_resume; - stream_class->create_monitor = pulse_sink_create_monitor; + stream_class = MATE_MIXER_STREAM_CLASS (klass); + stream_class->list_controls = pulse_sink_list_controls; + stream_class->list_switches = pulse_sink_list_switches; g_type_class_add_private (klass, sizeof (PulseSinkPrivate)); } @@ -83,247 +78,190 @@ pulse_sink_init (PulseSink *sink) PULSE_TYPE_SINK, PulseSinkPrivate); - sink->priv->index_monitor = PA_INVALID_INDEX; + sink->priv->inputs = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + + sink->priv->monitor = PA_INVALID_INDEX; } -PulseStream * -pulse_sink_new (PulseConnection *connection, - const pa_sink_info *info, - PulseDevice *device) +static void +pulse_sink_dispose (GObject *object) { - PulseStream *stream; + PulseSink *sink; - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); - g_return_val_if_fail (info != NULL, NULL); + sink = PULSE_SINK (object); - /* Consider the sink index as unchanging parameter */ - stream = g_object_new (PULSE_TYPE_SINK, - "connection", connection, - "index", info->index, - NULL); + g_clear_object (&sink->priv->control); + g_clear_object (&sink->priv->pswitch); - /* Other data may change at any time, so let's make a use of our update function */ - pulse_sink_update (stream, info, device); + g_hash_table_remove_all (sink->priv->inputs); - return stream; + G_OBJECT_CLASS (pulse_sink_parent_class)->dispose (object); } -guint32 -pulse_sink_get_monitor_index (PulseStream *pstream) +static void +pulse_sink_finalize (GObject *object) { - g_return_val_if_fail (PULSE_IS_SINK (pstream), PA_INVALID_INDEX); + PulseSink *sink; + + sink = PULSE_SINK (object); - return PULSE_SINK (pstream)->priv->index_monitor; + g_hash_table_unref (sink->priv->inputs); + + G_OBJECT_CLASS (pulse_sink_parent_class)->finalize (object); } -gboolean -pulse_sink_update (PulseStream *pstream, const pa_sink_info *info, PulseDevice *device) +PulseSink * +pulse_sink_new (PulseConnection *connection, + const pa_sink_info *info, + PulseDevice *device) { - MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME | - MATE_MIXER_STREAM_CAN_SUSPEND; PulseSink *sink; - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - g_return_val_if_fail (info != NULL, FALSE); - - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (pstream)); - - pulse_stream_update_name (pstream, info->name); - pulse_stream_update_description (pstream, info->description); - pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); - - /* Stream state */ - switch (info->state) { - case PA_SINK_RUNNING: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_RUNNING); - break; - case PA_SINK_IDLE: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); - break; - case PA_SINK_SUSPENDED: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); - break; - default: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_UNKNOWN); - break; - } - - /* Build the flag list */ - if (info->flags & PA_SINK_DECIBEL_VOLUME) - flags |= MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME; - if (info->flags & PA_SINK_FLAT_VOLUME) - flags |= MATE_MIXER_STREAM_HAS_FLAT_VOLUME; - - sink = PULSE_SINK (pstream); - - if (sink->priv->index_monitor == PA_INVALID_INDEX) - sink->priv->index_monitor = info->monitor_source; + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); - if (sink->priv->index_monitor != PA_INVALID_INDEX) - flags |= MATE_MIXER_STREAM_HAS_MONITOR; + sink = g_object_new (PULSE_TYPE_SINK, + "name", info->name, + "label", info->description, + "device", device, + "direction", MATE_MIXER_DIRECTION_OUTPUT, + "connection", connection, + "index", info->index, + NULL); + + sink->priv->control = pulse_sink_control_new (sink, info); + + if (info->n_ports > 0) { + pa_sink_port_info **ports = info->ports; + + /* Create the port switch */ + sink->priv->pswitch = pulse_sink_switch_new ("port", _("Port"), sink); + + while (*ports != NULL) { + pa_sink_port_info *p = *ports++; + PulsePort *port; + const gchar *icon = NULL; + + /* A port may include an icon but in PulseAudio sink and source ports + * the property is not included, for this reason ports are also read from + * devices where the icons may be present */ + if (device != NULL) { + port = pulse_device_get_port (PULSE_DEVICE (device), p->name); + if (port != NULL) + icon = mate_mixer_switch_option_get_icon (MATE_MIXER_SWITCH_OPTION (port)); + } - /* Flags must be updated before volume */ - pulse_stream_update_flags (pstream, flags); + port = pulse_port_new (p->name, + p->description, + icon, + p->priority); - pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_volume (pstream, &info->volume, info->base_volume); + pulse_port_switch_add_port (sink->priv->pswitch, port); - pulse_stream_update_device (pstream, MATE_MIXER_DEVICE (device)); + if (p == info->active_port) + pulse_port_switch_set_active_port (sink->priv->pswitch, port); + } - /* Ports must be updated after device */ - if (info->ports != NULL) { - update_ports (pstream, info->ports, info->active_port); + g_debug ("Created port list for sink %s", info->name); } - g_object_thaw_notify (G_OBJECT (pstream)); - return TRUE; -} - -static void -pulse_sink_reload (PulseStream *pstream) -{ - g_return_if_fail (PULSE_IS_SINK (pstream)); + pulse_sink_update (sink, info); - pulse_connection_load_sink_info (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (sink), + MATE_MIXER_STREAM_CONTROL (sink->priv->control)); + return sink; } -static gboolean -pulse_sink_set_mute (PulseStream *pstream, gboolean mute) +void +pulse_sink_add_input (PulseSink *sink, const pa_sink_input_info *info) { - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - - return pulse_connection_set_sink_mute (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - mute); + PulseSinkInput *input; + + /* This function is used for both creating and refreshing sink inputs */ + input = g_hash_table_lookup (sink->priv->inputs, GINT_TO_POINTER (info->index)); + if (input == NULL) { + const gchar *name; + + input = pulse_sink_input_new (sink, info); + g_hash_table_insert (sink->priv->inputs, + GINT_TO_POINTER (info->index), + input); + + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (input)); + g_signal_emit_by_name (G_OBJECT (sink), + "control-added", + name); + } else + pulse_sink_input_update (input, info); } -static gboolean -pulse_sink_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +void +pulse_sink_remove_input (PulseSink *sink, guint32 index) { - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - g_return_val_if_fail (cvolume != NULL, FALSE); + PulseSinkInput *input; + const gchar *name; - return pulse_connection_set_sink_volume (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - cvolume); -} + input = g_hash_table_lookup (sink->priv->inputs, GINT_TO_POINTER (index)); + if G_UNLIKELY (input == NULL) + return; -static gboolean -pulse_sink_set_active_port (PulseStream *pstream, MateMixerPort *port) -{ - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (input)); - return pulse_connection_set_sink_port (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - mate_mixer_port_get_name (port)); + g_hash_table_remove (sink->priv->inputs, GINT_TO_POINTER (index)); + g_signal_emit_by_name (G_OBJECT (sink), + "control-removed", + name); } -static gboolean -pulse_sink_suspend (PulseStream *pstream) +void +pulse_sink_update (PulseSink *sink, const pa_sink_info *info) { - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); + g_return_if_fail (PULSE_IS_SINK (sink)); + g_return_if_fail (info != NULL); - return pulse_connection_suspend_sink (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - TRUE); + /* The switch doesn't allow being unset, PulseAudio should always include + * the active port name if the are any ports available */ + if (info->active_port != NULL) + pulse_port_switch_set_active_port_by_name (sink->priv->pswitch, + info->active_port->name); + + sink->priv->monitor = info->monitor_source; } -static gboolean -pulse_sink_resume (PulseStream *pstream) +guint32 +pulse_sink_get_index_monitor (PulseSink *sink) { - g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK (sink), 0); - return pulse_connection_suspend_sink (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - FALSE); + return sink->priv->monitor; } -static PulseMonitor * -pulse_sink_create_monitor (PulseStream *pstream) +static const GList * +pulse_sink_list_controls (MateMixerStream *mms) { - guint32 index; + GList *list; - g_return_val_if_fail (PULSE_IS_SINK (pstream), NULL); + g_return_val_if_fail (PULSE_IS_SINK (mms), NULL); - index = pulse_sink_get_monitor_index (pstream); + // XXX + list = g_hash_table_get_values (PULSE_SINK (mms)->priv->inputs); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); - if (G_UNLIKELY (index == PA_INVALID_INDEX)) { - g_debug ("Not creating monitor for stream %s: not available", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); - return NULL; - } - - return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), - index, - PA_INVALID_INDEX); + return g_list_prepend (list, g_object_ref (PULSE_SINK (mms)->priv->control)); } -static void -update_ports (PulseStream *pstream, - pa_sink_port_info **ports, - pa_sink_port_info *active) +static const GList * +pulse_sink_list_switches (MateMixerStream *mms) { - MateMixerPort *port; - MateMixerDevice *device; - GHashTable *hash; - - hash = pulse_stream_get_ports (pstream); - - while (*ports != NULL) { - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - pa_sink_port_info *info = *ports; - const gchar *icon = NULL; - - device = mate_mixer_stream_get_device (MATE_MIXER_STREAM (pstream)); - if (device != NULL) { - port = mate_mixer_device_get_port (device, info->name); - - if (port != NULL) { - flags = mate_mixer_port_get_flags (port); - icon = mate_mixer_port_get_icon (port); - } - } - -#if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; - else - flags &= ~MATE_MIXER_PORT_AVAILABLE; -#endif - - port = g_hash_table_lookup (hash, info->name); - - if (port != NULL) { - /* Update existing port */ - _mate_mixer_port_update_description (port, info->description); - _mate_mixer_port_update_icon (port, icon); - _mate_mixer_port_update_priority (port, info->priority); - _mate_mixer_port_update_flags (port, flags); - } else { - /* Add previously unknown port to the hash table */ - port = _mate_mixer_port_new (info->name, - info->description, - icon, - info->priority, - flags); - - g_hash_table_insert (hash, g_strdup (info->name), port); - } - - ports++; - } + g_return_val_if_fail (PULSE_IS_SINK (mms), NULL); - /* Active port */ - if (G_LIKELY (active != NULL)) - port = g_hash_table_lookup (hash, active->name); - else - port = NULL; + // XXX + if (PULSE_SINK (mms)->priv->pswitch != NULL) + return g_list_prepend (NULL, PULSE_SINK (mms)->priv->pswitch); - pulse_stream_update_active_port (pstream, port); + return NULL; } diff --git a/backends/pulse/pulse-sink.h b/backends/pulse/pulse-sink.h index c0631ca..5eaeaa0 100644 --- a/backends/pulse/pulse-sink.h +++ b/backends/pulse/pulse-sink.h @@ -23,9 +23,8 @@ #include <pulse/pulseaudio.h> -#include "pulse-connection.h" -#include "pulse-device.h" #include "pulse-stream.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -42,7 +41,6 @@ G_BEGIN_DECLS #define PULSE_SINK_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK, PulseSinkClass)) -typedef struct _PulseSink PulseSink; typedef struct _PulseSinkClass PulseSinkClass; typedef struct _PulseSinkPrivate PulseSinkPrivate; @@ -59,17 +57,21 @@ struct _PulseSinkClass PulseStreamClass parent_class; }; -GType pulse_sink_get_type (void) G_GNUC_CONST; +GType pulse_sink_get_type (void) G_GNUC_CONST; -PulseStream *pulse_sink_new (PulseConnection *connection, - const pa_sink_info *info, - PulseDevice *device); +PulseSink *pulse_sink_new (PulseConnection *connection, + const pa_sink_info *info, + PulseDevice *device); -guint32 pulse_sink_get_monitor_index (PulseStream *pstream); +void pulse_sink_add_input (PulseSink *sink, + const pa_sink_input_info *info); -gboolean pulse_sink_update (PulseStream *pstream, - const pa_sink_info *info, - PulseDevice *device); +void pulse_sink_remove_input (PulseSink *sink, guint32 index); + +void pulse_sink_update (PulseSink *sink, + const pa_sink_info *info); + +guint32 pulse_sink_get_index_monitor (PulseSink *sink); G_END_DECLS diff --git a/backends/pulse/pulse-source-control.c b/backends/pulse/pulse-source-control.c new file mode 100644 index 0000000..3ed1573 --- /dev/null +++ b/backends/pulse/pulse-source-control.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-monitor.h" +#include "pulse-stream-control.h" +#include "pulse-source.h" +#include "pulse-source-control.h" + +static void pulse_source_control_class_init (PulseSourceControlClass *klass); +static void pulse_source_control_init (PulseSourceControl *control); + +G_DEFINE_TYPE (PulseSourceControl, pulse_source_control, PULSE_TYPE_STREAM_CONTROL); + +static gboolean pulse_source_control_set_mute (PulseStreamControl *psc, + gboolean mute); +static gboolean pulse_source_control_set_volume (PulseStreamControl *psc, + pa_cvolume *cvolume); +static PulseMonitor *pulse_source_control_create_monitor (PulseStreamControl *psc); + +static void +pulse_source_control_class_init (PulseSourceControlClass *klass) +{ + PulseStreamControlClass *control_class; + + control_class = PULSE_STREAM_CONTROL_CLASS (klass); + control_class->set_mute = pulse_source_control_set_mute; + control_class->set_volume = pulse_source_control_set_volume; + control_class->create_monitor = pulse_source_control_create_monitor; +} + +static void +pulse_source_control_init (PulseSourceControl *control) +{ +} + +PulseSourceControl * +pulse_source_control_new (PulseSource *source, + const pa_source_info *info) +{ + PulseSourceControl *control; + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_MUTE_READABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_MONITOR; + MateMixerStreamControlRole role; + + g_return_val_if_fail (PULSE_IS_SOURCE (source), NULL); + g_return_val_if_fail (info != NULL, NULL); + + if (info->active_port != NULL) + role = MATE_MIXER_STREAM_CONTROL_ROLE_PORT; + else + role = MATE_MIXER_STREAM_CONTROL_ROLE_MASTER; + + /* Build the flag list */ + if (info->flags & PA_SOURCE_DECIBEL_VOLUME) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; + + control = g_object_new (PULSE_TYPE_SOURCE_CONTROL, + "name", info->name, + "label", info->description, + "flags", flags, + "role", role, + "stream", source, + NULL); + + pulse_source_control_update (control, info); + return control; +} + +void +pulse_source_control_update (PulseSourceControl *control, const pa_source_info *info) +{ + g_return_if_fail (PULSE_IS_SOURCE_CONTROL (control)); + g_return_if_fail (info != NULL); + + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (control)); + + _mate_mixer_stream_control_set_mute (MATE_MIXER_STREAM_CONTROL (control), + info->mute ? TRUE : FALSE); + + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (control), + &info->channel_map); + + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (control), + &info->volume, + info->base_volume); + + g_object_thaw_notify (G_OBJECT (control)); +} + +static gboolean +pulse_source_control_set_mute (PulseStreamControl *psc, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SOURCE_CONTROL (psc), FALSE); + + return pulse_connection_set_source_mute (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc), + mute); +} + +static gboolean +pulse_source_control_set_volume (PulseStreamControl *psc, pa_cvolume *cvolume) +{ + g_return_val_if_fail (PULSE_IS_SOURCE_CONTROL (psc), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); + + return pulse_connection_set_source_volume (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc), + cvolume); +} + +static PulseMonitor * +pulse_source_control_create_monitor (PulseStreamControl *psc) +{ + guint32 index; + + g_return_val_if_fail (PULSE_IS_SOURCE_CONTROL (psc), NULL); + + index = PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc); + if G_UNLIKELY (index == PA_INVALID_INDEX) { + g_debug ("Monitor of stream control %s is not available", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (psc))); + return NULL; + } + + return pulse_connection_create_monitor (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + index, + PA_INVALID_INDEX); +} diff --git a/backends/pulse/pulse-source-control.h b/backends/pulse/pulse-source-control.h new file mode 100644 index 0000000..a8d659f --- /dev/null +++ b/backends/pulse/pulse-source-control.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_SOURCE_CONTROL_H +#define PULSE_SOURCE_CONTROL_H + +#include <glib.h> +#include <glib-object.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-stream-control.h" +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_SOURCE_CONTROL \ + (pulse_source_control_get_type ()) +#define PULSE_SOURCE_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_SOURCE_CONTROL, PulseSourceControl)) +#define PULSE_IS_SOURCE_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_SOURCE_CONTROL)) +#define PULSE_SOURCE_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SOURCE_CONTROL, PulseSourceControlClass)) +#define PULSE_IS_SOURCE_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE_CONTROL)) +#define PULSE_SOURCE_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE_CONTROL, PulseSourceControlClass)) + +typedef struct _PulseSourceControlClass PulseSourceControlClass; +typedef struct _PulseSourceControlPrivate PulseSourceControlPrivate; + +struct _PulseSourceControl +{ + PulseStreamControl parent; +}; + +struct _PulseSourceControlClass +{ + PulseStreamControlClass parent_class; +}; + +GType pulse_source_control_get_type (void) G_GNUC_CONST; + +PulseSourceControl *pulse_source_control_new (PulseSource *source, + const pa_source_info *info); + +void pulse_source_control_update (PulseSourceControl *control, + const pa_source_info *info); + +G_END_DECLS + +#endif /* PULSE_SOURCE_CONTROL_H */ diff --git a/backends/pulse/pulse-source-output.c b/backends/pulse/pulse-source-output.c index 6cbd888..69fc3e4 100644 --- a/backends/pulse/pulse-source-output.c +++ b/backends/pulse/pulse-source-output.c @@ -17,56 +17,39 @@ #include <glib.h> #include <glib-object.h> -#include <string.h> - -#include <libmatemixer/matemixer-client-stream.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" -#include "pulse-client-stream.h" #include "pulse-helpers.h" #include "pulse-monitor.h" -#include "pulse-stream.h" #include "pulse-source.h" #include "pulse-source-output.h" +#include "pulse-stream.h" +#include "pulse-stream-control.h" static void pulse_source_output_class_init (PulseSourceOutputClass *klass); static void pulse_source_output_init (PulseSourceOutput *output); -G_DEFINE_TYPE (PulseSourceOutput, pulse_source_output, PULSE_TYPE_CLIENT_STREAM); - -static void pulse_source_output_reload (PulseStream *pstream); - -static gboolean pulse_source_output_set_mute (PulseStream *pstream, - gboolean mute); -static gboolean pulse_source_output_set_volume (PulseStream *pstream, - pa_cvolume *cvolume); +G_DEFINE_TYPE (PulseSourceOutput, pulse_source_output, PULSE_TYPE_STREAM_CONTROL); -static gboolean pulse_source_output_set_parent (PulseClientStream *pclient, - PulseStream *parent); -static gboolean pulse_source_output_remove (PulseClientStream *pclient); - -static PulseMonitor *pulse_source_output_create_monitor (PulseStream *pstream); +static gboolean pulse_source_output_set_mute (PulseStreamControl *psc, + gboolean mute); +static gboolean pulse_source_output_set_volume (PulseStreamControl *psc, + pa_cvolume *cvolume); +static PulseMonitor *pulse_source_output_create_monitor (PulseStreamControl *psc); static void pulse_source_output_class_init (PulseSourceOutputClass *klass) { - PulseStreamClass *stream_class; - PulseClientStreamClass *client_class; - - stream_class = PULSE_STREAM_CLASS (klass); - - stream_class->reload = pulse_source_output_reload; - stream_class->set_mute = pulse_source_output_set_mute; - stream_class->set_volume = pulse_source_output_set_volume; - stream_class->create_monitor = pulse_source_output_create_monitor; - - client_class = PULSE_CLIENT_STREAM_CLASS (klass); + PulseStreamControlClass *control_class; - client_class->set_parent = pulse_source_output_set_parent; - client_class->remove = pulse_source_output_remove; + control_class = PULSE_STREAM_CONTROL_CLASS (klass); + control_class->set_mute = pulse_source_output_set_mute; + control_class->set_volume = pulse_source_output_set_volume; + control_class->create_monitor = pulse_source_output_create_monitor; } static void @@ -74,210 +57,157 @@ pulse_source_output_init (PulseSourceOutput *output) { } -PulseStream * -pulse_source_output_new (PulseConnection *connection, - const pa_source_output_info *info, - PulseStream *parent) +PulseSourceOutput * +pulse_source_output_new (PulseSource *source, + const pa_source_output_info *info) { PulseSourceOutput *output; + gchar *name; + const gchar *prop; + MateMixerAppInfo *app_info = NULL; - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); - g_return_val_if_fail (info != NULL, NULL); - - /* Consider the sink input index as unchanging parameter */ - output = g_object_new (PULSE_TYPE_SOURCE_OUTPUT, - "connection", connection, - "index", info->index, - NULL); - - /* Other data may change at any time, so let's make a use of our update function */ - pulse_source_output_update (PULSE_STREAM (output), info, parent); + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_MUTE_READABLE | + MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_MONITOR; + MateMixerStreamControlRole role = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; - return PULSE_STREAM (output); -} + MateMixerStreamControlMediaRole media_role = MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_UNKNOWN; -gboolean -pulse_source_output_update (PulseStream *pstream, - const pa_source_output_info *info, - PulseStream *parent) -{ - MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | - MATE_MIXER_STREAM_CLIENT; - PulseClientStream *pclient; - const gchar *prop; - const gchar *description = NULL; - gchar *name; + g_return_val_if_fail (PULSE_IS_SOURCE (source), NULL); + g_return_val_if_fail (info != NULL, NULL); - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); - g_return_val_if_fail (info != NULL, FALSE); + /* Many mixer applications query the Pulse client list and use the client + * name here, but we use the name only as an identifier, so let's avoid + * this unnecessary overhead and use a custom name. + * Also make sure to make the name unique by including the PulseAudio index. */ + name = g_strdup_printf ("pulse-input-control-%lu", (gulong) info->index); - pclient = PULSE_CLIENT_STREAM (pstream); +#if PA_CHECK_VERSION(1, 0, 0) + if (info->has_volume) { + flags |= + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (pstream)); + if (info->volume_writable) + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; + } +#else + /* Pre-1.0 PulseAudio does not include the has_volume and volume_writable + * fields, but does include the volume info, so let's give it a try */ + flags |= + MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; +#endif - /* Many other mixer applications query the Pulse client list and use the - * client name here, but we use the name only as an identifier, so let's avoid - * this unnecessary overhead and use a custom name. - * Also make sure to make the name unique by including the Pulse index. */ - name = g_strdup_printf ("pulse-stream-client-input-%lu", (gulong) info->index); + if (info->client != PA_INVALID_INDEX) { + app_info = _mate_mixer_app_info_new (); - pulse_stream_update_name (pstream, name); - g_free (name); + role = MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION; - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); - if (prop != NULL) - pulse_client_stream_update_app_name (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); + if (prop != NULL) + _mate_mixer_app_info_set_name (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); - if (prop != NULL) - pulse_client_stream_update_app_id (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); + if (prop != NULL) + _mate_mixer_app_info_set_id (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); - if (prop != NULL) - pulse_client_stream_update_app_version (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); + if (prop != NULL) + _mate_mixer_app_info_set_version (app_info, prop); - prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); - if (prop != NULL) - pulse_client_stream_update_app_icon (pclient, prop); + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); + if (prop != NULL) + _mate_mixer_app_info_set_icon (app_info, prop); + } prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); - if (prop != NULL) { - MateMixerClientStreamRole role = pulse_convert_media_role_name (prop); + if (prop != NULL) + media_role = pulse_convert_media_role_name (prop); - if (role == MATE_MIXER_CLIENT_STREAM_ROLE_EVENT) { - /* The event description seems to provide much better readable - * description for event streams */ - prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); + output = g_object_new (PULSE_TYPE_SOURCE_OUTPUT, + "name", name, + "label", info->name, + "flags", flags, + "role", role, + "media-role", media_role, + "index", info->index, + "stream", source, + NULL); + g_free (name); - if (G_LIKELY (prop != NULL)) - description = prop; - } - pulse_client_stream_update_role (pclient, role); - } else - pulse_client_stream_update_role (pclient, MATE_MIXER_CLIENT_STREAM_ROLE_NONE); + if (app_info != NULL) + pulse_stream_control_set_app_info (PULSE_STREAM_CONTROL (output), app_info); - if (description == NULL) - description = info->name; + pulse_source_output_update (output, info); + return output; +} - pulse_stream_update_description (pstream, description); +void +pulse_source_output_update (PulseSourceOutput *output, + const pa_source_output_info *info) +{ + g_return_if_fail (PULSE_IS_SOURCE_OUTPUT (output)); + g_return_if_fail (info != NULL); - if (info->client != PA_INVALID_INDEX) - pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_APPLICATION); - else - pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_NO_FLAGS); + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (output)); - if (G_LIKELY (parent != NULL)) { - pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); - flags |= MATE_MIXER_STREAM_HAS_MONITOR; - } else - pulse_client_stream_update_parent (pclient, NULL); + _mate_mixer_stream_control_set_mute (MATE_MIXER_STREAM_CONTROL (output), + info->mute ? TRUE : FALSE); #if PA_CHECK_VERSION(1, 0, 0) - if (info->has_volume) { - flags |= MATE_MIXER_STREAM_HAS_VOLUME; - - if (info->volume_writable) - flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; - } - - flags |= MATE_MIXER_STREAM_HAS_MUTE; - - /* Flags needed before volume */ - pulse_stream_update_flags (pstream, flags); - pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (output), + &info->channel_map); if (info->has_volume) - pulse_stream_update_volume (pstream, &info->volume, 0); + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (output), + &info->volume, + 0); else - pulse_stream_update_volume (pstream, NULL, 0); + pulse_stream_control_set_cvolume (PULSE_STREAM_CONTROL (output), + NULL, + 0); #else - /* Flags needed before volume */ - pulse_stream_update_flags (pstream, flags); + pulse_stream_control_set_channel_map (PULSE_STREAM_CONTROL (output), + &info->channel_map); - pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_volume (pstream, NULL, 0); + pulse_stream_control_set_volume (PULSE_STREAM_CONTROL (output), + &info->volume, + 0); #endif - // XXX needs to fix monitor if parent changes - - g_object_thaw_notify (G_OBJECT (pstream)); - return TRUE; -} - -static void -pulse_source_output_reload (PulseStream *pstream) -{ - g_return_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream)); - - pulse_connection_load_source_output_info (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); + g_object_thaw_notify (G_OBJECT (output)); } static gboolean -pulse_source_output_set_mute (PulseStream *pstream, gboolean mute) +pulse_source_output_set_mute (PulseStreamControl *psc, gboolean mute) { - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (psc), FALSE); - return pulse_connection_set_source_output_mute (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), + return pulse_connection_set_source_output_mute (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + pulse_stream_control_get_index (psc), mute); } static gboolean -pulse_source_output_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +pulse_source_output_set_volume (PulseStreamControl *psc, pa_cvolume *cvolume) { - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (psc), FALSE); g_return_val_if_fail (cvolume != NULL, FALSE); - return pulse_connection_set_source_output_volume (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), + return pulse_connection_set_source_output_volume (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + pulse_stream_control_get_index (psc), cvolume); } -static gboolean -pulse_source_output_set_parent (PulseClientStream *pclient, PulseStream *parent) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pclient), FALSE); - - pstream = PULSE_STREAM (pclient); - - return pulse_connection_move_sink_input (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - pulse_stream_get_index (parent)); -} - -static gboolean -pulse_source_output_remove (PulseClientStream *pclient) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pclient), FALSE); - - pstream = PULSE_STREAM (pclient); - - return pulse_connection_kill_source_output (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); -} - static PulseMonitor * -pulse_source_output_create_monitor (PulseStream *pstream) +pulse_source_output_create_monitor (PulseStreamControl *psc) { - MateMixerStream *parent; - - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), NULL); - - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); - if (G_UNLIKELY (parent == NULL)) { - g_debug ("Not creating monitor for client stream %s: not available", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); - return NULL; - } + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (psc), NULL); - return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), - pulse_stream_get_index (PULSE_STREAM (parent)), + return pulse_connection_create_monitor (PULSE_STREAM_CONTROL_GET_CONNECTION (psc), + PULSE_STREAM_CONTROL_GET_STREAM_INDEX (psc), PA_INVALID_INDEX); } diff --git a/backends/pulse/pulse-source-output.h b/backends/pulse/pulse-source-output.h index 845d439..1037f94 100644 --- a/backends/pulse/pulse-source-output.h +++ b/backends/pulse/pulse-source-output.h @@ -23,48 +23,43 @@ #include <pulse/pulseaudio.h> -#include "pulse-client-stream.h" -#include "pulse-connection.h" -#include "pulse-stream.h" +#include "pulse-stream-control.h" +#include "pulse-types.h" G_BEGIN_DECLS -#define PULSE_TYPE_SOURCE_OUTPUT \ +#define PULSE_TYPE_SOURCE_OUTPUT \ (pulse_source_output_get_type ()) -#define PULSE_SOURCE_OUTPUT(o) \ +#define PULSE_SOURCE_OUTPUT(o) \ (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_SOURCE_OUTPUT, PulseSourceOutput)) -#define PULSE_IS_SOURCE_OUTPUT(o) \ +#define PULSE_IS_SOURCE_OUTPUT(o) \ (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_SOURCE_OUTPUT)) -#define PULSE_SOURCE_OUTPUT_CLASS(k) \ +#define PULSE_SOURCE_OUTPUT_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SOURCE_OUTPUT, PulseSourceOutputClass)) -#define PULSE_IS_SOURCE_OUTPUT_CLASS(k) \ +#define PULSE_IS_SOURCE_OUTPUT_CLASS(k) \ (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE_OUTPUT)) -#define PULSE_SOURCE_OUTPUT_GET_CLASS(o) \ +#define PULSE_SOURCE_OUTPUT_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE_OUTPUT, PulseSourceOutputClass)) -typedef struct _PulseSourceOutput PulseSourceOutput; -typedef struct _PulseSourceOutputClass PulseSourceOutputClass; -typedef struct _PulseSourceOutputPrivate PulseSourceOutputPrivate; +typedef struct _PulseSourceOutputClass PulseSourceOutputClass; struct _PulseSourceOutput { - PulseClientStream parent; + PulseStreamControl parent; }; struct _PulseSourceOutputClass { - PulseClientStreamClass parent_class; + PulseStreamControlClass parent_class; }; -GType pulse_source_output_get_type (void) G_GNUC_CONST; +GType pulse_source_output_get_type (void) G_GNUC_CONST; -PulseStream *pulse_source_output_new (PulseConnection *connection, - const pa_source_output_info *info, - PulseStream *parent); +PulseSourceOutput *pulse_source_output_new (PulseSource *source, + const pa_source_output_info *info); -gboolean pulse_source_output_update (PulseStream *pstream, - const pa_source_output_info *info, - PulseStream *parent); +void pulse_source_output_update (PulseSourceOutput *output, + const pa_source_output_info *info); G_END_DECLS diff --git a/backends/pulse/pulse-source-switch.c b/backends/pulse/pulse-source-switch.c new file mode 100644 index 0000000..178702e --- /dev/null +++ b/backends/pulse/pulse-source-switch.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "pulse-connection.h" +#include "pulse-port.h" +#include "pulse-port-switch.h" +#include "pulse-source-switch.h" +#include "pulse-stream.h" + +static void pulse_source_switch_class_init (PulseSourceSwitchClass *klass); +static void pulse_source_switch_init (PulseSourceSwitch *swtch); + +G_DEFINE_TYPE (PulseSourceSwitch, pulse_source_switch, PULSE_TYPE_PORT_SWITCH) + +static gboolean pulse_source_switch_set_active_port (PulsePortSwitch *swtch, + PulsePort *port); + +static void +pulse_source_switch_class_init (PulseSourceSwitchClass *klass) +{ + PulsePortSwitchClass *switch_class; + + switch_class = PULSE_PORT_SWITCH_CLASS (klass); + switch_class->set_active_port = pulse_source_switch_set_active_port; +} + +static void +pulse_source_switch_init (PulseSourceSwitch *swtch) +{ +} + +PulsePortSwitch * +pulse_source_switch_new (const gchar *name, const gchar *label, PulseSource *source) +{ + return g_object_new (PULSE_TYPE_SOURCE_SWITCH, + "name", name, + "label", label, + "role", MATE_MIXER_SWITCH_ROLE_PORT, + "stream", source, + NULL); +} + +static gboolean +pulse_source_switch_set_active_port (PulsePortSwitch *swtch, PulsePort *port) +{ + PulseStream *stream; + + g_return_val_if_fail (PULSE_IS_SOURCE_SWITCH (swtch), FALSE); + g_return_val_if_fail (PULSE_IS_PORT (port), FALSE); + + stream = pulse_port_switch_get_stream (swtch); + + return pulse_connection_set_source_port (pulse_stream_get_connection (stream), + pulse_stream_get_index (stream), + pulse_port_get_name (port)); +} diff --git a/backends/pulse/pulse-source-switch.h b/backends/pulse/pulse-source-switch.h new file mode 100644 index 0000000..408e872 --- /dev/null +++ b/backends/pulse/pulse-source-switch.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_SOURCE_SWITCH_H +#define PULSE_SOURCE_SWITCH_H + +#include <glib.h> +#include <glib-object.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_SOURCE_SWITCH \ + (pulse_source_switch_get_type ()) +#define PULSE_SOURCE_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_SOURCE_SWITCH, PulseSourceSwitch)) +#define PULSE_IS_SOURCE_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_SOURCE_SWITCH)) +#define PULSE_SOURCE_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SOURCE_SWITCH, PulseSourceSwitchClass)) +#define PULSE_IS_SOURCE_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE_SWITCH)) +#define PULSE_SOURCE_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE_SWITCH, PulseSourceSwitchClass)) + +typedef struct _PulseSourceSwitchClass PulseSourceSwitchClass; +typedef struct _PulseSourceSwitchPrivate PulseSourceSwitchPrivate; + +struct _PulseSourceSwitch +{ + PulsePortSwitch parent; +}; + +struct _PulseSourceSwitchClass +{ + PulsePortSwitchClass parent_class; +}; + +GType pulse_source_switch_get_type (void) G_GNUC_CONST; + +PulsePortSwitch *pulse_source_switch_new (const gchar *name, + const gchar *label, + PulseSource *source); + +G_END_DECLS + +#endif /* PULSE_SOURCE_SWITCH_H */ diff --git a/backends/pulse/pulse-source.c b/backends/pulse/pulse-source.c index e7dce6f..0d095a7 100644 --- a/backends/pulse/pulse-source.c +++ b/backends/pulse/pulse-source.c @@ -16,253 +16,238 @@ */ #include <glib.h> +#include <glib/gi18n.h> #include <glib-object.h> - -#include <libmatemixer/matemixer-port.h> -#include <libmatemixer/matemixer-port-private.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" #include "pulse-device.h" #include "pulse-monitor.h" +#include "pulse-port.h" +#include "pulse-port-switch.h" #include "pulse-stream.h" #include "pulse-source.h" +#include "pulse-source-control.h" +#include "pulse-source-output.h" +#include "pulse-source-switch.h" + +struct _PulseSourcePrivate +{ + GHashTable *outputs; + PulsePortSwitch *pswitch; + PulseSourceControl *control; +}; static void pulse_source_class_init (PulseSourceClass *klass); static void pulse_source_init (PulseSource *source); +static void pulse_source_dispose (GObject *object); +static void pulse_source_finalize (GObject *object); G_DEFINE_TYPE (PulseSource, pulse_source, PULSE_TYPE_STREAM); -static void pulse_source_reload (PulseStream *pstream); +static const GList *pulse_source_list_controls (MateMixerStream *mms); +static const GList *pulse_source_list_switches (MateMixerStream *mms); -static gboolean pulse_source_set_mute (PulseStream *pstream, - gboolean mute); -static gboolean pulse_source_set_volume (PulseStream *pstream, - pa_cvolume *cvolume); -static gboolean pulse_source_set_active_port (PulseStream *pstream, - MateMixerPort *port); +static void +pulse_source_class_init (PulseSourceClass *klass) +{ + GObjectClass *object_class; + MateMixerStreamClass *stream_class; -static PulseMonitor *pulse_source_create_monitor (PulseStream *pstream); + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_source_dispose; + object_class->finalize = pulse_source_finalize; -static void update_ports (PulseStream *pstream, - pa_source_port_info **ports, - pa_source_port_info *active); + stream_class = MATE_MIXER_STREAM_CLASS (klass); + stream_class->list_controls = pulse_source_list_controls; + stream_class->list_switches = pulse_source_list_switches; + + g_type_class_add_private (klass, sizeof (PulseSourcePrivate)); +} static void -pulse_source_class_init (PulseSourceClass *klass) +pulse_source_init (PulseSource *source) +{ + source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, + PULSE_TYPE_SOURCE, + PulseSourcePrivate); + + source->priv->outputs = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); +} + +static void +pulse_source_dispose (GObject *object) { - PulseStreamClass *stream_class; + PulseSource *source; - stream_class = PULSE_STREAM_CLASS (klass); + source = PULSE_SOURCE (object); - stream_class->reload = pulse_source_reload; - stream_class->set_mute = pulse_source_set_mute; - stream_class->set_volume = pulse_source_set_volume; - stream_class->set_active_port = pulse_source_set_active_port; - stream_class->create_monitor = pulse_source_create_monitor; + g_clear_object (&source->priv->control); + g_clear_object (&source->priv->pswitch); + + g_hash_table_remove_all (source->priv->outputs); + + G_OBJECT_CLASS (pulse_source_parent_class)->dispose (object); } static void -pulse_source_init (PulseSource *source) +pulse_source_finalize (GObject *object) { + PulseSource *source; + + source = PULSE_SOURCE (object); + + g_hash_table_unref (source->priv->outputs); + + G_OBJECT_CLASS (pulse_source_parent_class)->finalize (object); } -PulseStream * +PulseSource * pulse_source_new (PulseConnection *connection, const pa_source_info *info, PulseDevice *device) { - PulseStream *stream; + PulseSource *source; g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); g_return_val_if_fail (info != NULL, NULL); - /* Consider the sink index as unchanging parameter */ - stream = g_object_new (PULSE_TYPE_SOURCE, + source = g_object_new (PULSE_TYPE_SOURCE, + "name", info->name, + "label", info->description, + "device", device, + "direction", MATE_MIXER_DIRECTION_INPUT, "connection", connection, "index", info->index, NULL); - /* Other data may change at any time, so let's make a use of our update function */ - pulse_source_update (stream, info, device); + source->priv->control = pulse_source_control_new (source, info); - return stream; -} + if (info->n_ports > 0) { + pa_source_port_info **ports = info->ports; -gboolean -pulse_source_update (PulseStream *pstream, - const pa_source_info *info, - PulseDevice *device) -{ - MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_HAS_MONITOR | - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME | - MATE_MIXER_STREAM_CAN_SUSPEND; - - g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); - g_return_val_if_fail (info != NULL, FALSE); - - /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (pstream)); - - pulse_stream_update_name (pstream, info->name); - pulse_stream_update_description (pstream, info->description); - pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); - - /* Stream state */ - switch (info->state) { - case PA_SOURCE_RUNNING: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_RUNNING); - break; - case PA_SOURCE_IDLE: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); - break; - case PA_SOURCE_SUSPENDED: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); - break; - default: - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_UNKNOWN); - break; - } + /* Create the port switch */ + source->priv->pswitch = pulse_source_switch_new ("port", _("Port"), source); + + while (*ports != NULL) { + pa_source_port_info *p = *ports++; + PulsePort *port; + const gchar *icon = NULL; - /* Build the flag list */ - if (info->flags & PA_SINK_DECIBEL_VOLUME) - flags |= MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME; - if (info->flags & PA_SINK_FLAT_VOLUME) - flags |= MATE_MIXER_STREAM_HAS_FLAT_VOLUME; + /* A port may include an icon but in PulseAudio sink and source ports + * the property is not included, for this reason ports are also read from + * devices where the icons may be present */ + if (device != NULL) { + port = pulse_device_get_port (PULSE_DEVICE (device), p->name); + if (port != NULL) + icon = mate_mixer_switch_option_get_icon (MATE_MIXER_SWITCH_OPTION (port)); + } - /* Flags must be updated before volume */ - pulse_stream_update_flags (pstream, flags); + port = pulse_port_new (p->name, + p->description, + icon, + p->priority); - pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_volume (pstream, &info->volume, info->base_volume); + pulse_port_switch_add_port (source->priv->pswitch, port); - pulse_stream_update_device (pstream, MATE_MIXER_DEVICE (device)); + if (p == info->active_port) + pulse_port_switch_set_active_port (source->priv->pswitch, port); + } - /* Ports must be updated after device */ - if (info->ports != NULL) { - update_ports (pstream, info->ports, info->active_port); + g_debug ("Created port list for source %s", info->name); } - g_object_thaw_notify (G_OBJECT (pstream)); - return TRUE; -} + pulse_source_update (source, info); -static void -pulse_source_reload (PulseStream *pstream) -{ - g_return_if_fail (PULSE_IS_SOURCE (pstream)); - - pulse_connection_load_source_info (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream)); + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (source), + MATE_MIXER_STREAM_CONTROL (source->priv->control)); + return source; } -static gboolean -pulse_source_set_mute (PulseStream *pstream, gboolean mute) +void +pulse_source_add_output (PulseSource *source, const pa_source_output_info *info) { - g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); - - return pulse_connection_set_source_mute (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - mute); + PulseSourceOutput *output; + + /* This function is used for both creating and refreshing source outputs */ + output = g_hash_table_lookup (source->priv->outputs, GINT_TO_POINTER (info->index)); + if (output == NULL) { + const gchar *name; + + output = pulse_source_output_new (source, info); + g_hash_table_insert (source->priv->outputs, + GINT_TO_POINTER (info->index), + output); + + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (output)); + g_signal_emit_by_name (G_OBJECT (source), + "control-added", + name); + } else + pulse_source_output_update (output, info); } -static gboolean -pulse_source_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +void +pulse_source_remove_output (PulseSource *source, guint32 index) { - g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); - g_return_val_if_fail (cvolume != NULL, FALSE); + PulseSourceOutput *output; + const gchar *name; - return pulse_connection_set_source_volume (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - cvolume); -} + output = g_hash_table_lookup (source->priv->outputs, GINT_TO_POINTER (index)); + if G_UNLIKELY (output == NULL) + return; -static gboolean -pulse_source_set_active_port (PulseStream *pstream, MateMixerPort *port) -{ - g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (output)); - return pulse_connection_set_source_port (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - mate_mixer_port_get_name (port)); + g_hash_table_remove (source->priv->outputs, GINT_TO_POINTER (index)); + g_signal_emit_by_name (G_OBJECT (source), + "control-removed", + name); } -static PulseMonitor * -pulse_source_create_monitor (PulseStream *pstream) +void +pulse_source_update (PulseSource *source, + const pa_source_info *info) { - g_return_val_if_fail (PULSE_IS_SOURCE (pstream), NULL); - - return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), - pulse_stream_get_index (pstream), - PA_INVALID_INDEX); + g_return_if_fail (PULSE_IS_SOURCE (source)); + g_return_if_fail (info != NULL); + + /* The switch doesn't allow being unset, PulseAudio should always include + * the active port name if the are any ports available */ + if (info->active_port != NULL) + pulse_port_switch_set_active_port_by_name (source->priv->pswitch, + info->active_port->name); } -static void -update_ports (PulseStream *pstream, - pa_source_port_info **ports, - pa_source_port_info *active) +static const GList * +pulse_source_list_controls (MateMixerStream *mms) { - MateMixerPort *port; - MateMixerDevice *device; - GHashTable *hash; - - hash = pulse_stream_get_ports (pstream); + GList *list; - while (*ports != NULL) { - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - pa_source_port_info *info = *ports; - const gchar *icon = NULL; + g_return_val_if_fail (PULSE_IS_SOURCE (mms), NULL); - device = mate_mixer_stream_get_device (MATE_MIXER_STREAM (pstream)); - if (device != NULL) { - port = mate_mixer_device_get_port (device, info->name); - - if (port != NULL) { - flags = mate_mixer_port_get_flags (port); - icon = mate_mixer_port_get_icon (port); - } - } + // XXX + list = g_hash_table_get_values (PULSE_SOURCE (mms)->priv->outputs); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); -#if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; - else - flags &= ~MATE_MIXER_PORT_AVAILABLE; -#endif - - port = g_hash_table_lookup (hash, info->name); - - if (port != NULL) { - /* Update existing port */ - _mate_mixer_port_update_description (port, info->description); - _mate_mixer_port_update_icon (port, icon); - _mate_mixer_port_update_priority (port, info->priority); - _mate_mixer_port_update_flags (port, flags); - } else { - /* Add previously unknown port to the hash table */ - port = _mate_mixer_port_new (info->name, - info->description, - icon, - info->priority, - flags); - - g_hash_table_insert (hash, g_strdup (info->name), port); - } + return g_list_prepend (list, g_object_ref (PULSE_SOURCE (mms)->priv->control)); +} - ports++; - } +static const GList * +pulse_source_list_switches (MateMixerStream *mms) +{ + g_return_val_if_fail (PULSE_IS_SOURCE (mms), NULL); - /* Active port */ - if (G_LIKELY (active != NULL)) - port = g_hash_table_lookup (hash, active->name); - else - port = NULL; + // XXX + if (PULSE_SOURCE (mms)->priv->pswitch != NULL) + return g_list_prepend (NULL, PULSE_SOURCE (mms)->priv->pswitch); - pulse_stream_update_active_port (pstream, port); + return NULL; } diff --git a/backends/pulse/pulse-source.h b/backends/pulse/pulse-source.h index 9abf6d8..fdc3d5e 100644 --- a/backends/pulse/pulse-source.h +++ b/backends/pulse/pulse-source.h @@ -23,9 +23,8 @@ #include <pulse/pulseaudio.h> -#include "pulse-connection.h" -#include "pulse-device.h" #include "pulse-stream.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -42,12 +41,15 @@ G_BEGIN_DECLS #define PULSE_SOURCE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE, PulseSourceClass)) -typedef struct _PulseSource PulseSource; typedef struct _PulseSourceClass PulseSourceClass; +typedef struct _PulseSourcePrivate PulseSourcePrivate; struct _PulseSource { PulseStream parent; + + /*< private >*/ + PulseSourcePrivate *priv; }; struct _PulseSourceClass @@ -55,15 +57,20 @@ struct _PulseSourceClass PulseStreamClass parent_class; }; -GType pulse_source_get_type (void) G_GNUC_CONST; +GType pulse_source_get_type (void) G_GNUC_CONST; + +PulseSource *pulse_source_new (PulseConnection *connection, + const pa_source_info *info, + PulseDevice *device); + +void pulse_source_add_output (PulseSource *source, + const pa_source_output_info *info); -PulseStream *pulse_source_new (PulseConnection *connection, - const pa_source_info *info, - PulseDevice *device); +void pulse_source_remove_output (PulseSource *source, + guint32 index); -gboolean pulse_source_update (PulseStream *pstream, - const pa_source_info *info, - PulseDevice *device); +void pulse_source_update (PulseSource *source, + const pa_source_info *info); G_END_DECLS diff --git a/backends/pulse/pulse-stream-control.c b/backends/pulse/pulse-stream-control.c new file mode 100644 index 0000000..fa17e6b --- /dev/null +++ b/backends/pulse/pulse-stream-control.c @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-helpers.h" +#include "pulse-monitor.h" +#include "pulse-stream-control.h" + +struct _PulseStreamControlPrivate +{ + guint32 index; + guint volume; + pa_cvolume cvolume; + pa_volume_t base_volume; + pa_channel_map channel_map; + PulseConnection *connection; + PulseMonitor *monitor; + MateMixerAppInfo *app_info; +}; + +enum { + PROP_0, + PROP_INDEX, + PROP_CONNECTION, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +static void pulse_stream_control_class_init (PulseStreamControlClass *klass); + +static void pulse_stream_control_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_stream_control_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void pulse_stream_control_init (PulseStreamControl *control); +static void pulse_stream_control_dispose (GObject *object); +static void pulse_stream_control_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE (PulseStreamControl, pulse_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL) + +static MateMixerAppInfo * pulse_stream_control_get_app_info (MateMixerStreamControl *mmsc); + +static gboolean pulse_stream_control_set_mute (MateMixerStreamControl *mmsc, + gboolean mute); + +static guint pulse_stream_control_get_num_channels (MateMixerStreamControl *mmsc); + +static guint pulse_stream_control_get_volume (MateMixerStreamControl *mmsc); +static gboolean pulse_stream_control_set_volume (MateMixerStreamControl *mmsc, + guint volume); + +static gdouble pulse_stream_control_get_decibel (MateMixerStreamControl *mmsc); +static gboolean pulse_stream_control_set_decibel (MateMixerStreamControl *mmsc, + gdouble decibel); + +static guint pulse_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, + guint channel); +static gboolean pulse_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, + guint channel, + guint volume); + +static gdouble pulse_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, + guint channel); +static gboolean pulse_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, + guint channel, + gdouble decibel); + +static MateMixerChannelPosition pulse_stream_control_get_channel_position (MateMixerStreamControl *mmsc, + guint channel); +static gboolean pulse_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position); + +static gboolean pulse_stream_control_set_balance (MateMixerStreamControl *mmsc, + gfloat balance); + +static gboolean pulse_stream_control_set_fade (MateMixerStreamControl *mmsc, + gfloat fade); + +static gboolean pulse_stream_control_get_monitor_enabled (MateMixerStreamControl *mmsc); +static gboolean pulse_stream_control_set_monitor_enabled (MateMixerStreamControl *mmsc, + gboolean enabled); + +static guint pulse_stream_control_get_min_volume (MateMixerStreamControl *mmsc); +static guint pulse_stream_control_get_max_volume (MateMixerStreamControl *mmsc); +static guint pulse_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); +static guint pulse_stream_control_get_base_volume (MateMixerStreamControl *mmsc); + +static void on_monitor_value (PulseMonitor *monitor, + gdouble value, + PulseStreamControl *control); + +static void set_balance_fade (PulseStreamControl *control); + +static gboolean set_cvolume (PulseStreamControl *control, + pa_cvolume *cvolume); + +static void +pulse_stream_control_class_init (PulseStreamControlClass *klass) +{ + GObjectClass *object_class; + MateMixerStreamControlClass *control_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_stream_control_dispose; + object_class->finalize = pulse_stream_control_finalize; + object_class->get_property = pulse_stream_control_get_property; + object_class->set_property = pulse_stream_control_set_property; + + control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); + control_class->get_app_info = pulse_stream_control_get_app_info; + control_class->set_mute = pulse_stream_control_set_mute; + control_class->get_num_channels = pulse_stream_control_get_num_channels; + control_class->get_volume = pulse_stream_control_get_volume; + control_class->set_volume = pulse_stream_control_set_volume; + control_class->get_decibel = pulse_stream_control_get_decibel; + control_class->set_decibel = pulse_stream_control_set_decibel; + control_class->get_channel_volume = pulse_stream_control_get_channel_volume; + control_class->set_channel_volume = pulse_stream_control_set_channel_volume; + control_class->get_channel_decibel = pulse_stream_control_get_channel_decibel; + control_class->set_channel_decibel = pulse_stream_control_set_channel_decibel; + control_class->get_channel_position = pulse_stream_control_get_channel_position; + control_class->has_channel_position = pulse_stream_control_has_channel_position; + control_class->set_balance = pulse_stream_control_set_balance; + control_class->set_fade = pulse_stream_control_set_fade; + control_class->get_monitor_enabled = pulse_stream_control_get_monitor_enabled; + control_class->set_monitor_enabled = pulse_stream_control_set_monitor_enabled; + control_class->get_min_volume = pulse_stream_control_get_min_volume; + control_class->get_max_volume = pulse_stream_control_get_max_volume; + control_class->get_normal_volume = pulse_stream_control_get_normal_volume; + control_class->get_base_volume = pulse_stream_control_get_base_volume; + + properties[PROP_INDEX] = + g_param_spec_uint ("index", + "Index", + "Index of the stream control", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_CONNECTION] = + g_param_spec_object ("connection", + "Connection", + "PulseAudio connection", + PULSE_TYPE_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_class_add_private (object_class, sizeof (PulseStreamControlPrivate)); +} + +static void +pulse_stream_control_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + PulseStreamControl *control; + + control = PULSE_STREAM_CONTROL (object); + + switch (param_id) { + case PROP_INDEX: + g_value_set_uint (value, control->priv->index); + break; + case PROP_CONNECTION: + g_value_set_object (value, control->priv->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_stream_control_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + PulseStreamControl *control; + + control = PULSE_STREAM_CONTROL (object); + + switch (param_id) { + case PROP_INDEX: + control->priv->index = g_value_get_uint (value); + break; + case PROP_CONNECTION: + control->priv->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_stream_control_init (PulseStreamControl *control) +{ + control->priv = G_TYPE_INSTANCE_GET_PRIVATE (control, + PULSE_TYPE_STREAM_CONTROL, + PulseStreamControlPrivate); + + /* Initialize empty volume and channel map structures, they will be used + * if the stream does not support volume */ + pa_cvolume_init (&control->priv->cvolume); + + pa_channel_map_init (&control->priv->channel_map); +} + +static void +pulse_stream_control_dispose (GObject *object) +{ + PulseStreamControl *control; + + control = PULSE_STREAM_CONTROL (object); + + g_clear_object (&control->priv->monitor); + g_clear_object (&control->priv->connection); + + G_OBJECT_CLASS (pulse_stream_control_parent_class)->dispose (object); +} + +static void +pulse_stream_control_finalize (GObject *object) +{ + PulseStreamControl *control; + + control = PULSE_STREAM_CONTROL (object); + + if (control->priv->app_info != NULL) + _mate_mixer_app_info_free (control->priv->app_info); + + G_OBJECT_CLASS (pulse_stream_control_parent_class)->finalize (object); +} + +guint32 +pulse_stream_control_get_index (PulseStreamControl *control) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), 0); + + return control->priv->index; +} + +PulseConnection * +pulse_stream_control_get_connection (PulseStreamControl *control) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); + + return control->priv->connection; +} + +PulseMonitor * +pulse_stream_control_get_monitor (PulseStreamControl *control) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); + + return control->priv->monitor; +} + +const pa_cvolume * +pulse_stream_control_get_cvolume (PulseStreamControl *control) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); + + return &control->priv->cvolume; +} + +const pa_channel_map * +pulse_stream_control_get_channel_map (PulseStreamControl *control) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); + + return &control->priv->channel_map; +} + +void +pulse_stream_control_set_app_info (PulseStreamControl *control, MateMixerAppInfo *info) +{ + g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); + + if G_UNLIKELY (control->priv->app_info) + _mate_mixer_app_info_free (control->priv->app_info); + + control->priv->app_info = info; +} + +void +pulse_stream_control_set_channel_map (PulseStreamControl *control, const pa_channel_map *map) +{ + MateMixerStreamControlFlags flags; + + g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); + g_return_if_fail (map != NULL); + + flags = mate_mixer_stream_control_get_flags (MATE_MIXER_STREAM_CONTROL (control)); + + if (pa_channel_map_valid (map)) { + if (pa_channel_map_can_balance (map)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + else + flags &= ~MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + + if (pa_channel_map_can_fade (map)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_FADE; + else + flags &= ~MATE_MIXER_STREAM_CONTROL_CAN_FADE; + + control->priv->channel_map = *map; + } else { + flags &= ~(MATE_MIXER_STREAM_CONTROL_CAN_BALANCE | MATE_MIXER_STREAM_CONTROL_CAN_FADE); + + /* If the channel map is not valid, create an empty channel map, which + * also won't validate, but at least we know what it is */ + pa_channel_map_init (&control->priv->channel_map); + } + + _mate_mixer_stream_control_set_flags (MATE_MIXER_STREAM_CONTROL (control), flags); +} + +void +pulse_stream_control_set_cvolume (PulseStreamControl *control, + const pa_cvolume *cvolume, + pa_volume_t base_volume) +{ + MateMixerStreamControlFlags flags; + + g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); + + /* The base volume is not a property */ + control->priv->base_volume = base_volume; + + flags = mate_mixer_stream_control_get_flags (MATE_MIXER_STREAM_CONTROL (control)); + + g_object_freeze_notify (G_OBJECT (control)); + + if (cvolume != NULL && pa_cvolume_valid (cvolume)) { + /* Decibel volume and volume settability flags must be provided by + * the implementation */ + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE; + + if (pa_cvolume_equal (&control->priv->cvolume, cvolume) == 0) { + control->priv->cvolume = *cvolume; + control->priv->volume = (guint) pa_cvolume_max (&control->priv->cvolume); + + g_object_notify (G_OBJECT (control), "volume"); + } + } else { + flags &= ~(MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | + MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL); + + /* If the cvolume is not valid, create an empty cvolume, which also + * won't validate, but at least we know what it is */ + pa_cvolume_init (&control->priv->cvolume); + + if (control->priv->volume != (guint) PA_VOLUME_MUTED) { + control->priv->volume = (guint) PA_VOLUME_MUTED; + + g_object_notify (G_OBJECT (control), "volume"); + } + } + + _mate_mixer_stream_control_set_flags (MATE_MIXER_STREAM_CONTROL (control), flags); + + /* Changing volume may change the balance and fade values as well */ + set_balance_fade (control); + + g_object_thaw_notify (G_OBJECT (control)); +} + +static MateMixerAppInfo * +pulse_stream_control_get_app_info (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), NULL); + + return PULSE_STREAM_CONTROL (mmsc)->priv->app_info; +} + +static gboolean +pulse_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + return PULSE_STREAM_CONTROL_GET_CLASS (mmsc)->set_mute (PULSE_STREAM_CONTROL (mmsc), mute); +} + +static guint +pulse_stream_control_get_num_channels (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), 0); + + return PULSE_STREAM_CONTROL (mmsc)->priv->channel_map.channels; +} + +static guint +pulse_stream_control_get_volume (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); + + return PULSE_STREAM_CONTROL (mmsc)->priv->volume; +} + +static gboolean +pulse_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) +{ + PulseStreamControl *control; + pa_cvolume cvolume; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + cvolume = control->priv->cvolume; + + if (pa_cvolume_scale (&cvolume, (pa_volume_t) volume) == NULL) + return FALSE; + + return set_cvolume (control, &cvolume); +} + +static gdouble +pulse_stream_control_get_decibel (MateMixerStreamControl *mmsc) +{ + gdouble value; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); + + value = pa_sw_volume_to_dB (pulse_stream_control_get_volume (mmsc)); + + /* PA_VOLUME_MUTED is converted to PA_DECIBEL_MININFTY */ + return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; +} + +static gboolean +pulse_stream_control_set_decibel (MateMixerStreamControl *mmsc, gdouble decibel) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + return pulse_stream_control_set_volume (mmsc, + pa_sw_volume_from_dB (decibel)); +} + +static guint +pulse_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel) +{ + PulseStreamControl *control; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->cvolume.channels) + return (guint) PA_VOLUME_MUTED; + + return (guint) control->priv->cvolume.values[channel]; +} + +static gboolean +pulse_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint channel, guint volume) +{ + PulseStreamControl *control; + pa_cvolume cvolume; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->cvolume.channels) + return FALSE; + + /* This is safe, because the cvolume is validated by set_cvolume() */ + cvolume = control->priv->cvolume; + cvolume.values[channel] = (pa_volume_t) volume; + + return set_cvolume (control, &cvolume); +} + +static gdouble +pulse_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint channel) +{ + PulseStreamControl *control; + gdouble value; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->cvolume.channels) + return -MATE_MIXER_INFINITY; + + value = pa_sw_volume_to_dB (control->priv->cvolume.values[channel]); + + return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; +} + +static gboolean +pulse_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, + guint channel, + gdouble decibel) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + return pulse_stream_control_set_channel_volume (mmsc, + channel, + pa_sw_volume_from_dB (decibel)); +} + +static MateMixerChannelPosition +pulse_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel) +{ + PulseStreamControl *control; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), MATE_MIXER_CHANNEL_UNKNOWN); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->channel_map.channels) + return MATE_MIXER_CHANNEL_UNKNOWN; + + return pulse_convert_position_to_pulse (control->priv->channel_map.map[channel]); +} + +static gboolean +pulse_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position) +{ + PulseStreamControl *control; + pa_channel_position_t p; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + + /* Handle invalid position as a special case, otherwise this function would + * return TRUE for e.g. unknown index in a default channel map */ + p = pulse_convert_position_to_pulse (position); + + if (p == PA_CHANNEL_POSITION_INVALID) + return FALSE; + + if (pa_channel_map_has_position (&control->priv->channel_map, p) != 0) + return TRUE; + else + return FALSE; +} + +static gboolean +pulse_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) +{ + PulseStreamControl *control; + pa_cvolume cvolume; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + cvolume = control->priv->cvolume; + + if (pa_cvolume_set_balance (&cvolume, &control->priv->channel_map, balance) == NULL) + return FALSE; + + return set_cvolume (control, &cvolume); +} + +static gboolean +pulse_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade) +{ + PulseStreamControl *control; + pa_cvolume cvolume; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + cvolume = control->priv->cvolume; + + if (pa_cvolume_set_fade (&cvolume, &control->priv->channel_map, fade) == NULL) + return FALSE; + + return set_cvolume (control, &cvolume); +} + +static gboolean +pulse_stream_control_get_monitor_enabled (MateMixerStreamControl *mmsc) +{ + PulseStreamControl *control; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (control->priv->monitor != NULL) + return pulse_monitor_get_enabled (control->priv->monitor); + + return FALSE; +} + +static gboolean +pulse_stream_control_set_monitor_enabled (MateMixerStreamControl *mmsc, gboolean enabled) +{ + PulseStreamControl *control; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (enabled == TRUE) { + if (control->priv->monitor == NULL) { + control->priv->monitor = + PULSE_STREAM_CONTROL_GET_CLASS (control)->create_monitor (control); + + if G_UNLIKELY (control->priv->monitor == NULL) + return FALSE; + + g_signal_connect (G_OBJECT (control->priv->monitor), + "value", + G_CALLBACK (on_monitor_value), + control); + } + } else { + if (control->priv->monitor == NULL) + return FALSE; + } + return pulse_monitor_set_enabled (control->priv->monitor, enabled); +} + +static guint +pulse_stream_control_get_min_volume (MateMixerStreamControl *mmsc) +{ + return (guint) PA_VOLUME_MUTED; +} + +static guint +pulse_stream_control_get_max_volume (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); + + return (guint) PA_VOLUME_UI_MAX; +} + +static guint +pulse_stream_control_get_normal_volume (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); + + return (guint) PA_VOLUME_NORM; +} + +static guint +pulse_stream_control_get_base_volume (MateMixerStreamControl *mmsc) +{ + PulseStreamControl *control; + + g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); + + control = PULSE_STREAM_CONTROL (mmsc); + + if (control->priv->base_volume > 0) + return control->priv->base_volume; + else + return (guint) PA_VOLUME_NORM; +} + +static void +on_monitor_value (PulseMonitor *monitor, gdouble value, PulseStreamControl *control) +{ + g_signal_emit_by_name (G_OBJECT (control), + "monitor-value", + value); +} + +static void +set_balance_fade (PulseStreamControl *control) +{ + gfloat value; + + /* PulseAudio returns the default 0.0f value on error, so skip checking validity + * of the channel map and cvolume */ + value = pa_cvolume_get_balance (&control->priv->cvolume, + &control->priv->channel_map); + + _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), value); + + value = pa_cvolume_get_fade (&control->priv->cvolume, + &control->priv->channel_map); + + _mate_mixer_stream_control_set_fade (MATE_MIXER_STREAM_CONTROL (control), value); +} + +static gboolean +set_cvolume (PulseStreamControl *control, pa_cvolume *cvolume) +{ + PulseStreamControlClass *klass; + + if (pa_cvolume_valid (cvolume) == 0) + return FALSE; + if (pa_cvolume_equal (cvolume, &control->priv->cvolume) != 0) + return TRUE; + + klass = PULSE_STREAM_CONTROL_GET_CLASS (control); + + if (klass->set_volume (control, cvolume) == FALSE) + return FALSE; + + control->priv->cvolume = *cvolume; + control->priv->volume = (guint) pa_cvolume_max (cvolume); + + g_object_notify (G_OBJECT (control), "volume"); + + /* Changing volume may change the balance and fade values as well */ + set_balance_fade (control); + return TRUE; +} diff --git a/backends/pulse/pulse-stream-control.h b/backends/pulse/pulse-stream-control.h new file mode 100644 index 0000000..abc3f98 --- /dev/null +++ b/backends/pulse/pulse-stream-control.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_STREAM_CONTROL_H +#define PULSE_STREAM_CONTROL_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-types.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_STREAM_CONTROL \ + (pulse_stream_control_get_type ()) +#define PULSE_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_STREAM_CONTROL, PulseStreamControl)) +#define PULSE_IS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_STREAM_CONTROL)) +#define PULSE_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_STREAM_CONTROL, PulseStreamControlClass)) +#define PULSE_IS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_STREAM_CONTROL)) +#define PULSE_STREAM_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_STREAM_CONTROL, PulseStreamControlClass)) + +#define PULSE_STREAM_CONTROL_GET_CONNECTION(psc) \ + (pulse_stream_get_connection (PULSE_STREAM (mate_mixer_stream_control_get_stream (MATE_MIXER_STREAM_CONTROL (psc))))) +#define PULSE_STREAM_CONTROL_GET_STREAM_INDEX(psc) \ + (pulse_stream_get_index (PULSE_STREAM (mate_mixer_stream_control_get_stream (MATE_MIXER_STREAM_CONTROL (psc))))) + +typedef struct _PulseStreamControlClass PulseStreamControlClass; +typedef struct _PulseStreamControlPrivate PulseStreamControlPrivate; + +struct _PulseStreamControl +{ + MateMixerStreamControl parent; + + /*< private >*/ + PulseStreamControlPrivate *priv; +}; + +struct _PulseStreamControlClass +{ + MateMixerStreamControlClass parent_class; + + /*< private >*/ + gboolean (*set_mute) (PulseStreamControl *control, + gboolean mute); + gboolean (*set_volume) (PulseStreamControl *control, + pa_cvolume *volume); + + PulseMonitor *(*create_monitor) (PulseStreamControl *control); +}; + +GType pulse_stream_control_get_type (void) G_GNUC_CONST; + +guint32 pulse_stream_control_get_index (PulseStreamControl *control); + +PulseConnection * pulse_stream_control_get_connection (PulseStreamControl *control); +PulseMonitor * pulse_stream_control_get_monitor (PulseStreamControl *control); + +const pa_cvolume * pulse_stream_control_get_cvolume (PulseStreamControl *control); +const pa_channel_map *pulse_stream_control_get_channel_map (PulseStreamControl *control); + +void pulse_stream_control_set_app_info (PulseStreamControl *stream, + MateMixerAppInfo *info); + +void pulse_stream_control_set_channel_map (PulseStreamControl *control, + const pa_channel_map *map); +void pulse_stream_control_set_cvolume (PulseStreamControl *control, + const pa_cvolume *cvolume, + pa_volume_t base_volume); + +G_END_DECLS + +#endif /* PULSE_STREAM_CONTROL_H */ diff --git a/backends/pulse/pulse-stream.c b/backends/pulse/pulse-stream.c index fb738ad..752c3e6 100644 --- a/backends/pulse/pulse-stream.c +++ b/backends/pulse/pulse-stream.c @@ -15,62 +15,34 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ -#include <string.h> #include <glib.h> #include <glib-object.h> - -#include <libmatemixer/matemixer-device.h> -#include <libmatemixer/matemixer-enums.h> -#include <libmatemixer/matemixer-stream.h> -#include <libmatemixer/matemixer-port.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" +#include "pulse-device.h" #include "pulse-helpers.h" #include "pulse-monitor.h" +#include "pulse-port.h" #include "pulse-stream.h" struct _PulseStreamPrivate { - guint32 index; - gchar *name; - gchar *description; - MateMixerDevice *device; - MateMixerStreamFlags flags; - MateMixerStreamState state; - gboolean mute; - guint volume; - pa_cvolume cvolume; - pa_volume_t base_volume; - pa_channel_map channel_map; - gfloat balance; - gfloat fade; - GHashTable *ports; - GList *ports_list; - MateMixerPort *port; - PulseConnection *connection; - PulseMonitor *monitor; - gchar *monitor_name; + guint32 index; + PulseConnection *connection; }; enum { PROP_0, - PROP_NAME, - PROP_DESCRIPTION, - PROP_DEVICE, - PROP_FLAGS, - PROP_STATE, - PROP_MUTE, - PROP_VOLUME, - PROP_BALANCE, - PROP_FADE, - PROP_ACTIVE_PORT, PROP_INDEX, - PROP_CONNECTION + PROP_CONNECTION, + N_PROPERTIES }; -static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface); +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; static void pulse_stream_class_init (PulseStreamClass *klass); @@ -83,174 +55,45 @@ static void pulse_stream_set_property (GObject *object, const GValue *value, GParamSpec *pspec); -static void pulse_stream_init (PulseStream *pstream); +static void pulse_stream_init (PulseStream *stream); static void pulse_stream_dispose (GObject *object); -static void pulse_stream_finalize (GObject *object); - -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseStream, pulse_stream, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM, - mate_mixer_stream_interface_init)) - -static const gchar * pulse_stream_get_name (MateMixerStream *stream); -static const gchar * pulse_stream_get_description (MateMixerStream *stream); -static MateMixerDevice * pulse_stream_get_device (MateMixerStream *stream); -static MateMixerStreamFlags pulse_stream_get_flags (MateMixerStream *stream); -static MateMixerStreamState pulse_stream_get_state (MateMixerStream *stream); - -static gboolean pulse_stream_get_mute (MateMixerStream *stream); -static gboolean pulse_stream_set_mute (MateMixerStream *stream, - gboolean mute); - -static guint pulse_stream_get_num_channels (MateMixerStream *stream); - -static guint pulse_stream_get_volume (MateMixerStream *stream); -static gboolean pulse_stream_set_volume (MateMixerStream *stream, - guint volume); - -static gdouble pulse_stream_get_decibel (MateMixerStream *stream); -static gboolean pulse_stream_set_decibel (MateMixerStream *stream, - gdouble decibel); - -static guint pulse_stream_get_channel_volume (MateMixerStream *stream, - guint channel); -static gboolean pulse_stream_set_channel_volume (MateMixerStream *stream, - guint channel, - guint volume); - -static gdouble pulse_stream_get_channel_decibel (MateMixerStream *stream, - guint channel); -static gboolean pulse_stream_set_channel_decibel (MateMixerStream *stream, - guint channel, - gdouble decibel); - -static MateMixerChannelPosition pulse_stream_get_channel_position (MateMixerStream *stream, - guint channel); -static gboolean pulse_stream_has_channel_position (MateMixerStream *stream, - MateMixerChannelPosition position); - -static gfloat pulse_stream_get_balance (MateMixerStream *stream); -static gboolean pulse_stream_set_balance (MateMixerStream *stream, - gfloat balance); - -static gfloat pulse_stream_get_fade (MateMixerStream *stream); -static gboolean pulse_stream_set_fade (MateMixerStream *stream, - gfloat fade); - -static gboolean pulse_stream_suspend (MateMixerStream *stream); -static gboolean pulse_stream_resume (MateMixerStream *stream); -static gboolean pulse_stream_monitor_start (MateMixerStream *stream); -static void pulse_stream_monitor_stop (MateMixerStream *stream); -static gboolean pulse_stream_monitor_is_running (MateMixerStream *stream); -static gboolean pulse_stream_monitor_set_name (MateMixerStream *stream, - const gchar *name); - -static const GList * pulse_stream_list_ports (MateMixerStream *stream); - -static MateMixerPort * pulse_stream_get_active_port (MateMixerStream *stream); -static gboolean pulse_stream_set_active_port (MateMixerStream *stream, - MateMixerPort *port); - -static guint pulse_stream_get_min_volume (MateMixerStream *stream); -static guint pulse_stream_get_max_volume (MateMixerStream *stream); -static guint pulse_stream_get_normal_volume (MateMixerStream *stream); -static guint pulse_stream_get_base_volume (MateMixerStream *stream); - -static void on_monitor_value (PulseMonitor *monitor, - gdouble value, - PulseStream *pstream); - -static gboolean update_balance_fade (PulseStream *pstream); - -static gboolean set_cvolume (PulseStream *pstream, - pa_cvolume *cvolume); - -static gint compare_ports (gconstpointer a, - gconstpointer b); - -static void -mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) -{ - iface->get_name = pulse_stream_get_name; - iface->get_description = pulse_stream_get_description; - iface->get_device = pulse_stream_get_device; - iface->get_flags = pulse_stream_get_flags; - iface->get_state = pulse_stream_get_state; - iface->get_mute = pulse_stream_get_mute; - iface->set_mute = pulse_stream_set_mute; - iface->get_num_channels = pulse_stream_get_num_channels; - iface->get_volume = pulse_stream_get_volume; - iface->set_volume = pulse_stream_set_volume; - iface->get_decibel = pulse_stream_get_decibel; - iface->set_decibel = pulse_stream_set_decibel; - iface->get_channel_volume = pulse_stream_get_channel_volume; - iface->set_channel_volume = pulse_stream_set_channel_volume; - iface->get_channel_decibel = pulse_stream_get_channel_decibel; - iface->set_channel_decibel = pulse_stream_set_channel_decibel; - iface->get_channel_position = pulse_stream_get_channel_position; - iface->has_channel_position = pulse_stream_has_channel_position; - iface->get_balance = pulse_stream_get_balance; - iface->set_balance = pulse_stream_set_balance; - iface->get_fade = pulse_stream_get_fade; - iface->set_fade = pulse_stream_set_fade; - iface->suspend = pulse_stream_suspend; - iface->resume = pulse_stream_resume; - iface->monitor_start = pulse_stream_monitor_start; - iface->monitor_stop = pulse_stream_monitor_stop; - iface->monitor_is_running = pulse_stream_monitor_is_running; - iface->monitor_set_name = pulse_stream_monitor_set_name; - iface->list_ports = pulse_stream_list_ports; - iface->get_active_port = pulse_stream_get_active_port; - iface->set_active_port = pulse_stream_set_active_port; - iface->get_min_volume = pulse_stream_get_min_volume; - iface->get_max_volume = pulse_stream_get_max_volume; - iface->get_normal_volume = pulse_stream_get_normal_volume; - iface->get_base_volume = pulse_stream_get_base_volume; -} +G_DEFINE_ABSTRACT_TYPE (PulseStream, pulse_stream, MATE_MIXER_TYPE_STREAM) static void pulse_stream_class_init (PulseStreamClass *klass) { - GObjectClass *object_class; + GObjectClass *object_class; + MateMixerStreamClass *stream_class; object_class = G_OBJECT_CLASS (klass); object_class->dispose = pulse_stream_dispose; - object_class->finalize = pulse_stream_finalize; object_class->get_property = pulse_stream_get_property; object_class->set_property = pulse_stream_set_property; - g_object_class_install_property (object_class, - PROP_INDEX, - g_param_spec_uint ("index", - "Index", - "Stream index", - 0, - G_MAXUINT, - 0, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, - PROP_CONNECTION, - g_param_spec_object ("connection", - "Connection", - "PulseAudio connection", - PULSE_TYPE_CONNECTION, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); - - g_object_class_override_property (object_class, PROP_NAME, "name"); - g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); - g_object_class_override_property (object_class, PROP_DEVICE, "device"); - g_object_class_override_property (object_class, PROP_FLAGS, "flags"); - g_object_class_override_property (object_class, PROP_STATE, "state"); - g_object_class_override_property (object_class, PROP_MUTE, "mute"); - g_object_class_override_property (object_class, PROP_VOLUME, "volume"); - g_object_class_override_property (object_class, PROP_BALANCE, "balance"); - g_object_class_override_property (object_class, PROP_FADE, "fade"); - g_object_class_override_property (object_class, PROP_ACTIVE_PORT, "active-port"); + stream_class = MATE_MIXER_STREAM_CLASS (klass); + + properties[PROP_INDEX] = + g_param_spec_uint ("index", + "Index", + "Index of the stream", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_CONNECTION] = + g_param_spec_object ("connection", + "Connection", + "PulseAudio connection", + PULSE_TYPE_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); g_type_class_add_private (object_class, sizeof (PulseStreamPrivate)); } @@ -261,46 +104,16 @@ pulse_stream_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - PulseStream *pstream; + PulseStream *stream; - pstream = PULSE_STREAM (object); + stream = PULSE_STREAM (object); switch (param_id) { - case PROP_NAME: - g_value_set_string (value, pstream->priv->name); - break; - case PROP_DESCRIPTION: - g_value_set_string (value, pstream->priv->description); - break; - case PROP_DEVICE: - g_value_set_object (value, pstream->priv->device); - break; - case PROP_FLAGS: - g_value_set_flags (value, pstream->priv->flags); - break; - case PROP_STATE: - g_value_set_enum (value, pstream->priv->state); - break; - case PROP_MUTE: - g_value_set_boolean (value, pstream->priv->mute); - break; - case PROP_VOLUME: - g_value_set_uint (value, pstream->priv->volume); - break; - case PROP_BALANCE: - g_value_set_float (value, pstream->priv->balance); - break; - case PROP_FADE: - g_value_set_float (value, pstream->priv->fade); - break; - case PROP_ACTIVE_PORT: - g_value_set_object (value, pstream->priv->port); - break; case PROP_INDEX: - g_value_set_uint (value, pstream->priv->index); + g_value_set_uint (value, stream->priv->index); break; case PROP_CONNECTION: - g_value_set_object (value, pstream->priv->connection); + g_value_set_object (value, stream->priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -314,17 +127,16 @@ pulse_stream_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - PulseStream *pstream; + PulseStream *stream; - pstream = PULSE_STREAM (object); + stream = PULSE_STREAM (object); switch (param_id) { case PROP_INDEX: - pstream->priv->index = g_value_get_uint (value); + stream->priv->index = g_value_get_uint (value); break; case PROP_CONNECTION: - /* Construct-only object */ - pstream->priv->connection = g_value_dup_object (value); + stream->priv->connection = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -333,962 +145,51 @@ pulse_stream_set_property (GObject *object, } static void -pulse_stream_init (PulseStream *pstream) +pulse_stream_init (PulseStream *stream) { - pstream->priv = G_TYPE_INSTANCE_GET_PRIVATE (pstream, - PULSE_TYPE_STREAM, - PulseStreamPrivate); - - pstream->priv->ports = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); - - /* Initialize empty volume and channel map structures, they will be used - * if the stream does not support volume */ - pa_cvolume_init (&pstream->priv->cvolume); - - pa_channel_map_init (&pstream->priv->channel_map); + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + PULSE_TYPE_STREAM, + PulseStreamPrivate); } static void pulse_stream_dispose (GObject *object) { - PulseStream *pstream; + PulseStream *stream; - pstream = PULSE_STREAM (object); + stream = PULSE_STREAM (object); - if (pstream->priv->ports_list != NULL) { - g_list_free_full (pstream->priv->ports_list, g_object_unref); - pstream->priv->ports_list = NULL; - } - g_hash_table_remove_all (pstream->priv->ports); - - g_clear_object (&pstream->priv->port); - g_clear_object (&pstream->priv->device); - g_clear_object (&pstream->priv->monitor); - g_clear_object (&pstream->priv->connection); + g_clear_object (&stream->priv->connection); G_OBJECT_CLASS (pulse_stream_parent_class)->dispose (object); } -static void -pulse_stream_finalize (GObject *object) -{ - PulseStream *pstream; - - pstream = PULSE_STREAM (object); - - g_free (pstream->priv->name); - g_free (pstream->priv->description); - g_free (pstream->priv->monitor_name); - - g_hash_table_destroy (pstream->priv->ports); - - G_OBJECT_CLASS (pulse_stream_parent_class)->finalize (object); -} - guint32 -pulse_stream_get_index (PulseStream *pstream) +pulse_stream_get_index (PulseStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (pstream), 0); + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - return pstream->priv->index; + return stream->priv->index; } PulseConnection * -pulse_stream_get_connection (PulseStream *pstream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - - return pstream->priv->connection; -} - -PulseMonitor * -pulse_stream_get_monitor (PulseStream *pstream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - - return pstream->priv->monitor; -} - -const pa_cvolume * -pulse_stream_get_cvolume (PulseStream *pstream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - - return &pstream->priv->cvolume; -} - -const pa_channel_map * -pulse_stream_get_channel_map (PulseStream *pstream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - - return &pstream->priv->channel_map; -} - -GHashTable * -pulse_stream_get_ports (PulseStream *pstream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - - return pstream->priv->ports; -} - -gboolean -pulse_stream_update_name (PulseStream *pstream, const gchar *name) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - /* Allow the name to be NULL */ - if (g_strcmp0 (name, pstream->priv->name) != 0) { - g_free (pstream->priv->name); - pstream->priv->name = g_strdup (name); - - g_object_notify (G_OBJECT (pstream), "name"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_description (PulseStream *pstream, const gchar *description) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - /* Allow the description to be NULL */ - if (g_strcmp0 (description, pstream->priv->description) != 0) { - g_free (pstream->priv->description); - pstream->priv->description = g_strdup (description); - - g_object_notify (G_OBJECT (pstream), "description"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_device (PulseStream *pstream, MateMixerDevice *device) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - if (pstream->priv->device != device) { - g_clear_object (&pstream->priv->device); - - if (G_LIKELY (device != NULL)) - pstream->priv->device = g_object_ref (device); - - g_object_notify (G_OBJECT (pstream), "device"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_flags (PulseStream *pstream, MateMixerStreamFlags flags) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - if (pstream->priv->flags != flags) { - pstream->priv->flags = flags; - - g_object_notify (G_OBJECT (pstream), "flags"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_state (PulseStream *pstream, MateMixerStreamState state) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - if (pstream->priv->state != state) { - pstream->priv->state = state; - - g_object_notify (G_OBJECT (pstream), "state"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_channel_map (PulseStream *pstream, const pa_channel_map *map) -{ - MateMixerStreamFlags flags; - - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - g_return_val_if_fail (map != NULL, FALSE); - - flags = pstream->priv->flags; - - if (pa_channel_map_valid (map)) { - if (pa_channel_map_can_balance (map)) - flags |= MATE_MIXER_STREAM_CAN_BALANCE; - else - flags &= ~MATE_MIXER_STREAM_CAN_BALANCE; - - if (pa_channel_map_can_fade (map)) - flags |= MATE_MIXER_STREAM_CAN_FADE; - else - flags &= ~MATE_MIXER_STREAM_CAN_FADE; - - pstream->priv->channel_map = *map; - } else { - flags &= ~(MATE_MIXER_STREAM_CAN_BALANCE | MATE_MIXER_STREAM_CAN_FADE); - - /* If the channel map is not valid, create an empty channel map, which - * also won't validate, but at least we know what it is */ - pa_channel_map_init (&pstream->priv->channel_map); - } - - pulse_stream_update_flags (pstream, flags); - return TRUE; -} - -gboolean -pulse_stream_update_volume (PulseStream *pstream, - const pa_cvolume *cvolume, - pa_volume_t base_volume) -{ - MateMixerStreamFlags flags; - - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - /* The base volume is not a property */ - pstream->priv->base_volume = base_volume; - - flags = pstream->priv->flags; - - if (cvolume != NULL && pa_cvolume_valid (cvolume)) { - /* Decibel volume and volume settability flags must be provided by - * the implementation */ - flags |= MATE_MIXER_STREAM_HAS_VOLUME; - - if (pa_cvolume_equal (&pstream->priv->cvolume, cvolume) == 0) { - pstream->priv->cvolume = *cvolume; - pstream->priv->volume = (guint) pa_cvolume_max (&pstream->priv->cvolume); - - g_object_notify (G_OBJECT (pstream), "volume"); - } - } else { - flags &= ~(MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME); - - /* If the cvolume is not valid, create an empty cvolume, which also - * won't validate, but at least we know what it is */ - pa_cvolume_init (&pstream->priv->cvolume); - - if (pstream->priv->volume != (guint) PA_VOLUME_MUTED) { - pstream->priv->volume = (guint) PA_VOLUME_MUTED; - - g_object_notify (G_OBJECT (pstream), "volume"); - } - } - - pulse_stream_update_flags (pstream, flags); - - /* Changing volume may change the balance and fade values as well */ - update_balance_fade (pstream); - return TRUE; -} - -gboolean -pulse_stream_update_mute (PulseStream *pstream, gboolean mute) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - - if (pstream->priv->mute != mute) { - pstream->priv->mute = mute; - - g_object_notify (G_OBJECT (pstream), "mute"); - return TRUE; - } - return FALSE; -} - -gboolean -pulse_stream_update_active_port (PulseStream *pstream, MateMixerPort *port) -{ - g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - - if (pstream->priv->port != port) { - if (pstream->priv->port != NULL) - g_clear_object (&pstream->priv->port); - - if (port != NULL) - pstream->priv->port = g_object_ref (port); - - g_object_notify (G_OBJECT (pstream), "active-port"); - return TRUE; - } - return FALSE; -} - -static const gchar * -pulse_stream_get_name (MateMixerStream *stream) +pulse_stream_get_connection (PulseStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - return PULSE_STREAM (stream)->priv->name; + return stream->priv->connection; } -static const gchar * -pulse_stream_get_description (MateMixerStream *stream) +PulseDevice * +pulse_stream_get_device (PulseStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - - return PULSE_STREAM (stream)->priv->description; -} + MateMixerDevice *device; -static MateMixerDevice * -pulse_stream_get_device (MateMixerStream *stream) -{ g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - return PULSE_STREAM (stream)->priv->device; -} - -static MateMixerStreamFlags -pulse_stream_get_flags (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_NO_FLAGS); - - return PULSE_STREAM (stream)->priv->flags; -} - -static MateMixerStreamState -pulse_stream_get_state (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_STATE_UNKNOWN); - - return PULSE_STREAM (stream)->priv->state; -} - -static gboolean -pulse_stream_get_mute (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MUTE)) - return FALSE; - - return pstream->priv->mute; -} - -static gboolean -pulse_stream_set_mute (MateMixerStream *stream, gboolean mute) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MUTE)) - return FALSE; - - if (pstream->priv->mute != mute) { - PulseStreamClass *klass = PULSE_STREAM_GET_CLASS (pstream); - - if (klass->set_mute (pstream, mute) == FALSE) - return FALSE; - - pstream->priv->mute = mute; - - g_object_notify (G_OBJECT (stream), "mute"); - } - return TRUE; -} - -static guint -pulse_stream_get_num_channels (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - - return PULSE_STREAM (stream)->priv->channel_map.channels; -} - -static guint -pulse_stream_get_volume (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) - return (guint) PA_VOLUME_MUTED; - - return pstream->priv->volume; -} - -static gboolean -pulse_stream_set_volume (MateMixerStream *stream, guint volume) -{ - PulseStream *pstream; - pa_cvolume cvolume; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) - return FALSE; - - cvolume = pstream->priv->cvolume; - - if (pa_cvolume_scale (&cvolume, (pa_volume_t) volume) == NULL) - return FALSE; - - return set_cvolume (pstream, &cvolume); -} - -static gdouble -pulse_stream_get_decibel (MateMixerStream *stream) -{ - PulseStream *pstream; - gdouble value; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), -MATE_MIXER_INFINITY); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) - return -MATE_MIXER_INFINITY; - - value = pa_sw_volume_to_dB (pulse_stream_get_volume (stream)); - - /* PA_VOLUME_MUTED is converted to PA_DECIBEL_MININFTY */ - return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; -} - -static gboolean -pulse_stream_set_decibel (MateMixerStream *stream, gdouble decibel) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || - !(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) - return FALSE; - - return pulse_stream_set_volume (stream, pa_sw_volume_from_dB (decibel)); -} - -static guint -pulse_stream_get_channel_volume (MateMixerStream *stream, guint channel) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) - return (guint) PA_VOLUME_MUTED; - - if (channel >= pstream->priv->cvolume.channels) - return (guint) PA_VOLUME_MUTED; - - return (guint) pstream->priv->cvolume.values[channel]; -} - -static gboolean -pulse_stream_set_channel_volume (MateMixerStream *stream, guint channel, guint volume) -{ - PulseStream *pstream; - pa_cvolume cvolume; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) - return FALSE; - - if (channel >= pstream->priv->cvolume.channels) - return FALSE; - - /* This is safe, because the cvolume is validated by set_cvolume() */ - cvolume = pstream->priv->cvolume; - cvolume.values[channel] = (pa_volume_t) volume; - - return set_cvolume (pstream, &cvolume); -} - -static gdouble -pulse_stream_get_channel_decibel (MateMixerStream *stream, guint channel) -{ - PulseStream *pstream; - gdouble value; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), -MATE_MIXER_INFINITY); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) - return -MATE_MIXER_INFINITY; - - if (channel >= pstream->priv->cvolume.channels) - return -MATE_MIXER_INFINITY; - - value = pa_sw_volume_to_dB (pstream->priv->cvolume.values[channel]); - - return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; -} - -static gboolean -pulse_stream_set_channel_decibel (MateMixerStream *stream, - guint channel, - gdouble decibel) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || - !(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) - return FALSE; - - return pulse_stream_set_channel_volume (stream, channel, pa_sw_volume_from_dB (decibel)); -} - -static MateMixerChannelPosition -pulse_stream_get_channel_position (MateMixerStream *stream, guint channel) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_CHANNEL_UNKNOWN); - - pstream = PULSE_STREAM (stream); - - if (channel >= pstream->priv->channel_map.channels) - return MATE_MIXER_CHANNEL_UNKNOWN; - - return pulse_convert_position_to_pulse (pstream->priv->channel_map.map[channel]); -} - -static gboolean -pulse_stream_has_channel_position (MateMixerStream *stream, - MateMixerChannelPosition position) -{ - PulseStream *pstream; - pa_channel_position_t p; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - /* Handle invalid position as a special case, otherwise this function would - * return TRUE for e.g. unknown index in a default channel map */ - p = pulse_convert_position_to_pulse (position); - - if (p == PA_CHANNEL_POSITION_INVALID) - return FALSE; - - if (pa_channel_map_has_position (&pstream->priv->channel_map, p) != 0) - return TRUE; - else - return FALSE; -} - -static gfloat -pulse_stream_get_balance (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0f); - - return PULSE_STREAM (stream)->priv->balance; -} - -static gboolean -pulse_stream_set_balance (MateMixerStream *stream, gfloat balance) -{ - PulseStream *pstream; - pa_cvolume cvolume; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_BALANCE)) - return FALSE; - - cvolume = pstream->priv->cvolume; - - if (pa_cvolume_set_balance (&cvolume, &pstream->priv->channel_map, balance) == NULL) - return FALSE; - - return set_cvolume (pstream, &cvolume); -} - -static gfloat -pulse_stream_get_fade (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0f); - - return PULSE_STREAM (stream)->priv->fade; -} - -static gboolean -pulse_stream_set_fade (MateMixerStream *stream, gfloat fade) -{ - PulseStream *pstream; - pa_cvolume cvolume; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_FADE)) - return FALSE; - - cvolume = pstream->priv->cvolume; - - if (pa_cvolume_set_fade (&cvolume, &pstream->priv->channel_map, fade) == NULL) - return FALSE; - - return set_cvolume (pstream, &cvolume); -} - -static gboolean -pulse_stream_suspend (MateMixerStream *stream) -{ - PulseStream *pstream; - PulseStreamClass *klass; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) - return FALSE; - - if (pstream->priv->state == MATE_MIXER_STREAM_STATE_SUSPENDED) - return FALSE; - - klass = PULSE_STREAM_GET_CLASS (pstream); - - if (klass->suspend (pstream) == FALSE) - return FALSE; - - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); - return TRUE; -} - -static gboolean -pulse_stream_resume (MateMixerStream *stream) -{ - PulseStream *pstream; - PulseStreamClass *klass; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) - return FALSE; - - if (pstream->priv->state != MATE_MIXER_STREAM_STATE_SUSPENDED) - return FALSE; - - klass = PULSE_STREAM_GET_CLASS (pstream); - - if (klass->resume (pstream) == FALSE) - return FALSE; - - /* The state when resumed should be either RUNNING or IDLE, let's assume - * IDLE for now and request an immediate update */ - pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); - - klass->reload (pstream); - return TRUE; -} - -static gboolean -pulse_stream_monitor_start (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MONITOR)) - return FALSE; - - if (pstream->priv->monitor == NULL) { - pstream->priv->monitor = PULSE_STREAM_GET_CLASS (pstream)->create_monitor (pstream); - - if (G_UNLIKELY (pstream->priv->monitor == NULL)) - return FALSE; - - pulse_monitor_set_name (pstream->priv->monitor, - pstream->priv->monitor_name); - - g_signal_connect (G_OBJECT (pstream->priv->monitor), - "value", - G_CALLBACK (on_monitor_value), - pstream); - } - - return pulse_monitor_set_enabled (pstream->priv->monitor, TRUE); -} - -static void -pulse_stream_monitor_stop (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_if_fail (PULSE_IS_STREAM (stream)); - - pstream = PULSE_STREAM (stream); - - if (pstream->priv->monitor != NULL) - pulse_monitor_set_enabled (pstream->priv->monitor, FALSE); -} - -static gboolean -pulse_stream_monitor_is_running (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (pstream->priv->monitor != NULL) - return pulse_monitor_get_enabled (pstream->priv->monitor); - - return FALSE; -} - -static gboolean -pulse_stream_monitor_set_name (MateMixerStream *stream, const gchar *name) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pstream = PULSE_STREAM (stream); - - if (pstream->priv->monitor != NULL) - pulse_monitor_set_name (pstream->priv->monitor, name); - - pstream->priv->monitor_name = g_strdup (name); - return TRUE; -} - -static const GList * -pulse_stream_list_ports (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - - pstream = PULSE_STREAM (stream); - - if (pstream->priv->ports_list == NULL) { - GList *list = g_hash_table_get_values (pstream->priv->ports); - - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - pstream->priv->ports_list = g_list_sort (list, compare_ports); - } - } - - return (const GList *) pstream->priv->ports_list; -} - -static MateMixerPort * -pulse_stream_get_active_port (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - - return PULSE_STREAM (stream)->priv->port; -} - -static gboolean -pulse_stream_set_active_port (MateMixerStream *stream, MateMixerPort *port) -{ - PulseStream *pstream; - PulseStreamClass *klass; - const gchar *name; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - - pstream = PULSE_STREAM (stream); - - /* Make sure the port comes from this stream */ - name = mate_mixer_port_get_name (port); - - if (g_hash_table_lookup (pstream->priv->ports, name) == NULL) { - g_warning ("Port %s does not belong to stream %s", - mate_mixer_port_get_name (port), - mate_mixer_stream_get_name (stream)); - return FALSE; - } - - klass = PULSE_STREAM_GET_CLASS (pstream); - - /* Change the port */ - if (klass->set_active_port (pstream, port) == FALSE) - return FALSE; - - if (pstream->priv->port != NULL) - g_object_unref (pstream->priv->port); - - pstream->priv->port = g_object_ref (port); - - g_object_notify (G_OBJECT (stream), "active-port"); - return TRUE; -} - -static guint -pulse_stream_get_min_volume (MateMixerStream *stream) -{ - return (guint) PA_VOLUME_MUTED; -} - -static guint -pulse_stream_get_max_volume (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) - return (guint) PA_VOLUME_MUTED; - - return (guint) PA_VOLUME_UI_MAX; -} - -static guint -pulse_stream_get_normal_volume (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) - return (guint) PA_VOLUME_MUTED; - - return (guint) PA_VOLUME_NORM; -} - -static guint -pulse_stream_get_base_volume (MateMixerStream *stream) -{ - PulseStream *pstream; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); - - pstream = PULSE_STREAM (stream); - - if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) - return (guint) PA_VOLUME_MUTED; - - if (pstream->priv->base_volume > 0) - return pstream->priv->base_volume; - else - return (guint) PA_VOLUME_NORM; -} - -static void -on_monitor_value (PulseMonitor *monitor, gdouble value, PulseStream *pstream) -{ - g_signal_emit_by_name (G_OBJECT (pstream), - "monitor-value", - value); -} - -static gboolean -update_balance_fade (PulseStream *pstream) -{ - gfloat fade; - gfloat balance; - gboolean changed = FALSE; - - /* The PulseAudio return the default 0.0f values on errors, so skip checking - * validity of the channel map and volume */ - balance = pa_cvolume_get_balance (&pstream->priv->cvolume, - &pstream->priv->channel_map); - - if (pstream->priv->balance != balance) { - pstream->priv->balance = balance; - - g_object_notify (G_OBJECT (pstream), "balance"); - changed = TRUE; - } - - fade = pa_cvolume_get_fade (&pstream->priv->cvolume, - &pstream->priv->channel_map); - - if (pstream->priv->fade != fade) { - pstream->priv->fade = fade; - - g_object_notify (G_OBJECT (pstream), "fade"); - changed = TRUE; - } - - return changed; -} - -static gboolean -set_cvolume (PulseStream *pstream, pa_cvolume *cvolume) -{ - PulseStreamClass *klass; - - if (pa_cvolume_valid (cvolume) == 0) - return FALSE; - if (pa_cvolume_equal (cvolume, &pstream->priv->cvolume) != 0) - return TRUE; - - klass = PULSE_STREAM_GET_CLASS (pstream); - - if (klass->set_volume (pstream, cvolume) == FALSE) - return FALSE; - - pstream->priv->cvolume = *cvolume; - pstream->priv->volume = (guint) pa_cvolume_max (cvolume); - - g_object_notify (G_OBJECT (pstream), "volume"); - - /* Changing volume may change the balance and fade values as well */ - update_balance_fade (pstream); - return TRUE; -} - -static gint -compare_ports (gconstpointer a, gconstpointer b) -{ - MateMixerPort *p1 = MATE_MIXER_PORT (a); - MateMixerPort *p2 = MATE_MIXER_PORT (b); + device = mate_mixer_stream_get_device (MATE_MIXER_STREAM (stream)); + if (device != NULL) + return PULSE_DEVICE (device); - gint ret = (gint) (mate_mixer_port_get_priority (p2) - - mate_mixer_port_get_priority (p1)); - if (ret != 0) - return ret; - else - return strcmp (mate_mixer_port_get_name (p1), - mate_mixer_port_get_name (p2)); + return NULL; } diff --git a/backends/pulse/pulse-stream.h b/backends/pulse/pulse-stream.h index e4c6a00..eafe457 100644 --- a/backends/pulse/pulse-stream.h +++ b/backends/pulse/pulse-stream.h @@ -20,15 +20,11 @@ #include <glib.h> #include <glib-object.h> - -#include <libmatemixer/matemixer-device.h> -#include <libmatemixer/matemixer-port.h> -#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer.h> #include <pulse/pulseaudio.h> -#include "pulse-connection.h" -#include "pulse-monitor.h" +#include "pulse-types.h" G_BEGIN_DECLS @@ -45,13 +41,12 @@ G_BEGIN_DECLS #define PULSE_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_STREAM, PulseStreamClass)) -typedef struct _PulseStream PulseStream; typedef struct _PulseStreamClass PulseStreamClass; typedef struct _PulseStreamPrivate PulseStreamPrivate; struct _PulseStream { - GObject parent; + MateMixerStream parent; /*< private >*/ PulseStreamPrivate *priv; @@ -59,58 +54,15 @@ struct _PulseStream struct _PulseStreamClass { - GObjectClass parent_class; - - /*< private >*/ - /* Virtual table */ - void (*reload) (PulseStream *stream); - - gboolean (*set_mute) (PulseStream *stream, - gboolean mute); - gboolean (*set_volume) (PulseStream *stream, - pa_cvolume *volume); - - gboolean (*set_active_port) (PulseStream *stream, - MateMixerPort *port); - - gboolean (*suspend) (PulseStream *stream); - gboolean (*resume) (PulseStream *stream); - - PulseMonitor *(*create_monitor) (PulseStream *stream); + MateMixerStreamClass parent_class; }; -GType pulse_stream_get_type (void) G_GNUC_CONST; - -guint32 pulse_stream_get_index (PulseStream *pstream); -PulseConnection * pulse_stream_get_connection (PulseStream *pstream); -PulseMonitor * pulse_stream_get_monitor (PulseStream *pstream); -GHashTable * pulse_stream_get_ports (PulseStream *pstream); - -const pa_cvolume * pulse_stream_get_cvolume (PulseStream *pstream); -const pa_channel_map *pulse_stream_get_channel_map (PulseStream *pstream); - -gboolean pulse_stream_update_name (PulseStream *pstream, - const gchar *name); -gboolean pulse_stream_update_description (PulseStream *pstream, - const gchar *description); -gboolean pulse_stream_update_device (PulseStream *pstream, - MateMixerDevice *device); -gboolean pulse_stream_update_flags (PulseStream *pstream, - MateMixerStreamFlags flags); -gboolean pulse_stream_update_state (PulseStream *pstream, - MateMixerStreamState state); - -gboolean pulse_stream_update_channel_map (PulseStream *pstream, - const pa_channel_map *map); -gboolean pulse_stream_update_volume (PulseStream *pstream, - const pa_cvolume *volume, - pa_volume_t base_volume); +GType pulse_stream_get_type (void) G_GNUC_CONST; -gboolean pulse_stream_update_mute (PulseStream *pstream, - gboolean mute); +guint32 pulse_stream_get_index (PulseStream *stream); +PulseConnection *pulse_stream_get_connection (PulseStream *stream); -gboolean pulse_stream_update_active_port (PulseStream *pstream, - MateMixerPort *port); +PulseDevice * pulse_stream_get_device (PulseStream *stream); G_END_DECLS diff --git a/backends/pulse/pulse-types.h b/backends/pulse/pulse-types.h new file mode 100644 index 0000000..c664268 --- /dev/null +++ b/backends/pulse/pulse-types.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_TYPES_H +#define PULSE_TYPES_H + +G_BEGIN_DECLS + +typedef struct _PulseBackend PulseBackend; +typedef struct _PulseConnection PulseConnection; +typedef struct _PulseDevice PulseDevice; +typedef struct _PulseDeviceProfile PulseDeviceProfile; +typedef struct _PulseDeviceSwitch PulseDeviceSwitch; +typedef struct _PulseExtStream PulseExtStream; +typedef struct _PulseMonitor PulseMonitor; +typedef struct _PulsePort PulsePort; +typedef struct _PulsePortSwitch PulsePortSwitch; +typedef struct _PulseSink PulseSink; +typedef struct _PulseSinkControl PulseSinkControl; +typedef struct _PulseSinkInput PulseSinkInput; +typedef struct _PulseSinkSwitch PulseSinkSwitch; +typedef struct _PulseSource PulseSource; +typedef struct _PulseSourceControl PulseSourceControl; +typedef struct _PulseSourceOutput PulseSourceOutput; +typedef struct _PulseSourceSwitch PulseSourceSwitch; +typedef struct _PulseStream PulseStream; +typedef struct _PulseStreamControl PulseStreamControl; + +G_END_DECLS + +#endif /* PULSE_TYPES_H */ |