volume.c 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /* See LICENSE file for copyright and license details. */
  2. #include <fcntl.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <sys/ioctl.h>
  6. #include <unistd.h>
  7. #include "../util.h"
  8. #if defined(__OpenBSD__)
  9. #include <sys/queue.h>
  10. #include <poll.h>
  11. #include <sndio.h>
  12. #include <stdlib.h>
  13. struct control {
  14. LIST_ENTRY(control) next;
  15. unsigned int addr;
  16. #define CTRL_NONE 0
  17. #define CTRL_LEVEL 1
  18. #define CTRL_MUTE 2
  19. unsigned int type;
  20. unsigned int maxval;
  21. unsigned int val;
  22. };
  23. static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls);
  24. static struct pollfd *pfds;
  25. static struct sioctl_hdl *hdl;
  26. static int initialized;
  27. /*
  28. * Call-back to obtain the description of all audio controls.
  29. */
  30. static void
  31. ondesc(void *unused, struct sioctl_desc *desc, int val)
  32. {
  33. struct control *c, *ctmp;
  34. unsigned int type = CTRL_NONE;
  35. if (desc == NULL)
  36. return;
  37. /* Delete existing audio control with the same address. */
  38. LIST_FOREACH_SAFE(c, &controls, next, ctmp) {
  39. if (desc->addr == c->addr) {
  40. LIST_REMOVE(c, next);
  41. free(c);
  42. break;
  43. }
  44. }
  45. /* Only match output.level and output.mute audio controls. */
  46. if (desc->group[0] != 0 ||
  47. strcmp(desc->node0.name, "output") != 0)
  48. return;
  49. if (desc->type == SIOCTL_NUM &&
  50. strcmp(desc->func, "level") == 0)
  51. type = CTRL_LEVEL;
  52. else if (desc->type == SIOCTL_SW &&
  53. strcmp(desc->func, "mute") == 0)
  54. type = CTRL_MUTE;
  55. else
  56. return;
  57. c = malloc(sizeof(struct control));
  58. if (c == NULL) {
  59. warn("sndio: failed to allocate audio control\n");
  60. return;
  61. }
  62. c->addr = desc->addr;
  63. c->type = type;
  64. c->maxval = desc->maxval;
  65. c->val = val;
  66. LIST_INSERT_HEAD(&controls, c, next);
  67. }
  68. /*
  69. * Call-back invoked whenever an audio control changes.
  70. */
  71. static void
  72. onval(void *unused, unsigned int addr, unsigned int val)
  73. {
  74. struct control *c;
  75. LIST_FOREACH(c, &controls, next) {
  76. if (c->addr == addr)
  77. break;
  78. }
  79. c->val = val;
  80. }
  81. static void
  82. cleanup(void)
  83. {
  84. struct control *c;
  85. if (hdl) {
  86. sioctl_close(hdl);
  87. hdl = NULL;
  88. }
  89. free(pfds);
  90. pfds = NULL;
  91. while (!LIST_EMPTY(&controls)) {
  92. c = LIST_FIRST(&controls);
  93. LIST_REMOVE(c, next);
  94. free(c);
  95. }
  96. }
  97. static int
  98. init(void)
  99. {
  100. hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
  101. if (hdl == NULL) {
  102. warn("sndio: cannot open device");
  103. goto failed;
  104. }
  105. if (!sioctl_ondesc(hdl, ondesc, NULL)) {
  106. warn("sndio: cannot set control description call-back");
  107. goto failed;
  108. }
  109. if (!sioctl_onval(hdl, onval, NULL)) {
  110. warn("sndio: cannot set control values call-back");
  111. goto failed;
  112. }
  113. pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
  114. if (pfds == NULL) {
  115. warn("sndio: cannot allocate pollfd structures");
  116. goto failed;
  117. }
  118. return 1;
  119. failed:
  120. cleanup();
  121. return 0;
  122. }
  123. const char *
  124. vol_perc(const char *unused)
  125. {
  126. struct control *c;
  127. int n, v, value;
  128. if (!initialized)
  129. initialized = init();
  130. if (hdl == NULL)
  131. return NULL;
  132. n = sioctl_pollfd(hdl, pfds, POLLIN);
  133. if (n > 0) {
  134. n = poll(pfds, n, 0);
  135. if (n > 0) {
  136. if (sioctl_revents(hdl, pfds) & POLLHUP) {
  137. warn("sndio: disconnected");
  138. cleanup();
  139. return NULL;
  140. }
  141. }
  142. }
  143. value = 100;
  144. LIST_FOREACH(c, &controls, next) {
  145. if (c->type == CTRL_MUTE && c->val == 1)
  146. value = 0;
  147. else if (c->type == CTRL_LEVEL) {
  148. v = (c->val * 100 + c->maxval / 2) / c->maxval;
  149. /* For multiple channels return the minimum. */
  150. if (v < value)
  151. value = v;
  152. }
  153. }
  154. return bprintf("%d", value);
  155. }
  156. #else
  157. #include <sys/soundcard.h>
  158. const char *
  159. vol_perc(const char *card)
  160. {
  161. size_t i;
  162. int v, afd, devmask;
  163. char *vnames[] = SOUND_DEVICE_NAMES;
  164. if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
  165. warn("open '%s':", card);
  166. return NULL;
  167. }
  168. if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
  169. warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
  170. close(afd);
  171. return NULL;
  172. }
  173. for (i = 0; i < LEN(vnames); i++) {
  174. if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
  175. if (ioctl(afd, MIXER_READ(i), &v) < 0) {
  176. warn("ioctl 'MIXER_READ(%ld)':", i);
  177. close(afd);
  178. return NULL;
  179. }
  180. }
  181. }
  182. close(afd);
  183. return bprintf("%d", v & 0xff);
  184. }
  185. #endif