1 /*
2  * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <glib.h>
19 #include <glib-object.h>
20 
21 #include "matemixer.h"
22 #include "matemixer-backend.h"
23 #include "matemixer-backend-module.h"
24 #include "matemixer-context.h"
25 #include "matemixer-enums.h"
26 #include "matemixer-enum-types.h"
27 #include "matemixer-private.h"
28 #include "matemixer-stream.h"
29 
30 /**
31  * SECTION:matemixer-context
32  * @short_description:The main class for interfacing with the library
33  * @include: libmatemixer/matemixer.h
34  *
35  * After the library is initialized, a context should be created to gain
36  * access to a sound system.
37  *
38  * To create a new context, use the mate_mixer_context_new() function.
39  *
40  * The mate_mixer_context_set_backend_type() function can be used to associate
41  * the context with a particular type of sound system. Using this function is
42  * not necessary, by default the context will select a working sound system
43  * backend automatically.
44  *
45  * To connect to a sound system, use mate_mixer_context_open().
46  *
47  * When the connection is established, it is possible to query a list of sound
48  * devices with mate_mixer_context_list_devices() and streams with
49  * mate_mixer_context_list_streams().
50  *
51  * A device represents a hardware or software sound device in the system,
52  * typically a sound card.
53  *
54  * A stream is an input or output channel that may exist either as a part of a
55  * sound device, or independently. Streams essentially serve as containers for
56  * volume controls and switches, for example a sound card with microphone and
57  * line-in connectors may have an input stream containing volume controls for
58  * each of these connectors and possibly a switch allowing to change the active
59  * connector.
60  *
61  * Streams may also exist independently as the sound system may for example
62  * allow audio streaming over a network.
63  *
64  * For a more thorough description of devices and streams, see #MateMixerDevice
65  * and #MateMixerStream.
66  *
67  * Devices and streams (as almost all other elements in the library) may appear
68  * and disappear at any time, for example when external sound cards are plugged
69  * and unplugged. The application should connect to the appropriate signals to
70  * handle these events.
71  */
72 
73 struct _MateMixerContextPrivate
74 {
75     gboolean                backend_chosen;
76     gchar                  *server_address;
77     MateMixerState          state;
78     MateMixerBackend       *backend;
79     MateMixerAppInfo       *app_info;
80     MateMixerBackendType    backend_type;
81     MateMixerBackendModule *module;
82 };
83 
84 enum {
85     PROP_0,
86     PROP_APP_NAME,
87     PROP_APP_ID,
88     PROP_APP_VERSION,
89     PROP_APP_ICON,
90     PROP_SERVER_ADDRESS,
91     PROP_STATE,
92     PROP_DEFAULT_INPUT_STREAM,
93     PROP_DEFAULT_OUTPUT_STREAM,
94     N_PROPERTIES
95 };
96 
97 static GParamSpec *properties[N_PROPERTIES] = { NULL, };
98 
99 enum {
100     DEVICE_ADDED,
101     DEVICE_REMOVED,
102     STREAM_ADDED,
103     STREAM_REMOVED,
104     STORED_CONTROL_ADDED,
105     STORED_CONTROL_REMOVED,
106     N_SIGNALS
107 };
108 
109 static guint signals[N_SIGNALS] = { 0, };
110 
111 static void mate_mixer_context_get_property (GObject               *object,
112                                              guint                  param_id,
113                                              GValue                *value,
114                                              GParamSpec            *pspec);
115 static void mate_mixer_context_set_property (GObject               *object,
116                                              guint                  param_id,
117                                              const GValue          *value,
118                                              GParamSpec            *pspec);
119 
120 static void mate_mixer_context_dispose      (GObject               *object);
121 static void mate_mixer_context_finalize     (GObject               *object);
122 
123 G_DEFINE_TYPE_WITH_PRIVATE (MateMixerContext, mate_mixer_context, G_TYPE_OBJECT);
124 
125 static void     on_backend_state_notify                 (MateMixerBackend *backend,
126                                                          GParamSpec       *pspec,
127                                                          MateMixerContext *context);
128 
129 static void     on_backend_device_added                 (MateMixerBackend *backend,
130                                                          const gchar      *name,
131                                                          MateMixerContext *context);
132 static void     on_backend_device_removed               (MateMixerBackend *backend,
133                                                          const gchar      *name,
134                                                          MateMixerContext *context);
135 
136 static void     on_backend_stream_added                 (MateMixerBackend *backend,
137                                                          const gchar      *name,
138                                                          MateMixerContext *context);
139 static void     on_backend_stream_removed               (MateMixerBackend *backend,
140                                                          const gchar      *name,
141                                                          MateMixerContext *context);
142 
143 static void     on_backend_stored_control_added         (MateMixerBackend *backend,
144                                                          const gchar      *name,
145                                                          MateMixerContext *context);
146 static void     on_backend_stored_control_removed       (MateMixerBackend *backend,
147                                                          const gchar      *name,
148                                                          MateMixerContext *context);
149 
150 static void     on_backend_default_input_stream_notify  (MateMixerBackend *backend,
151                                                          GParamSpec       *pspec,
152                                                          MateMixerContext *context);
153 static void     on_backend_default_output_stream_notify (MateMixerBackend *backend,
154                                                          GParamSpec       *pspec,
155                                                          MateMixerContext *context);
156 
157 static gboolean try_next_backend                        (MateMixerContext *context);
158 
159 static void     change_state                            (MateMixerContext *context,
160                                                          MateMixerState    state);
161 
162 static void     close_context                           (MateMixerContext *context);
163 
164 static void
mate_mixer_context_class_init(MateMixerContextClass * klass)165 mate_mixer_context_class_init (MateMixerContextClass *klass)
166 {
167     GObjectClass *object_class;
168 
169     object_class = G_OBJECT_CLASS (klass);
170     object_class->dispose      = mate_mixer_context_dispose;
171     object_class->finalize     = mate_mixer_context_finalize;
172     object_class->get_property = mate_mixer_context_get_property;
173     object_class->set_property = mate_mixer_context_set_property;
174 
175     /**
176      * MateMixerContext:app-name:
177      *
178      * Localized human readable name of the application.
179      */
180     properties[PROP_APP_NAME] =
181         g_param_spec_string ("app-name",
182                              "App name",
183                              "Application name",
184                              NULL,
185                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
186 
187     /**
188      * MateMixerContext:app-id:
189      *
190      * Identifier of the application (e.g. org.example.app).
191      */
192     properties[PROP_APP_ID] =
193         g_param_spec_string ("app-id",
194                              "App ID",
195                              "Application identifier",
196                              NULL,
197                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
198 
199     /**
200      * MateMixerContext:app-version:
201      *
202      * Version of the application.
203      */
204     properties[PROP_APP_VERSION] =
205         g_param_spec_string ("app-version",
206                              "App version",
207                              "Application version",
208                              NULL,
209                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
210 
211     /**
212      * MateMixerContext:app-icon:
213      *
214      * The XDG icon name of the application.
215      */
216     properties[PROP_APP_ICON] =
217         g_param_spec_string ("app-icon",
218                              "App icon",
219                              "Application XDG icon name",
220                              NULL,
221                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
222 
223     /**
224      * MateMixerContext:server-address:
225      *
226      * Address of the sound server to connect to.
227      *
228      * This feature is only supported by the PulseAudio sound system.
229      * There is no need to specify an address in order to connect to the
230      * local PulseAudio daemon.
231      */
232     properties[PROP_SERVER_ADDRESS] =
233         g_param_spec_string ("server-address",
234                              "Server address",
235                              "Sound server address",
236                              NULL,
237                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
238 
239     /**
240      * MateMixerContext:state:
241      *
242      * The current state of the connection to a sound system.
243      */
244     properties[PROP_STATE] =
245         g_param_spec_enum ("state",
246                            "State",
247                            "Current backend connection state",
248                            MATE_MIXER_TYPE_STATE,
249                            MATE_MIXER_STATE_IDLE,
250                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
251 
252     /**
253      * MateMixerContext:default-input-stream:
254      *
255      * The stream sound input most likely comes from by default.
256      *
257      * See mate_mixer_context_set_default_input_stream() for more information
258      * about changing the default input stream.
259      */
260     properties[PROP_DEFAULT_INPUT_STREAM] =
261         g_param_spec_object ("default-input-stream",
262                              "Default input stream",
263                              "Default input stream",
264                              MATE_MIXER_TYPE_STREAM,
265                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
266 
267     /**
268      * MateMixerContext:default-output-stream:
269      *
270      * The stream sound output is most likely directed to by default.
271      *
272      * See mate_mixer_context_set_default_output_stream() for more information
273      * about changing the default output stream.
274      */
275     properties[PROP_DEFAULT_OUTPUT_STREAM] =
276         g_param_spec_object ("default-output-stream",
277                              "Default output stream",
278                              "Default output stream",
279                              MATE_MIXER_TYPE_STREAM,
280                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
281 
282     g_object_class_install_properties (object_class, N_PROPERTIES, properties);
283 
284     /**
285      * MateMixerContext::device-added:
286      * @context: a #MateMixerContext
287      * @name: name of the added device
288      *
289      * The signal is emitted each time a device is added to the system.
290      *
291      * Use mate_mixer_context_get_device() to get the #MateMixerDevice.
292      *
293      * Note that at the time this signal is emitted, the streams and switches
294      * of the device may not yet be known.
295      */
296     signals[DEVICE_ADDED] =
297         g_signal_new ("device-added",
298                       G_TYPE_FROM_CLASS (object_class),
299                       G_SIGNAL_RUN_FIRST,
300                       G_STRUCT_OFFSET (MateMixerContextClass, device_added),
301                       NULL,
302                       NULL,
303                       g_cclosure_marshal_VOID__STRING,
304                       G_TYPE_NONE,
305                       1,
306                       G_TYPE_STRING);
307 
308     /**
309      * MateMixerContext::device-removed:
310      * @context: a #MateMixerContext
311      * @name: name of the removed device
312      *
313      * The signal is emitted each time a device is removed from the system.
314      *
315      * When this signal is emitted, the device is no longer known to the library,
316      * it will not be included in the device list provided by the
317      * mate_mixer_context_list_devices() function and it is not possible to get
318      * the device with mate_mixer_context_get_device().
319      */
320     signals[DEVICE_REMOVED] =
321         g_signal_new ("device-removed",
322                       G_TYPE_FROM_CLASS (object_class),
323                       G_SIGNAL_RUN_FIRST,
324                       G_STRUCT_OFFSET (MateMixerContextClass, device_removed),
325                       NULL,
326                       NULL,
327                       g_cclosure_marshal_VOID__STRING,
328                       G_TYPE_NONE,
329                       1,
330                       G_TYPE_STRING);
331 
332     /**
333      * MateMixerContext::stream-added:
334      * @context: a #MateMixerContext
335      * @name: name of the added stream
336      *
337      * The signal is emitted each time a stream is added.
338      *
339      * This signal is emitted for streams which belong to devices as well as
340      * streams which do not. If you are only interested in streams of a
341      * specific device, the signal is also available in #MateMixerDevice.
342      *
343      * Note that at the time this signal is emitted, the controls and switches
344      * of the stream may not yet be known.
345      */
346     signals[STREAM_ADDED] =
347         g_signal_new ("stream-added",
348                       G_TYPE_FROM_CLASS (object_class),
349                       G_SIGNAL_RUN_FIRST,
350                       G_STRUCT_OFFSET (MateMixerContextClass, stream_added),
351                       NULL,
352                       NULL,
353                       g_cclosure_marshal_VOID__STRING,
354                       G_TYPE_NONE,
355                       1,
356                       G_TYPE_STRING);
357 
358     /**
359      * MateMixerContext::stream-removed:
360      * @context: a #MateMixerContext
361      * @name: name of the removed stream
362      *
363      * The signal is emitted each time a stream is removed.
364      *
365      * When this signal is emitted, the stream is no longer known to the library,
366      * it will not be included in the stream list provided by the
367      * mate_mixer_context_list_streams() function and it is not possible to get
368      * the stream with mate_mixer_context_get_stream().
369      *
370      * This signal is emitted for streams which belong to devices as well as
371      * streams which do not. If you are only interested in streams of a
372      * specific device, the signal is also available in #MateMixerDevice.
373      */
374     signals[STREAM_REMOVED] =
375         g_signal_new ("stream-removed",
376                       G_TYPE_FROM_CLASS (object_class),
377                       G_SIGNAL_RUN_FIRST,
378                       G_STRUCT_OFFSET (MateMixerContextClass, stream_removed),
379                       NULL,
380                       NULL,
381                       g_cclosure_marshal_VOID__STRING,
382                       G_TYPE_NONE,
383                       1,
384                       G_TYPE_STRING);
385 
386     /**
387      * MateMixerContext::stored-control-added:
388      * @context: a #MateMixerContext
389      * @name: name of the added stored control
390      *
391      * The signal is emitted each time a stored control is added.
392      *
393      * Use mate_mixer_context_get_stored_control() to get the #MateMixerStoredControl.
394      */
395     signals[STORED_CONTROL_ADDED] =
396         g_signal_new ("stored-control-added",
397                       G_TYPE_FROM_CLASS (object_class),
398                       G_SIGNAL_RUN_FIRST,
399                       G_STRUCT_OFFSET (MateMixerContextClass, stored_control_added),
400                       NULL,
401                       NULL,
402                       g_cclosure_marshal_VOID__STRING,
403                       G_TYPE_NONE,
404                       1,
405                       G_TYPE_STRING);
406 
407     /**
408      * MateMixerContext::stored-control-removed:
409      * @context: a #MateMixerContext
410      * @name: name of the removed stored control
411      *
412      * The signal is emitted each time a stored control is removed.
413      *
414      * When this signal is emitted, the stored control is no longer known to the
415      * library, it will not be included in the stream list provided by the
416      * mate_mixer_context_list_stored_controls() function and it is not possible to
417      * get the stored control with mate_mixer_context_get_stored_control().
418      */
419     signals[STORED_CONTROL_REMOVED] =
420         g_signal_new ("stored-control-removed",
421                       G_TYPE_FROM_CLASS (object_class),
422                       G_SIGNAL_RUN_FIRST,
423                       G_STRUCT_OFFSET (MateMixerContextClass, stored_control_removed),
424                       NULL,
425                       NULL,
426                       g_cclosure_marshal_VOID__STRING,
427                       G_TYPE_NONE,
428                       1,
429                       G_TYPE_STRING);
430 }
431 
432 static void
mate_mixer_context_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)433 mate_mixer_context_get_property (GObject    *object,
434                                  guint       param_id,
435                                  GValue     *value,
436                                  GParamSpec *pspec)
437 {
438     MateMixerContext *context;
439 
440     context = MATE_MIXER_CONTEXT (object);
441 
442     switch (param_id) {
443     case PROP_APP_NAME:
444         g_value_set_string (value, mate_mixer_app_info_get_name (context->priv->app_info));
445         break;
446     case PROP_APP_ID:
447         g_value_set_string (value, mate_mixer_app_info_get_id (context->priv->app_info));
448         break;
449     case PROP_APP_VERSION:
450         g_value_set_string (value, mate_mixer_app_info_get_version (context->priv->app_info));
451         break;
452     case PROP_APP_ICON:
453         g_value_set_string (value, mate_mixer_app_info_get_icon (context->priv->app_info));
454         break;
455     case PROP_SERVER_ADDRESS:
456         g_value_set_string (value, context->priv->server_address);
457         break;
458     case PROP_STATE:
459         g_value_set_enum (value, context->priv->state);
460         break;
461     case PROP_DEFAULT_INPUT_STREAM:
462         g_value_set_object (value, mate_mixer_context_get_default_input_stream (context));
463         break;
464     case PROP_DEFAULT_OUTPUT_STREAM:
465         g_value_set_object (value, mate_mixer_context_get_default_output_stream (context));
466         break;
467 
468     default:
469         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
470         break;
471     }
472 }
473 
474 static void
mate_mixer_context_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)475 mate_mixer_context_set_property (GObject      *object,
476                                  guint         param_id,
477                                  const GValue *value,
478                                  GParamSpec   *pspec)
479 {
480     MateMixerContext *context;
481 
482     context = MATE_MIXER_CONTEXT (object);
483 
484     switch (param_id) {
485     case PROP_APP_NAME:
486         mate_mixer_context_set_app_name (context, g_value_get_string (value));
487         break;
488     case PROP_APP_ID:
489         mate_mixer_context_set_app_id (context, g_value_get_string (value));
490         break;
491     case PROP_APP_VERSION:
492         mate_mixer_context_set_app_version (context, g_value_get_string (value));
493         break;
494     case PROP_APP_ICON:
495         mate_mixer_context_set_app_icon (context, g_value_get_string (value));
496         break;
497     case PROP_SERVER_ADDRESS:
498         mate_mixer_context_set_server_address (context, g_value_get_string (value));
499         break;
500     case PROP_DEFAULT_INPUT_STREAM:
501         mate_mixer_context_set_default_input_stream (context, g_value_get_object (value));
502         break;
503     case PROP_DEFAULT_OUTPUT_STREAM:
504         mate_mixer_context_set_default_output_stream (context, g_value_get_object (value));
505         break;
506 
507     default:
508         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
509         break;
510     }
511 }
512 
513 static void
mate_mixer_context_init(MateMixerContext * context)514 mate_mixer_context_init (MateMixerContext *context)
515 {
516     context->priv = mate_mixer_context_get_instance_private (context);
517 
518     context->priv->app_info = _mate_mixer_app_info_new ();
519 }
520 
521 static void
mate_mixer_context_dispose(GObject * object)522 mate_mixer_context_dispose (GObject *object)
523 {
524     MateMixerContext *context;
525 
526     context = MATE_MIXER_CONTEXT (object);
527 
528     close_context (context);
529 
530     G_OBJECT_CLASS (mate_mixer_context_parent_class)->dispose (object);
531 }
532 
533 static void
mate_mixer_context_finalize(GObject * object)534 mate_mixer_context_finalize (GObject *object)
535 {
536     MateMixerContext *context;
537 
538     context = MATE_MIXER_CONTEXT (object);
539 
540     _mate_mixer_app_info_free (context->priv->app_info);
541 
542     g_free (context->priv->server_address);
543 
544     G_OBJECT_CLASS (mate_mixer_context_parent_class)->finalize (object);
545 }
546 
547 /**
548  * mate_mixer_context_new:
549  *
550  * Creates a new #MateMixerContext instance.
551  *
552  * Returns: a new #MateMixerContext instance or %NULL if the library has not
553  * been initialized with mate_mixer_init().
554  */
555 MateMixerContext *
mate_mixer_context_new(void)556 mate_mixer_context_new (void)
557 {
558     if (mate_mixer_is_initialized () == FALSE) {
559         g_critical ("The library has not been initialized");
560         return NULL;
561     }
562 
563     return g_object_new (MATE_MIXER_TYPE_CONTEXT, NULL);
564 }
565 
566 /**
567  * mate_mixer_context_set_backend_type:
568  * @context: a #MateMixerContext
569  * @backend_type: the sound system backend to use
570  *
571  * Makes the #MateMixerContext use the given #MateMixerBackendType.
572  *
573  * By default the backend type is determined automatically. This function can
574  * be used to alter this behavior and make the @context use the selected sound
575  * system.
576  *
577  * Setting the backend type only succeeds if the selected backend module is
578  * available in the target system.
579  *
580  * If you have used this function before and want restore the default automatic
581  * backend type discovery, set the backend type to %MATE_MIXER_BACKEND_UNKNOWN.
582  *
583  * This function must be used before opening a connection to a sound system with
584  * mate_mixer_context_open(), otherwise it will fail.
585  *
586  * Returns: %TRUE on success or %FALSE on failure.
587  */
588 gboolean
mate_mixer_context_set_backend_type(MateMixerContext * context,MateMixerBackendType backend_type)589 mate_mixer_context_set_backend_type (MateMixerContext    *context,
590                                      MateMixerBackendType backend_type)
591 {
592     MateMixerBackendModule     *module;
593     const GList                *modules;
594     const MateMixerBackendInfo *info;
595 
596     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
597 
598     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
599         context->priv->state == MATE_MIXER_STATE_READY)
600         return FALSE;
601 
602     /* Allow setting the backend to unknown to restore the auto-detection */
603     if (backend_type == MATE_MIXER_BACKEND_UNKNOWN) {
604         context->priv->backend_type = backend_type;
605         return TRUE;
606     }
607 
608     modules = _mate_mixer_list_modules ();
609     while (modules != NULL) {
610         module = MATE_MIXER_BACKEND_MODULE (modules->data);
611         info   = mate_mixer_backend_module_get_info (module);
612 
613         if (info->backend_type == backend_type) {
614             context->priv->backend_type = backend_type;
615             return TRUE;
616         }
617         modules = modules->next;
618     }
619     return FALSE;
620 }
621 
622 /**
623  * mate_mixer_context_set_app_name:
624  * @context: a #MateMixerContext
625  * @app_name: the name of your application, or %NULL to unset
626  *
627  * Sets the name of your application. This information may be used when
628  * registering with the sound system.
629  *
630  * This function must be used before opening a connection to a sound system with
631  * mate_mixer_context_open(), otherwise it will fail.
632  *
633  * Returns: %TRUE on success or %FALSE on failure.
634  */
635 gboolean
mate_mixer_context_set_app_name(MateMixerContext * context,const gchar * app_name)636 mate_mixer_context_set_app_name (MateMixerContext *context, const gchar *app_name)
637 {
638     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
639 
640     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
641         context->priv->state == MATE_MIXER_STATE_READY)
642         return FALSE;
643 
644     _mate_mixer_app_info_set_name (context->priv->app_info, app_name);
645 
646     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_APP_NAME]);
647     return TRUE;
648 }
649 
650 /**
651  * mate_mixer_context_set_app_id:
652  * @context: a #MateMixerContext
653  * @app_id: the identifier of your application, or %NULL to unset
654  *
655  * Sets the identifier of your application (e.g. org.example.app). This
656  * information may be used when registering with the sound system.
657  *
658  * This function must be used before opening a connection to a sound system with
659  * mate_mixer_context_open(), otherwise it will fail.
660  *
661  * Returns: %TRUE on success or %FALSE on failure.
662  */
663 gboolean
mate_mixer_context_set_app_id(MateMixerContext * context,const gchar * app_id)664 mate_mixer_context_set_app_id (MateMixerContext *context, const gchar *app_id)
665 {
666     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
667 
668     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
669         context->priv->state == MATE_MIXER_STATE_READY)
670         return FALSE;
671 
672     _mate_mixer_app_info_set_id (context->priv->app_info, app_id);
673 
674     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_APP_ID]);
675     return TRUE;
676 }
677 
678 /**
679  * mate_mixer_context_set_app_version:
680  * @context: a #MateMixerContext
681  * @app_version: the version of your application, or %NULL to unset
682  *
683  * Sets the version of your application. This information may be used when
684  * registering with the sound system.
685  *
686  * This function must be used before opening a connection to a sound system with
687  * mate_mixer_context_open(), otherwise it will fail.
688  *
689  * Returns: %TRUE on success or %FALSE on failure.
690  */
691 gboolean
mate_mixer_context_set_app_version(MateMixerContext * context,const gchar * app_version)692 mate_mixer_context_set_app_version (MateMixerContext *context, const gchar *app_version)
693 {
694     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
695 
696     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
697         context->priv->state == MATE_MIXER_STATE_READY)
698         return FALSE;
699 
700     _mate_mixer_app_info_set_version (context->priv->app_info, app_version);
701 
702     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_APP_VERSION]);
703     return TRUE;
704 }
705 
706 /**
707  * mate_mixer_context_set_app_icon:
708  * @context: a #MateMixerContext
709  * @app_icon: the XDG icon name of your application, or %NULL to unset
710  *
711  * Sets the XDG icon name of your application. This information may be used when
712  * registering with the sound system.
713  *
714  * This function must be used before opening a connection to a sound system with
715  * mate_mixer_context_open(), otherwise it will fail.
716  *
717  * Returns: %TRUE on success or %FALSE on failure.
718  */
719 gboolean
mate_mixer_context_set_app_icon(MateMixerContext * context,const gchar * app_icon)720 mate_mixer_context_set_app_icon (MateMixerContext *context, const gchar *app_icon)
721 {
722     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
723 
724     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
725         context->priv->state == MATE_MIXER_STATE_READY)
726         return FALSE;
727 
728     _mate_mixer_app_info_set_icon (context->priv->app_info, app_icon);
729 
730     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_APP_ICON]);
731     return TRUE;
732 }
733 
734 /**
735  * mate_mixer_context_set_server_address:
736  * @context: a #MateMixerContext
737  * @address: the address of the sound server to connect to or %NULL
738  *
739  * Sets the address of the sound server. This feature is only supported in the
740  * PulseAudio backend. If the address is not set, the default PulseAudio sound
741  * server will be used, which is normally the local daemon.
742  *
743  * This function must be used before opening a connection to a sound system with
744  * mate_mixer_context_open(), otherwise it will fail.
745  *
746  * Returns: %TRUE on success or %FALSE on failure.
747  */
748 gboolean
mate_mixer_context_set_server_address(MateMixerContext * context,const gchar * address)749 mate_mixer_context_set_server_address (MateMixerContext *context, const gchar *address)
750 {
751     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
752 
753     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
754         context->priv->state == MATE_MIXER_STATE_READY)
755         return FALSE;
756 
757     g_free (context->priv->server_address);
758 
759     context->priv->server_address = g_strdup (address);
760 
761     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_SERVER_ADDRESS]);
762     return TRUE;
763 }
764 
765 /**
766  * mate_mixer_context_open:
767  * @context: a #MateMixerContext
768  *
769  * Opens connection to a sound system. Unless the sound system backend type
770  * was chosen manually with mate_mixer_context_set_backend_type(), the library
771  * will find a working sound system automatically.
772  *
773  * This function can complete the operation either synchronously or
774  * asynchronously and it may go through a series of connection
775  * #MateMixerContext:state transitions.
776  *
777  * If this function returns %TRUE, the connection has either been established, or
778  * it hasn't been established yet and the result will be determined asynchronously.
779  * You can differentiate between these two possibilities by checking the connection
780  * #MateMixerContext:state after this function returns.
781  *
782  * The %MATE_MIXER_STATE_READY state indicates that the connection has been
783  * established successfully.
784  *
785  * The %MATE_MIXER_STATE_CONNECTING state is reached when the connection has not been
786  * established yet and you should wait for the state to change to either
787  * %MATE_MIXER_STATE_READY or %MATE_MIXER_STATE_FAILED. It is required to have a main
788  * loop running to allow an asynchronous connection to proceed. The library will use
789  * the thread's default main context for this purpose.
790  *
791  * If this function returns %FALSE, it was not possible to connect to a sound system
792  * and the #MateMixerContext:state will be set to %MATE_MIXER_STATE_FAILED.
793  *
794  * Returns: %TRUE on success or if the result will be determined asynchronously,
795  * or %FALSE on failure.
796  */
797 gboolean
mate_mixer_context_open(MateMixerContext * context)798 mate_mixer_context_open (MateMixerContext *context)
799 {
800     MateMixerBackendModule     *module = NULL;
801     MateMixerState              state;
802     const GList                *modules;
803     const MateMixerBackendInfo *info = NULL;
804 
805     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
806 
807     if (context->priv->state == MATE_MIXER_STATE_CONNECTING ||
808         context->priv->state == MATE_MIXER_STATE_READY)
809         return FALSE;
810 
811     /* We are going to choose the first backend to try. It will be either the one
812      * selected by the application or the one with the highest priority */
813     modules = _mate_mixer_list_modules ();
814 
815     if (context->priv->backend_type != MATE_MIXER_BACKEND_UNKNOWN) {
816         while (modules != NULL) {
817             const MateMixerBackendInfo *info;
818 
819             module = MATE_MIXER_BACKEND_MODULE (modules->data);
820             info   = mate_mixer_backend_module_get_info (module);
821 
822             if (info->backend_type == context->priv->backend_type)
823                 break;
824 
825             module = NULL;
826             modules = modules->next;
827         }
828         if (module == NULL) {
829             /* The selected backend is not available */
830             change_state (context, MATE_MIXER_STATE_FAILED);
831             return FALSE;
832         }
833     } else {
834         /* The highest priority module is on the top of the list */
835         module = MATE_MIXER_BACKEND_MODULE (modules->data);
836     }
837 
838     if (info == NULL)
839         info = mate_mixer_backend_module_get_info (module);
840 
841     context->priv->module  = g_object_ref (module);
842     context->priv->backend = g_object_new (info->g_type, NULL);
843 
844     mate_mixer_backend_set_app_info (context->priv->backend, context->priv->app_info);
845     mate_mixer_backend_set_server_address (context->priv->backend, context->priv->server_address);
846 
847     g_debug ("Trying to open backend %s", info->name);
848 
849     /* This transitional state is always present, it will change to MATE_MIXER_STATE_READY
850      * or MATE_MIXER_STATE_FAILED either instantly or asynchronously */
851     change_state (context, MATE_MIXER_STATE_CONNECTING);
852 
853     /* The backend initialization might fail in case it is known right now that
854      * the backend is unusable */
855     if (mate_mixer_backend_open (context->priv->backend) == FALSE) {
856         if (context->priv->backend_type == MATE_MIXER_BACKEND_UNKNOWN) {
857             /* User didn't request a specific backend, so try another one */
858             return try_next_backend (context);
859         }
860 
861         /* User requested a specific backend and it failed */
862         close_context (context);
863         change_state (context, MATE_MIXER_STATE_FAILED);
864         return FALSE;
865     }
866 
867     state = mate_mixer_backend_get_state (context->priv->backend);
868 
869     if (G_UNLIKELY (state != MATE_MIXER_STATE_READY &&
870                     state != MATE_MIXER_STATE_CONNECTING)) {
871         /* This would be a backend bug */
872         g_warn_if_reached ();
873 
874         if (context->priv->backend_type == MATE_MIXER_BACKEND_UNKNOWN)
875             return try_next_backend (context);
876 
877         close_context (context);
878         change_state (context, MATE_MIXER_STATE_FAILED);
879         return FALSE;
880     }
881 
882     g_signal_connect (G_OBJECT (context->priv->backend),
883                       "notify::state",
884                       G_CALLBACK (on_backend_state_notify),
885                       context);
886 
887     change_state (context, state);
888     return TRUE;
889 }
890 
891 /**
892  * mate_mixer_context_close:
893  * @context: a #MateMixerContext
894  *
895  * Closes an open connection to the sound system. The #MateMixerContext:state
896  * will be set to %MATE_MIXER_STATE_IDLE.
897  */
898 void
mate_mixer_context_close(MateMixerContext * context)899 mate_mixer_context_close (MateMixerContext *context)
900 {
901     g_return_if_fail (MATE_MIXER_IS_CONTEXT (context));
902 
903     close_context (context);
904     change_state (context, MATE_MIXER_STATE_IDLE);
905 }
906 
907 /**
908  * mate_mixer_context_get_state:
909  * @context: a #MateMixerContext
910  *
911  * Gets the state of the @context's connection to a sound system.
912  *
913  * Returns: the connection state.
914  */
915 MateMixerState
mate_mixer_context_get_state(MateMixerContext * context)916 mate_mixer_context_get_state (MateMixerContext *context)
917 {
918     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), MATE_MIXER_STATE_UNKNOWN);
919 
920     return context->priv->state;
921 }
922 
923 /**
924  * mate_mixer_context_get_device:
925  * @context: a #MateMixerContext
926  * @name: a device name
927  *
928  * Gets the device with the given name.
929  *
930  * Returns: a #MateMixerDevice or %NULL if there is no such device.
931  */
932 MateMixerDevice *
mate_mixer_context_get_device(MateMixerContext * context,const gchar * name)933 mate_mixer_context_get_device (MateMixerContext *context, const gchar *name)
934 {
935     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
936     g_return_val_if_fail (name != NULL, NULL);
937 
938     if (context->priv->state != MATE_MIXER_STATE_READY)
939         return NULL;
940 
941     return mate_mixer_backend_get_device (MATE_MIXER_BACKEND (context->priv->backend), name);
942 }
943 
944 /**
945  * mate_mixer_context_get_stream:
946  * @context: a #MateMixerContext
947  * @name: a stream name
948  *
949  * Gets the stream with the given name.
950  *
951  * Returns: a #MateMixerStream or %NULL if there is no such stream.
952  */
953 MateMixerStream *
mate_mixer_context_get_stream(MateMixerContext * context,const gchar * name)954 mate_mixer_context_get_stream (MateMixerContext *context, const gchar *name)
955 {
956     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
957     g_return_val_if_fail (name != NULL, NULL);
958 
959     if (context->priv->state != MATE_MIXER_STATE_READY)
960         return NULL;
961 
962     return mate_mixer_backend_get_stream (MATE_MIXER_BACKEND (context->priv->backend), name);
963 }
964 
965 /**
966  * mate_mixer_context_get_stored_control:
967  * @context: a #MateMixerContext
968  * @name: a stored control name
969  *
970  * Gets the stored control with the given name.
971  *
972  * Returns: a #MateMixerStoredControl or %NULL if there is no such stored control.
973  */
974 MateMixerStoredControl *
mate_mixer_context_get_stored_control(MateMixerContext * context,const gchar * name)975 mate_mixer_context_get_stored_control (MateMixerContext *context, const gchar *name)
976 {
977     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
978     g_return_val_if_fail (name != NULL, NULL);
979 
980     if (context->priv->state != MATE_MIXER_STATE_READY)
981         return NULL;
982 
983     return mate_mixer_backend_get_stored_control (MATE_MIXER_BACKEND (context->priv->backend), name);
984 }
985 
986 /**
987  * mate_mixer_context_list_devices:
988  * @context: a #MateMixerContext
989  *
990  * Gets a list of devices. Each item in the list is a #MateMixerDevice representing
991  * a sound device in the system.
992  *
993  * The returned #GList is owned by the library and may be invalidated at any time.
994  *
995  * Returns: a #GList of all devices in the system or %NULL if there are none or
996  * you are not connected to a sound system.
997  */
998 const GList *
mate_mixer_context_list_devices(MateMixerContext * context)999 mate_mixer_context_list_devices (MateMixerContext *context)
1000 {
1001     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1002 
1003     if (context->priv->state != MATE_MIXER_STATE_READY)
1004         return NULL;
1005 
1006     return mate_mixer_backend_list_devices (MATE_MIXER_BACKEND (context->priv->backend));
1007 }
1008 
1009 /**
1010  * mate_mixer_context_list_streams:
1011  * @context: a #MateMixerContext
1012  *
1013  * Gets a list of streams. Each item in the list is a #MateMixerStream representing
1014  * an input or output stream.
1015  *
1016  * Note that the list will contain streams which belong to devices as well
1017  * as streams which do not. If you are only interested in streams of a specific
1018  * device, use mate_mixer_device_list_streams().
1019  *
1020  * The returned #GList is owned by the library and may be invalidated at any time.
1021  *
1022  * Returns: a #GList of all streams in the system or %NULL if there are none or
1023  * you are not connected to a sound system.
1024  */
1025 const GList *
mate_mixer_context_list_streams(MateMixerContext * context)1026 mate_mixer_context_list_streams (MateMixerContext *context)
1027 {
1028     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1029 
1030     if (context->priv->state != MATE_MIXER_STATE_READY)
1031         return NULL;
1032 
1033     return mate_mixer_backend_list_streams (MATE_MIXER_BACKEND (context->priv->backend));
1034 }
1035 
1036 /**
1037  * mate_mixer_context_list_stored_controls:
1038  * @context: a #MateMixerContext
1039  *
1040  * Gets a list of stored controls. Each item in the list is a #MateMixerStoredControl.
1041  *
1042  * The returned #GList is owned by the library and may be invalidated at any time.
1043  *
1044  * Returns: a #GList of stored controls or %NULL if there are none or you are not
1045  * connected to a sound system.
1046  */
1047 const GList *
mate_mixer_context_list_stored_controls(MateMixerContext * context)1048 mate_mixer_context_list_stored_controls (MateMixerContext *context)
1049 {
1050     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1051 
1052     if (context->priv->state != MATE_MIXER_STATE_READY)
1053         return NULL;
1054 
1055     return mate_mixer_backend_list_stored_controls (MATE_MIXER_BACKEND (context->priv->backend));
1056 }
1057 
1058 /**
1059  * mate_mixer_context_get_default_input_stream:
1060  * @context: a #MateMixerContext
1061  *
1062  * Gets the default input stream. The returned stream is where sound input
1063  * most likely comes from by default.
1064  *
1065  * Returns: a #MateMixerStream or %NULL if there is no default input stream.
1066  */
1067 MateMixerStream *
mate_mixer_context_get_default_input_stream(MateMixerContext * context)1068 mate_mixer_context_get_default_input_stream (MateMixerContext *context)
1069 {
1070     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1071 
1072     if (context->priv->state != MATE_MIXER_STATE_READY)
1073         return NULL;
1074 
1075     return mate_mixer_backend_get_default_input_stream (context->priv->backend);
1076 }
1077 
1078 /**
1079  * mate_mixer_context_set_default_input_stream:
1080  * @context: a #MateMixerContext
1081  * @stream: a #MateMixerStream to set as the default input stream
1082  *
1083  * Changes the default input stream. The given @stream must be an input stream.
1084  *
1085  * Changing the default input stream may not be supported by the sound system.
1086  * Use mate_mixer_context_get_backend_flags() to find out.
1087  *
1088  * Returns: %TRUE on success or %FALSE on failure.
1089  */
1090 gboolean
mate_mixer_context_set_default_input_stream(MateMixerContext * context,MateMixerStream * stream)1091 mate_mixer_context_set_default_input_stream (MateMixerContext *context,
1092                                              MateMixerStream  *stream)
1093 {
1094     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
1095     g_return_val_if_fail (MATE_MIXER_IS_STREAM (stream), FALSE);
1096 
1097     if (context->priv->state != MATE_MIXER_STATE_READY)
1098         return FALSE;
1099 
1100     return mate_mixer_backend_set_default_input_stream (context->priv->backend, stream);
1101 }
1102 
1103 /**
1104  * mate_mixer_context_get_default_output_stream:
1105  * @context: a #MateMixerContext
1106  *
1107  * Gets the default output stream. The returned stream is where sound output is
1108  * most likely directed to by default.
1109  *
1110  * Returns: a #MateMixerStream or %NULL if there are no output streams in
1111  * the system.
1112  */
1113 MateMixerStream *
mate_mixer_context_get_default_output_stream(MateMixerContext * context)1114 mate_mixer_context_get_default_output_stream (MateMixerContext *context)
1115 {
1116     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1117 
1118     if (context->priv->state != MATE_MIXER_STATE_READY)
1119         return NULL;
1120 
1121     return mate_mixer_backend_get_default_output_stream (context->priv->backend);
1122 }
1123 
1124 /**
1125  * mate_mixer_context_set_default_output_stream:
1126  * @context: a #MateMixerContext
1127  * @stream: a #MateMixerStream to set as the default output stream
1128  *
1129  * Changes the default output stream. The given @stream must be an output stream.
1130  *
1131  * Changing the default output stream may not be supported by the sound system.
1132  * Use mate_mixer_context_get_backend_flags() to find out.
1133  *
1134  * Returns: %TRUE on success or %FALSE on failure.
1135  */
1136 gboolean
mate_mixer_context_set_default_output_stream(MateMixerContext * context,MateMixerStream * stream)1137 mate_mixer_context_set_default_output_stream (MateMixerContext *context,
1138                                               MateMixerStream *stream)
1139 {
1140     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), FALSE);
1141     g_return_val_if_fail (MATE_MIXER_IS_STREAM (stream), FALSE);
1142 
1143     if (context->priv->state != MATE_MIXER_STATE_READY)
1144         return FALSE;
1145 
1146     return mate_mixer_backend_set_default_output_stream (context->priv->backend, stream);
1147 }
1148 
1149 /**
1150  * mate_mixer_context_get_backend_name:
1151  * @context: a #MateMixerContext
1152  *
1153  * Gets the name of the currently used sound system backend.
1154  *
1155  * This function will not work until the @context is connected to a sound system.
1156  *
1157  * Returns: the name or %NULL on error.
1158  */
1159 const gchar *
mate_mixer_context_get_backend_name(MateMixerContext * context)1160 mate_mixer_context_get_backend_name (MateMixerContext *context)
1161 {
1162     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), NULL);
1163 
1164     if (context->priv->backend_chosen == FALSE)
1165         return NULL;
1166 
1167     return mate_mixer_backend_module_get_info (context->priv->module)->name;
1168 }
1169 
1170 /**
1171  * mate_mixer_context_get_backend_type:
1172  * @context: a #MateMixerContext
1173  *
1174  * Gets the type of the currently used sound system backend.
1175  *
1176  * This function will not work until the @context is connected to a sound system.
1177  *
1178  * Returns: the backend type or %MATE_MIXER_BACKEND_UNKNOWN on error.
1179  */
1180 MateMixerBackendType
mate_mixer_context_get_backend_type(MateMixerContext * context)1181 mate_mixer_context_get_backend_type (MateMixerContext *context)
1182 {
1183     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), MATE_MIXER_BACKEND_UNKNOWN);
1184 
1185     if (context->priv->backend_chosen == FALSE)
1186         return MATE_MIXER_BACKEND_UNKNOWN;
1187 
1188     return mate_mixer_backend_module_get_info (context->priv->module)->backend_type;
1189 }
1190 
1191 /**
1192  * mate_mixer_context_get_backend_flags:
1193  * @context: a #MateMixerContext
1194  *
1195  * Gets the capability flags of the currently used sound system backend.
1196  *
1197  * This function will not work until the @context is connected to a sound system.
1198  *
1199  * Returns: the capability flags.
1200  */
1201 MateMixerBackendFlags
mate_mixer_context_get_backend_flags(MateMixerContext * context)1202 mate_mixer_context_get_backend_flags (MateMixerContext *context)
1203 {
1204     g_return_val_if_fail (MATE_MIXER_IS_CONTEXT (context), MATE_MIXER_BACKEND_NO_FLAGS);
1205 
1206     if (context->priv->backend_chosen == FALSE)
1207         return MATE_MIXER_BACKEND_NO_FLAGS;
1208 
1209     return mate_mixer_backend_module_get_info (context->priv->module)->backend_flags;
1210 }
1211 
1212 static void
on_backend_state_notify(MateMixerBackend * backend,GParamSpec * pspec,MateMixerContext * context)1213 on_backend_state_notify (MateMixerBackend *backend,
1214                          GParamSpec       *pspec,
1215                          MateMixerContext *context)
1216 {
1217     MateMixerState state = mate_mixer_backend_get_state (backend);
1218 
1219     switch (state) {
1220     case MATE_MIXER_STATE_CONNECTING:
1221         g_debug ("Backend %s changed state to CONNECTING",
1222                  mate_mixer_backend_module_get_info (context->priv->module)->name);
1223 
1224         change_state (context, state);
1225         break;
1226 
1227     case MATE_MIXER_STATE_READY:
1228         g_debug ("Backend %s changed state to READY",
1229                  mate_mixer_backend_module_get_info (context->priv->module)->name);
1230 
1231         change_state (context, state);
1232         break;
1233 
1234     case MATE_MIXER_STATE_FAILED:
1235         g_debug ("Backend %s changed state to FAILED",
1236                  mate_mixer_backend_module_get_info (context->priv->module)->name);
1237 
1238         if (context->priv->backend_type == MATE_MIXER_BACKEND_UNKNOWN) {
1239             /* User didn't request a specific backend, so try another one */
1240             try_next_backend (context);
1241         } else {
1242             /* User requested a specific backend and it failed */
1243             close_context (context);
1244             change_state (context, state);
1245         }
1246         break;
1247 
1248     default:
1249         break;
1250     }
1251 }
1252 
1253 static void
on_backend_device_added(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1254 on_backend_device_added (MateMixerBackend *backend,
1255                          const gchar      *name,
1256                          MateMixerContext *context)
1257 {
1258     g_signal_emit (G_OBJECT (context),
1259                    signals[DEVICE_ADDED],
1260                    0,
1261                    name);
1262 }
1263 
1264 static void
on_backend_device_removed(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1265 on_backend_device_removed (MateMixerBackend *backend,
1266                            const gchar      *name,
1267                            MateMixerContext *context)
1268 {
1269     g_signal_emit (G_OBJECT (context),
1270                    signals[DEVICE_REMOVED],
1271                    0,
1272                    name);
1273 }
1274 
1275 static void
on_backend_stream_added(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1276 on_backend_stream_added (MateMixerBackend *backend,
1277                          const gchar      *name,
1278                          MateMixerContext *context)
1279 {
1280     g_signal_emit (G_OBJECT (context),
1281                    signals[STREAM_ADDED],
1282                    0,
1283                    name);
1284 }
1285 
1286 static void
on_backend_stream_removed(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1287 on_backend_stream_removed (MateMixerBackend *backend,
1288                            const gchar      *name,
1289                            MateMixerContext *context)
1290 {
1291     g_signal_emit (G_OBJECT (context),
1292                    signals[STREAM_REMOVED],
1293                    0,
1294                    name);
1295 }
1296 
1297 static void
on_backend_stored_control_added(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1298 on_backend_stored_control_added (MateMixerBackend *backend,
1299                                  const gchar      *name,
1300                                  MateMixerContext *context)
1301 {
1302     g_signal_emit (G_OBJECT (context),
1303                    signals[STORED_CONTROL_ADDED],
1304                    0,
1305                    name);
1306 }
1307 
1308 static void
on_backend_stored_control_removed(MateMixerBackend * backend,const gchar * name,MateMixerContext * context)1309 on_backend_stored_control_removed (MateMixerBackend *backend,
1310                                    const gchar      *name,
1311                                    MateMixerContext *context)
1312 {
1313     g_signal_emit (G_OBJECT (context),
1314                    signals[STORED_CONTROL_REMOVED],
1315                    0,
1316                    name);
1317 }
1318 
1319 static void
on_backend_default_input_stream_notify(MateMixerBackend * backend,GParamSpec * pspec,MateMixerContext * context)1320 on_backend_default_input_stream_notify (MateMixerBackend *backend,
1321                                         GParamSpec       *pspec,
1322                                         MateMixerContext *context)
1323 {
1324     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_DEFAULT_INPUT_STREAM]);
1325 }
1326 
1327 static void
on_backend_default_output_stream_notify(MateMixerBackend * backend,GParamSpec * pspec,MateMixerContext * context)1328 on_backend_default_output_stream_notify (MateMixerBackend *backend,
1329                                          GParamSpec       *pspec,
1330                                          MateMixerContext *context)
1331 {
1332     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_DEFAULT_OUTPUT_STREAM]);
1333 }
1334 
1335 static gboolean
try_next_backend(MateMixerContext * context)1336 try_next_backend (MateMixerContext *context)
1337 {
1338     MateMixerBackendModule     *module = NULL;
1339     MateMixerState              state;
1340     const GList                *modules;
1341     const MateMixerBackendInfo *info = NULL;
1342 
1343     modules = _mate_mixer_list_modules ();
1344 
1345     while (modules != NULL) {
1346         if (context->priv->module == modules->data) {
1347             /* Found the last tested backend, try to use the next one with a lower
1348              * priority unless we have reached the end of the list */
1349             if (modules->next != NULL)
1350                 module = MATE_MIXER_BACKEND_MODULE (modules->next->data);
1351             break;
1352         }
1353         modules = modules->next;
1354     }
1355     close_context (context);
1356 
1357     if (module == NULL) {
1358         /* We have tried all the modules and all of them failed */
1359         change_state (context, MATE_MIXER_STATE_FAILED);
1360         return FALSE;
1361     }
1362 
1363     info = mate_mixer_backend_module_get_info (module);
1364 
1365     context->priv->module  = g_object_ref (module);
1366     context->priv->backend = g_object_new (info->g_type, NULL);
1367 
1368     mate_mixer_backend_set_app_info (context->priv->backend, context->priv->app_info);
1369     mate_mixer_backend_set_server_address (context->priv->backend, context->priv->server_address);
1370 
1371     g_debug ("Trying to open backend %s", info->name);
1372 
1373     /* Try to open this backend and in case of failure keep trying until we find
1374      * one that works or reach the end of the list */
1375     if (mate_mixer_backend_open (context->priv->backend) == FALSE)
1376         return try_next_backend (context);
1377 
1378     state = mate_mixer_backend_get_state (context->priv->backend);
1379 
1380     if (G_UNLIKELY (state != MATE_MIXER_STATE_READY &&
1381                     state != MATE_MIXER_STATE_CONNECTING)) {
1382         /* This would be a backend bug */
1383         g_warn_if_reached ();
1384 
1385         return try_next_backend (context);
1386     }
1387 
1388     g_signal_connect (G_OBJECT (context->priv->backend),
1389                       "notify::state",
1390                       G_CALLBACK (on_backend_state_notify),
1391                       context);
1392 
1393     change_state (context, state);
1394     return TRUE;
1395 }
1396 
1397 static void
change_state(MateMixerContext * context,MateMixerState state)1398 change_state (MateMixerContext *context, MateMixerState state)
1399 {
1400     if (context->priv->state == state)
1401         return;
1402 
1403     context->priv->state = state;
1404 
1405     if (state == MATE_MIXER_STATE_READY && context->priv->backend_chosen == FALSE) {
1406         /* It is safe to connect to the backend signals after reaching the READY
1407          * state, because the app is not allowed to query any data before that state;
1408          * therefore we won't end up in an inconsistent state by caching a list and
1409          * then missing a notification about a change in the list */
1410         g_signal_connect (G_OBJECT (context->priv->backend),
1411                           "device-added",
1412                           G_CALLBACK (on_backend_device_added),
1413                           context);
1414         g_signal_connect (G_OBJECT (context->priv->backend),
1415                           "device-removed",
1416                           G_CALLBACK (on_backend_device_removed),
1417                           context);
1418         g_signal_connect (G_OBJECT (context->priv->backend),
1419                           "stream-added",
1420                           G_CALLBACK (on_backend_stream_added),
1421                           context);
1422         g_signal_connect (G_OBJECT (context->priv->backend),
1423                           "stream-removed",
1424                           G_CALLBACK (on_backend_stream_removed),
1425                           context);
1426         g_signal_connect (G_OBJECT (context->priv->backend),
1427                           "stored-control-added",
1428                           G_CALLBACK (on_backend_stored_control_added),
1429                           context);
1430         g_signal_connect (G_OBJECT (context->priv->backend),
1431                           "stored-control-removed",
1432                           G_CALLBACK (on_backend_stored_control_removed),
1433                           context);
1434 
1435         g_signal_connect (G_OBJECT (context->priv->backend),
1436                           "notify::default-input-stream",
1437                           G_CALLBACK (on_backend_default_input_stream_notify),
1438                           context);
1439         g_signal_connect (G_OBJECT (context->priv->backend),
1440                           "notify::default-output-stream",
1441                           G_CALLBACK (on_backend_default_output_stream_notify),
1442                           context);
1443 
1444         context->priv->backend_chosen = TRUE;
1445     }
1446 
1447     g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_STATE]);
1448 }
1449 
1450 static void
close_context(MateMixerContext * context)1451 close_context (MateMixerContext *context)
1452 {
1453     if (context->priv->backend != NULL) {
1454         g_signal_handlers_disconnect_by_data (G_OBJECT (context->priv->backend),
1455                                               context);
1456 
1457         mate_mixer_backend_close (context->priv->backend);
1458         g_clear_object (&context->priv->backend);
1459     }
1460 
1461     g_clear_object (&context->priv->module);
1462 
1463     context->priv->backend_chosen = FALSE;
1464 }
1465