]> git.sur5r.net Git - i3/i3status/blobdiff - src/print_volume.c
Fixed ALSA capture device monitoring.
[i3/i3status] / src / print_volume.c
index be6a1d7be2613a5463d85107a2c0d337482903b0..aa739a1fc2c09899b7457ffe38471d64993b8115 100644 (file)
@@ -11,6 +11,7 @@
 #ifdef LINUX
 #include <alsa/asoundlib.h>
 #include <alloca.h>
+#include <math.h>
 #endif
 
 #if defined(__FreeBSD__) || defined(__DragonFly__)
 #include "i3status.h"
 #include "queue.h"
 
+#define ALSA_VOLUME(channel)                                                    \
+    err = snd_mixer_selem_get_##channel##_dB_range(elem, &min, &max) ||         \
+          snd_mixer_selem_get_##channel##_dB(elem, 0, &val);                    \
+    if (err != 0 || min >= max) {                                               \
+        err = snd_mixer_selem_get_##channel##_volume_range(elem, &min, &max) || \
+              snd_mixer_selem_get_##channel##_volume(elem, 0, &val);            \
+        force_linear = true;                                                    \
+    }
+
+#define ALSA_MUTE_SWITCH(channel)                                                        \
+    if ((err = snd_mixer_selem_get_##channel##_switch(elem, 0, &pbval)) < 0)             \
+        fprintf(stderr, "i3status: ALSA: " #channel "_switch: %s\n", snd_strerror(err)); \
+    if (!pbval) {                                                                        \
+        START_COLOR("color_degraded");                                                   \
+        fmt = fmt_muted;                                                                 \
+    }
+
 static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) {
     const char *walk = fmt;
 
     for (; *walk != '\0'; walk++) {
         if (*walk != '%') {
             *(outwalk++) = *walk;
-            continue;
-        }
-        if (BEGINS_WITH(walk + 1, "%")) {
+
+        } else if (BEGINS_WITH(walk + 1, "%")) {
             outwalk += sprintf(outwalk, "%s", pct_mark);
             walk += strlen("%");
-        }
-        if (BEGINS_WITH(walk + 1, "volume")) {
+
+        } else if (BEGINS_WITH(walk + 1, "volume")) {
             outwalk += sprintf(outwalk, "%d%s", ivolume, pct_mark);
             walk += strlen("volume");
+
+        } else {
+            *(outwalk++) = '%';
         }
     }
     return outwalk;
@@ -61,7 +81,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *
         free(instance);
     }
 
-#ifndef __OpenBSD__
+#if !defined(__DragonFly__) && !defined(__OpenBSD__)
     /* Try PulseAudio first */
 
     /* If the device name has the format "pulse[:N]" where N is the
@@ -109,11 +129,13 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *
 #endif
 
 #ifdef LINUX
+    const long MAX_LINEAR_DB_SCALE = 24;
     int err;
     snd_mixer_t *m;
     snd_mixer_selem_id_t *sid;
     snd_mixer_elem_t *elem;
     long min, max, val;
+    bool force_linear = false;
     int avg;
 
     if ((err = snd_mixer_open(&m, 0)) < 0) {
@@ -151,7 +173,7 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *
     snd_mixer_selem_id_set_index(sid, mixer_idx);
     snd_mixer_selem_id_set_name(sid, mixer);
     if (!(elem = snd_mixer_find_selem(m, sid))) {
-        fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n",
+        fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %u)\n",
                 snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
         snd_mixer_close(m);
         snd_mixer_selem_id_free(sid);
@@ -159,25 +181,38 @@ void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *
     }
 
     /* Get the volume range to convert the volume later */
-    snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
-
     snd_mixer_handle_events(m);
-    snd_mixer_selem_get_playback_volume(elem, 0, &val);
-    if (max != 100) {
-        float avgf = ((float)val / max) * 100;
+    if (!strncasecmp(mixer, "capture", strlen("capture"))) {
+        ALSA_VOLUME(capture)
+    } else {
+        ALSA_VOLUME(playback)
+    }
+
+    if (err != 0) {
+        fprintf(stderr, "i3status: ALSA: Cannot get playback volume.\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;
         avg = (int)avgf;
         avg = (avgf - avg < 0.5 ? avg : (avg + 1));
-    } else
-        avg = (int)val;
+    } else {
+        /* mapped volume to be more natural for the human ear */
+        double normalized = exp10((val - max) / 6000.0);
+        if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
+            double min_norm = exp10((min - max) / 6000.0);
+            normalized = (normalized - min_norm) / (1 - min_norm);
+        }
+        avg = lround(normalized * 100);
+    }
 
     /* Check for mute */
     if (snd_mixer_selem_has_playback_switch(elem)) {
-        if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
-            fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
-        if (!pbval) {
-            START_COLOR("color_degraded");
-            fmt = fmt_muted;
-        }
+        ALSA_MUTE_SWITCH(playback)
+    } else if (snd_mixer_selem_has_capture_switch(elem)) {
+        ALSA_MUTE_SWITCH(capture)
     }
 
     snd_mixer_close(m);