Sunday, March 08, 2020

sysutils/tray-app: use sndio

This diff switches sysutils/tray-app to the new audio control API. Now
the programm displays and controls the sndiod master volume knob. This
has two advantages:

- tray-app now works on any device, including those with no volume
controls.

- in case there are multiple audio devices, tray-app won't pick the
wrong one anymore

OK?

Index: Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/Makefile,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 Makefile
--- Makefile 12 Jul 2019 20:49:53 -0000 1.8
+++ Makefile 8 Mar 2020 15:19:47 -0000
@@ -5,7 +5,7 @@ ONLY_FOR_ARCHS= ${APM_ARCHS}
COMMENT= small utilities for X11 system tray: eject, battery, mixer

DISTNAME= tray-app-0.3.1
-REVISION= 0
+REVISION= 1

CATEGORIES= sysutils x11

Index: patches/patch-sound_Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/patches/patch-sound_Makefile,v
retrieving revision 1.1.1.1
diff -u -p -u -p -r1.1.1.1 patch-sound_Makefile
--- patches/patch-sound_Makefile 17 Sep 2013 11:21:50 -0000 1.1.1.1
+++ patches/patch-sound_Makefile 8 Mar 2020 15:19:47 -0000
@@ -1,6 +1,7 @@
$OpenBSD: patch-sound_Makefile,v 1.1.1.1 2013/09/17 11:21:50 sthen Exp $
---- sound/Makefile.orig Mon Mar 12 08:46:04 2012
-+++ sound/Makefile Tue Sep 17 11:39:18 2013
+Index: sound/Makefile
+--- sound/Makefile.orig
++++ sound/Makefile
@@ -8,13 +8,13 @@ MAN=

gtk_CFLAGS!= pkg-config --cflags gtk+-2.0
@@ -8,7 +9,8 @@ $OpenBSD: patch-sound_Makefile,v 1.1.1.1
-CFLAGS= -W -Wall -g -O0 -I../lib $(gtk_CFLAGS)
+CFLAGS+= -W -Wall -I../lib $(gtk_CFLAGS)
LDFLAGS= -L../lib $(gtk_LDFLAGS)
- LDADD= -ltrayapp
+-LDADD= -ltrayapp
++LDADD= -ltrayapp -lsndio

-BINDIR=/usr/local/libexec/tray-app
+BINDIR=${TRUEPREFIX}/libexec/tray-app
Index: patches/patch-sound_sound_c
===================================================================
RCS file: patches/patch-sound_sound_c
diff -N patches/patch-sound_sound_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-sound_sound_c 8 Mar 2020 15:19:47 -0000
@@ -0,0 +1,539 @@
+$OpenBSD$
+
+Index: sound/sound.c
+--- sound/sound.c.orig
++++ sound/sound.c
+@@ -4,12 +4,13 @@
+ */
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+-#include <sys/audioio.h>
++#include <sndio.h>
+
+ #include <gtk/gtk.h>
+
+ #include <err.h>
+ #include <fcntl.h>
++#include <poll.h>
+ #include <stdlib.h>
+ #include <string.h>
+
+@@ -19,16 +20,20 @@
+ #include "sound-1.xpm"
+ #include "sound-2.xpm"
+
++struct control {
++ struct control *next;
++ unsigned int addr;
++ unsigned int value;
++ unsigned int max;
++ int ismute;
++};
++
+ static void usage(const char *prog);
+-static int get_mixer_index(int fd, mixer_devinfo_t *devinfo);
+-static int get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume);
+-static int set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume);
+-static int get_mute(int fd, mixer_devinfo_t *devinfo, int *mute);
+-static int set_mute(int fd, mixer_devinfo_t *devinfo, int mute);
++static void set_state(int ismute, int mute);
++static void get_state(int *rvolume, int *rmute);
+
+ static void prepare_tooltip(int mute, u_char volume, char *text, size_t sz);
+
+-static gboolean cb_timer(GtkWidget *widget);
+ static void cb_button_toggled(GtkWidget *widget, gpointer data);
+ static void cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj);
+ static gboolean cb_window_delete(GtkWidget *widget,GdkEvent *event,
+@@ -44,12 +49,13 @@ static gboolean cb_tray_query_tooltip(GtkStatusIcon *i
+ static int init_gui(void);
+ static void show_gui(void);
+ static void hide_gui(void);
++static void refresh_gui(void);
+
+ static int init_tray(int doinvert);
+ static void set_tray_icon(u_char volume);
+
+-static int mixer_fd;
+-static mixer_devinfo_t outputs_master_dev[2]; /* volume, mute */
++static struct sioctl_hdl *hdl;
++static struct control *controls;
+
+ static GtkWidget *gui_window = NULL;
+ static GtkObject *gui_adj = NULL;
+@@ -63,9 +69,6 @@ static GdkPixbuf *tray_pixbuf_0 = NULL;
+ static GdkPixbuf *tray_pixbuf_1 = NULL;
+ static GdkPixbuf *tray_pixbuf_2 = NULL;
+
+-static u_char current_volume = 0;
+-static int current_mute = 0;
+-
+ void
+ usage(const char *prog)
+ {
+@@ -76,104 +79,134 @@ usage(const char *prog)
+ prog);
+ }
+
+-static int
+-get_mixer_index(int fd, mixer_devinfo_t *devinfo)
++/*
++ * new control registered
++ */
++static void
++cb_control_desc(void *unused, struct sioctl_desc *d, int val)
+ {
+- int error;
+- int i, outputs_idx;
++ struct control *c, **pc;
++ int has_volume, has_mute;
++ int ismute;
+
+- i = 0;
+- outputs_idx = -1;
+- devinfo[0].index = 0;
+- for (;;) {
+- error = ioctl(fd, AUDIO_MIXER_DEVINFO, devinfo + i);
+- if (error == -1)
++ if (d == NULL) {
++ /*
++ * NULL indicates that we got all changes and its time
++ * to rebuild the GUI
++ */
++ has_volume = has_mute = 0;
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute)
++ has_mute = 1;
++ else
++ has_volume = 1;
++ }
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), has_volume);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_check), has_mute);
++ return;
++ }
++
++ /*
++ * delete existing control with the same address
++ */
++ for (pc = &controls; (c = *pc) != NULL; pc = &c->next) {
++ if (d->addr == c->addr) {
++ *pc = c->next;
++ free(c);
+ break;
++ }
++ }
+
+- if (i == 0 && devinfo[0].type == AUDIO_MIXER_CLASS &&
+- strcmp(devinfo[0].label.name, "outputs") == 0)
+- outputs_idx = devinfo[0].index;
+- else if (i == 0 && devinfo[0].type == AUDIO_MIXER_VALUE &&
+- strcmp(devinfo[0].label.name, "master") == 0 &&
+- outputs_idx != -1 &&
+- devinfo[0].mixer_class == outputs_idx) {
+- devinfo[1].index = devinfo[0].index;
+- i++;
+- } else if (i == 1 && devinfo[1].prev == devinfo[0].index &&
+- devinfo[1].type == AUDIO_MIXER_ENUM &&
+- strcmp(devinfo[1].label.name, "mute") == 0 &&
+- devinfo[1].mixer_class == outputs_idx)
+- return (0);
++ /*
++ * SIOCTL_NONE means control was deleted
++ */
++ if (d->type == SIOCTL_NONE)
++ return;
+
+- devinfo[i].index++;
+- }
+- return (-1);
+-}
++ /*
++ * we're interested in top-level output.xxx controls only
++ */
++ if (strcmp(d->group, "") != 0 || strcmp(d->node0.name, "output") != 0)
++ return;
+
+-static int
+-get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume)
+-{
+- mixer_ctrl_t mctl;
+- int error;
++ if (strcmp(d->func, "level") == 0)
++ ismute = 0;
++ else if (strcmp(d->func, "mute") == 0)
++ ismute = 1;
++ else
++ return;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_VALUE;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *volume = mctl.un.value.level[0];
+- return (0);
++ c = malloc(sizeof(struct control));
++ if (c == NULL)
++ err(1, "malloc");
++
++ c->addr = d->addr;
++ c->max = d->maxval;
++ c->value = val;
++ c->ismute = ismute;
++ c->next = controls;
++ controls = c;
+ }
+
+-static int
+-set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume)
++/*
++ * control value changed
++ */
++static void
++cb_control_value(void *unused, unsigned int addr, unsigned int value)
+ {
+- mixer_ctrl_t mctl;
+- int i, error;
++ struct control *c;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.value.num_channels = devinfo->un.v.num_channels;
+- for (i = 0; i < devinfo->un.v.num_channels; i++)
+- mctl.un.value.level[i] = volume;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; ; c = c->next) {
++ if (c == NULL)
++ return;
++ if (c->addr == addr)
++ break;
++ }
++ if (c->value == value)
++ return;
++
++ c->value = value;
++ refresh_gui();
+ }
+
+-static int
+-get_mute(int fd, mixer_devinfo_t *devinfo, int *mute)
++/*
++ * Return current volume and mute states:
++ * - the returned volume is the minimum volume of all channels.
++ * - the returned mute set 1 if all channels are muted
++ */
++static void
++get_state(int *rvolume, int *rmute)
+ {
+- mixer_ctrl_t mctl;
+- int error;
+-
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_ENUM;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *mute = mctl.un.ord;
+- return (0);
++ struct control *c;
++ int mute = 0;
++ int v, volume = 100;
++
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute) {
++ mute |= !!c->value;
++ } else {
++ v = (c->value * 100 + c->max / 2) / c->max;
++ if (v < volume)
++ volume = v;
++ }
++ }
++ *rvolume = volume;
++ *rmute = mute;
+ }
+
+-static int
+-set_mute(int fd, mixer_devinfo_t *devinfo, int mute)
++static void
++set_state(int ismute, int value)
+ {
+- mixer_ctrl_t mctl;
+- int error;
++ struct control *c;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.ord = mute;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute != ismute)
++ continue;
++ c->value = c->ismute ? !!value : (value * c->max + 50) / 100;
++ sioctl_setval(hdl, c->addr, c->value);
++ }
++
++ refresh_gui();
+ }
+
+ static void
+@@ -182,63 +215,20 @@ prepare_tooltip(int mute, u_char volume, char *text, s
+ if (mute) {
+ strlcpy(text, "Audio is muted", sz);
+ } else {
+- snprintf(text, sz, "Audio volume: %u%%",
+- 100U * (u_int)volume / AUDIO_MAX_GAIN);
++ snprintf(text, sz, "Audio volume: %u%%", volume);
+ }
+ }
+
+-static gboolean
+-cb_timer(GtkWidget *widget)
+-{
+- int mute;
+- u_char volume;
+-
+- if (get_mute(mixer_fd, outputs_master_dev + 1, &mute) == -1)
+- return (TRUE);
+- else if (get_volume(mixer_fd, outputs_master_dev, &volume) == -1)
+- return (TRUE);
+- if (mute != current_mute || volume != current_volume) {
+- set_tray_icon(mute ? 0 : volume);
+-
+- /* Move slider. Change "check" button state. */
+- if (widget->window != NULL) {
+- gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+- AUDIO_MAX_GAIN - volume);
+- gtk_toggle_button_set_active(
+- GTK_TOGGLE_BUTTON(gui_check), mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- }
+-
+- current_volume = volume;
+- current_mute = mute;
+- }
+-
+- return (TRUE);
+-}
+-
+ static void
+ cb_button_toggled(GtkWidget *widget, gpointer data)
+ {
+- int mute;
+-
+- mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+- if (set_mute(mixer_fd, outputs_master_dev + 1, mute) == 0) {
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- set_tray_icon(mute ? 0 : current_volume);
+- current_mute = mute;
+- }
++ set_state(1, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
+ }
+
+ static void
+ cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj)
+ {
+- u_char volume;
+-
+- volume = AUDIO_MAX_GAIN - (u_char)gtk_adjustment_get_value(adj);
+- if (set_volume(mixer_fd, outputs_master_dev, volume) == 0) {
+- set_tray_icon(current_mute ? 0 : volume);
+- current_volume = volume;
+- }
++ set_state(0, 100 - (int)gtk_adjustment_get_value(adj));
+ }
+
+ static gboolean
+@@ -277,8 +267,10 @@ cb_tray_query_tooltip(GtkStatusIcon *icon, gint x, gin
+ gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data)
+ {
+ char text[30];
++ int volume, mute;
+
+- prepare_tooltip(current_mute, current_volume, text, sizeof(text));
++ get_state(&volume, &mute);
++ prepare_tooltip(mute, volume, text, sizeof(text));
+ gtk_tooltip_set_text(tooltip, text);
+ return (TRUE);
+ }
+@@ -304,8 +296,7 @@ init_gui(void)
+ gtk_window_set_deletable(GTK_WINDOW(gui_window), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(gui_window), FALSE);
+
+- gui_adj = gtk_adjustment_new(0.0, AUDIO_MIN_GAIN,
+- AUDIO_MAX_GAIN, 1.0, 10.0, 0.0);
++ gui_adj = gtk_adjustment_new(0.0, 0, 100, 1.0, 10.0, 0.0);
+ if (gui_adj == NULL)
+ return (-1);
+
+@@ -355,12 +346,14 @@ show_gui(void)
+ GdkRectangle area;
+ GtkOrientation orientation;
+ int width, height, x, y;
++ int volume, mute;
+
++ get_state(&volume, &mute);
++
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+- AUDIO_MAX_GAIN - current_volume);
++ 100 - volume);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui_check),
+- current_mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !current_mute);
++ mute);
+ gtk_widget_show_all(GTK_WIDGET(gui_window));
+
+ gtk_status_icon_get_geometry(tray_icon, NULL, &area, &orientation);
+@@ -391,12 +384,33 @@ hide_gui(void)
+ gtk_widget_hide(GTK_WIDGET(gui_window));
+ }
+
++/*
++ * refresh gui state
++ */
++static void
++refresh_gui(void)
++{
++ GtkWidget *widget = (gpointer)gui_window;
++ int volume, mute;
++
++ get_state(&volume, &mute);
++
++ set_tray_icon(mute ? 0 : volume);
++
++ /* Move slider. Change "check" button state. */
++ if (widget->window != NULL) {
++ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
++ 100 - volume);
++ gtk_toggle_button_set_active(
++ GTK_TOGGLE_BUTTON(gui_check), mute);
++ }
++}
++
+ static int
+ init_tray(int doinvert)
+ {
+ char tooltip[30];
+- u_char volume;
+- int mute;
++ int volume, mute;
+
+ tray_pixbuf = gdk_pixbuf_new_from_xpm_data(sound_xpm);
+ if (tray_pixbuf == NULL)
+@@ -421,13 +435,9 @@ init_tray(int doinvert)
+ tray_icon = gtk_status_icon_new();
+ if (tray_icon == NULL)
+ return (-1);
+- volume = 0;
+- mute = 0;
+- get_volume(mixer_fd, outputs_master_dev, &volume);
+- get_mute(mixer_fd, outputs_master_dev + 1, &mute);
++
++ get_state(&volume, &mute);
+ set_tray_icon(mute ? 0 : volume);
+- current_volume = volume;
+- current_mute = mute;
+
+ prepare_tooltip(mute, volume, tooltip, sizeof(tooltip));
+ gtk_status_icon_set_tooltip_text(tray_icon, tooltip);
+@@ -455,7 +465,43 @@ set_tray_icon(u_char volume)
+ gtk_status_icon_set_from_pixbuf(tray_icon, pb);
+ }
+
++/*
++ * Call poll(2), for both gtk and sndio descriptors.
++ */
+ int
++do_poll(GPollFD *gtk_pfds, guint gtk_nfds, gint timeout)
++{
++#define MAXFDS 64
++ struct pollfd pfds[MAXFDS], *sioctl_pfds;
++ unsigned int sioctl_nfds;
++ unsigned int i;
++ int revents;
++ int rc;
++
++ for (i = 0; i < gtk_nfds; i++) {
++ pfds[i].fd = gtk_pfds[i].fd;
++ pfds[i].events = gtk_pfds[i].events;
++ }
++ if (hdl != NULL) {
++ sioctl_pfds = pfds + gtk_nfds;
++ sioctl_nfds = sioctl_pollfd(hdl, sioctl_pfds, POLLIN);
++ } else
++ sioctl_nfds = 0;
++
++ rc = poll(pfds, gtk_nfds + sioctl_nfds, timeout);
++ if (rc > 0 && hdl != NULL) {
++ revents = sioctl_revents(hdl, sioctl_pfds);
++ if (revents & POLLHUP)
++ errx(1, "Device disconnected");
++ }
++
++ for (i = 0; i < gtk_nfds; i++)
++ gtk_pfds[i].revents = pfds[i].revents;
++
++ return rc;
++}
++
++int
+ main(int argc, char **argv)
+ {
+ char *progname;
+@@ -481,30 +527,34 @@ main(int argc, char **argv)
+ }
+ argc -= optind;
+ argv += optind;
+-
+- mixer_fd = open("/dev/mixer", O_RDWR);
+- if (mixer_fd == -1)
++
++ hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
++ if (hdl == NULL) {
+ errx(1, "Cannot open mixer device");
+-
+- error = get_mixer_index(mixer_fd, outputs_master_dev);
+- if (error == -1) {
+- close(mixer_fd);
+- errx(1, "Cannot get mixer information");
+ }
+-
+ error = init_tray(invert_flag);
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize notification area");
+ }
+ error = init_gui();
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize program window");
+ }
+- g_timeout_add(1000, (GSourceFunc)cb_timer, (gpointer)gui_window);
++
++ if (!sioctl_ondesc(hdl, cb_control_desc, NULL)) {
++ sioctl_close(hdl);
++ errx(1, "Cannot get mixer information");
++ }
++ if (!sioctl_onval(hdl, cb_control_value, NULL)) {
++ sioctl_close(hdl);
++ errx(1, "Cannot get mixer values");
++ }
++
++ g_main_context_set_poll_func(g_main_context_default(), do_poll);
+ gtk_main();
+
+- close(mixer_fd);
++ sioctl_close(hdl);
+ return (0);
+ }

No comments:

Post a Comment