volume.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 "../slstatus.h"
  8. #include "../util.h"
  9. #if defined(__OpenBSD__) | defined(__FreeBSD__)
  10. #include <poll.h>
  11. #include <sndio.h>
  12. #include <stdlib.h>
  13. #include <sys/queue.h>
  14. struct control {
  15. LIST_ENTRY(control) next;
  16. unsigned int addr;
  17. #define CTRL_NONE 0
  18. #define CTRL_LEVEL 1
  19. #define CTRL_MUTE 2
  20. unsigned int type;
  21. unsigned int maxval;
  22. unsigned int val;
  23. };
  24. static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls);
  25. static struct pollfd *pfds;
  26. static struct sioctl_hdl *hdl;
  27. static int initialized;
  28. /*
  29. * Call-back to obtain the description of all audio controls.
  30. */
  31. static void
  32. ondesc(void *unused, struct sioctl_desc *desc, int val)
  33. {
  34. struct control *c, *ctmp;
  35. unsigned int type = CTRL_NONE;
  36. if (desc == NULL)
  37. return;
  38. /* Delete existing audio control with the same address. */
  39. LIST_FOREACH_SAFE(c, &controls, next, ctmp) {
  40. if (desc->addr == c->addr) {
  41. LIST_REMOVE(c, next);
  42. free(c);
  43. break;
  44. }
  45. }
  46. /* Only match output.level and output.mute audio controls. */
  47. if (desc->group[0] != 0 ||
  48. strcmp(desc->node0.name, "output") != 0)
  49. return;
  50. if (desc->type == SIOCTL_NUM &&
  51. strcmp(desc->func, "level") == 0)
  52. type = CTRL_LEVEL;
  53. else if (desc->type == SIOCTL_SW &&
  54. strcmp(desc->func, "mute") == 0)
  55. type = CTRL_MUTE;
  56. else
  57. return;
  58. c = malloc(sizeof(struct control));
  59. if (c == NULL) {
  60. warn("sndio: failed to allocate audio control\n");
  61. return;
  62. }
  63. c->addr = desc->addr;
  64. c->type = type;
  65. c->maxval = desc->maxval;
  66. c->val = val;
  67. LIST_INSERT_HEAD(&controls, c, next);
  68. }
  69. /*
  70. * Call-back invoked whenever an audio control changes.
  71. */
  72. static void
  73. onval(void *unused, unsigned int addr, unsigned int val)
  74. {
  75. struct control *c;
  76. LIST_FOREACH(c, &controls, next) {
  77. if (c->addr == addr)
  78. break;
  79. }
  80. c->val = val;
  81. }
  82. static void
  83. cleanup(void)
  84. {
  85. struct control *c;
  86. if (hdl) {
  87. sioctl_close(hdl);
  88. hdl = NULL;
  89. }
  90. free(pfds);
  91. pfds = NULL;
  92. while (!LIST_EMPTY(&controls)) {
  93. c = LIST_FIRST(&controls);
  94. LIST_REMOVE(c, next);
  95. free(c);
  96. }
  97. }
  98. static int
  99. init(void)
  100. {
  101. hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
  102. if (hdl == NULL) {
  103. warn("sndio: cannot open device");
  104. goto failed;
  105. }
  106. if (!sioctl_ondesc(hdl, ondesc, NULL)) {
  107. warn("sndio: cannot set control description call-back");
  108. goto failed;
  109. }
  110. if (!sioctl_onval(hdl, onval, NULL)) {
  111. warn("sndio: cannot set control values call-back");
  112. goto failed;
  113. }
  114. pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
  115. if (pfds == NULL) {
  116. warn("sndio: cannot allocate pollfd structures");
  117. goto failed;
  118. }
  119. return 1;
  120. failed:
  121. cleanup();
  122. return 0;
  123. }
  124. const char *
  125. vol_perc(const char *unused)
  126. {
  127. struct control *c;
  128. int n, v, value;
  129. if (!initialized)
  130. initialized = init();
  131. if (hdl == NULL)
  132. return NULL;
  133. n = sioctl_pollfd(hdl, pfds, POLLIN);
  134. if (n > 0) {
  135. n = poll(pfds, n, 0);
  136. if (n > 0) {
  137. if (sioctl_revents(hdl, pfds) & POLLHUP) {
  138. warn("sndio: disconnected");
  139. cleanup();
  140. initialized = 0;
  141. return NULL;
  142. }
  143. }
  144. }
  145. value = 100;
  146. LIST_FOREACH(c, &controls, next) {
  147. if (c->type == CTRL_MUTE && c->val == 1)
  148. value = 0;
  149. else if (c->type == CTRL_LEVEL) {
  150. v = (c->val * 100 + c->maxval / 2) / c->maxval;
  151. /* For multiple channels return the minimum. */
  152. if (v < value)
  153. value = v;
  154. }
  155. }
  156. return bprintf("%d", value);
  157. }
  158. #elif defined(__NetBSD__)
  159. #include <sys/audioio.h>
  160. #include <sys/ioctl.h>
  161. #include <sys/types.h>
  162. #include <paths.h>
  163. #include <stdlib.h>
  164. #include <string.h>
  165. static struct field {
  166. char *name;
  167. mixer_ctrl_t *valp;
  168. mixer_devinfo_t *infp;
  169. char changed;
  170. } *fields, *rfields;
  171. static mixer_ctrl_t *values;
  172. static mixer_devinfo_t *infos;
  173. static const char mixer_path[] = _PATH_MIXER;
  174. static char *
  175. catstr(char *p, char *q)
  176. {
  177. char *r;
  178. asprintf(&r, "%s.%s", p, q);
  179. if (!r)
  180. warn("malloc");
  181. return r;
  182. }
  183. const char *
  184. vol_perc(const char *mixterctlVariable)
  185. {
  186. int fd, i, j, pos;
  187. const char *file;
  188. mixer_devinfo_t dinfo;
  189. int ndev;
  190. int cvol = 0;
  191. int chan = 1;
  192. file = mixer_path;
  193. fd = open(file, O_RDWR);
  194. /* Try with mixer0 but only if using the default device. */
  195. if (fd == -1 && file == mixer_path) {
  196. file = _PATH_MIXER0;
  197. fd = open(file, O_RDWR);
  198. }
  199. if (fd == -1)
  200. warn("Can't open `%s'", file);
  201. for (ndev = 0; ; ndev++) {
  202. dinfo.index = ndev;
  203. if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1)
  204. break;
  205. }
  206. rfields = calloc(ndev, sizeof *rfields);
  207. fields = calloc(ndev, sizeof *fields);
  208. infos = calloc(ndev, sizeof *infos);
  209. values = calloc(ndev, sizeof *values);
  210. for (i = 0; i < ndev; i++) {
  211. infos[i].index = i;
  212. if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1)
  213. warn("AUDIO_MIXER_DEVINFO for %d", i);
  214. }
  215. for (i = 0; i < ndev; i++) {
  216. rfields[i].name = infos[i].label.name;
  217. rfields[i].valp = &values[i];
  218. rfields[i].infp = &infos[i];
  219. }
  220. for (i = 0; i < ndev; i++) {
  221. values[i].dev = i;
  222. values[i].type = infos[i].type;
  223. if (infos[i].type != AUDIO_MIXER_CLASS) {
  224. values[i].un.value.num_channels = 2;
  225. if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) {
  226. values[i].un.value.num_channels = 1;
  227. if (ioctl(fd, AUDIO_MIXER_READ, &values[i])
  228. == -1)
  229. warn("AUDIO_MIXER_READ");
  230. }
  231. }
  232. }
  233. for (j = i = 0; i < ndev; i++) {
  234. if (infos[i].type != AUDIO_MIXER_CLASS &&
  235. infos[i].type != -1) {
  236. fields[j++] = rfields[i];
  237. for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
  238. pos = infos[pos].next) {
  239. fields[j] = rfields[pos];
  240. fields[j].name = catstr(rfields[i].name,
  241. infos[pos].label.name);
  242. infos[pos].type = -1;
  243. j++;
  244. }
  245. }
  246. }
  247. for (i = 0; i < j; i++) {
  248. int cls = fields[i].infp->mixer_class;
  249. if (cls >= 0 && cls < ndev)
  250. fields[i].name = catstr(infos[cls].label.name,
  251. fields[i].name);
  252. }
  253. /*
  254. If dual channel, add both channels up and take it into account
  255. when we get the percent.
  256. */
  257. for (i = 0; i < j; i++) {
  258. mixer_ctrl_t *m;
  259. m = fields[i].valp;
  260. if(strncmp(fields[i].name, mixterctlVariable, strlen(mixterctlVariable)) == 0) {
  261. if (m->un.value.num_channels == 1) {
  262. cvol = m->un.value.level[0];
  263. } else {
  264. cvol = m->un.value.level[0] + m->un.value.level[1];
  265. chan = 2;
  266. }
  267. break;
  268. }
  269. }
  270. if(chan == 1) {
  271. cvol = cvol * 100 / AUDIO_MAX_GAIN;
  272. } else {
  273. cvol = cvol * 50 / AUDIO_MAX_GAIN;
  274. }
  275. return bprintf("%d", cvol);
  276. }
  277. #else
  278. #include <sys/soundcard.h>
  279. const char *
  280. vol_perc(const char *card)
  281. {
  282. size_t i;
  283. int v, afd, devmask;
  284. char *vnames[] = SOUND_DEVICE_NAMES;
  285. if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
  286. warn("open '%s':", card);
  287. return NULL;
  288. }
  289. if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
  290. warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
  291. close(afd);
  292. return NULL;
  293. }
  294. for (i = 0; i < LEN(vnames); i++) {
  295. if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
  296. if (ioctl(afd, MIXER_READ(i), &v) < 0) {
  297. warn("ioctl 'MIXER_READ(%ld)':", i);
  298. close(afd);
  299. return NULL;
  300. }
  301. }
  302. }
  303. close(afd);
  304. return bprintf("%d", v & 0xff);
  305. }
  306. #endif