Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
sndiod: Add the server.mode control making the server mode dynamic
To:
tech@openbsd.org
Date:
Sun, 15 Mar 2026 21:17:43 +0100

Download raw body.

Thread
  • Alexandre Ratchov:

    sndiod: Add the server.mode control making the server mode dynamic

This diff allows the server mode to be changed at run-time with the
new server.mode control exposed by sndioctl(1), ex:

$ sndioctl server.mode=play,rec,mon

Useful to record your screen with voiceover without restarting sndiod
twice. The -m option defines its initial value.

If the server is switched to play-only mode, then existing clients
will start recording silence. Similarly if it's switched to rec-only
mode, clients are muted. Connections are never dropped.

At sndiod startup, trying to determine the hardware mode based on the
-m options doesn't make sense anymore. So -m can no longer be used to
force the hardware mode, which is not very useful these days imho.

OK?

Index: dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
diff -u -p -r1.132 dev.c
--- dev.c	15 Mar 2026 14:24:43 -0000	1.132
+++ dev.c	15 Mar 2026 14:38:07 -0000
@@ -42,7 +42,6 @@ void dev_sub_bcopy(struct dev *, struct 
 void dev_onmove(struct dev *, int);
 void dev_master(struct dev *, unsigned int);
 void dev_cycle(struct dev *);
-void dev_adjpar(struct dev *, int, int, int);
 int dev_allocbufs(struct dev *);
 void dev_freebufs(struct dev *);
 int dev_ref(struct dev *);
@@ -747,8 +746,7 @@ dev_master(struct dev *d, unsigned int m
  * Create a sndio device
  */
 struct dev *
-dev_new(char *path, struct aparams *par,
-    unsigned int mode, unsigned int bufsz, unsigned int round,
+dev_new(char *path, struct aparams *par, unsigned int bufsz, unsigned int round,
     unsigned int rate, unsigned int hold, unsigned int autovol)
 {
 	struct dev *d, **pd;
@@ -762,7 +760,6 @@ dev_new(char *path, struct aparams *par,
 	d->num = dev_sndnum++;
 
 	d->reqpar = *par;
-	d->reqmode = mode;
 	d->reqpchan = d->reqrchan = 0;
 	d->reqbufsz = bufsz;
 	d->reqround = round;
@@ -786,18 +783,12 @@ dev_new(char *path, struct aparams *par,
  * adjust device parameters and mode
  */
 void
-dev_adjpar(struct dev *d, int mode,
-    int pmax, int rmax)
+dev_adjpar(struct dev *d, int pmax, int rmax)
 {
-	d->reqmode |= mode & MODE_AUDIOMASK;
-	if (mode & MODE_PLAY) {
-		if (d->reqpchan < pmax + 1)
-			d->reqpchan = pmax + 1;
-	}
-	if (mode & MODE_REC) {
-		if (d->reqrchan < rmax + 1)
-			d->reqrchan = rmax + 1;
-	}
+	if (d->reqpchan < pmax + 1)
+		d->reqpchan = pmax + 1;
+	if (d->reqrchan < rmax + 1)
+		d->reqrchan = rmax + 1;
 }
 
 /*
@@ -867,7 +858,7 @@ dev_allocbufs(struct dev *d)
 int
 dev_open(struct dev *d)
 {
-	d->mode = d->reqmode;
+	d->mode = MODE_AUDIOMASK;
 	d->round = d->reqround;
 	d->bufsz = d->reqbufsz;
 	d->rate = d->reqrate;
@@ -996,12 +987,6 @@ dev_unref(struct dev *d)
 int
 dev_init(struct dev *d)
 {
-	if ((d->reqmode & MODE_AUDIOMASK) == 0) {
-#ifdef DEBUG
-		logx(1, "%s: has no streams", d->path);
-#endif
-		return 0;
-	}
 	if (d->hold && !dev_ref(d))
 		return 0;
 	return 1;
@@ -1473,12 +1458,6 @@ slot_attach(struct slot *s)
 	struct dev *d = s->opt->dev;
 	long long pos;
 
-	if (((s->mode & MODE_PLAY) && !(s->opt->mode & MODE_PLAY)) ||
-	    ((s->mode & MODE_RECMASK) && !(s->opt->mode & MODE_RECMASK))) {
-		logx(1, "slot%zu at %s: mode not allowed", s - slot_array, s->opt->name);
-		return;
-	}
-
 	/*
 	 * setup conversions layer
 	 */
@@ -1814,6 +1793,7 @@ ctlslot_visible(struct ctlslot *s, struc
 	case CTL_DEV_MASTER:
 		return (s->opt->dev == c->u.any.arg0);
 	case CTL_OPT_DEV:
+	case CTL_OPT_MODE:
 		return (s->opt == c->u.any.arg0);
 	case CTL_APP_LEVEL:
 		return (s->opt == c->u.app_level.opt);
@@ -1894,6 +1874,9 @@ ctl_scope_fmt(char *buf, size_t size, st
 	case CTL_OPT_DEV:
 		return snprintf(buf, size, "opt_dev:%s/%s",
 		    c->u.opt_dev.opt->name, c->u.opt_dev.dev->name);
+	case CTL_OPT_MODE:
+		return snprintf(buf, size, "opt_mode:%s/%s",
+		    c->u.opt_mode.opt->name, opt_modes[c->u.opt_mode.idx].name);
 	default:
 		return snprintf(buf, size, "unknown");
 	}
@@ -1962,6 +1945,11 @@ ctl_setval(struct ctl *c, int val)
 			opt_setalt(c->u.opt_dev.opt, c->u.opt_dev.dev);
 		}
 		return 1;
+	case CTL_OPT_MODE:
+		opt_setmode(c->u.opt_mode.opt, c->u.opt_mode.idx, val);
+		c->val_mask = ~0U;
+		c->curval = val;
+		return 1;
 	default:
 		logx(2, "ctl%u: not writable", c->addr);
 		return 1;
@@ -2018,6 +2006,9 @@ ctl_new(int scope, void *arg0, void *arg
 	case CTL_APP_LEVEL:
 		c->u.any.arg1 = arg1;
 		break;
+	case CTL_OPT_MODE:
+		c->u.opt_mode.idx = *(int *)arg1;
+		break;
 	default:
 		c->u.any.arg1 = NULL;
 	}
@@ -2083,6 +2074,10 @@ ctl_match(struct ctl *c, int scope, void
 	case CTL_OPT_DEV:
 	case CTL_APP_LEVEL:
 		if (arg1 != NULL && c->u.any.arg1 != arg1)
+			return 0;
+		break;
+	case CTL_OPT_MODE:
+		if (arg1 != NULL && c->u.opt_mode.idx != *(int *)arg1)
 			return 0;
 		break;
 	}
Index: dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
diff -u -p -r1.53 dev.h
--- dev.h	15 Mar 2026 14:24:43 -0000	1.53
+++ dev.h	15 Mar 2026 14:38:07 -0000
@@ -124,6 +124,7 @@ struct ctl {
 #define CTL_DEV_MASTER	1
 #define CTL_OPT_DEV	2
 #define CTL_APP_LEVEL	3
+#define CTL_OPT_MODE	4
 	unsigned int scope;
 	union {
 		struct {
@@ -145,6 +146,10 @@ struct ctl {
 			struct opt *opt;
 			struct dev *dev;
 		} opt_dev;
+		struct {
+			struct opt *opt;
+			int idx;
+		} opt_mode;
 	} u;
 
 	unsigned int addr;		/* slot side control address */
@@ -240,7 +245,6 @@ struct dev {
 	/*
 	 * desired parameters
 	 */
-	unsigned int reqmode;			/* mode */
 	struct aparams reqpar;			/* parameters */
 	int reqpchan, reqrchan;			/* play & rec chans */
 	unsigned int reqbufsz;			/* buffer size */
@@ -280,11 +284,11 @@ int dev_open(struct dev *);
 void dev_close(struct dev *);
 void dev_abort(struct dev *);
 void dev_migrate(struct dev *);
-struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int,
+struct dev *dev_new(char *, struct aparams *, unsigned int,
     unsigned int, unsigned int, unsigned int, unsigned int);
 struct dev *dev_bynum(int);
 void dev_del(struct dev *);
-void dev_adjpar(struct dev *, int, int, int);
+void dev_adjpar(struct dev *, int, int);
 int  dev_init(struct dev *);
 void dev_done(struct dev *);
 int dev_ref(struct dev *);
Index: opt.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.c,v
diff -u -p -r1.16 opt.c
--- opt.c	26 Nov 2025 08:40:16 -0000	1.16
+++ opt.c	15 Mar 2026 14:38:08 -0000
@@ -37,6 +37,12 @@ struct midiops opt_midiops = {
 	opt_midi_exit
 };
 
+struct opt_mode opt_modes[] = {
+	{MODE_PLAY, "play"},
+	{MODE_REC, "rec"},
+	{MODE_MON, "mon"},
+};
+
 struct app *
 opt_mkapp(struct opt *o, char *who)
 {
@@ -378,14 +384,10 @@ opt_new(struct dev *d, char *name,
 	o->midi = midi_new(&opt_midiops, o, MODE_MIDIIN | MODE_MIDIOUT);
 	midi_tag(o->midi, o->num);
 
-	if (mode & MODE_PLAY) {
-		o->pmin = pmin;
-		o->pmax = pmax;
-	}
-	if (mode & MODE_RECMASK) {
-		o->rmin = rmin;
-		o->rmax = rmax;
-	}
+	o->pmin = pmin;
+	o->pmax = pmax;
+	o->rmin = rmin;
+	o->rmax = rmax;
 	o->maxweight = maxweight;
 	o->mtc = mmc ? &mtc_array[0] : NULL;
 	o->dup = dup;
@@ -486,19 +488,52 @@ opt_del(struct opt *o)
 void
 opt_init(struct opt *o)
 {
+	int i;
+
+	for (i = 0; i < sizeof(opt_modes) / sizeof(opt_modes[0]); i++) {
+		ctl_new(CTL_OPT_MODE, o, &i, CTL_VEC, "",
+		    o->name, "server", -1, "mode", opt_modes[i].name, -1,
+		    1, (o->mode & opt_modes[i].bit) ? 1 : 0);
+	}
 }
 
 void
 opt_done(struct opt *o)
 {
 	struct dev *d;
+	int i;
 
 	if (o->refcnt != 0) {
 		// XXX: all clients are already kicked, so this never happens
 		logx(0, "%s: still has refs", o->name);
 	}
+
+	for (i = 0; i < sizeof(opt_modes) / sizeof(opt_modes[0]); i++)
+		ctl_del(CTL_OPT_MODE, o, &i);
+
 	for (d = dev_list; d != NULL; d = d->next)
 		ctl_del(CTL_OPT_DEV, o, d);
+}
+
+/*
+ * Flip a bit of opt's mode
+ */
+void
+opt_setmode(struct opt *o, int idx, int val)
+{
+	int mode;
+
+	/*
+	 * The o->mode field is directly used by the device,
+	 * so just flipping the bit is OK.
+	 */
+	mode = opt_modes[idx].bit;
+	if (val)
+		o->mode |= mode;
+	else
+		o->mode &= ~mode;
+
+	logx(2, "%s: %s -> %d", __func__, opt_modes[idx].name, val);
 }
 
 /*
Index: opt.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.h,v
diff -u -p -r1.11 opt.h
--- opt.h	26 Nov 2025 08:40:16 -0000	1.11
+++ opt.h	15 Mar 2026 14:38:08 -0000
@@ -55,8 +55,15 @@ struct opt {
 	int refcnt;
 };
 
+struct opt_mode {
+	int bit;
+	char *name;
+};
+
 extern struct opt *opt_list;
 
+extern struct opt_mode opt_modes[];
+
 struct app *opt_mkapp(struct opt *o, char *who);
 void opt_appvol(struct opt *o, struct app *a, int vol);
 void opt_midi_vol(struct opt *, struct app *);
@@ -70,6 +77,7 @@ struct opt *opt_byname(char *);
 struct opt *opt_bynum(int);
 void opt_init(struct opt *);
 void opt_done(struct opt *);
+void opt_setmode(struct opt *, int, int);
 int opt_setdev(struct opt *, struct dev *);
 void opt_migrate(struct opt *, struct dev *);
 struct dev *opt_ref(struct opt *);
Index: siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
diff -u -p -r1.30 siofile.c
--- siofile.c	22 Jan 2026 09:24:26 -0000	1.30
+++ siofile.c	15 Mar 2026 14:38:08 -0000
@@ -104,7 +104,7 @@ int
 dev_sio_open(struct dev *d)
 {
 	struct sio_par par;
-	unsigned int rate, mode = d->reqmode & (SIO_PLAY | SIO_REC);
+	unsigned int rate, mode = SIO_PLAY | SIO_REC;
 
 	d->sio.hdl = fdpass_sio_open(d->num, mode);
 	if (d->sio.hdl == NULL) {
Index: sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
diff -u -p -r1.53 sndiod.c
--- sndiod.c	15 Mar 2026 10:05:09 -0000	1.53
+++ sndiod.c	15 Mar 2026 14:38:08 -0000
@@ -103,8 +103,7 @@ unsigned int opt_mode(void);
 void getbasepath(char *);
 void setsig(void);
 void unsetsig(void);
-struct dev *mkdev(char *, struct aparams *,
-    int, int, int, int, int, int);
+struct dev *mkdev(char *, struct aparams *, int, int, int, int, int);
 struct port *mkport(char *, int);
 struct opt *mkopt(char *, struct dev *, struct opt_alt *,
     int, int, int, int, int, int, int, int);
@@ -366,7 +365,7 @@ unsetsig(void)
 
 struct dev *
 mkdev(char *path, struct aparams *par,
-    int mode, int bufsz, int round, int rate, int hold, int autovol)
+    int bufsz, int round, int rate, int hold, int autovol)
 {
 	struct dev *d;
 
@@ -381,7 +380,7 @@ mkdev(char *path, struct aparams *par,
 		bufsz = round * 2;
 	} else if (!round)
 		round = bufsz / 2;
-	d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol);
+	d = dev_new(path, par, bufsz, round, rate, hold, autovol);
 	if (d == NULL)
 		exit(1);
 	return d;
@@ -414,7 +413,7 @@ mkopt(char *path, struct dev *d, struct 
 	    MIDI_TO_ADATA(vol), mmc, dup, mode);
 	if (o == NULL)
 		return NULL;
-	dev_adjpar(d, o->mode, o->pmax, o->rmax);
+	dev_adjpar(d, o->pmax, o->rmax);
 	for (a = alt_list; a != NULL; a = a->next)
 		opt_setalt(o, a->dev);
 	return o;
@@ -602,7 +601,7 @@ main(int argc, char **argv)
 		case 's':
 			if (d == NULL) {
 				for (i = 0; default_devs[i] != NULL; i++) {
-					mkdev(default_devs[i], &par, 0,
+					mkdev(default_devs[i], &par,
 					    bufsz, round, rate, 0, autovol);
 				}
 				d = dev_list;
@@ -642,7 +641,7 @@ main(int argc, char **argv)
 				errx(1, "%s: block size is %s", optarg, str);
 			break;
 		case 'f':
-			d = mkdev(optarg, &par, 0, bufsz, round,
+			d = mkdev(optarg, &par, bufsz, round,
 			    rate, hold, autovol);
 			while ((a = alt_list) != NULL) {
 				alt_list = a->next;
@@ -653,7 +652,7 @@ main(int argc, char **argv)
 			if (d == NULL)
 				errx(1, "-F %s: no devices defined", optarg);
 			a = xmalloc(sizeof(struct opt_alt));
-			a->dev = mkdev(optarg, &par, 0, bufsz, round,
+			a->dev = mkdev(optarg, &par, bufsz, round,
 			    rate, hold, autovol);
 			for (pa = &alt_list; *pa != NULL; pa = &(*pa)->next)
 				;
@@ -677,7 +676,7 @@ main(int argc, char **argv)
 	}
 	if (dev_list == NULL) {
 		for (i = 0; default_devs[i] != NULL; i++) {
-			mkdev(default_devs[i], &par, 0,
+			mkdev(default_devs[i], &par,
 			    bufsz, round, rate, 0, autovol);
 		}
 	}
@@ -701,7 +700,7 @@ main(int argc, char **argv)
 		if (opt_new(d, NULL, o->pmin, o->pmax, o->rmin, o->rmax,
 			o->maxweight, o->mtc != NULL, o->dup, o->mode) == NULL)
 			return 1;
-		dev_adjpar(d, o->mode, o->pmax, o->rmax);
+		dev_adjpar(d, o->pmax, o->rmax);
 	}
 
 	while ((a = alt_list) != NULL) {