]> git.sur5r.net Git - i3/i3status/commitdiff
Implement %devicename specifier for volume module (#325)
authorDenton Liu <liu.denton+github@gmail.com>
Wed, 23 Jan 2019 07:45:51 +0000 (23:45 -0800)
committerMichael Stapelberg <stapelberg@users.noreply.github.com>
Wed, 23 Jan 2019 07:45:51 +0000 (08:45 +0100)
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.

include/i3status.h
man/i3status.man
src/print_volume.c
src/pulse.c

index d79ab280a00b1bf5626840c4528da340b5222341..7c22cbc4db977080d5a0cd4547c0f810b625e3ab 100644 (file)
@@ -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 */
index cc363f53e44629dbf89f4078786a70711e79ca16..4bc7d0aecb5817778838483bf6fc98356445c2ae 100644 (file)
@@ -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*:
 -------------------------------------------------------------
index aa739a1fc2c09899b7457ffe38471d64993b8115..1d81e1365744045d4a708bf02ccb36c0426acc63 100644 (file)
@@ -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
 
index 66e6a3d87282db832d7045c7803d15ebc925bc7e..9e278c5c842ba40e708be37eaed5a70f0237c93d 100644 (file)
 #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
  */