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.
This commit is contained in:
parent
be0be599d9
commit
7efbeeaf6c
@ -36,6 +36,7 @@ extern char *pct_mark;
|
||||
#define COMPOSE_VOLUME_MUTE(vol, mute) ((vol) | ((mute) ? (1 << 30) : 0))
|
||||
#define DECOMPOSE_VOLUME(cvol) ((cvol) & ~(1 << 30))
|
||||
#define DECOMPOSE_MUTED(cvol) (((cvol) & (1 << 30)) != 0)
|
||||
#define MAX_SINK_DESCRIPTION_LEN (128) /* arbitrary */
|
||||
|
||||
#if defined(LINUX)
|
||||
|
||||
@ -228,6 +229,7 @@ void print_memory(yajl_gen json_gen, char *buffer, const char *format, const cha
|
||||
void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *fmt_muted, const char *device, const char *mixer, int mixer_idx);
|
||||
bool process_runs(const char *path);
|
||||
int volume_pulseaudio(uint32_t sink_idx, const char *sink_name);
|
||||
bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]);
|
||||
bool pulse_initialize(void);
|
||||
|
||||
/* socket file descriptor for general purposes */
|
||||
|
@ -581,9 +581,9 @@ to "default", PulseAudio will be tried if detected and will fallback to ALSA
|
||||
|
||||
*Example order*: +volume master+
|
||||
|
||||
*Example format*: +♪: %volume+
|
||||
*Example format*: +♪ (%devicename): %volume+
|
||||
|
||||
*Example format_muted*: +♪: 0%%+
|
||||
*Example format_muted*: +♪ (%devicename): 0%%+
|
||||
|
||||
*Example configuration*:
|
||||
-------------------------------------------------------------
|
||||
|
@ -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
|
||||
|
||||
|
112
src/pulse.c
112
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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user