[BACK]Return to sndioctl.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sndioctl

File: [local] / src / usr.bin / sndioctl / sndioctl.c (download)

Revision 1.6, Mon Apr 27 21:44:47 2020 UTC (4 years, 1 month ago) by schwarze
Branch: MAIN
CVS Tags: OPENBSD_6_7_BASE, OPENBSD_6_7
Changes since 1.5: +14 -3 lines

Display multi-state controls with exactly three decimal places.
Because for now, the maximum raw value of such controls is either
127 or 255, that assures that whenever the raw value changes, the
displayed value changes, too.  At the same time, it preserves the
property that control values are not shown with excessive, misleading
precision.
If controls will ever be introduced that will have maximum raw
values of less than 100 or of more than 999, the number of decimal
places should then be reduced or increased for such controls as
appropriate.
With important help and an OK from ratchov@.

/*	$OpenBSD: sndioctl.c,v 1.6 2020/04/27 21:44:47 schwarze Exp $	*/
/*
 * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <errno.h>
#include <poll.h>
#include <sndio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

struct info {
	struct info *next;
	struct sioctl_desc desc;
	unsigned ctladdr;
#define MODE_IGNORE	0	/* ignore this value */
#define MODE_PRINT	1	/* print-only, don't change value */
#define MODE_SET	2	/* set to newval value */
#define MODE_ADD	3	/* increase current value by newval */
#define MODE_SUB	4	/* decrease current value by newval */
#define MODE_TOGGLE	5	/* toggle current value */
	unsigned mode;
	int curval, newval;
};

int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
int isdiag(struct info *);
struct info *vecent(struct info *, char *, int);
struct info *nextfunc(struct info *);
struct info *nextpar(struct info *);
struct info *firstent(struct info *, char *);
struct info *nextent(struct info *, int);
int matchpar(struct info *, char *, int);
int matchent(struct info *, char *, int);
int ismono(struct info *);
void print_node(struct sioctl_node *, int);
void print_desc(struct info *, int);
void print_val(struct info *, int);
void print_par(struct info *, int, char *);
int parse_name(char **, char *);
int parse_unit(char **, int *);
int parse_val(char **, float *);
int parse_node(char **, char *, int *);
int parse_modeval(char **, int *, float *);
void dump(void);
int cmd(char *);
void commit(void);
void list(void);
void ondesc(void *, struct sioctl_desc *, int);
void onctl(void *, unsigned, unsigned);

struct sioctl_hdl *hdl;
struct info *infolist;
int i_flag = 0, v_flag = 0, m_flag = 0, n_flag = 0, q_flag = 0;

static inline int
isname_first(int c)
{
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

static inline int
isname_next(int c)
{
	return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
}

static int
ftoi(float f)
{
	return f + 0.5;
}

/*
 * compare two sioctl_desc structures, used to sort infolist
 */
int
cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
{
	int res;

	res = strcmp(d1->group, d2->group);
	if (res != 0)
		return res;
	res = strcmp(d1->node0.name, d2->node0.name);
	if (res != 0)
		return res;
	res = d1->type - d2->type;
	if (res != 0)
		return res;
	res = strcmp(d1->func, d2->func);
	if (res != 0)
		return res;
	res = d1->node0.unit - d2->node0.unit;
	if (d1->type == SIOCTL_VEC ||
	    d1->type == SIOCTL_LIST) {
		if (res != 0)
			return res;
		res = strcmp(d1->node1.name, d2->node1.name);
		if (res != 0)
			return res;
		res = d1->node1.unit - d2->node1.unit;
	}
	return res;
}

/*
 * return true of the vector entry is diagonal
 */
int
isdiag(struct info *e)
{
	if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
		return 1;
	return e->desc.node1.unit == e->desc.node0.unit;
}

/*
 * find the selector or vector entry with the given name and channels
 */
struct info *
vecent(struct info *i, char *vstr, int vunit)
{
	while (i != NULL) {
		if ((strcmp(i->desc.node1.name, vstr) == 0) &&
		    (vunit < 0 || i->desc.node1.unit == vunit))
			break;
		i = i->next;
	}
	return i;
}

/*
 * skip all parameters with the same group, name, and func
 */
struct info *
nextfunc(struct info *i)
{
	char *str, *group, *func;

	group = i->desc.group;
	func = i->desc.func;
	str = i->desc.node0.name;
	for (i = i->next; i != NULL; i = i->next) {
		if (strcmp(i->desc.group, group) != 0 ||
		    strcmp(i->desc.node0.name, str) != 0 ||
		    strcmp(i->desc.func, func) != 0)
			return i;
	}
	return NULL;
}

/*
 * find the next parameter with the same group, name, func
 */
struct info *
nextpar(struct info *i)
{
	char *str, *group, *func;
	int unit;

	group = i->desc.group;
	func = i->desc.func;
	str = i->desc.node0.name;
	unit = i->desc.node0.unit;
	for (i = i->next; i != NULL; i = i->next) {
		if (strcmp(i->desc.group, group) != 0 ||
		    strcmp(i->desc.node0.name, str) != 0 ||
		    strcmp(i->desc.func, func) != 0)
			break;
		/* XXX: need to check for -1 ? */
		if (i->desc.node0.unit != unit)
			return i;
	}
	return NULL;
}

/*
 * return the first vector entry with the given name
 */
struct info *
firstent(struct info *g, char *vstr)
{
	char *astr, *group, *func;
	struct info *i;

	group = g->desc.group;
	astr = g->desc.node0.name;
	func = g->desc.func;
	for (i = g; i != NULL; i = i->next) {
		if (strcmp(i->desc.group, group) != 0 ||
		    strcmp(i->desc.node0.name, astr) != 0 ||
		    strcmp(i->desc.func, func) != 0)
			break;
		if (!isdiag(i))
			continue;
		if (strcmp(i->desc.node1.name, vstr) == 0)
			return i;
	}
	return NULL;
}

/*
 * find the next entry of the given vector, if the mono flag
 * is set then the whole group is searched and off-diagonal entries are
 * skipped
 */
struct info *
nextent(struct info *i, int mono)
{
	char *str, *group, *func;
	int unit;

	group = i->desc.group;
	func = i->desc.func;
	str = i->desc.node0.name;
	unit = i->desc.node0.unit;
	for (i = i->next; i != NULL; i = i->next) {
		if (strcmp(i->desc.group, group) != 0 ||
		    strcmp(i->desc.node0.name, str) != 0 ||
		    strcmp(i->desc.func, func) != 0)
			return NULL;
		if (mono)
			return i;
		if (i->desc.node0.unit == unit)
			return i;
	}
	return NULL;
}

/*
 * return true if parameter matches the given name and channel
 */
int
matchpar(struct info *i, char *astr, int aunit)
{
	if (strcmp(i->desc.node0.name, astr) != 0)
		return 0;
	if (aunit < 0)
		return 1;
	else if (i->desc.node0.unit < 0) {
		fprintf(stderr, "unit used for parameter with no unit\n");
		exit(1);
	}
	return i->desc.node0.unit == aunit;
}

/*
 * return true if selector or vector entry matches the given name and
 * channel range
 */
int
matchent(struct info *i, char *vstr, int vunit)
{
	if (strcmp(i->desc.node1.name, vstr) != 0)
		return 0;
	if (vunit < 0)
		return 1;
	else if (i->desc.node1.unit < 0) {
		fprintf(stderr, "unit used for parameter with no unit\n");
		exit(1);
	}
	return i->desc.node1.unit == vunit;
}

/*
 * return true if the given group can be represented as a signle mono
 * parameter
 */
int
ismono(struct info *g)
{
	struct info *p1, *p2;
	struct info *e1, *e2;

	p1 = g;
	switch (g->desc.type) {
	case SIOCTL_NUM:
	case SIOCTL_SW:
		for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
			if (p2->curval != p1->curval)
				return 0;
		}
		break;
	case SIOCTL_VEC:
	case SIOCTL_LIST:
		for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
			for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
				if (!isdiag(e2)) {
					if (e2->curval != 0)
						return 0;
				} else {
					e1 = vecent(p1,
					    e2->desc.node1.name,
					    p1->desc.node0.unit);
					if (e1 == NULL)
						continue;
					if (e1->curval != e2->curval)
						return 0;
				}
			}
		}
		break;
	}
	return 1;
}

/*
 * print a sub-stream, eg. "spkr[4]"
 */
void
print_node(struct sioctl_node *c, int mono)
{
	printf("%s", c->name);
	if (!mono && c->unit >= 0)
		printf("[%d]", c->unit);
}

/*
 * print info about the parameter
 */
void
print_desc(struct info *p, int mono)
{
	struct info *e;
	int more;

	switch (p->desc.type) {
	case SIOCTL_NUM:
	case SIOCTL_SW:
		printf("*");
		break;
	case SIOCTL_VEC:
	case SIOCTL_LIST:
		more = 0;
		for (e = p; e != NULL; e = nextent(e, mono)) {
			if (mono) {
				if (!isdiag(e))
					continue;
				if (e != firstent(p, e->desc.node1.name))
					continue;
			}
			if (more)
				printf(",");
			print_node(&e->desc.node1, mono);
			printf(":*");
			more = 1;
		}
	}
}

/*
 * print parameter value
 */
void
print_val(struct info *p, int mono)
{
	struct info *e;
	int more;

	switch (p->desc.type) {
	case SIOCTL_NUM:
	case SIOCTL_SW:
		if (p->desc.maxval == 1)
			printf("%d", p->curval);
		else
			/*
			 * For now, maxval is always 127 or 255,
			 * so three decimals is always ideal.
			 */
			printf("%.3f", p->curval / (float)p->desc.maxval);
		break;
	case SIOCTL_VEC:
	case SIOCTL_LIST:
		more = 0;
		for (e = p; e != NULL; e = nextent(e, mono)) {
			if (mono) {
				if (!isdiag(e))
					continue;
				if (e != firstent(p, e->desc.node1.name))
					continue;
			}
			if (more)
				printf(",");
			print_node(&e->desc.node1, mono);
			if (e->desc.maxval == 1)
				printf(":%d", e->curval);
			else
				printf(":%.3f",
				    e->curval / (float)e->desc.maxval);
			more = 1;
		}
	}
}

/*
 * print ``<parameter>=<value>'' string (including '\n')
 */
void
print_par(struct info *p, int mono, char *comment)
{
	if (!n_flag) {
		if (p->desc.group[0] != 0) {
			printf("%s", p->desc.group);
			printf("/");
		}
		print_node(&p->desc.node0, mono);
		printf(".%s=", p->desc.func);
	}
	if (i_flag)
		print_desc(p, mono);
	else
		print_val(p, mono);
	if (comment)
		printf(" # %s", comment);
	printf("\n");
}

/*
 * parse a stream name or parameter name
 */
int
parse_name(char **line, char *name)
{
	char *p = *line;
	unsigned len = 0;

	if (!isname_first(*p)) {
		fprintf(stderr, "letter expected near '%s'\n", p);
		return 0;
	}
	while (isname_next(*p)) {
		if (len >= SIOCTL_NAMEMAX - 1) {
			name[SIOCTL_NAMEMAX - 1] = '\0';
			fprintf(stderr, "%s...: too long\n", name);
			return 0;
		}
		name[len++] = *p;
		p++;
	}
	name[len] = '\0';
	*line = p;
	return 1;
}

/*
 * parse a decimal integer
 */
int
parse_unit(char **line, int *num)
{
	char *p = *line;
	unsigned int val;
	int n;

	if (sscanf(p, "%u%n", &val, &n) != 1) {
		fprintf(stderr, "number expected near '%s'\n", p);
		return 0;
	}
	if (val >= 255) {
		fprintf(stderr, "%d: too large\n", val);
		return 0;
	}
	*num = val;
	*line = p + n;
	return 1;
}

int
parse_val(char **line, float *num)
{
	char *p = *line;
	float val;
	int n;

	if (sscanf(p, "%g%n", &val, &n) != 1) {
		fprintf(stderr, "number expected near '%s'\n", p);
		return 0;
	}
	if (val < 0 || val > 1) {
		fprintf(stderr, "%g: expected number between 0 and 1\n", val);
		return 0;
	}
	*num = val;
	*line = p + n;
	return 1;
}

/*
 * parse a sub-stream, eg. "spkr[7]"
 */
int
parse_node(char **line, char *str, int *unit)
{
	char *p = *line;

	if (!parse_name(&p, str))
		return 0;
	if (*p != '[') {
		*unit = -1;
		*line = p;
		return 1;
	}
	p++;
	if (!parse_unit(&p, unit))
		return 0;
	if (*p != ']') {
		fprintf(stderr, "']' expected near '%s'\n", p);
		return 0;
	}
	p++;
	*line = p;
	return 1;
}

/*
 * parse a decimal prefixed by the optional mode
 */
int
parse_modeval(char **line, int *rmode, float *rval)
{
	char *p = *line;
	unsigned mode;

	switch (*p) {
	case '+':
		mode = MODE_ADD;
		p++;
		break;
	case '-':
		mode = MODE_SUB;
		p++;
		break;
	case '!':
		mode = MODE_TOGGLE;
		p++;
		break;
	default:
		mode = MODE_SET;
	}
	if (mode != MODE_TOGGLE) {
		if (!parse_val(&p, rval))
			return 0;
	}
	*line = p;
	*rmode = mode;
	return 1;
}

/*
 * dump the whole controls list, useful for debugging
 */
void
dump(void)
{
	struct info *i;

	for (i = infolist; i != NULL; i = i->next) {
		printf("%03u:", i->ctladdr);
		print_node(&i->desc.node0, 0);
		printf(".%s", i->desc.func);
		printf("=");
		switch (i->desc.type) {
		case SIOCTL_NUM:
		case SIOCTL_SW:
			printf("0..%d (%u)", i->desc.maxval, i->curval);
			break;
		case SIOCTL_VEC:
		case SIOCTL_LIST:
			print_node(&i->desc.node1, 0);
			printf(":0..%d (%u)", i->desc.maxval, i->curval);
		}
		printf("\n");
	}
}

/*
 * parse and execute a command ``<parameter>[=<value>]''
 */
int
cmd(char *line)
{
	char *pos, *group;
	struct info *i, *e, *g;
	char func[SIOCTL_NAMEMAX];
	char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
	int aunit, vunit;
	unsigned npar = 0, nent = 0;
	int comma, mode;
	float val;

	pos = strrchr(line, '/');
	if (pos != NULL) {
		group = line;
		pos[0] = 0;
		pos++;
	} else {
		group = "";
		pos = line;
	}
	if (!parse_node(&pos, astr, &aunit))
		return 0;
	if (*pos != '.') {
		fprintf(stderr, "'.' expected near '%s'\n", pos);
		return 0;
	}
	pos++;
	if (!parse_name(&pos, func))
		return 0;
	for (g = infolist;; g = g->next) {
		if (g == NULL) {
			fprintf(stderr, "%s.%s: no such control\n", astr, func);
			return 0;
		}
		if (strcmp(g->desc.group, group) == 0 &&
		    strcmp(g->desc.func, func) == 0 &&
		    strcmp(g->desc.node0.name, astr) == 0)
			break;
	}
	g->mode = MODE_PRINT;
	if (*pos != '=') {
		if (*pos != '\0') {
			fprintf(stderr, "junk at end of command\n");
			return 0;
		}
		return 1;
	}
	pos++;
	if (i_flag) {
		printf("can't set values in info mode\n");
		return 0;
	}
	npar = 0;
	switch (g->desc.type) {
	case SIOCTL_NUM:
	case SIOCTL_SW:
		if (!parse_modeval(&pos, &mode, &val))
			return 0;
		for (i = g; i != NULL; i = nextpar(i)) {
			if (!matchpar(i, astr, aunit))
				continue;
			i->mode = mode;
			i->newval = ftoi(val * i->desc.maxval);
			npar++;
		}
		break;
	case SIOCTL_VEC:
	case SIOCTL_LIST:
		for (i = g; i != NULL; i = nextpar(i)) {
			if (!matchpar(i, astr, aunit))
				continue;
			for (e = i; e != NULL; e = nextent(e, 0)) {
				e->newval = 0;
				e->mode = MODE_SET;
			}
			npar++;
		}
		comma = 0;
		for (;;) {
			if (*pos == '\0')
				break;
			if (comma) {
				if (*pos != ',')
					break;
				pos++;
			}
			if (!parse_node(&pos, vstr, &vunit))
				return 0;
			if (*pos == ':') {
				pos++;
				if (!parse_modeval(&pos, &mode, &val))
					return 0;
			} else {
				val = 1.;
				mode = MODE_SET;
			}
			nent = 0;
			for (i = g; i != NULL; i = nextpar(i)) {
				if (!matchpar(i, astr, aunit))
					continue;
				for (e = i; e != NULL; e = nextent(e, 0)) {
					if (matchent(e, vstr, vunit)) {
						e->newval = ftoi(val * e->desc.maxval);
						e->mode = mode;
						nent++;
					}
				}
			}
			if (nent == 0) {
				/* XXX: use print_node()-like routine */
				fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
				print_par(g, 0, NULL);
				exit(1);
			}
			comma = 1;
		}
	}
	if (npar == 0) {
		fprintf(stderr, "%s: invalid parameter\n", line);
		exit(1);
	}
	if (*pos != '\0') {
		printf("%s: junk at end of command\n", pos);
		exit(1);
	}
	return 1;
}

/*
 * write the controls with the ``set'' flag on the device
 */
void
commit(void)
{
	struct info *i;
	int val;

	for (i = infolist; i != NULL; i = i->next) {
		val = 0xdeadbeef;
		switch (i->mode) {
		case MODE_IGNORE:
		case MODE_PRINT:
			continue;
		case MODE_SET:
			val = i->newval;
			break;
		case MODE_ADD:
			val = i->curval + i->newval;
			if (val > i->desc.maxval)
				val = i->desc.maxval;
			break;
		case MODE_SUB:
			val = i->curval - i->newval;
			if (val < 0)
				val = 0;
			break;
		case MODE_TOGGLE:
			val = i->curval ? 0 : i->desc.maxval;
		}
		sioctl_setval(hdl, i->ctladdr, val);
		i->curval = val;
	}
}

/*
 * print all parameters
 */
void
list(void)
{
	struct info *p, *g;

	for (g = infolist; g != NULL; g = nextfunc(g)) {
		if (g->mode == MODE_IGNORE)
			continue;
		if (i_flag) {
			if (v_flag) {
				for (p = g; p != NULL; p = nextpar(p))
					print_par(p, 0, NULL);
			} else
				print_par(g, 1, NULL);
		} else {
			if (v_flag || !ismono(g)) {
				for (p = g; p != NULL; p = nextpar(p))
					print_par(p, 0, NULL);
			} else
				print_par(g, 1, NULL);
		}
	}
}

/*
 * register a new knob/button, called from the poll() loop.  this may be
 * called when label string changes, in which case we update the
 * existing label widged rather than inserting a new one.
 */
void
ondesc(void *arg, struct sioctl_desc *d, int curval)
{
	struct info *i, **pi;
	int cmp;

	if (d == NULL)
		return;

	/*
	 * delete control
	 */
	for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
		if (d->addr == i->desc.addr) {
			if (m_flag)
				print_par(i, 0, "deleted");
			*pi = i->next;
			free(i);
			break;
		}
	}

	if (d->type == SIOCTL_NONE)
		return;

	/*
	 * find the right position to insert the new widget
	 */
	for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
		cmp = cmpdesc(d, &i->desc);
		if (cmp == 0) {
			fprintf(stderr, "fatal: duplicate control:\n");
			print_par(i, 0, "duplicate");
			exit(1);
		}
		if (cmp < 0)
			break;
	}
	i = malloc(sizeof(struct info));
	if (i == NULL) {
		perror("malloc");
		exit(1);
	}
	i->desc = *d;
	i->ctladdr = d->addr;
	i->curval = i->newval = curval;
	i->mode = MODE_IGNORE;
	i->next = *pi;
	*pi = i;
	if (m_flag)
		print_par(i, 0, "added");
}

/*
 * update a knob/button state, called from the poll() loop
 */
void
onctl(void *arg, unsigned addr, unsigned val)
{
	struct info *i;

	for (i = infolist; i != NULL; i = i->next) {
		if (i->ctladdr != addr)
			continue;
		i->curval = val;
		if (m_flag)
			print_par(i, 0, "changed");
	}
}

int
main(int argc, char **argv)
{
	char *devname = SIO_DEVANY;
	int i, c, d_flag = 0;
	struct info *g;
	struct pollfd *pfds;
	int nfds, revents;

	while ((c = getopt(argc, argv, "df:imnqv")) != -1) {
		switch (c) {
		case 'd':
			d_flag = 1;
			break;
		case 'f':
			devname = optarg;
			break;
		case 'i':
			i_flag = 1;
			break;
		case 'm':
			m_flag = 1;
			break;
		case 'n':
			n_flag = 1;
			break;
		case 'q':
			q_flag = 1;
			break;
		case 'v':
			v_flag++;
			break;
		default:
			fprintf(stderr, "usage: sndioctl "
			    "[-dimnqv] [-f device] [command ...]\n");
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
	if (hdl == NULL) {
		fprintf(stderr, "%s: can't open control device\n", devname);
		exit(1);
	}
	if (!sioctl_ondesc(hdl, ondesc, NULL)) {
		fprintf(stderr, "%s: can't get device description\n", devname);
		exit(1);
	}
	sioctl_onval(hdl, onctl, NULL);

	if (d_flag) {
		if (argc > 0) {
			fprintf(stderr,
			    "commands are not allowed with -d option\n");
			exit(1);
		}
		dump();
	} else {
		if (argc == 0) {
			for (g = infolist; g != NULL; g = nextfunc(g))
				g->mode = MODE_PRINT;
		} else {
			for (i = 0; i < argc; i++) {
				if (!cmd(argv[i]))
					return 1;
			}
		}
		commit();
		if (!q_flag)
			list();
	}
	if (m_flag) {
		pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
		if (pfds == NULL) {
			perror("malloc");
			exit(1);
		}
		for (;;) {
			nfds = sioctl_pollfd(hdl, pfds, POLLIN);
			if (nfds == 0)
				break;
			while (poll(pfds, nfds, -1) < 0) {
				if (errno != EINTR) {
					perror("poll");
					exit(1);
				}
			}
			revents = sioctl_revents(hdl, pfds);
			if (revents & POLLHUP) {
				fprintf(stderr, "disconnected\n");
				break;
			}
		}
		free(pfds);
	}
	sioctl_close(hdl);
	return 0;
}