mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-23 04:25:42 +02:00
Merge branch 'alsa-capture-devices'
This commit is contained in:
commit
149798fe98
2 changed files with 213 additions and 106 deletions
|
@ -29,6 +29,7 @@
|
||||||
channels to use as source for the volume level and muted state.
|
channels to use as source for the volume level and muted state.
|
||||||
* foreign-toplevel: Wayland module that provides information about
|
* foreign-toplevel: Wayland module that provides information about
|
||||||
currently opened windows.
|
currently opened windows.
|
||||||
|
* alsa: support for capture devices.
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
300
modules/alsa.c
300
modules/alsa.c
|
@ -16,32 +16,59 @@
|
||||||
#include "../config.h"
|
#include "../config.h"
|
||||||
#include "../plugin.h"
|
#include "../plugin.h"
|
||||||
|
|
||||||
struct private {
|
enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE };
|
||||||
char *card;
|
|
||||||
char *mixer;
|
|
||||||
char *volume_channel;
|
|
||||||
char *muted_channel;
|
|
||||||
struct particle *label;
|
|
||||||
|
|
||||||
tll(snd_mixer_selem_channel_id_t) channels;
|
struct channel {
|
||||||
|
snd_mixer_selem_channel_id_t id;
|
||||||
|
enum channel_type type;
|
||||||
|
char *name;
|
||||||
|
|
||||||
bool online;
|
|
||||||
long vol_min;
|
|
||||||
long vol_max;
|
|
||||||
long vol_cur;
|
long vol_cur;
|
||||||
bool muted;
|
bool muted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct private {
|
||||||
|
char *card;
|
||||||
|
char *mixer;
|
||||||
|
char *volume_name;
|
||||||
|
char *muted_name;
|
||||||
|
struct particle *label;
|
||||||
|
|
||||||
|
tll(struct channel) channels;
|
||||||
|
|
||||||
|
bool online;
|
||||||
|
|
||||||
|
bool has_playback_volume;
|
||||||
|
long playback_vol_min;
|
||||||
|
long playback_vol_max;
|
||||||
|
|
||||||
|
bool has_capture_volume;
|
||||||
|
long capture_vol_min;
|
||||||
|
long capture_vol_max;
|
||||||
|
|
||||||
|
const struct channel *volume_chan;
|
||||||
|
const struct channel *muted_chan;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
channel_free(struct channel *chan)
|
||||||
|
{
|
||||||
|
free(chan->name);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
destroy(struct module *mod)
|
destroy(struct module *mod)
|
||||||
{
|
{
|
||||||
struct private *m = mod->private;
|
struct private *m = mod->private;
|
||||||
tll_free(m->channels);
|
tll_foreach(m->channels, it) {
|
||||||
|
channel_free(&it->item);
|
||||||
|
tll_remove(m->channels, it);
|
||||||
|
}
|
||||||
m->label->destroy(m->label);
|
m->label->destroy(m->label);
|
||||||
free(m->card);
|
free(m->card);
|
||||||
free(m->mixer);
|
free(m->mixer);
|
||||||
free(m->volume_channel);
|
free(m->volume_name);
|
||||||
free(m->muted_channel);
|
free(m->muted_name);
|
||||||
free(m);
|
free(m);
|
||||||
module_default_destroy(mod);
|
module_default_destroy(mod);
|
||||||
}
|
}
|
||||||
|
@ -60,17 +87,35 @@ content(struct module *mod)
|
||||||
{
|
{
|
||||||
struct private *m = mod->private;
|
struct private *m = mod->private;
|
||||||
|
|
||||||
int percent = m->vol_max - m->vol_min > 0
|
mtx_lock(&mod->lock);
|
||||||
? round(100. * m->vol_cur / (m->vol_max - m->vol_min))
|
|
||||||
|
const struct channel *volume_chan = m->volume_chan;
|
||||||
|
const struct channel *muted_chan = m->muted_chan;
|
||||||
|
|
||||||
|
bool muted = muted_chan != NULL ? muted_chan->muted : false;
|
||||||
|
long vol_min = 0, vol_max = 0, vol_cur = 0;
|
||||||
|
|
||||||
|
if (volume_chan != NULL) {
|
||||||
|
if (volume_chan->type == CHANNEL_PLAYBACK) {
|
||||||
|
vol_min = m->playback_vol_min;
|
||||||
|
vol_max = m->playback_vol_max;
|
||||||
|
} else {
|
||||||
|
vol_min = m->capture_vol_min;
|
||||||
|
vol_max = m->capture_vol_max;
|
||||||
|
}
|
||||||
|
vol_cur = volume_chan->vol_cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
int percent = vol_max - vol_min > 0
|
||||||
|
? round(100. * vol_cur / (vol_max - vol_min))
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
mtx_lock(&mod->lock);
|
|
||||||
struct tag_set tags = {
|
struct tag_set tags = {
|
||||||
.tags = (struct tag *[]){
|
.tags = (struct tag *[]){
|
||||||
tag_new_bool(mod, "online", m->online),
|
tag_new_bool(mod, "online", m->online),
|
||||||
tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max),
|
tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max),
|
||||||
tag_new_int_range(mod, "percent", percent, 0, 100),
|
tag_new_int_range(mod, "percent", percent, 0, 100),
|
||||||
tag_new_bool(mod, "muted", m->muted),
|
tag_new_bool(mod, "muted", muted),
|
||||||
},
|
},
|
||||||
.count = 4,
|
.count = 4,
|
||||||
};
|
};
|
||||||
|
@ -87,89 +132,75 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
|
||||||
{
|
{
|
||||||
struct private *m = mod->private;
|
struct private *m = mod->private;
|
||||||
|
|
||||||
/* Get min/max volume levels */
|
|
||||||
long min = 0, max = 0;
|
|
||||||
int r = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
|
|
||||||
|
|
||||||
if (r < 0) {
|
|
||||||
LOG_DBG("%s,%s: failed to get volume min/max (mixer is digital?)",
|
|
||||||
m->card, m->mixer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure min <= max */
|
|
||||||
if (min > max) {
|
|
||||||
LOG_WARN(
|
|
||||||
"%s,%s: indicated minimum volume is greater than the maximum: "
|
|
||||||
"%ld > %ld", m->card, m->mixer, min, max);
|
|
||||||
min = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
long cur = 0;
|
|
||||||
|
|
||||||
/* If volume level can be changed (i.e. this isn't just a switch;
|
/* If volume level can be changed (i.e. this isn't just a switch;
|
||||||
* e.g. a digital channel), get current level */
|
* e.g. a digital channel), get current channel levels */
|
||||||
if (max > 0) {
|
|
||||||
tll_foreach(m->channels, it) {
|
tll_foreach(m->channels, it) {
|
||||||
const char *name = snd_mixer_selem_channel_name(it->item);
|
struct channel *chan = &it->item;
|
||||||
if (m->volume_channel != NULL && strcmp(name, m->volume_channel) != 0)
|
|
||||||
|
const bool has_volume = chan->type == CHANNEL_PLAYBACK
|
||||||
|
? m->has_playback_volume : m->has_capture_volume;
|
||||||
|
const long min = chan->type == CHANNEL_PLAYBACK
|
||||||
|
? m->playback_vol_min : m->capture_vol_min;
|
||||||
|
const long max = chan->type == CHANNEL_PLAYBACK
|
||||||
|
? m->playback_vol_max : m->capture_vol_max;
|
||||||
|
|
||||||
|
if (!has_volume)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int r = snd_mixer_selem_get_playback_volume(elem, it->item, &cur);
|
assert(min <= max);
|
||||||
|
|
||||||
|
int r = chan->type == CHANNEL_PLAYBACK
|
||||||
|
? snd_mixer_selem_get_playback_volume(elem, chan->id, &chan->vol_cur)
|
||||||
|
: snd_mixer_selem_get_capture_volume(elem, chan->id, &chan->vol_cur);
|
||||||
|
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
LOG_WARN("%s,%s: %s: failed to get current volume",
|
LOG_ERR("%s,%s: %s: failed to get current volume",
|
||||||
m->card, m->mixer, name);
|
m->card, m->mixer, chan->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer, name, cur);
|
if (chan->vol_cur < min) {
|
||||||
}
|
LOG_WARN(
|
||||||
|
"%s,%s: %s: current volume is less than the indicated minimum: "
|
||||||
|
"%ld < %ld", m->card, m->mixer, chan->name, chan->vol_cur, min);
|
||||||
|
chan->vol_cur = min;
|
||||||
}
|
}
|
||||||
|
|
||||||
int unmuted = 0;
|
if (chan->vol_cur > max) {
|
||||||
|
LOG_WARN(
|
||||||
|
"%s,%s: %s: current volume is greater than the indicated maximum: "
|
||||||
|
"%ld > %ld", m->card, m->mixer, chan->name, chan->vol_cur, max);
|
||||||
|
chan->vol_cur = max;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get muted state */
|
assert(chan->vol_cur >= min);
|
||||||
|
assert(chan->vol_cur <= max );
|
||||||
|
|
||||||
|
LOG_DBG("%s,%s: %s: volume: %ld",
|
||||||
|
m->card, m->mixer, chan->name, chan->vol_cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get channels’ muted state */
|
||||||
tll_foreach(m->channels, it) {
|
tll_foreach(m->channels, it) {
|
||||||
const char *name = snd_mixer_selem_channel_name(it->item);
|
struct channel *chan = &it->item;
|
||||||
if (m->muted_channel != NULL && strcmp(name, m->muted_channel) != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int r = snd_mixer_selem_get_playback_switch(elem, it->item, &unmuted);
|
int unmuted;
|
||||||
|
|
||||||
|
int r = chan->type == CHANNEL_PLAYBACK
|
||||||
|
? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted)
|
||||||
|
: snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted);
|
||||||
|
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
LOG_WARN("%s,%s: %s: failed to get muted state",
|
LOG_WARN("%s,%s: %s: failed to get muted state",
|
||||||
m->card, m->mixer, name);
|
m->card, m->mixer, chan->name);
|
||||||
unmuted = 1;
|
unmuted = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, name, !unmuted);
|
chan->muted = !unmuted;
|
||||||
|
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure min <= cur <= max */
|
|
||||||
if (cur < min) {
|
|
||||||
LOG_WARN(
|
|
||||||
"%s,%s: current volume is less than the indicated minimum: "
|
|
||||||
"%ld < %ld", m->card, m->mixer, cur, min);
|
|
||||||
cur = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur > max) {
|
|
||||||
LOG_WARN(
|
|
||||||
"%s,%s: current volume is greater than the indicated maximum: "
|
|
||||||
"%ld > %ld", m->card, m->mixer, cur, max);
|
|
||||||
cur = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(cur >= min);
|
|
||||||
assert(cur <= max);
|
|
||||||
|
|
||||||
LOG_DBG("muted=%d, cur=%ld, min=%ld, max=%ld", !unmuted, cur, min, max);
|
|
||||||
|
|
||||||
mtx_lock(&mod->lock);
|
mtx_lock(&mod->lock);
|
||||||
m->vol_min = min;
|
|
||||||
m->vol_max = max;
|
|
||||||
m->vol_cur = cur;
|
|
||||||
m->online = true;
|
m->online = true;
|
||||||
m->muted = !unmuted;
|
|
||||||
mtx_unlock(&mod->lock);
|
mtx_unlock(&mod->lock);
|
||||||
|
|
||||||
mod->bar->refresh(mod->bar);
|
mod->bar->refresh(mod->bar);
|
||||||
|
@ -217,55 +248,128 @@ run_while_online(struct module *mod)
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get playback volume range */
|
||||||
|
m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0;
|
||||||
|
if (m->has_playback_volume) {
|
||||||
|
if (snd_mixer_selem_get_playback_volume_range(
|
||||||
|
elem, &m->playback_vol_min, &m->playback_vol_max) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("%s,%s: failed to get playback volume range",
|
||||||
|
m->card, m->mixer);
|
||||||
|
assert(m->playback_vol_min == 0);
|
||||||
|
assert(m->playback_vol_max == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m->playback_vol_min > m->playback_vol_max) {
|
||||||
|
LOG_WARN(
|
||||||
|
"%s,%s: indicated minimum playback volume is greater than the "
|
||||||
|
"maximum: %ld > %ld",
|
||||||
|
m->card, m->mixer, m->playback_vol_min, m->playback_vol_max);
|
||||||
|
m->playback_vol_min = m->playback_vol_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get capture volume range */
|
||||||
|
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
|
||||||
|
if (m->has_capture_volume) {
|
||||||
|
if (snd_mixer_selem_get_capture_volume_range(
|
||||||
|
elem, &m->capture_vol_min, &m->capture_vol_max) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("%s,%s: failed to get capture volume range",
|
||||||
|
m->card, m->mixer);
|
||||||
|
assert(m->capture_vol_min == 0);
|
||||||
|
assert(m->capture_vol_max == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m->capture_vol_min > m->capture_vol_max) {
|
||||||
|
LOG_WARN(
|
||||||
|
"%s,%s: indicated minimum capture volume is greater than the "
|
||||||
|
"maximum: %ld > %ld",
|
||||||
|
m->card, m->mixer, m->capture_vol_min, m->capture_vol_max);
|
||||||
|
m->capture_vol_min = m->capture_vol_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Get available channels */
|
/* Get available channels */
|
||||||
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
||||||
if (snd_mixer_selem_has_playback_channel(elem, i)) {
|
bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1;
|
||||||
tll_push_back(m->channels, i);
|
bool is_capture = snd_mixer_selem_has_capture_channel(elem, i) == 1;
|
||||||
|
|
||||||
|
if (is_playback || is_capture) {
|
||||||
|
struct channel chan = {
|
||||||
|
.id = i,
|
||||||
|
.type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE,
|
||||||
|
.name = strdup(snd_mixer_selem_channel_name( i)),
|
||||||
|
};
|
||||||
|
tll_push_back(m->channels, chan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tll_length(m->channels) == 0) {
|
||||||
|
LOG_ERR("%s,%s: no channels", m->card, m->mixer);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
char channels_str[1024];
|
char channels_str[1024];
|
||||||
int channels_idx = 0;
|
int channels_idx = 0;
|
||||||
tll_foreach(m->channels, it) {
|
tll_foreach(m->channels, it) {
|
||||||
|
const struct channel *chan = &it->item;
|
||||||
|
|
||||||
channels_idx += snprintf(
|
channels_idx += snprintf(
|
||||||
&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
||||||
channels_idx == 0 ? "%s" : ", %s",
|
channels_idx == 0 ? "%s (%s)" : ", %s (%s)",
|
||||||
snd_mixer_selem_channel_name(it->item));
|
chan->name, chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤");
|
||||||
assert(channels_idx <= sizeof(channels_str));
|
assert(channels_idx <= sizeof(channels_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("%s,%s: channels: %s", m->card, m->mixer, channels_str);
|
LOG_INFO("%s,%s: channels: %s", m->card, m->mixer, channels_str);
|
||||||
|
|
||||||
/* Verify volume/muted channel names are valid and exists */
|
/* Verify volume/muted channel names are valid and exists */
|
||||||
bool volume_channel_is_valid = m->volume_channel == NULL;
|
bool volume_channel_is_valid = m->volume_name == NULL;
|
||||||
bool muted_channel_is_valid = m->muted_channel == NULL;
|
bool muted_channel_is_valid = m->muted_name == NULL;
|
||||||
|
|
||||||
tll_foreach(m->channels, it) {
|
tll_foreach(m->channels, it) {
|
||||||
const char *chan_name = snd_mixer_selem_channel_name(it->item);
|
const struct channel *chan = &it->item;
|
||||||
if (m->volume_channel != NULL && strcmp(chan_name, m->volume_channel) == 0)
|
if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) {
|
||||||
|
m->volume_chan = chan;
|
||||||
volume_channel_is_valid = true;
|
volume_channel_is_valid = true;
|
||||||
if (m->muted_channel != NULL && strcmp(chan_name, m->muted_channel) == 0)
|
}
|
||||||
|
if (m->muted_name != NULL && strcmp(chan->name, m->muted_name) == 0) {
|
||||||
|
m->muted_chan = chan;
|
||||||
muted_channel_is_valid = true;
|
muted_channel_is_valid = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m->volume_name == NULL)
|
||||||
|
m->volume_chan = &tll_front(m->channels);
|
||||||
|
if (m->muted_name == NULL)
|
||||||
|
m->muted_chan = &tll_front(m->channels);
|
||||||
|
|
||||||
if (!volume_channel_is_valid) {
|
if (!volume_channel_is_valid) {
|
||||||
assert(m->volume_channel != NULL);
|
assert(m->volume_name != NULL);
|
||||||
LOG_ERR("volume: invalid channel name: %s", m->volume_channel);
|
LOG_ERR("volume: invalid channel name: %s", m->volume_name);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!muted_channel_is_valid) {
|
if (!muted_channel_is_valid) {
|
||||||
assert(m->muted_channel != NULL);
|
assert(m->muted_name != NULL);
|
||||||
LOG_ERR("muted: invalid channel name: %s", m->muted_channel);
|
LOG_ERR("muted: invalid channel name: %s", m->muted_name);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initial state */
|
/* Initial state */
|
||||||
update_state(mod, elem);
|
update_state(mod, elem);
|
||||||
|
|
||||||
LOG_INFO("%s,%s: volume min=%ld, max=%ld, current=%ld%s",
|
LOG_INFO(
|
||||||
m->card, m->mixer, m->vol_min, m->vol_max, m->vol_cur,
|
"%s,%s: volume range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)",
|
||||||
m->muted ? ", muted" : "");
|
m->card, m->mixer,
|
||||||
|
m->volume_chan->type == CHANNEL_PLAYBACK
|
||||||
|
? m->playback_vol_min : m->capture_vol_min,
|
||||||
|
m->volume_chan->type == CHANNEL_PLAYBACK
|
||||||
|
? m->playback_vol_max : m->capture_vol_max,
|
||||||
|
m->volume_chan->vol_cur,
|
||||||
|
m->muted_chan->muted ? " (muted)" : "",
|
||||||
|
m->volume_chan->name, m->muted_chan->name);
|
||||||
|
|
||||||
mod->bar->refresh(mod->bar);
|
mod->bar->refresh(mod->bar);
|
||||||
|
|
||||||
|
@ -446,15 +550,17 @@ out:
|
||||||
|
|
||||||
static struct module *
|
static struct module *
|
||||||
alsa_new(const char *card, const char *mixer,
|
alsa_new(const char *card, const char *mixer,
|
||||||
const char *volume_channel, const char *muted_channel,
|
const char *volume_channel_name, const char *muted_channel_name,
|
||||||
struct particle *label)
|
struct particle *label)
|
||||||
{
|
{
|
||||||
struct private *priv = calloc(1, sizeof(*priv));
|
struct private *priv = calloc(1, sizeof(*priv));
|
||||||
priv->label = label;
|
priv->label = label;
|
||||||
priv->card = strdup(card);
|
priv->card = strdup(card);
|
||||||
priv->mixer = strdup(mixer);
|
priv->mixer = strdup(mixer);
|
||||||
priv->volume_channel = volume_channel != NULL ? strdup(volume_channel) : NULL;
|
priv->volume_name =
|
||||||
priv->muted_channel = muted_channel != NULL ? strdup(muted_channel) : NULL;
|
volume_channel_name != NULL ? strdup(volume_channel_name) : NULL;
|
||||||
|
priv->muted_name =
|
||||||
|
muted_channel_name != NULL ? strdup(muted_channel_name) : NULL;
|
||||||
|
|
||||||
struct module *mod = module_common_new();
|
struct module *mod = module_common_new();
|
||||||
mod->private = priv;
|
mod->private = priv;
|
||||||
|
|
Loading…
Add table
Reference in a new issue