slstatus

status monitor
git clone git://git.suckless.org/slstatus
Log | Files | Refs | README | LICENSE

volume.c (4249B)


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