Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sndio(7) support which is the only supported mixer backend on OpenBSD #470

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/i3status.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ void print_volume(volume_ctx_t *ctx);

bool process_runs(const char *path);
int volume_pulseaudio(uint32_t sink_idx, const char *sink_name);
int volume_sndio(int *vol, int *muted);
bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]);
bool pulse_initialize(void);

Expand Down
10 changes: 10 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ if get_option('pulseaudio')
cdata.set('HAS_PULSEAUDIO', 1)
endif

if get_option('sndio')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making this feature optional, can you make it non-optional on OpenBSD please?

cdata.set('HAS_SNDIO', 1)
endif

# Instead of generating config.h directly, make vcs_tag generate it so that
# @VCS_TAG@ is replaced.
config_h_in = configure_file(
Expand Down Expand Up @@ -191,6 +195,12 @@ if get_option('pulseaudio')
i3status_srcs += ['src/pulse.c']
endif

if get_option('sndio')
sndio_dep = cc.find_library('sndio')
i3status_deps += [sndio_dep]
i3status_srcs += ['src/sndio.c']
endif

host_os = host_machine.system()
if host_os == 'linux'
nlgenl_dep = dependency('libnl-genl-3.0', method: 'pkg-config')
Expand Down
3 changes: 3 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ option('docdir', type: 'string', value: '',

option('pulseaudio', type: 'boolean', value: true,
description: 'Build with pulseaudio support')

option('sndio', type: 'boolean', value: true,
description: 'Build with sndio support')
29 changes: 24 additions & 5 deletions src/print_volume.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <sys/soundcard.h>
#endif

#if defined(__NetBSD__) || defined(__OpenBSD__)
#if defined(__NetBSD__)
#include <fcntl.h>
#include <unistd.h>
#include <sys/audioio.h>
Expand Down Expand Up @@ -137,7 +137,26 @@ void print_volume(volume_ctx_t *ctx) {
/* negative result or NULL description means error, fail PulseAudio attempt */
}
/* If some other device was specified or PulseAudio is not detected,
* proceed to ALSA / OSS */
* proceed to sndio / ALSA / OSS */
#endif

#ifdef HAS_SNDIO
int vol, mute;

if (volume_sndio(&vol, &mute) == 0) {
if (mute) {
START_COLOR("color_degraded");
pbval = 0;
}
char *formatted = apply_volume_format(mute ? ctx->fmt_muted : ctx->fmt,
vol & 0x7f, "sndio");
OUTPUT_FORMATTED;
free(formatted);
goto out_with_format;
}

goto out;
/* If sndio is not detected, proceed to ALSA / OSS */
#endif

#ifdef __linux__
Expand Down Expand Up @@ -243,7 +262,7 @@ void print_volume(volume_ctx_t *ctx) {
goto out_with_format;

#endif
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
char *mixerpath;
char defaultmixer[] = "/dev/mixer";
int mixfd, vol, devmask = 0;
Expand All @@ -256,7 +275,7 @@ void print_volume(volume_ctx_t *ctx) {
mixerpath = defaultmixer;

if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
#if defined(__NetBSD__) || defined(__OpenBSD__)
#if defined(__NetBSD__)
warn("audioio: Cannot open mixer");
#else
warn("OSS: Cannot open mixer");
Expand All @@ -267,7 +286,7 @@ void print_volume(volume_ctx_t *ctx) {
if (ctx->mixer_idx > 0)
free(mixerpath);

#if defined(__NetBSD__) || defined(__OpenBSD__)
#if defined(__NetBSD__)
int oclass_idx = -1, master_idx = -1, master_mute_idx = -1;
int master_next = AUDIO_MIXER_LAST;
mixer_devinfo_t devinfo, devinfo2;
Expand Down
195 changes: 195 additions & 0 deletions src/sndio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#include <poll.h>
#include <sndio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "i3status.h"

struct control {
struct control *next;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use the include/queue.h macros for this list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't see the reason why for such simple code, but doable if you insist

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s not really about complexity, but rather uniformity and code re-use. No need to have multiple linked list implementations within the same project… :)

char name[32];
unsigned int addr;
unsigned int max;
unsigned int value;
unsigned int muted;
unsigned int muteaddr;
};

static int initialized;
static struct sioctl_hdl *hdl;
static struct control *controls;
static struct pollfd *pfds;

/*
* new control registered or control changed
*/
static void ondesc(void *unused, struct sioctl_desc *d, int val) {
struct control *i, **pi;

if (d == NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere, please add braces:

if (d == NULL) {
  return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

return;

/*
* delete existing control with the same address
*/
for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
if (d->addr == i->addr) {
*pi = i->next;
free(i);
break;
}
}

/*
* if we find an output.mute, associate it with its output.level
*/
if (d->type == SIOCTL_SW &&
d->group[0] == 0 &&
strcmp(d->node0.name, "output") == 0 &&
strcmp(d->func, "mute") == 0) {
char name[32];
snprintf(name, sizeof(name), "%s%d", d->node0.name, d->node0.unit);
for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
if (strcmp(name, i->name) == 0) {
i->muted = val;
i->muteaddr = d->addr;
break;
}
}
return;
}

/*
* we're interested in top-level output.level controls only
*/
if (d->type != SIOCTL_NUM ||
d->group[0] != 0 ||
strcmp(d->node0.name, "output") != 0 ||
strcmp(d->func, "level") != 0)
return;

i = malloc(sizeof(struct control));
if (i == NULL) {
fprintf(stderr, "sndio: failed to allocate control\n");
return;
}

snprintf(i->name, sizeof(i->name), "%s%d", d->node0.name, d->node0.unit);
i->addr = d->addr;
i->max = d->maxval;
i->value = val;
i->next = controls;
i->muted = 0;
i->muteaddr = -1;
controls = i;
}

/*
* control value changed
*/
static void onval(void *unused, unsigned int addr, unsigned int value) {
struct control *c;

for (c = controls;; c = c->next) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not for (c = controls; c != NULL; c = c->next) instead of the explicit if (c == NULL) { return } below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (c == NULL)
return;
if (c->addr == addr) {
c->value = value;
return;
}
if (c->muteaddr == addr) {
c->muted = value;
return;
}
}
}

static void cleanup(void) {
struct control *c;

if (hdl) {
sioctl_close(hdl);
hdl = NULL;
}
if (pfds) {
free(pfds);
pfds = NULL;
}
while ((c = controls) != NULL) {
controls = c->next;
free(c);
}
}

static int init(void) {
/* open device */
hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
if (hdl == NULL) {
fprintf(stderr, "sndio: cannot open device\n");
goto failed;
}

/* register call-back for control description changes */
if (!sioctl_ondesc(hdl, ondesc, NULL)) {
fprintf(stderr, "sndio: cannot get description\n");
goto failed;
}

/* register call-back for volume changes */
if (!sioctl_onval(hdl, onval, NULL)) {
fprintf(stderr, "sndio: cannot get values\n");
goto failed;
}

/* allocate structures for poll() syscall */
pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
if (pfds == NULL) {
fprintf(stderr, "sndio: cannot allocate pollfd structures\n");
goto failed;
}
return 1;
failed:
cleanup();
return 0;
}

int volume_sndio(int *vol, int *muted) {
struct control *c;
int n, v;

if (!initialized) {
initialized = 1;
init();
}
if (hdl == NULL)
return -1;

/* check if controls changed */
n = sioctl_pollfd(hdl, pfds, POLLIN);
if (n > 0) {
n = poll(pfds, n, 0);
if (n > 0) {
if (sioctl_revents(hdl, pfds) & POLLHUP) {
fprintf(stderr, "sndio: disconnected\n");
cleanup();
return -1;
}
}
}

/*
* get control value: as there may be multiple
* channels, return the minimum
*/
*vol = 100;
*muted = 0;
for (c = controls; c != NULL; c = c->next) {
v = (c->value * 100 + c->max / 2) / c->max;
if (v < *vol) {
*vol = v;
*muted = c->muted;
}
}

return 0;
}