1 /*
2 * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
3 *
4 * This program 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 program 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 program; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <string.h>
19 #include <locale.h>
20 #include <glib.h>
21 #include <glib-object.h>
22
23 #ifdef G_OS_UNIX
24 #include <glib-unix.h>
25 #endif
26
27 #include <libmatemixer/matemixer.h>
28
29 static MateMixerContext *context;
30 static GMainLoop *mainloop;
31
32 /* Convert MateMixerStreamControlRole constant to a string */
33 static const gchar *
get_role_string(MateMixerStreamControlRole role)34 get_role_string (MateMixerStreamControlRole role)
35 {
36 switch (role) {
37 case MATE_MIXER_STREAM_CONTROL_ROLE_MASTER:
38 return "Master";
39 case MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION:
40 return "Application";
41 case MATE_MIXER_STREAM_CONTROL_ROLE_PCM:
42 return "PCM";
43 case MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER:
44 return "Speaker";
45 case MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE:
46 return "Microphone";
47 case MATE_MIXER_STREAM_CONTROL_ROLE_PORT:
48 return "Port";
49 case MATE_MIXER_STREAM_CONTROL_ROLE_BOOST:
50 return "Boost";
51 case MATE_MIXER_STREAM_CONTROL_ROLE_BASS:
52 return "Bass";
53 case MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE:
54 return "Treble";
55 case MATE_MIXER_STREAM_CONTROL_ROLE_CD:
56 return "CD";
57 case MATE_MIXER_STREAM_CONTROL_ROLE_VIDEO:
58 return "Video";
59 case MATE_MIXER_STREAM_CONTROL_ROLE_MUSIC:
60 return "Music";
61 default:
62 return "Unknown";
63 }
64 }
65
66 /* Convert MateMixerStreamControlMediaRole constant to a string */
67 static const gchar *
get_media_role_string(MateMixerStreamControlMediaRole role)68 get_media_role_string (MateMixerStreamControlMediaRole role)
69 {
70 switch (role) {
71 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_VIDEO:
72 return "Video";
73 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_MUSIC:
74 return "Music";
75 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_GAME:
76 return "Game";
77 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT:
78 return "Event";
79 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_PHONE:
80 return "Phone";
81 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ANIMATION:
82 return "Animation";
83 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_PRODUCTION:
84 return "Production";
85 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_A11Y:
86 return "A11y";
87 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_TEST:
88 return "Test";
89 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ABSTRACT:
90 return "Abstract";
91 case MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_FILTER:
92 return "Filter";
93 default:
94 return "Unknown";
95 }
96 }
97
98 /* Convert MateMixerDeviceSwitchRole constant to a string */
99 static const gchar *
get_device_switch_role_string(MateMixerDeviceSwitchRole role)100 get_device_switch_role_string (MateMixerDeviceSwitchRole role)
101 {
102 switch (role) {
103 case MATE_MIXER_DEVICE_SWITCH_ROLE_PROFILE:
104 return "Device Profile";
105 default:
106 return "Unknown";
107 }
108 }
109
110 /* Convert MateMixerStreamSwitchRole constant to a string */
111 static const gchar *
get_stream_switch_role_string(MateMixerStreamSwitchRole role)112 get_stream_switch_role_string (MateMixerStreamSwitchRole role)
113 {
114 switch (role) {
115 case MATE_MIXER_STREAM_SWITCH_ROLE_PORT:
116 return "Port";
117 case MATE_MIXER_STREAM_SWITCH_ROLE_BOOST:
118 return "Boost";
119 default:
120 return "Unknown";
121 }
122 }
123
124 /* Convert MateMixerDirection constant to a string */
125 static const gchar *
get_direction_string(MateMixerDirection direction)126 get_direction_string (MateMixerDirection direction)
127 {
128 switch (direction) {
129 case MATE_MIXER_DIRECTION_INPUT:
130 return "Record";
131 case MATE_MIXER_DIRECTION_OUTPUT:
132 return "Playback";
133 default:
134 return "Unknown";
135 }
136 }
137
138 static gdouble
get_volume_percentage(MateMixerStreamControl * control)139 get_volume_percentage (MateMixerStreamControl *control)
140 {
141 guint volume;
142 guint volume_min;
143 guint volume_max;
144
145 volume = mate_mixer_stream_control_get_volume (control);
146 volume_min = mate_mixer_stream_control_get_min_volume (control);
147 volume_max = mate_mixer_stream_control_get_normal_volume (control);
148
149 return (gdouble) (volume - volume_min) / (volume_max - volume_min) * 100;
150 }
151
152 /* Print a list of sound devices */
153 static void
print_devices(void)154 print_devices (void)
155 {
156 const GList *devices;
157
158 /* Read a list of devices from the context */
159 devices = mate_mixer_context_list_devices (context);
160 while (devices != NULL) {
161 const GList *switches;
162 MateMixerDevice *device = MATE_MIXER_DEVICE (devices->data);
163
164 g_print ("Device %s:\n"
165 "\tLabel : %s\n"
166 "\tIcon Name : %s\n\n",
167 mate_mixer_device_get_name (device),
168 mate_mixer_device_get_label (device),
169 mate_mixer_device_get_icon (device));
170
171 /* Read a list of switches that belong to the device */
172 switches = mate_mixer_device_list_switches (device);
173 while (switches != NULL) {
174 const GList *options;
175 MateMixerSwitch *swtch = MATE_MIXER_SWITCH (switches->data);
176 MateMixerSwitchOption *active = mate_mixer_switch_get_active_option (swtch);
177
178 g_print ("\tSwitch %s:\n"
179 "\t\tLabel : %s\n"
180 "\t\tRole : %s\n",
181 mate_mixer_switch_get_name (swtch),
182 mate_mixer_switch_get_label (swtch),
183 get_device_switch_role_string (mate_mixer_device_switch_get_role (MATE_MIXER_DEVICE_SWITCH (swtch))));
184
185 g_print ("\tOptions:\n");
186
187 /* Read a list of switch options that belong to the switch */
188 options = mate_mixer_switch_list_options (swtch);
189 while (options != NULL) {
190 MateMixerSwitchOption *option = MATE_MIXER_SWITCH_OPTION (options->data);
191
192 g_print ("\t\t|%c| %s\n",
193 (active != NULL && option == active)
194 ? '*'
195 : '-',
196 mate_mixer_switch_option_get_label (option));
197
198 options = options->next;
199 }
200
201 g_print ("\n");
202 switches = switches->next;
203 }
204
205 devices = devices->next;
206 }
207 }
208
209 /* Print a list of streams */
210 static void
print_streams(void)211 print_streams (void)
212 {
213 const GList *streams;
214
215 /* Read a list of streams from the context */
216 streams = mate_mixer_context_list_streams (context);
217 while (streams != NULL) {
218 MateMixerStream *stream = MATE_MIXER_STREAM (streams->data);
219 const GList *controls;
220 const GList *switches;
221
222 g_print ("Stream %s:\n"
223 "\tLabel : %s\n"
224 "\tDirection : %s\n\n",
225 mate_mixer_stream_get_name (stream),
226 mate_mixer_stream_get_label (stream),
227 get_direction_string (mate_mixer_stream_get_direction (stream)));
228
229 /* Read a list of controls in the stream */
230 controls = mate_mixer_stream_list_controls (stream);
231 while (controls != NULL) {
232 MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (controls->data);
233
234 g_print ("\tControl %s:\n"
235 "\t\tLabel : %s\n"
236 "\t\tVolume : %.0f %%\n"
237 "\t\tMuted : %s\n"
238 "\t\tChannels : %d\n"
239 "\t\tBalance : %.1f\n"
240 "\t\tFade : %.1f\n"
241 "\t\tRole : %s\n"
242 "\t\tMedia role : %s\n",
243 mate_mixer_stream_control_get_name (control),
244 mate_mixer_stream_control_get_label (control),
245 get_volume_percentage (control),
246 mate_mixer_stream_control_get_mute (control) ? "Yes" : "No",
247 mate_mixer_stream_control_get_num_channels (control),
248 mate_mixer_stream_control_get_balance (control),
249 mate_mixer_stream_control_get_fade (control),
250 get_role_string (mate_mixer_stream_control_get_role (control)),
251 get_media_role_string (mate_mixer_stream_control_get_media_role (control)));
252
253 g_print ("\n");
254 controls = controls->next;
255 }
256
257 /* Read a list of switches in the stream */
258 switches = mate_mixer_stream_list_switches (stream);
259 while (switches != NULL) {
260 const GList *options;
261 MateMixerSwitch *swtch = MATE_MIXER_SWITCH (switches->data);
262 MateMixerSwitchOption *active = mate_mixer_switch_get_active_option (swtch);
263
264 g_print ("\tSwitch %s:\n"
265 "\t\tLabel : %s\n"
266 "\t\tRole : %s\n",
267 mate_mixer_switch_get_name (swtch),
268 mate_mixer_switch_get_label (swtch),
269 get_stream_switch_role_string (mate_mixer_stream_switch_get_role (MATE_MIXER_STREAM_SWITCH (swtch))));
270
271 g_print ("\tOptions:\n");
272
273 /* Read a list of switch options that belong to the switch */
274 options = mate_mixer_switch_list_options (swtch);
275 while (options != NULL) {
276 MateMixerSwitchOption *option = MATE_MIXER_SWITCH_OPTION (options->data);
277
278 g_print ("\t\t|%c| %s\n",
279 (active != NULL && option == active)
280 ? '*'
281 : '-',
282 mate_mixer_switch_option_get_label (option));
283
284 options = options->next;
285 }
286
287 g_print ("\n");
288 switches = switches->next;
289 }
290
291 streams = streams->next;
292 }
293 }
294
295 static void
connected(void)296 connected (void)
297 {
298 g_print ("Connected using the %s backend.\n\n",
299 mate_mixer_context_get_backend_name (context));
300
301 print_devices ();
302 print_streams ();
303
304 g_print ("Waiting for events. Hit CTRL+C to quit.\n");
305 }
306
307 static void
on_context_state_notify(void)308 on_context_state_notify (void)
309 {
310 MateMixerState state;
311
312 state = mate_mixer_context_get_state (context);
313
314 switch (state) {
315 case MATE_MIXER_STATE_READY:
316 /* This state can be reached repeatedly if the context is connected
317 * to a sound server, the connection is dropped and then reestablished */
318 connected ();
319 break;
320 case MATE_MIXER_STATE_FAILED:
321 g_printerr ("Connection failed.\n");
322 g_main_loop_quit (mainloop);
323 break;
324 default:
325 break;
326 }
327 }
328
329 static void
on_context_device_added(MateMixerContext * context,const gchar * name)330 on_context_device_added (MateMixerContext *context, const gchar *name)
331 {
332 g_print ("Device added: %s\n", name);
333 }
334
335 static void
on_context_device_removed(MateMixerContext * context,const gchar * name)336 on_context_device_removed (MateMixerContext *context, const gchar *name)
337 {
338 g_print ("Device removed: %s\n", name);
339 }
340
341 static void
on_context_stream_added(MateMixerContext * context,const gchar * name)342 on_context_stream_added (MateMixerContext *context, const gchar *name)
343 {
344 g_print ("Stream added: %s\n", name);
345 }
346
347 static void
on_context_stream_removed(MateMixerContext * context,const gchar * name)348 on_context_stream_removed (MateMixerContext *context, const gchar *name)
349 {
350 g_print ("Stream removed: %s\n", name);
351 }
352
353 #ifdef G_OS_UNIX
354 static gboolean
on_signal(gpointer mainloop)355 on_signal (gpointer mainloop)
356 {
357 g_idle_add ((GSourceFunc) g_main_loop_quit, mainloop);
358
359 return G_SOURCE_REMOVE;
360 }
361 #endif
362
main(int argc,char * argv[])363 int main (int argc, char *argv[])
364 {
365 MateMixerState state;
366 GOptionContext *ctx;
367 gboolean debug = FALSE;
368 gchar *backend = NULL;
369 gchar *server = NULL;
370 GError *error = NULL;
371 GOptionEntry entries[] = {
372 { "backend", 'b', 0, G_OPTION_ARG_STRING, &backend, "Sound system to use (pulseaudio, alsa, oss, null)", NULL },
373 { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "Enable debug", NULL },
374 { "server", 's', 0, G_OPTION_ARG_STRING, &server, "Sound server address", NULL },
375 { NULL }
376 };
377
378 ctx = g_option_context_new ("- libmatemixer monitor");
379
380 g_option_context_add_main_entries (ctx, entries, NULL);
381
382 if (g_option_context_parse (ctx, &argc, &argv, &error) == FALSE) {
383 g_printerr ("%s\n", error->message);
384 g_error_free (error);
385 g_option_context_free (ctx);
386 return 1;
387 }
388
389 g_option_context_free (ctx);
390
391 if (debug == TRUE)
392 g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
393
394 /* Initialize the library.
395 * If the function returns FALSE, the library is not usable. */
396 if (mate_mixer_init () == FALSE)
397 return 1;
398
399 setlocale (LC_ALL, "");
400
401 /* Create a libmatemixer context to access the library */
402 context = mate_mixer_context_new ();
403
404 /* Fill in some details about our application, only used with the PulseAudio backend */
405 mate_mixer_context_set_app_name (context, "MateMixer Monitor");
406 mate_mixer_context_set_app_id (context, "org.mate-desktop.libmatemixer-monitor");
407 mate_mixer_context_set_app_version (context, "1.0");
408 mate_mixer_context_set_app_icon (context, "multimedia-volume-control");
409
410 if (backend != NULL) {
411 if (strcmp (backend, "pulseaudio") == 0)
412 mate_mixer_context_set_backend_type (context, MATE_MIXER_BACKEND_PULSEAUDIO);
413 else if (strcmp (backend, "alsa") == 0)
414 mate_mixer_context_set_backend_type (context, MATE_MIXER_BACKEND_ALSA);
415 else if (strcmp (backend, "oss") == 0)
416 mate_mixer_context_set_backend_type (context, MATE_MIXER_BACKEND_OSS);
417 else if (strcmp (backend, "null") == 0)
418 mate_mixer_context_set_backend_type (context, MATE_MIXER_BACKEND_NULL);
419 else
420 g_printerr ("Sound system backend '%s' is unknown, the backend will be auto-detected.\n",
421 backend);
422
423 g_free (backend);
424 }
425
426 /* Set PulseAudio server address if requested */
427 if (server != NULL) {
428 mate_mixer_context_set_server_address (context, server);
429 g_free (server);
430 }
431
432 /* Initiate connection to a sound system */
433 if (mate_mixer_context_open (context) == FALSE) {
434 g_printerr ("Could not connect to a sound system, quitting.\n");
435 g_object_unref (context);
436 return 1;
437 }
438
439 /* Connect to some basic signals of the context */
440 g_signal_connect (G_OBJECT (context),
441 "device-added",
442 G_CALLBACK (on_context_device_added),
443 NULL);
444 g_signal_connect (G_OBJECT (context),
445 "device-removed",
446 G_CALLBACK (on_context_device_removed),
447 NULL);
448 g_signal_connect (G_OBJECT (context),
449 "stream-added",
450 G_CALLBACK (on_context_stream_added),
451 NULL);
452 g_signal_connect (G_OBJECT (context),
453 "stream-removed",
454 G_CALLBACK (on_context_stream_removed),
455 NULL);
456
457 /* When mate_mixer_context_open() returns TRUE, the state must be either
458 * MATE_MIXER_STATE_READY or MATE_MIXER_STATE_CONNECTING. */
459 state = mate_mixer_context_get_state (context);
460
461 switch (state) {
462 case MATE_MIXER_STATE_READY:
463 connected ();
464 break;
465 case MATE_MIXER_STATE_CONNECTING:
466 g_print ("Waiting for connection...\n");
467
468 /* The state will change asynchronously to either MATE_MIXER_STATE_READY
469 * or MATE_MIXER_STATE_FAILED, wait for the change in a main loop */
470 g_signal_connect (G_OBJECT (context),
471 "notify::state",
472 G_CALLBACK (on_context_state_notify),
473 NULL);
474 break;
475 default:
476 g_assert_not_reached ();
477 break;
478 }
479
480 mainloop = g_main_loop_new (NULL, FALSE);
481
482 #ifdef G_OS_UNIX
483 g_unix_signal_add (SIGTERM, on_signal, mainloop);
484 g_unix_signal_add (SIGINT, on_signal, mainloop);
485 #endif
486
487 g_main_loop_run (mainloop);
488
489 g_object_unref (context);
490 return 0;
491 }
492