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