diff options
| -rw-r--r-- | include/i3status.h | 2 | ||||
| -rw-r--r-- | man/i3status.man | 4 | ||||
| -rw-r--r-- | src/print_volume.c | 45 | ||||
| -rw-r--r-- | src/pulse.c | 112 | 
4 files changed, 124 insertions, 39 deletions
diff --git a/include/i3status.h b/include/i3status.h index d79ab28..7c22cbc 100644 --- a/include/i3status.h +++ b/include/i3status.h @@ -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 */ diff --git a/man/i3status.man b/man/i3status.man index cc363f5..4bc7d0a 100644 --- a/man/i3status.man +++ b/man/i3status.man @@ -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*:  ------------------------------------------------------------- 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   */  | 
