From 7efbeeaf6ce9232f7479f76c1c79ff73c0db49e4 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Tue, 22 Jan 2019 23:45:51 -0800 Subject: Implement %devicename specifier for volume module (#325) This commit implements the %devicename specifier for the volume module for both PulseAudio and ALSA. This way, i3status will be able to display the specific device that corresponds to the volume indicator. Note that this is not implemented for the OSS API but is left in a state where someone can pick it up for the future. --- src/print_volume.c | 45 ++++++++++++++++----- src/pulse.c | 112 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 120 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/print_volume.c b/src/print_volume.c index aa739a1..1d81e13 100644 --- a/src/print_volume.c +++ b/src/print_volume.c @@ -47,7 +47,7 @@ fmt = fmt_muted; \ } -static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) { +static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume, const char *devicename) { const char *walk = fmt; for (; *walk != '\0'; walk++) { @@ -62,6 +62,10 @@ static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) { outwalk += sprintf(outwalk, "%d%s", ivolume, pct_mark); walk += strlen("volume"); + } else if (BEGINS_WITH(walk + 1, "devicename")) { + outwalk += sprintf(outwalk, "%s", devicename); + walk += strlen("devicename"); + } else { *(outwalk++) = '%'; } @@ -93,36 +97,51 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * !isdigit(device[strlen("pulse:")]) ? device + strlen("pulse:") : NULL; - int cvolume = pulse_initialize() ? volume_pulseaudio(sink_idx, sink_name) : 0; + int cvolume = 0; + char description[MAX_SINK_DESCRIPTION_LEN] = {'\0'}; + + if (pulse_initialize()) { + cvolume = volume_pulseaudio(sink_idx, sink_name); + /* false result means error, stick to empty-string */ + if (!description_pulseaudio(sink_idx, sink_name, description)) { + description[0] = '\0'; + } + } + int ivolume = DECOMPOSE_VOLUME(cvolume); bool muted = DECOMPOSE_MUTED(cvolume); if (muted) { START_COLOR("color_degraded"); pbval = 0; } + /* negative result means error, stick to 0 */ if (ivolume < 0) ivolume = 0; outwalk = apply_volume_format(muted ? fmt_muted : fmt, outwalk, - ivolume); + ivolume, + description); goto out; } else if (!strcasecmp(device, "default") && pulse_initialize()) { /* no device specified or "default" set */ + char description[MAX_SINK_DESCRIPTION_LEN]; + bool success = description_pulseaudio(DEFAULT_SINK_INDEX, NULL, description); int cvolume = volume_pulseaudio(DEFAULT_SINK_INDEX, NULL); int ivolume = DECOMPOSE_VOLUME(cvolume); bool muted = DECOMPOSE_MUTED(cvolume); - if (ivolume >= 0) { + if (ivolume >= 0 && success) { if (muted) { START_COLOR("color_degraded"); pbval = 0; } outwalk = apply_volume_format(muted ? fmt_muted : fmt, outwalk, - ivolume); + ivolume, + description); goto out; } - /* negative result means error, fail PulseAudio attempt */ + /* 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 */ @@ -135,6 +154,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; long min, max, val; + const char *mixer_name; bool force_linear = false; int avg; @@ -193,6 +213,12 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * goto out; } + mixer_name = snd_mixer_selem_get_name(elem); + if (!mixer_name) { + fprintf(stderr, "i3status: ALSA: NULL mixer_name.\n"); + goto out; + } + /* Use linear mapping for raw register values or small ranges of 24 dB */ if (force_linear || max - min <= MAX_LINEAR_DB_SCALE * 100) { float avgf = ((float)(val - min) / (max - min)) * 100; @@ -215,16 +241,17 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * ALSA_MUTE_SWITCH(capture) } + outwalk = apply_volume_format(fmt, outwalk, avg, mixer_name); + snd_mixer_close(m); snd_mixer_selem_id_free(sid); - outwalk = apply_volume_format(fmt, outwalk, avg); - #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) char *mixerpath; char defaultmixer[] = "/dev/mixer"; int mixfd, vol, devmask = 0; + const char *devicename = "UNSUPPORTED"; /* TODO: implement support for this */ pbval = 1; if (mixer_idx > 0) @@ -326,7 +353,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char * } #endif - outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f); + outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f, devicename); close(mixfd); #endif diff --git a/src/pulse.c b/src/pulse.c index 66e6a3d..9e278c5 100644 --- a/src/pulse.c +++ b/src/pulse.c @@ -10,13 +10,14 @@ #define APP_NAME "i3status" #define APP_ID "org.i3wm" -typedef struct indexed_volume_s { +typedef struct index_info_s { char *name; uint32_t idx; int volume; - TAILQ_ENTRY(indexed_volume_s) + char description[MAX_SINK_DESCRIPTION_LEN]; + TAILQ_ENTRY(index_info_s) entries; -} indexed_volume_t; +} index_info_t; static pa_threaded_mainloop *main_loop = NULL; static pa_context *context = NULL; @@ -24,9 +25,9 @@ static pa_mainloop_api *api = NULL; static bool context_ready = false; static bool mainloop_thread_running = false; static uint32_t default_sink_idx = DEFAULT_SINK_INDEX; -TAILQ_HEAD(tailhead, indexed_volume_s) -cached_volume = - TAILQ_HEAD_INITIALIZER(cached_volume); +TAILQ_HEAD(tailhead, index_info_s) +cached_info = + TAILQ_HEAD_INITIALIZER(cached_info); static pthread_mutex_t pulse_mutex = PTHREAD_MUTEX_INITIALIZER; static void pulseaudio_error_log(pa_context *c) { @@ -45,13 +46,20 @@ static bool pulseaudio_free_operation(pa_context *c, pa_operation *o) { } /* - * save the volume for the specified sink index + * save the info for the specified sink index * returning true if the value was changed */ -static bool save_volume(uint32_t sink_idx, int new_volume, const char *name) { +static bool save_info(uint32_t sink_idx, int new_volume, const char *new_description, const char *name) { pthread_mutex_lock(&pulse_mutex); - indexed_volume_t *entry; - TAILQ_FOREACH(entry, &cached_volume, entries) { + index_info_t *entry; + + /* if this is NULL, gracefully handle and replace with empty-string */ + if (!new_description) { + new_description = ""; + fprintf(stderr, "i3status: PulseAudio: NULL new_description provided\n"); + } + + TAILQ_FOREACH(entry, &cached_info, entries) { if (name) { if (!entry->name || strcmp(entry->name, name)) { continue; @@ -61,16 +69,30 @@ static bool save_volume(uint32_t sink_idx, int new_volume, const char *name) { continue; } } - const bool changed = (new_volume != entry->volume); - entry->volume = new_volume; + + bool changed = false; + + if (new_volume != entry->volume) { + entry->volume = new_volume; + changed = true; + } + + if (strncmp(entry->description, new_description, sizeof(entry->description))) { + strncpy(entry->description, new_description, sizeof(entry->description) - 1); + entry->description[sizeof(entry->description) - 1] = '\0'; + changed = true; + } + pthread_mutex_unlock(&pulse_mutex); return changed; } /* index not found, store it */ entry = malloc(sizeof(*entry)); - TAILQ_INSERT_HEAD(&cached_volume, entry, entries); + TAILQ_INSERT_HEAD(&cached_info, entry, entries); entry->idx = sink_idx; entry->volume = new_volume; + strncpy(entry->description, new_description, sizeof(entry->description) - 1); + entry->description[sizeof(entry->description) - 1] = '\0'; if (name) { entry->name = malloc(strlen(name) + 1); strcpy(entry->name, name); @@ -81,10 +103,10 @@ static bool save_volume(uint32_t sink_idx, int new_volume, const char *name) { return true; } -static void store_volume_from_sink_cb(pa_context *c, - const pa_sink_info *info, - int eol, - void *userdata) { +static void store_info_from_sink_cb(pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata) { if (eol < 0) { if (pa_context_errno(c) == PA_ERR_NOENTITY) return; @@ -104,9 +126,9 @@ static void store_volume_from_sink_cb(pa_context *c, * DEFAULT_SINK_INDEX as the index, and another with its proper value * (using bitwise OR to avoid early-out logic) */ if ((info->index == default_sink_idx && - save_volume(DEFAULT_SINK_INDEX, composed_volume, NULL)) | - save_volume(info->index, composed_volume, info->name)) { - /* if the volume or mute flag changed, wake the main thread */ + save_info(DEFAULT_SINK_INDEX, composed_volume, info->description, NULL)) | + save_info(info->index, composed_volume, info->description, info->name)) { + /* if the volume, mute flag or description changed, wake the main thread */ pthread_kill(main_thread, SIGUSR1); } } @@ -116,10 +138,10 @@ static void get_sink_info(pa_context *c, uint32_t idx, const char *name) { if (name || idx == DEFAULT_SINK_INDEX) { o = pa_context_get_sink_info_by_name( - c, name ? name : "@DEFAULT_SINK@", store_volume_from_sink_cb, NULL); + c, name ? name : "@DEFAULT_SINK@", store_info_from_sink_cb, NULL); } else { o = pa_context_get_sink_info_by_index( - c, idx, store_volume_from_sink_cb, NULL); + c, idx, store_info_from_sink_cb, NULL); } if (o) { pulseaudio_free_operation(c, o); @@ -134,7 +156,7 @@ static void store_default_sink_cb(pa_context *c, if (default_sink_idx != i->index) { /* default sink changed? */ default_sink_idx = i->index; - store_volume_from_sink_cb(c, i, eol, userdata); + store_info_from_sink_cb(c, i, eol, userdata); } } } @@ -210,8 +232,8 @@ int volume_pulseaudio(uint32_t sink_idx, const char *sink_name) { return -1; pthread_mutex_lock(&pulse_mutex); - const indexed_volume_t *entry; - TAILQ_FOREACH(entry, &cached_volume, entries) { + const index_info_t *entry; + TAILQ_FOREACH(entry, &cached_info, entries) { if (sink_name) { if (!entry->name || strcmp(entry->name, sink_name)) { continue; @@ -226,9 +248,9 @@ int volume_pulseaudio(uint32_t sink_idx, const char *sink_name) { return vol; } pthread_mutex_unlock(&pulse_mutex); - /* first time requires a prime callback call because we only get - * updates when the volume actually changes, but we need it to - * be correct even if it never changes */ + /* first time requires a prime callback call because we only get updates + * when the description or volume actually changes, but we need it to be + * correct even if it never changes */ pa_threaded_mainloop_lock(main_loop); get_sink_info(context, sink_idx, sink_name); pa_threaded_mainloop_unlock(main_loop); @@ -236,6 +258,40 @@ int volume_pulseaudio(uint32_t sink_idx, const char *sink_name) { return 0; } +bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]) { + if (!context_ready || default_sink_idx == DEFAULT_SINK_INDEX) { + return false; + } + + pthread_mutex_lock(&pulse_mutex); + const index_info_t *entry; + TAILQ_FOREACH(entry, &cached_info, entries) { + if (sink_name) { + if (!entry->name || strcmp(entry->name, sink_name)) { + continue; + } + } else { + if (entry->idx != sink_idx) { + continue; + } + } + strncpy(buffer, entry->description, sizeof(entry->description) - 1); + pthread_mutex_unlock(&pulse_mutex); + buffer[sizeof(entry->description) - 1] = '\0'; + return true; + } + pthread_mutex_unlock(&pulse_mutex); + /* first time requires a prime callback call because we only get updates + * when the description or volume actually changes, but we need it to be + * correct even if it never changes */ + pa_threaded_mainloop_lock(main_loop); + get_sink_info(context, sink_idx, sink_name); + pa_threaded_mainloop_unlock(main_loop); + /* show empty string while we don't have this information */ + buffer[0] = '\0'; + return true; +} + /* * detect and, if necessary, initialize the PulseAudio API */ -- cgit v1.2.3