Index: sound/sound.c --- sound/sound.c.orig +++ sound/sound.c @@ -4,12 +4,13 @@ */ #include #include -#include +#include #include #include #include +#include #include #include @@ -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); }