This mostly comes down to tracking whether each channel is a playback,
or capture channel, and using the appropriate APIs when dealing with
it.
Some cleanup related to this:
* Add a channel struct, for per-channel data. Previously, our channel
list was just a list of ALSA channel IDs.
* We now store current volume per-channel (but volume min/max is
per-device)
* Muted state is stored per-channel
* Track both the device’s playback and capture volume ranges, as well
as whether the device *has* playback or capture volume.
* Get the playback/capture volume ranges once, during init, instead of
at each update.
* Use struct pointers for the volume/muted channels. This way we don’t
have to iterate all channels and to string comparisons on the name
each time we update our state.
Otherwise we’ll keep adding the same channel(s) over and over again,
for each (successful) connect attempt.
I.e. if you plug and unplug an USB soundcard repeatedly, we’ll keep
extending the channel list each time.
When e.g. a USB soundcard is inserted, we get several CREATE
events. In my experiments, we only succeed in connecting to ALSA after
the last event.
This means, we’ll have several CREATE events that we receive, remove
the watcher, attempt to connect, fail, and then re-add the watcher.
What if that “last” CREATE event occurs while our watcher has been
removed? That’s right, we miss it, and will get stuck waiting forever.
The solution is keep the watcher around.
Now, if we’ve been successfully connected to ALSA for a long time,
chances are we’ve built up events (for other cards, for example). We
don’t want to trigger a storm of re-connect attempts, so drain the
event queue after having been disconnected from ALSA.
There *is* a small race here - if a card is removed and
re-added *very* fast, we _may_ accidentally drain the CREATE event. I
don’t see this happening in reality though.
While waiting for the configured ALSA card to become available, use
inotify and watch for CREATE events on /dev/snd instead of
polling (using a timeout in the poll(3) call).
Note that we don’t know the actual names of the files that (will) be
created. This means:
* Every time we see a CREATE event on /dev/snd, we *try* to connect to
ALSA. If we fail, we go back to watching /dev/snd again.
* ALSA (not yambar) will log an error message each time we fail.
These options allows you to select which channel to use as volume
source, and which channel to use as the source for the muted state.
With this, we can also remove the check for *all* (playback) channels
having the same volume/muted state. And with that, we no longer need
to warn when not all channels have the same volume/muted state.
With this patch, a non-existing ALSA device is no longer considered a
fatal error. Instead, we keep retrying until we succeed.
Furthermore, if we have successfully opened the ALSA device, and it
then disappears, we a) no longer crash, or cause 100% CPU usage, and
b) try to re-connect to the device.
With this, we now handle e.g. USB soundcards being disconnected and
then re-connected. We should also handle pseudo devices, like pipewire
provides ones, when yambar is started before pipewire.
Closes#59Closes#61Closes#86
In cases where it makes sense, use calloc() instead of malloc():
* When allocating large objects with many members, many for which
NULL/0 is a good default value.
* Arrays etc where we explicitly initialize to NULL anyway.
Since this struct only contained function pointers, make all modules
export those functions directly.
The plugin manager now defines a module interface struct, and fills it
it by dlsym:ing the functions that used to be in module_info.
All modules are expected to handle a call to content() after having
been instantiated.
I.e. modules *cannot* even expect run() to have started running.
Previously we allowed it on the bar, and on all particles. Now we also
allow it on all modules.
This allows us to specify a "default" font/foreground on a per-module
basis, having it applied to all the modules particles.
Warn if volume and/or muted state is inconsistent (as we only expose a
single volume/muted state).
Also, don't query for current volume if max == 0 (i.e. typically a
digital output).
Finally, make absolutely sure that volume min is really less (or
equal) to volume max, and that the current volume level is between min
and max.
When snd_mixer_selem_get_*() fails, the values were left
uninitialized, trigger a valgrind warning.
In addition to fixing this, the error(s) are now logged (but only as
warnings, as they are not fatal).