alsa.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #include <err.h>
  2. #include <math.h>
  3. #include <inttypes.h>
  4. #include <alsa/asoundlib.h>
  5. #include <alsa/control.h>
  6. /* header file inclusion order is important */
  7. #ifndef USE_ALSA
  8. # define USE_ALSA
  9. #endif
  10. #include "../volume.h"
  11. #include "../../lib/util.h"
  12. #include "../../aslstatus.h"
  13. #include "../../components_config.h"
  14. #ifndef VOLUME_SYM
  15. # define VOLUME_SYM ""
  16. #endif
  17. #ifndef VOLUME_PERCENT
  18. # define VOLUME_PERCENT " %"
  19. #endif
  20. #ifndef VOLUME_MUTED
  21. # define VOLUME_MUTED "muted"
  22. #endif
  23. #ifndef VOLUME_ALSA_CARD
  24. # define VOLUME_ALSA_CARD "default"
  25. #endif
  26. #ifndef VOLUME_ALSA_MIXER_NAME
  27. # define VOLUME_ALSA_MIXER_NAME "Master"
  28. #endif
  29. static const size_t CTL_NAME_MAX = 3 /* "hw:" */ + INT_STR_SIZE;
  30. typedef struct volume_static_data alsa_data;
  31. static uint8_t is_muted(snd_mixer_selem_id_t **sid);
  32. static percent_t get_percentage(typeof_field(alsa_data, volume) * v,
  33. snd_mixer_selem_id_t **sid);
  34. static char *get_ctl_name(snd_mixer_selem_id_t **sid);
  35. static void alsa_cleanup(void *ptr);
  36. void
  37. vol_perc(char *volume,
  38. const char __unused *_a,
  39. uint32_t __unused _i,
  40. static_data_t *static_data)
  41. {
  42. int err;
  43. char *ctl_name;
  44. alsa_data *data = static_data->data;
  45. if (!data->ctl) {
  46. if (!static_data->cleanup) static_data->cleanup = alsa_cleanup;
  47. if (!(ctl_name = get_ctl_name(&data->sid))) ERRRET(volume);
  48. snd_ctl_open(&data->ctl, ctl_name, SND_CTL_READONLY);
  49. free(ctl_name);
  50. err = snd_ctl_subscribe_events(data->ctl, 1);
  51. if (err < 0) {
  52. snd_ctl_close(data->ctl);
  53. data->ctl = NULL;
  54. warnx("cannot subscribe to alsa events: %s",
  55. snd_strerror(err));
  56. ERRRET(volume);
  57. }
  58. snd_ctl_event_malloc(&data->e);
  59. } else {
  60. snd_ctl_read(data->ctl, data->e);
  61. }
  62. if (is_muted(&data->sid))
  63. bprintf(volume, "%s", VOLUME_MUTED);
  64. else
  65. bprintf(volume,
  66. "%s%" PRIperc "%s",
  67. VOLUME_SYM,
  68. get_percentage(&data->volume, &data->sid),
  69. VOLUME_PERCENT);
  70. }
  71. static inline snd_mixer_t *
  72. get_mixer_elem(snd_mixer_elem_t **ret, snd_mixer_selem_id_t **sid)
  73. /*
  74. * after using `mixer_elem`
  75. * to free memory returned `mixer` must be closed with:
  76. * `snd_mixer_close`
  77. *
  78. * (see `is_muted` function)
  79. */
  80. {
  81. int err;
  82. snd_mixer_t *handle;
  83. if (!*sid) {
  84. if ((err = snd_mixer_selem_id_malloc(sid)) < 0) {
  85. warnx("failed to allocate memory for: %s",
  86. snd_strerror(err));
  87. return NULL;
  88. }
  89. snd_mixer_selem_id_set_name(*sid, VOLUME_ALSA_MIXER_NAME);
  90. }
  91. if ((err = snd_mixer_open(&handle, 0)) < 0) {
  92. warnx("cannot open mixer: %s", snd_strerror(err));
  93. return NULL;
  94. }
  95. if ((err = snd_mixer_attach(handle, VOLUME_ALSA_CARD)) < 0) {
  96. warnx("cannot attach mixer: %s", snd_strerror(err));
  97. snd_mixer_close(handle);
  98. return NULL;
  99. }
  100. if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
  101. warnx("cannot register mixer: %s", snd_strerror(err));
  102. snd_mixer_close(handle);
  103. return NULL;
  104. }
  105. if ((err = snd_mixer_load(handle)) < 0) {
  106. warnx("failed to load mixer: %s", snd_strerror(err));
  107. snd_mixer_close(handle);
  108. return NULL;
  109. }
  110. *ret = snd_mixer_find_selem(handle, *sid);
  111. return handle;
  112. }
  113. static inline uint8_t
  114. is_muted(snd_mixer_selem_id_t **sid)
  115. {
  116. int psw;
  117. snd_mixer_t *handle;
  118. snd_mixer_elem_t *elem;
  119. if (!(handle = get_mixer_elem(&elem, sid))) return 0;
  120. snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw);
  121. snd_mixer_close(handle);
  122. return !psw;
  123. }
  124. static inline percent_t
  125. get_percentage(typeof_field(alsa_data, volume) * v, snd_mixer_selem_id_t **sid)
  126. {
  127. int err;
  128. long int vol;
  129. snd_mixer_t *handle;
  130. snd_mixer_elem_t *elem;
  131. if (!(handle = get_mixer_elem(&elem, sid))) return 0;
  132. if (!v->max)
  133. snd_mixer_selem_get_playback_volume_range(elem,
  134. &v->min,
  135. &v->max);
  136. err = snd_mixer_selem_get_playback_volume(elem, 0, &vol);
  137. snd_mixer_close(handle);
  138. if (err < 0) {
  139. warnx("cannot get playback volume: %s", snd_strerror(err));
  140. return 0;
  141. }
  142. return (percent_t)((vol - v->min) * 100 / (v->max - v->min));
  143. }
  144. static inline char *
  145. get_ctl_name(snd_mixer_selem_id_t **sid)
  146. /* after using return must be freed */
  147. {
  148. char *ctl_name;
  149. uint32_t index;
  150. snd_mixer_t *handle;
  151. snd_mixer_elem_t *elem;
  152. if (!(handle = get_mixer_elem(&elem, sid))) {
  153. return NULL;
  154. } else {
  155. index = snd_mixer_selem_get_index(elem);
  156. snd_mixer_close(handle);
  157. }
  158. if (!(ctl_name = calloc(CTL_NAME_MAX, 1))) {
  159. warnx("failed to allocate memory for ctl_name");
  160. return NULL;
  161. }
  162. snprintf(ctl_name, CTL_NAME_MAX, "hw:%" PRIu32, index);
  163. return ctl_name;
  164. }
  165. static inline void
  166. alsa_cleanup(void *ptr)
  167. {
  168. alsa_data *data = ptr;
  169. if (!!data->ctl) snd_ctl_close(data->ctl);
  170. if (!!data->e) snd_ctl_event_free(data->e);
  171. if (!!data->sid) snd_mixer_selem_id_free(data->sid);
  172. }