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