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 <string.h>
19 #include <glib.h>
20 #include <glib/gi18n.h>
21 #include <glib-object.h>
22 
23 #include <libmatemixer/matemixer.h>
24 #include <libmatemixer/matemixer-private.h>
25 
26 #include <pulse/pulseaudio.h>
27 
28 #include "pulse-connection.h"
29 #include "pulse-device.h"
30 #include "pulse-device-profile.h"
31 #include "pulse-device-switch.h"
32 #include "pulse-port.h"
33 #include "pulse-stream.h"
34 
35 struct _PulseDevicePrivate
36 {
37     guint32            index;
38     GHashTable        *ports;
39     GHashTable        *streams;
40     GList             *streams_list;
41     PulseConnection   *connection;
42     PulseDeviceSwitch *pswitch;
43     GList             *pswitch_list;
44 };
45 
46 enum {
47     PROP_0,
48     PROP_INDEX,
49     PROP_CONNECTION,
50     N_PROPERTIES
51 };
52 
53 static GParamSpec *properties[N_PROPERTIES] = { NULL, };
54 
55 static void pulse_device_get_property (GObject          *object,
56                                        guint             param_id,
57                                        GValue           *value,
58                                        GParamSpec       *pspec);
59 static void pulse_device_set_property (GObject          *object,
60                                        guint             param_id,
61                                        const GValue     *value,
62                                        GParamSpec       *pspec);
63 
64 static void pulse_device_dispose      (GObject          *object);
65 static void pulse_device_finalize     (GObject          *object);
66 
67 G_DEFINE_TYPE_WITH_PRIVATE (PulseDevice, pulse_device, MATE_MIXER_TYPE_DEVICE)
68 
69 static MateMixerStream *pulse_device_get_stream    (MateMixerDevice    *mmd,
70                                                     const gchar        *name);
71 
72 static const GList *    pulse_device_list_streams  (MateMixerDevice    *mmd);
73 static const GList *    pulse_device_list_switches (MateMixerDevice    *mmd);
74 
75 static void             pulse_device_load          (PulseDevice        *device,
76                                                     const pa_card_info *info);
77 
78 static void             free_list_streams          (PulseDevice        *device);
79 
80 static void
pulse_device_class_init(PulseDeviceClass * klass)81 pulse_device_class_init (PulseDeviceClass *klass)
82 {
83     GObjectClass         *object_class;
84     MateMixerDeviceClass *device_class;
85 
86     object_class = G_OBJECT_CLASS (klass);
87     object_class->dispose      = pulse_device_dispose;
88     object_class->finalize     = pulse_device_finalize;
89     object_class->get_property = pulse_device_get_property;
90     object_class->set_property = pulse_device_set_property;
91 
92     device_class = MATE_MIXER_DEVICE_CLASS (klass);
93     device_class->get_stream         = pulse_device_get_stream;
94     device_class->list_streams       = pulse_device_list_streams;
95     device_class->list_switches      = pulse_device_list_switches;
96 
97     properties[PROP_INDEX] =
98         g_param_spec_uint ("index",
99                            "Index",
100                            "Index of the device",
101                            0,
102                            G_MAXUINT,
103                            0,
104                            G_PARAM_READWRITE |
105                            G_PARAM_CONSTRUCT_ONLY |
106                            G_PARAM_STATIC_STRINGS);
107 
108     properties[PROP_CONNECTION] =
109         g_param_spec_object ("connection",
110                              "Connection",
111                              "PulseAudio connection",
112                              PULSE_TYPE_CONNECTION,
113                              G_PARAM_READWRITE |
114                              G_PARAM_CONSTRUCT_ONLY |
115                              G_PARAM_STATIC_STRINGS);
116 
117     g_object_class_install_properties (object_class, N_PROPERTIES, properties);
118 }
119 
120 static void
pulse_device_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)121 pulse_device_get_property (GObject    *object,
122                            guint       param_id,
123                            GValue     *value,
124                            GParamSpec *pspec)
125 {
126     PulseDevice *device;
127 
128     device = PULSE_DEVICE (object);
129 
130     switch (param_id) {
131     case PROP_INDEX:
132         g_value_set_uint (value, device->priv->index);
133         break;
134     case PROP_CONNECTION:
135         g_value_set_object (value, device->priv->connection);
136         break;
137     default:
138         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
139         break;
140     }
141 }
142 
143 static void
pulse_device_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)144 pulse_device_set_property (GObject      *object,
145                            guint         param_id,
146                            const GValue *value,
147                            GParamSpec   *pspec)
148 {
149     PulseDevice *device;
150 
151     device = PULSE_DEVICE (object);
152 
153     switch (param_id) {
154     case PROP_INDEX:
155         device->priv->index = g_value_get_uint (value);
156         break;
157     case PROP_CONNECTION:
158         device->priv->connection = g_value_dup_object (value);
159         break;
160     default:
161         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
162         break;
163     }
164 }
165 
166 static void
pulse_device_init(PulseDevice * device)167 pulse_device_init (PulseDevice *device)
168 {
169     device->priv = pulse_device_get_instance_private (device);
170 
171     device->priv->ports = g_hash_table_new_full (g_str_hash,
172                                                  g_str_equal,
173                                                  g_free,
174                                                  g_object_unref);
175 
176     device->priv->streams = g_hash_table_new_full (g_str_hash,
177                                                    g_str_equal,
178                                                    g_free,
179                                                    g_object_unref);
180 }
181 
182 static void
pulse_device_dispose(GObject * object)183 pulse_device_dispose (GObject *object)
184 {
185     PulseDevice *device;
186 
187     device = PULSE_DEVICE (object);
188 
189     g_hash_table_remove_all (device->priv->ports);
190     g_hash_table_remove_all (device->priv->streams);
191 
192     g_clear_object (&device->priv->connection);
193     g_clear_object (&device->priv->pswitch);
194 
195     free_list_streams (device);
196 
197     if (device->priv->pswitch_list != NULL) {
198         g_list_free (device->priv->pswitch_list);
199         device->priv->pswitch_list = NULL;
200     }
201     G_OBJECT_CLASS (pulse_device_parent_class)->dispose (object);
202 }
203 
204 static void
pulse_device_finalize(GObject * object)205 pulse_device_finalize (GObject *object)
206 {
207     PulseDevice *device;
208 
209     device = PULSE_DEVICE (object);
210 
211     g_hash_table_unref (device->priv->ports);
212     g_hash_table_unref (device->priv->streams);
213 
214     G_OBJECT_CLASS (pulse_device_parent_class)->finalize (object);
215 }
216 
217 PulseDevice *
pulse_device_new(PulseConnection * connection,const pa_card_info * info)218 pulse_device_new (PulseConnection *connection, const pa_card_info *info)
219 {
220     PulseDevice *device;
221     const gchar *label;
222     const gchar *icon;
223 
224     g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL);
225     g_return_val_if_fail (info != NULL, NULL);
226 
227     label = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_DESCRIPTION);
228     if (G_UNLIKELY (label == NULL))
229         label = info->name;
230 
231     icon = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_ICON_NAME);
232     if (G_UNLIKELY (icon == NULL))
233         icon = "audio-card";
234 
235     /* Consider the device index as unchanging parameter */
236     device = g_object_new (PULSE_TYPE_DEVICE,
237                            "index", info->index,
238                            "connection", connection,
239                            "name", info->name,
240                            "label", label,
241                            "icon", icon,
242                            NULL);
243 
244     pulse_device_load (device, info);
245     pulse_device_update (device, info);
246 
247     return device;
248 }
249 
250 void
pulse_device_update(PulseDevice * device,const pa_card_info * info)251 pulse_device_update (PulseDevice *device, const pa_card_info *info)
252 {
253     g_return_if_fail (PULSE_IS_DEVICE (device));
254     g_return_if_fail (info != NULL);
255 
256     if (G_LIKELY (info->active_profile2 != NULL))
257         pulse_device_switch_set_active_profile_by_name (device->priv->pswitch,
258                                                         info->active_profile2->name);
259 }
260 
261 void
pulse_device_add_stream(PulseDevice * device,PulseStream * stream)262 pulse_device_add_stream (PulseDevice *device, PulseStream *stream)
263 {
264     const gchar *name;
265 
266     g_return_if_fail (PULSE_IS_DEVICE (device));
267     g_return_if_fail (PULSE_IS_STREAM (stream));
268 
269     name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream));
270 
271     g_hash_table_insert (device->priv->streams,
272                          g_strdup (name),
273                          g_object_ref (stream));
274 
275     free_list_streams (device);
276 
277     g_signal_emit_by_name (G_OBJECT (device),
278                            "stream-added",
279                            name);
280 }
281 
282 void
pulse_device_remove_stream(PulseDevice * device,PulseStream * stream)283 pulse_device_remove_stream (PulseDevice *device, PulseStream *stream)
284 {
285     const gchar *name;
286 
287     g_return_if_fail (PULSE_IS_DEVICE (device));
288     g_return_if_fail (PULSE_IS_STREAM (stream));
289 
290     name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream));
291 
292     free_list_streams (device);
293 
294     g_hash_table_remove (device->priv->streams, name);
295     g_signal_emit_by_name (G_OBJECT (device),
296                            "stream-removed",
297                            name);
298 }
299 
300 guint32
pulse_device_get_index(PulseDevice * device)301 pulse_device_get_index (PulseDevice *device)
302 {
303     g_return_val_if_fail (PULSE_IS_DEVICE (device), 0);
304 
305     return device->priv->index;
306 }
307 
308 PulseConnection *
pulse_device_get_connection(PulseDevice * device)309 pulse_device_get_connection (PulseDevice *device)
310 {
311     g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL);
312 
313     return device->priv->connection;
314 }
315 
316 PulsePort *
pulse_device_get_port(PulseDevice * device,const gchar * name)317 pulse_device_get_port (PulseDevice *device, const gchar *name)
318 {
319     g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL);
320     g_return_val_if_fail (name != NULL, NULL);
321 
322     return g_hash_table_lookup (device->priv->ports, name);
323 }
324 
325 static MateMixerStream *
pulse_device_get_stream(MateMixerDevice * mmd,const gchar * name)326 pulse_device_get_stream (MateMixerDevice *mmd, const gchar *name)
327 {
328     g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL);
329     g_return_val_if_fail (name != NULL, NULL);
330 
331     return g_hash_table_lookup (PULSE_DEVICE (mmd)->priv->streams, name);
332 }
333 
334 static const GList *
pulse_device_list_streams(MateMixerDevice * mmd)335 pulse_device_list_streams (MateMixerDevice *mmd)
336 {
337     PulseDevice *device;
338 
339     g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL);
340 
341     device = PULSE_DEVICE (mmd);
342 
343     if (device->priv->streams_list == NULL) {
344         device->priv->streams_list = g_hash_table_get_values (device->priv->streams);
345         if (device->priv->streams_list != NULL)
346             g_list_foreach (device->priv->streams_list, (GFunc) g_object_ref, NULL);
347     }
348     return device->priv->streams_list;
349 }
350 
351 static const GList *
pulse_device_list_switches(MateMixerDevice * mmd)352 pulse_device_list_switches (MateMixerDevice *mmd)
353 {
354     g_return_val_if_fail (PULSE_IS_DEVICE (mmd), NULL);
355 
356     return PULSE_DEVICE (mmd)->priv->pswitch_list;
357 }
358 
359 static void
pulse_device_load(PulseDevice * device,const pa_card_info * info)360 pulse_device_load (PulseDevice *device, const pa_card_info *info)
361 {
362     guint i;
363 
364     for (i = 0; i < info->n_ports; i++) {
365         PulsePort   *port;
366         const gchar *name;
367         const gchar *icon;
368 
369         name = info->ports[i]->name;
370         icon = pa_proplist_gets (info->ports[i]->proplist, "device.icon_name");
371 
372         port = pulse_port_new (name,
373                                info->ports[i]->description,
374                                icon,
375                                info->ports[i]->priority);
376 
377         g_hash_table_insert (device->priv->ports,
378                              g_strdup (name),
379                              port);
380     }
381 
382     /* Create the device profile switch */
383     if (info->n_profiles > 0) {
384         device->priv->pswitch = pulse_device_switch_new ("profile",
385                                                          _("Profile"),
386                                                          device);
387 
388         device->priv->pswitch_list = g_list_prepend (NULL, device->priv->pswitch);
389     }
390 
391     for (i = 0; i < info->n_profiles; i++) {
392         PulseDeviceProfile *profile;
393 
394         pa_card_profile_info2 *p_info = info->profiles2[i];
395 
396         /* PulseAudio 5.0 includes a new pa_card_profile_info2 which only
397          * differs in the new available flag, we use it not to include profiles
398          * which are unavailable */
399         if (p_info->available == 0)
400             continue;
401 
402         profile = pulse_device_profile_new (p_info->name,
403                                             p_info->description,
404                                             p_info->priority);
405 
406         pulse_device_switch_add_profile (device->priv->pswitch, profile);
407 
408         g_object_unref (profile);
409     }
410 }
411 
412 static void
free_list_streams(PulseDevice * device)413 free_list_streams (PulseDevice *device)
414 {
415     if (device->priv->streams_list == NULL)
416         return;
417 
418     g_list_free_full (device->priv->streams_list, g_object_unref);
419 
420     device->priv->streams_list = NULL;
421 }
422