pnmixer
Volume mixer for the system tray
alsa.c
Go to the documentation of this file.
1 /* alsa.c
2  * PNmixer is written by Nick Lanham, a fork of OBmixer
3  * which was programmed by Lee Ferrett, derived
4  * from the program "AbsVolume" by Paul Sherman
5  * This program is free software; you can redistribute
6  * it and/or modify it under the terms of the GNU General
7  * Public License v3. source code is available at
8  * <http://github.com/nicklan/pnmixer>
9  */
10 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #define _GNU_SOURCE /* exp10() */
27 #include <math.h>
28 #include <glib.h>
29 #include <alsa/asoundlib.h>
30 
31 #include "support-log.h"
32 #include "alsa.h"
33 
34 #define ALSA_DEFAULT_CARD "(default)"
35 #define ALSA_DEFAULT_HCTL "default"
36 
37 /*
38  * Alsa log and debug macros.
39  */
40 
41 static void
42 alsa_log_msg(enum log_level level, const char *card,
43  const char *strerr, const char *format, ...)
44 {
45  va_list args;
46  char buf[1024];
47 
48  if (!card && !strerr)
49  snprintf(buf, sizeof buf, "%s", format);
50  else if (card && !strerr)
51  snprintf(buf, sizeof buf, "'%s': %s", card, format);
52  else if (!card && strerr)
53  snprintf(buf, sizeof buf, "%s: %s", format, strerr);
54  else // card && strerr
55  snprintf(buf, sizeof buf, "'%s': %s: %s", card, format, strerr);
56 
57  va_start(args, format);
58  log_msg_v(level, __FILE__, buf, args);
59  va_end(args);
60 }
61 
62 #define ALSA_ERR(err, ...) \
63  alsa_log_msg(LOG_ERROR, NULL, snd_strerror(err), __VA_ARGS__)
64 
65 #define ALSA_CARD_DEBUG(card, ...) \
66  alsa_log_msg(LOG_DEBUG, card, NULL, __VA_ARGS__)
67 
68 #define ALSA_CARD_WARN(card, ...) \
69  alsa_log_msg(LOG_WARN, card, NULL, __VA_ARGS__)
70 
71 #define ALSA_CARD_ERR(card, err, ...) \
72  alsa_log_msg(LOG_ERROR, card, snd_strerror(err), __VA_ARGS__)
73 
74 /*
75  * Alsa mixer elem handling (deals with 'snd_mixer_elem_t').
76  * Parts of this code were taken and adapted from the original
77  * `alsa-utils` package, `alsamixer` program, `volume_mapping.c` file.
78  *
79  * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
80  *
81  * Permission to use, copy, modify, and/or distribute this software for any
82  * purpose with or without fee is hereby granted, provided that the above
83  * copyright notice and this permission notice appear in all copies.
84  */
85 
86 #define MAX_LINEAR_DB_SCALE 24
87 
88 static inline gboolean
89 use_linear_dB_scale(long db_min, long db_max)
90 {
91  return db_max - db_min <= MAX_LINEAR_DB_SCALE * 100;
92 }
93 
94 static long
95 lrint_dir(double x, int dir)
96 {
97  if (dir > 0)
98  return lrint(ceil(x));
99  else if (dir < 0)
100  return lrint(floor(x));
101  else
102  return lrint(x);
103 }
104 
105 /* Return the name of a mixer element */
106 static const char *
107 elem_get_name(snd_mixer_elem_t *elem)
108 {
109  return snd_mixer_selem_get_name(elem);
110 }
111 
112 /* Get volume, return a value between 0 and 1 */
113 static double
114 elem_get_volume(const char *hctl, snd_mixer_elem_t *elem, double *volume)
115 {
116  snd_mixer_selem_channel_id_t channel = SND_MIXER_SCHN_FRONT_RIGHT;
117  int err;
118  long min, max, value;
119 
120  *volume = 0;
121 
122  err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
123  if (err < 0) {
124  ALSA_CARD_ERR(hctl, err, "Can't get playback volume range");
125  return FALSE;
126  }
127 
128  if (min >= max) {
129  ALSA_CARD_WARN(hctl, "Invalid playback volume range [%ld - %ld]", min, max);
130  return FALSE;
131  }
132 
133  err = snd_mixer_selem_get_playback_volume(elem, channel, &value);
134  if (err < 0) {
135  ALSA_CARD_ERR(hctl, err, "Can't get playback volume");
136  return FALSE;
137  }
138 
139  *volume = (value - min) / (double) (max - min);
140 
141  return TRUE;
142 }
143 
144 /* Set volume, input value between 0 and 1 */
145 static gboolean
146 elem_set_volume(const char *hctl, snd_mixer_elem_t *elem, double volume, int dir)
147 {
148  int err;
149  long min, max, value;
150 
151  err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
152  if (err < 0) {
153  ALSA_CARD_ERR(hctl, err, "Can't get playback volume range");
154  return FALSE;
155  }
156 
157  if (min >= max) {
158  ALSA_CARD_WARN(hctl, "Invalid playback volume range [%ld - %ld]", min, max);
159  return FALSE;
160  }
161 
162  value = lrint_dir(volume * (max - min), dir) + min;
163 
164  err = snd_mixer_selem_set_playback_volume_all(elem, value);
165  if (err < 0) {
166  ALSA_CARD_ERR(hctl, err, "Can't set playback volume to %ld", value);
167  return FALSE;
168  }
169 
170  return TRUE;
171 }
172 
173 /* Get normalized volume, return a value between 0 and 1 */
174 static gboolean
175 elem_get_volume_normalized(const char *hctl, snd_mixer_elem_t *elem, double *volume)
176 {
177  snd_mixer_selem_channel_id_t channel = SND_MIXER_SCHN_FRONT_RIGHT;
178  int err;
179  long min, max, value;
180  double normalized, min_norm;
181 
182  *volume = 0;
183 
184  err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max);
185  if (err < 0) {
186  ALSA_CARD_ERR(hctl, err, "Can't get playback dB range");
187  return FALSE;
188  }
189 
190  if (min >= max) {
191  ALSA_CARD_WARN(hctl, "Invalid playback dB range [%ld - %ld]", min, max);
192  return FALSE;
193  }
194 
195  err = snd_mixer_selem_get_playback_dB(elem, channel, &value);
196  if (err < 0) {
197  ALSA_CARD_ERR(hctl, err, "Can't get playback dB");
198  return FALSE;
199  }
200 
201  if (use_linear_dB_scale(min, max)) {
202  normalized = (value - min) / (double) (max - min);
203  } else {
204  normalized = exp10((value - max) / 6000.0);
205  if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
206  min_norm = exp10((min - max) / 6000.0);
207  normalized = (normalized - min_norm) / (1 - min_norm);
208  }
209  }
210 
211  *volume = normalized;
212 
213  // ALSA_CARD_DEBUG(hctl, "Getting normalized volume: %lf", *volume);
214 
215  return TRUE;
216 }
217 
218 /* Set normalized volume, input value between 0 and 1 */
219 static gboolean
220 elem_set_volume_normalized(const char *hctl, snd_mixer_elem_t *elem, double volume, int dir)
221 {
222  int err;
223  long min, max, value;
224 
225  err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max);
226  if (err < 0) {
227  ALSA_CARD_ERR(hctl, err, "Can't get playback dB range");
228  return FALSE;
229  }
230 
231  if (min >= max) {
232  ALSA_CARD_WARN(hctl, "Invalid playback dB range [%ld - %ld]", min, max);
233  return FALSE;
234  }
235 
236  if (use_linear_dB_scale(min, max)) {
237  value = lrint_dir(volume * (max - min), dir) + min;
238  } else {
239  if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
240  double min_norm = exp10((min - max) / 6000.0);
241  volume = volume * (1 - min_norm) + min_norm;
242  }
243  value = lrint_dir(6000.0 * log10(volume), dir) + max;
244  }
245 
246  err = snd_mixer_selem_set_playback_dB_all(elem, value, dir);
247  if (err < 0) {
248  ALSA_CARD_ERR(hctl, err, "Can't set playback dB to %ld", value);
249  return FALSE;
250  }
251 
252  // ALSA_CARD_DEBUG(hctl, "Normalized volume set: %lf (%d)", volume, dir);
253 
254  return TRUE;
255 }
256 
257 /* Whether the card can be muted */
258 static gboolean
259 elem_has_mute(G_GNUC_UNUSED const char *hctl, snd_mixer_elem_t *elem)
260 {
261  return snd_mixer_selem_has_playback_switch(elem) ? TRUE : FALSE;
262 }
263 
264 /* Get the mute state, either TRUE or FALSE */
265 static gboolean
266 elem_get_mute(const char *hctl, snd_mixer_elem_t *elem, gboolean *muted)
267 {
268  snd_mixer_selem_channel_id_t channel = SND_MIXER_SCHN_FRONT_RIGHT;
269  int err;
270  int value;
271 
272  *muted = FALSE;
273 
274  if (snd_mixer_selem_has_playback_switch(elem)) {
275  err = snd_mixer_selem_get_playback_switch(elem, channel, &value);
276  if (err < 0) {
277  ALSA_CARD_ERR(hctl, err, "Can't get playback switch");
278  return FALSE;
279  }
280  } else {
281  /* If there's no playback switch, assume not muted */
282  value = 1;
283  }
284 
285  /* Value returned: 0 = muted, 1 = not muted */
286  *muted = value == 0 ? TRUE : FALSE;
287 
288  // ALSA_CARD_DEBUG(hctl, "Getting mute: %d", *muted);
289 
290  return TRUE;
291 }
292 
293 /* Set the mute state, TRUE or FALSE */
294 static gboolean
295 elem_set_mute(const char *hctl, snd_mixer_elem_t *elem, gboolean mute)
296 {
297  int err;
298  int value;
299 
300  /* Value to set: 0 = muted, 1 = not muted */
301  value = mute ? 0 : 1;
302 
303  if (snd_mixer_selem_has_playback_switch(elem)) {
304  err = snd_mixer_selem_set_playback_switch_all(elem, value);
305  if (err < 0) {
306  ALSA_CARD_ERR(hctl, err, "Can't set playback switch");
307  return FALSE;
308  }
309  } else {
310  /* If there's no playback switch, do nothing */
311  }
312 
313  // ALSA_CARD_DEBUG(hctl, "Mute set: %d", mute);
314 
315  return TRUE;
316 }
317 
318 /*
319  * Alsa mixer handling (deals with 'snd_mixer_t').
320  */
321 
322 /* Get poll descriptors in a dynamic array (must be freed).
323  * The array is terminated by a fd set to -1.
324  */
325 static struct pollfd *
326 mixer_get_poll_descriptors(const char *hctl, snd_mixer_t *mixer)
327 {
328  int err, count;
329  struct pollfd *fds;
330 
331  /* Get the number of poll descriptors. Afaik, it's always one. */
332  count = snd_mixer_poll_descriptors_count(mixer);
333 
334  /* Get the poll descriptors in a dynamic array */
335  fds = g_new0(struct pollfd, count + 1);
336  err = snd_mixer_poll_descriptors(mixer, fds, count);
337  if (err < 0) {
338  ALSA_CARD_ERR(hctl, err, "Couldn't get poll descriptors");
339  return NULL;
340  }
341 
342  /* Terminate the array with a fd set to -1 */
343  fds[count].fd = -1;
344 
345  return fds;
346 }
347 
348 /* Get the list of playable channels */
349 static GSList *
350 mixer_list_playable(G_GNUC_UNUSED const char *hctl, snd_mixer_t *mixer)
351 {
352  GSList *list = NULL;
353  snd_mixer_elem_t *elem = NULL;
354 
355  elem = snd_mixer_first_elem(mixer);
356  while (elem) {
357  if (snd_mixer_selem_has_playback_volume(elem)) {
358  const char *chan_name = snd_mixer_selem_get_name(elem);
359  list = g_slist_append(list, g_strdup(chan_name));
360  }
361  elem = snd_mixer_elem_next(elem);
362  }
363 
364  return list;
365 }
366 
367 /* Return TRUE if the mixer has at least one playable channel, FALSE otherwise */
368 static gboolean
369 mixer_is_playable(const char *hctl, snd_mixer_t *mixer)
370 {
371  GSList *playable_chans;
372 
373  playable_chans = mixer_list_playable(hctl, mixer);
374  if (playable_chans) {
375  g_slist_free_full(playable_chans, g_free);
376  return TRUE;
377  }
378 
379  return FALSE;
380 }
381 
382 /* Get a playable mixer element by name */
383 static snd_mixer_elem_t *
384 mixer_get_playable_elem(const char *hctl, snd_mixer_t *mixer, const char *channel)
385 {
386  snd_mixer_elem_t *elem;
387  snd_mixer_selem_id_t *sid;
388 
389  if (!channel)
390  return NULL;
391 
392  ALSA_CARD_DEBUG(hctl, "Looking for playable mixer element '%s'", channel);
393 
394  /* Find the mixer element */
395  snd_mixer_selem_id_alloca(&sid);
396  snd_mixer_selem_id_set_name(sid, channel);
397  elem = snd_mixer_find_selem(mixer, sid);
398  if (elem == NULL) {
399  ALSA_CARD_WARN(hctl, "Can't find mixer element '%s'", channel);
400  return NULL;
401  }
402 
403  /* Check that it's playable */
404  if (!snd_mixer_selem_has_playback_volume(elem)) {
405  ALSA_CARD_WARN(hctl, "Mixer element '%s' is not playable", channel);
406  return NULL;
407  }
408 
409  return elem;
410 }
411 
412 /* Get the first playable mixer element */
413 static snd_mixer_elem_t *
414 mixer_get_first_playable_elem(const char *hctl, snd_mixer_t *mixer)
415 {
416  snd_mixer_elem_t *elem;
417 
418  ALSA_CARD_DEBUG(hctl, "Looking for the first playable mixer element...");
419 
420  /* Iterate on mixer elements, get the first playable */
421  elem = snd_mixer_first_elem(mixer);
422  while (elem) {
423  if (snd_mixer_selem_has_playback_volume(elem))
424  break;
425  elem = snd_mixer_elem_next(elem);
426  }
427 
428  if (elem == NULL) {
429  ALSA_CARD_DEBUG(hctl, "No playable mixer element found");
430  return NULL;
431  }
432 
433  return elem;
434 }
435 
436 /* Close a mixer */
437 static void
438 mixer_close(const char *hctl, snd_mixer_t *mixer)
439 {
440  int err;
441 
442  ALSA_CARD_DEBUG(hctl, "Closing mixer");
443 
444  snd_mixer_free(mixer);
445 
446  err = snd_mixer_detach(mixer, hctl);
447  if (err < 0)
448  ALSA_CARD_ERR(hctl, err, "Can't detach mixer");
449 
450  err = snd_mixer_close(mixer);
451  if (err < 0)
452  ALSA_CARD_ERR(hctl, err, "Can't close mixer");
453 }
454 
455 /* Open a mixer */
456 static snd_mixer_t *
457 mixer_open(const char *hctl)
458 {
459  int err;
460  snd_mixer_t *mixer = NULL;
461 
462  ALSA_CARD_DEBUG(hctl, "Opening mixer");
463 
464  err = snd_mixer_open(&mixer, 0);
465  if (err < 0) {
466  ALSA_CARD_ERR(hctl, err, "Can't open mixer");
467  return NULL;
468  }
469 
470  err = snd_mixer_attach(mixer, hctl);
471  if (err < 0) {
472  ALSA_CARD_ERR(hctl, err, "Can't attach card to mixer");
473  goto failure;
474  }
475 
476  err = snd_mixer_selem_register(mixer, NULL, NULL);
477  if (err < 0) {
478  ALSA_CARD_ERR(hctl, err, "Can't register mixer simple element");
479  goto failure;
480  }
481 
482  err = snd_mixer_load(mixer);
483  if (err < 0) {
484  ALSA_CARD_ERR(hctl, err, "Can't load mixer elements");
485  goto failure;
486  }
487 
488  return mixer;
489 
490 failure:
491  snd_mixer_close(mixer);
492  return NULL;
493 }
494 
495 /*
496  * Alsa card iterator.
497  * The Alsa API is really awkward when it comes to deal with cards
498  * and to get various informations from it.
499  * This iterator is here to make it less painful.
500  */
501 
503  int number;
504  char *name;
505  char *hctl;
506 };
507 
509 
510 /* Free an iterator */
511 static void
513 {
514  if (iter == NULL)
515  return;
516 
517  g_free(iter->name);
518  g_free(iter->hctl);
519  g_free(iter);
520 }
521 
522 /* Create a new iterator */
523 static AlsaCardIter *
525 {
526  AlsaCardIter *iter;
527 
528  iter = g_new0(AlsaCardIter, 1);
529  iter->number = -2;
530 
531  return iter;
532 }
533 
534 /* Iterate over alsa cards. Return TRUE as long as there is a card,
535  * and FALSE when there's no more card.
536  * After it returned FALSE, the iterator shouldn't be used anymore and
537  * should be freed.
538  */
539 static gboolean
541 {
542  int err;
543 
544  /* Free iter data at first */
545  g_free(iter->name);
546  iter->name = NULL;
547  g_free(iter->hctl);
548  iter->hctl = NULL;
549 
550  /* First elem is the default alsa soundcard.
551  * It's not really reachable as it with the ALSA API,
552  * so we must add it manually here.
553  */
554  if (iter->number == -2) {
555  iter->number = -1;
556  iter->name = g_strdup(ALSA_DEFAULT_CARD);
557  iter->hctl = g_strdup(ALSA_DEFAULT_HCTL);
558  return TRUE;
559  }
560 
561  /* Get next alsa soundcard */
562  err = snd_card_next(&iter->number);
563  if (err < 0) {
564  ALSA_ERR(err, "Can't enumerate sound cards");
565  return FALSE;
566  }
567 
568  /* No more soundcards ? */
569  if (iter->number < 0)
570  return FALSE;
571 
572  /* Get card name */
573  err = snd_card_get_name(iter->number, &(iter->name));
574  if (err < 0) {
575  ALSA_ERR(err, "Can't get card name");
576  return FALSE;
577  }
578 
579  /* Get HCTL name */
580  iter->hctl = g_strdup_printf("hw:%d", iter->number);
581 
582  return TRUE;
583 }
584 
585 /*
586  * Alsa poll descriptors handling with GIO.
587  */
588 
589 /* Start watching the poll descriptors provided in the input array */
590 static guint *
591 watch_poll_descriptors(const char *hctl, struct pollfd *pollfds,
592  GIOFunc func, gpointer data)
593 {
594  int nfds, i;
595  guint *watch_ids;
596 
597  /* Count the number of poll file descriptors */
598  nfds = i = 0;
599  while (pollfds[i++].fd != -1)
600  nfds++;
601 
602  /* Allocate a zero-termintated array to hold the watch ids */
603  watch_ids = g_new0(guint, nfds + 1);
604 
605  /* Watch every poll fd */
606  for (i = 0; i < nfds; i++) {
607  GIOChannel *gioc;
608 
609  gioc = g_io_channel_unix_new(pollfds[i].fd);
610  watch_ids[i] = g_io_add_watch(gioc, G_IO_IN | G_IO_ERR, func, data);
611  g_io_channel_unref(gioc);
612  }
613 
614  ALSA_CARD_DEBUG(hctl, "%d poll descriptors are now watched", nfds);
615 
616  return watch_ids;
617 }
618 
619 /* Stop watching poll descriptors */
620 static void
621 unwatch_poll_descriptors(guint *watch_ids)
622 {
623  int i;
624 
625  for (i = 0; watch_ids[i] != 0; i++) {
626  g_source_remove(watch_ids[i]);
627  watch_ids[i] = 0;
628  }
629 }
630 
631 /*
632  * Public functions & signal handling
633  */
634 
635 struct alsa_card {
636  gboolean normalize; /* Whether we work with normalized volume */
637  /* Card names */
638  char *name; /* Real card name like 'HDA Intel PCH' */
639  char *hctl; /* HTCL device name, like 'hw:0' */
640  /* Alsa data pointers */
641  snd_mixer_t *mixer; /* Alsa mixer */
642  snd_mixer_elem_t *mixer_elem; /* Alsa mixer elem */
643  /* Gio watch ids */
644  guint *watch_ids;
645  /* User callback, to notify when something happens */
647  gpointer cb_data;
648 };
649 
659 static gboolean
660 poll_watch_cb(GIOChannel *source, GIOCondition condition, AlsaCard *card)
661 {
662  gchar sbuf[256];
663  gsize sread = 1;
664  AlsaCb callback = card->cb_func;
665  gpointer data = card->cb_data;
666 
667  // DEBUG("Entering %s()", __func__);
668 
669  /* Handle pending mixer events.
670  * Everything is broken if we don't do that !
671  */
672  snd_mixer_handle_events(card->mixer);
673 
674  /* Check if the soundcard has been unplugged. In such case,
675  * the file descriptor we're watching disappeared, causing a G_IO_ERR.
676  */
677  if (condition == G_IO_ERR) {
678  if (callback)
679  callback(ALSA_CARD_DISCONNECTED, data);
680  return FALSE;
681  }
682 
683  /* Now read data from channel */
684  sread = 1;
685  while (sread) {
686  GIOStatus stat;
687 
688  /* This handles the case where mixer_elem_cb() doesn't read all
689  * the data on source. If we don't clear it out we'll go into an infinite
690  * callback loop since there will be data on the channel forever.
691  */
692  stat = g_io_channel_read_chars(source, sbuf, 256, &sread, NULL);
693 
694  switch (stat) {
695  case G_IO_STATUS_AGAIN:
696  /* Normal, means alsa_cb cleared out the channel */
697  continue;
698 
699  case G_IO_STATUS_NORMAL:
700  /* Actually bad, alsa failed to clear channel */
701  ERROR("Alsa failed to clear the channel");
702  if (callback)
703  callback(ALSA_CARD_ERROR, data);
704  break;
705 
706  case G_IO_STATUS_ERROR:
707  case G_IO_STATUS_EOF:
708  ERROR("GIO error has occurred");
709  if (callback)
710  callback(ALSA_CARD_ERROR, data);
711  break;
712 
713  default:
714  WARN("Unknown status from g_io_channel_read_chars()");
715  }
716 
717  return TRUE;
718  }
719 
720  /* Arriving here, no errors happened.
721  * We can safely notify that values changed.
722  */
723  if (callback)
724  callback(ALSA_CARD_VALUES_CHANGED, data);
725 
726  return TRUE;
727 }
728 
736 const char *
738 {
739  return card->name;
740 }
741 
749 const char *
751 {
752  return elem_get_name(card->mixer_elem);
753 }
754 
761 gboolean
763 {
764  return elem_has_mute(card->hctl, card->mixer_elem);
765 }
766 
773 gboolean
775 {
776  gboolean muted;
777 
778  elem_get_mute(card->hctl, card->mixer_elem, &muted);
779  return muted;
780 }
781 
787 void
789 {
790  gboolean muted;
791 
792  /* Set mute */
793  muted = alsa_card_is_muted(card);
794  elem_set_mute(card->hctl, card->mixer_elem, !muted);
795 }
796 
803 gdouble
805 {
806  gdouble volume = 0;
807  gboolean gotten = FALSE;
808 
809  if (card->normalize)
810  gotten = elem_get_volume_normalized(card->hctl, card->mixer_elem, &volume);
811 
812  if (!gotten)
813  elem_get_volume(card->hctl, card->mixer_elem, &volume);
814 
815  return volume * 100;
816 }
817 
826 void
827 alsa_card_set_volume(AlsaCard *card, gdouble value, int dir)
828 {
829  gdouble volume;
830  gboolean set = FALSE;
831 
832  volume = value / 100.0;
833 
834  /* Set volume */
835  if (card->normalize)
836  set = elem_set_volume_normalized(card->hctl, card->mixer_elem, volume, dir);
837 
838  if (!set)
839  elem_set_volume(card->hctl, card->mixer_elem, volume, dir);
840 }
841 
849 void
850 alsa_card_install_callback(AlsaCard *card, AlsaCb callback, gpointer user_data)
851 {
852  card->cb_func = callback;
853  card->cb_data = user_data;
854 }
855 
861 void
863 {
864  if (card == NULL)
865  return;
866 
867  if (card->watch_ids)
869 
870  if (card->mixer)
871  mixer_close(card->hctl, card->mixer);
872 
873  g_free(card->hctl);
874  g_free(card->name);
875  g_free(card);
876 }
877 
890 AlsaCard *
891 alsa_card_new(const char *card_name, const char *channel, gboolean normalize)
892 {
893  AlsaCard *card;
894  AlsaCardIter *iter;
895 
896  card = g_new0(AlsaCard, 1);
897 
898  /* Save normalize parameter */
899  card->normalize = normalize;
900 
901  /* Save card name */
902  if (!card_name)
903  card_name = ALSA_DEFAULT_CARD;
904  card->name = g_strdup(card_name);
905 
906  /* Get corresponding HCTL name */
907  iter = alsa_card_iter_new();
908  while (alsa_card_iter_loop(iter)) {
909  if (!g_strcmp0(iter->name, card_name)) {
910  card->hctl = g_strdup(iter->hctl);
911  break;
912  }
913  }
914  alsa_card_iter_free(iter);
915 
916  if (card->hctl == NULL)
917  goto failure;
918 
919  /* Open mixer */
920  card->mixer = mixer_open(card->hctl);
921  if (card->mixer == NULL)
922  goto failure;
923 
924  /* Get mixer element */
925  card->mixer_elem = mixer_get_playable_elem(card->hctl, card->mixer, channel);
926  if (card->mixer_elem == NULL)
927  card->mixer_elem = mixer_get_first_playable_elem(card->hctl, card->mixer);
928  if (card->mixer_elem == NULL)
929  goto failure;
930 
931  /* Get mixer poll descriptors and watch them using gio.
932  * That's how we get notified from every volume/mute changes,
933  * may it be external or due to PNMixer.
934  */
935  struct pollfd *pollfds;
936  pollfds = mixer_get_poll_descriptors(card->hctl, card->mixer);
937  if (pollfds == NULL)
938  goto failure;
939 
940  card->watch_ids = watch_poll_descriptors(card->hctl, pollfds,
941  (GIOFunc) poll_watch_cb, card);
942  g_free(pollfds);
943 
944  /* Sum up the situation */
945  DEBUG("'%s': Card '%s' with channel '%s' initialized !",
946  card->hctl, card->name, elem_get_name(card->mixer_elem));
947 
948  return card;
949 
950 failure:
951  alsa_card_free(card);
952  return NULL;
953 }
954 
955 /*
956  * Alsa listing functions
957  */
958 
965 GSList *
967 {
968  AlsaCardIter *iter;
969  GSList *list = NULL;
970 
971  iter = alsa_card_iter_new();
972 
973  while (alsa_card_iter_loop(iter)) {
974  snd_mixer_t *mixer;
975 
976  /* Open mixer */
977  mixer = mixer_open(iter->hctl);
978  if (mixer == NULL)
979  continue;
980 
981  /* Only keep cards with playable channels */
982  if (mixer_is_playable(iter->hctl, mixer))
983  list = g_slist_append(list, g_strdup(iter->name));
984 
985  /* Close mixer */
986  mixer_close(iter->hctl, mixer);
987  }
988 
989  alsa_card_iter_free(iter);
990 
991  return list;
992 }
993 
1001 GSList *
1002 alsa_list_channels(const char *card_name)
1003 {
1004  AlsaCardIter *iter;
1005  char *hctl = NULL;
1006  snd_mixer_t *mixer = NULL;
1007  GSList *list = NULL;
1008 
1009  /* Iterate over cards to find the one provided in argument */
1010  iter = alsa_card_iter_new();
1011  while (alsa_card_iter_loop(iter)) {
1012  if (!g_strcmp0(iter->name, card_name)) {
1013  hctl = g_strdup(iter->hctl);
1014  break;
1015  }
1016  }
1017  alsa_card_iter_free(iter);
1018 
1019  if (hctl == NULL)
1020  goto exit;
1021 
1022  /* Open the mixer */
1023  mixer = mixer_open(hctl);
1024  if (mixer == NULL)
1025  goto exit;
1026 
1027  /* Get a list of playable channels */
1028  list = mixer_list_playable(hctl, mixer);
1029 
1030 exit:
1031  /* Cleanup */
1032  if (mixer)
1033  mixer_close(hctl, mixer);
1034  if (hctl)
1035  g_free(hctl);
1036 
1037  return list;
1038 }
static GSList * mixer_list_playable(G_GNUC_UNUSED const char *hctl, snd_mixer_t *mixer)
Definition: alsa.c:350
#define ALSA_ERR(err,...)
Definition: alsa.c:62
#define ALSA_CARD_WARN(card,...)
Definition: alsa.c:68
static guint * watch_poll_descriptors(const char *hctl, struct pollfd *pollfds, GIOFunc func, gpointer data)
Definition: alsa.c:591
static struct pollfd * mixer_get_poll_descriptors(const char *hctl, snd_mixer_t *mixer)
Definition: alsa.c:326
Logging support.
static const char * elem_get_name(snd_mixer_elem_t *elem)
Definition: alsa.c:107
static void alsa_card_iter_free(AlsaCardIter *iter)
Definition: alsa.c:512
static snd_mixer_elem_t * mixer_get_first_playable_elem(const char *hctl, snd_mixer_t *mixer)
Definition: alsa.c:414
gboolean alsa_card_has_mute(AlsaCard *card)
Definition: alsa.c:762
char * name
Definition: alsa.c:638
static gboolean elem_get_volume_normalized(const char *hctl, snd_mixer_elem_t *elem, double *volume)
Definition: alsa.c:175
snd_mixer_elem_t * mixer_elem
Definition: alsa.c:642
static snd_mixer_t * mixer_open(const char *hctl)
Definition: alsa.c:457
const char * alsa_card_get_channel(AlsaCard *card)
Definition: alsa.c:750
static gboolean elem_get_mute(const char *hctl, snd_mixer_elem_t *elem, gboolean *muted)
Definition: alsa.c:266
char * hctl
Definition: alsa.c:505
Header for alsa.c.
static gboolean alsa_card_iter_loop(AlsaCardIter *iter)
Definition: alsa.c:540
static void alsa_log_msg(enum log_level level, const char *card, const char *strerr, const char *format,...)
Definition: alsa.c:42
gdouble alsa_card_get_volume(AlsaCard *card)
Definition: alsa.c:804
GSList * alsa_list_cards(void)
Definition: alsa.c:966
static gboolean elem_set_mute(const char *hctl, snd_mixer_elem_t *elem, gboolean mute)
Definition: alsa.c:295
#define DEBUG(...)
Definition: support-log.h:38
#define MAX_LINEAR_DB_SCALE
Definition: alsa.c:86
void alsa_card_toggle_mute(AlsaCard *card)
Definition: alsa.c:788
#define ALSA_DEFAULT_HCTL
Definition: alsa.c:35
char * name
Definition: alsa.c:504
static void unwatch_poll_descriptors(guint *watch_ids)
Definition: alsa.c:621
#define ALSA_DEFAULT_CARD
Definition: alsa.c:34
void alsa_card_free(AlsaCard *card)
Definition: alsa.c:862
gboolean alsa_card_is_muted(AlsaCard *card)
Definition: alsa.c:774
static gboolean elem_set_volume(const char *hctl, snd_mixer_elem_t *elem, double volume, int dir)
Definition: alsa.c:146
#define ALSA_CARD_DEBUG(card,...)
Definition: alsa.c:65
gpointer cb_data
Definition: alsa.c:647
static gboolean use_linear_dB_scale(long db_min, long db_max)
Definition: alsa.c:89
#define ERROR(...)
Definition: support-log.h:36
GSList * alsa_list_channels(const char *card_name)
Definition: alsa.c:1002
#define ALSA_CARD_ERR(card, err,...)
Definition: alsa.c:71
static gboolean elem_set_volume_normalized(const char *hctl, snd_mixer_elem_t *elem, double volume, int dir)
Definition: alsa.c:220
static AlsaCardIter * alsa_card_iter_new(void)
Definition: alsa.c:524
AlsaCard * alsa_card_new(const char *card_name, const char *channel, gboolean normalize)
Definition: alsa.c:891
void alsa_card_set_volume(AlsaCard *card, gdouble value, int dir)
Definition: alsa.c:827
void log_msg_v(enum log_level level, const char *file, const char *format, va_list args)
Definition: support-log.c:49
gboolean normalize
Definition: alsa.c:636
log_level
Definition: support-log.h:27
void alsa_card_install_callback(AlsaCard *card, AlsaCb callback, gpointer user_data)
Definition: alsa.c:850
char * hctl
Definition: alsa.c:639
AlsaCb cb_func
Definition: alsa.c:646
static gboolean elem_has_mute(G_GNUC_UNUSED const char *hctl, snd_mixer_elem_t *elem)
Definition: alsa.c:259
static double elem_get_volume(const char *hctl, snd_mixer_elem_t *elem, double *volume)
Definition: alsa.c:114
guint * watch_ids
Definition: alsa.c:644
void(* AlsaCb)(enum alsa_event event, gpointer data)
Definition: alsa.h:36
static gboolean poll_watch_cb(GIOChannel *source, GIOCondition condition, AlsaCard *card)
Definition: alsa.c:660
static long lrint_dir(double x, int dir)
Definition: alsa.c:95
static void mixer_close(const char *hctl, snd_mixer_t *mixer)
Definition: alsa.c:438
static gboolean mixer_is_playable(const char *hctl, snd_mixer_t *mixer)
Definition: alsa.c:369
const char * alsa_card_get_name(AlsaCard *card)
Definition: alsa.c:737
int number
Definition: alsa.c:503
#define WARN(...)
Definition: support-log.h:37
static snd_mixer_elem_t * mixer_get_playable_elem(const char *hctl, snd_mixer_t *mixer, const char *channel)
Definition: alsa.c:384
snd_mixer_t * mixer
Definition: alsa.c:641