From: Alexandre Ratchov 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 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) {