master ohmu/fastfetch-openbsd / src / detection / sound / sound_linux.c
ohmu  ·  2026-10-15
   1#include "sound.h"
   2#include "common/debug.h"
   3#include "common/endian.h"
   4#include "common/io.h"
   5#include "common/strutil.h"
   6
   7#include <errno.h>
   8#include <netdb.h>
   9#include <stddef.h>
  10#include <stdlib.h>
  11#include <string.h>
  12#include <sys/socket.h>
  13#include <sys/time.h>
  14#include <sys/un.h>
  15#include <unistd.h>
  16
  17// Linux sound comes from one of two places: the sound server a desktop runs, and the hardware
  18// underneath it.
  19//
  20// The server is PulseAudio -- PipeWire speaks its protocol too -- and it is asked first, because
  21// it is the only one of the two that knows what the desktop's volume is: the mixer the card
  22// exposes and the slider on screen are not the same thing. When no server answers, the cards are
  23// read from ALSA instead; that part lives in its own section at the end of this file.
  24//
  25// ---------------------------------------------------------------------------
  26// the server: PulseAudio's native protocol ("protocol-native")
  27// ---------------------------------------------------------------------------
  28//
  29// Instead of linking or dlopen-ing libpulse, fastfetch talks to the daemon
  30// directly: the daemon speaks a small length prefixed binary protocol on its
  31// Unix socket (TCP works as well) and the read only introspection commands used
  32// here need no event loop, no shared memory and no subscription.
  33//
  34// A frame is a 20 byte big endian header (payload length, channel, offset high,
  35// offset low, flags) followed by a "tagstruct" payload: a stream of one byte
  36// type tags, each followed by its value. All integers are big endian. The header
  37// of control packets always uses the channel 0xFFFFFFFF with a zero offset and
  38// flag word.
  39//
  40// The command numbers, the tag characters and the field orders below are the ones in pulseaudio's
  41// own sources -- pulsecore/native-common.h, pulsecore/tagstruct.{c,h} and
  42// pulsecore/protocol-native.c -- and the place each of them was read from is named where it is used.
  43
  44typedef enum FFSoundPulseCommand : uint32_t {
  45    FF_SOUND_PA_COMMAND_ERROR = 0,
  46    FF_SOUND_PA_COMMAND_REPLY = 2,
  47    FF_SOUND_PA_COMMAND_AUTH = 8,
  48    FF_SOUND_PA_COMMAND_SET_CLIENT_NAME = 9,
  49    FF_SOUND_PA_COMMAND_GET_SERVER_INFO = 20,
  50    FF_SOUND_PA_COMMAND_GET_SINK_INFO_LIST = 22,
  51} FFSoundPulseCommand;
  52
  53typedef enum FFSoundPulseTag : uint8_t {
  54    FF_SOUND_PA_TAG_STRING = 't',
  55    FF_SOUND_PA_TAG_STRING_NULL = 'N',
  56    FF_SOUND_PA_TAG_U32 = 'L',
  57    FF_SOUND_PA_TAG_U8 = 'B',
  58    FF_SOUND_PA_TAG_U64 = 'R',
  59    FF_SOUND_PA_TAG_S64 = 'r',
  60    FF_SOUND_PA_TAG_SAMPLE_SPEC = 'a',
  61    FF_SOUND_PA_TAG_ARBITRARY = 'x',
  62    FF_SOUND_PA_TAG_BOOLEAN_TRUE = '1',
  63    FF_SOUND_PA_TAG_BOOLEAN_FALSE = '0',
  64    FF_SOUND_PA_TAG_TIMEVAL = 'T',
  65    FF_SOUND_PA_TAG_USEC = 'U',
  66    FF_SOUND_PA_TAG_CHANNEL_MAP = 'm',
  67    FF_SOUND_PA_TAG_CVOLUME = 'v',
  68    FF_SOUND_PA_TAG_PROPLIST = 'P',
  69    FF_SOUND_PA_TAG_VOLUME = 'V',
  70    FF_SOUND_PA_TAG_FORMAT_INFO = 'f',
  71} FFSoundPulseTag;
  72
  73typedef enum FFSoundPulseError {
  74    FF_SOUND_PA_ERROR_ACCESS = 1,
  75    FF_SOUND_PA_ERROR_VERSION = 17,
  76} FFSoundPulseError;
  77
  78enum {
  79    FF_SOUND_PA_VERSION = 35,          // PA_PROTOCOL_VERSION
  80    FF_SOUND_PA_VERSION_MASK = 0xFFFF, // PA_PROTOCOL_VERSION_MASK
  81    FF_SOUND_PA_COOKIE_LENGTH = 256,   // PA_NATIVE_COOKIE_LENGTH
  82    FF_SOUND_PA_FRAME_HEADER_SIZE = 20,
  83    FF_SOUND_PA_FRAME_LENGTH_MAX = 1024 * 1024 * 16, // FRAME_SIZE_MAX_ALLOW
  84    FF_SOUND_PA_CHANNELS_MAX = 32,                   // PA_CHANNELS_MAX
  85    FF_SOUND_PA_VOLUME_NORM = 0x10000,               // PA_VOLUME_NORM, i.e. 100%
  86    FF_SOUND_PA_PORT_AVAILABLE_NO = 2,               // PA_PORT_AVAILABLE_NO
  87    FF_SOUND_PA_RECV_TIMEOUT_MS = 2000,
  88};
  89
  90#define FF_SOUND_PA_DEFAULT_PORT "4713" // PA_NATIVE_DEFAULT_PORT
  91#define FF_SOUND_PA_SOCKET_NAME "native"
  92
  93#ifdef MSG_NOSIGNAL
  94    #define FF_SOUND_PA_SEND_FLAGS MSG_NOSIGNAL
  95#else
  96    #define FF_SOUND_PA_SEND_FLAGS 0
  97#endif
  98
  99#if defined(__linux__) || defined(__ANDROID__)
 100    #define FF_SOUND_PA_HAS_CREDENTIALS 1
 101#else
 102    #define FF_SOUND_PA_HAS_CREDENTIALS 0
 103#endif
 104
 105typedef struct FFSoundPulseConn {
 106    int fd;
 107    uint32_t version; // protocol version negotiated with the server
 108    uint32_t nextTag;
 109    FFstrbuf frame; // payload of the last received frame
 110} FFSoundPulseConn;
 111
 112typedef struct FFSoundPulseReader {
 113    const char* data;
 114    uint32_t length;
 115    uint32_t offset;
 116    bool error;
 117} FFSoundPulseReader;
 118
 119// Everything this module needs to know about one sink. The fields point into the
 120// frame buffer of the connection.
 121typedef struct FFSoundPulseSinkInfo {
 122    const char* name;        // the sink name, reported as the device identifier
 123    const char* description; // the sink's own description field, reported as the device name
 124    uint32_t volume;         // cvolume of the first channel
 125    bool mute;
 126    bool main;   // the sink equals the server's default sink
 127    bool active; // the sink has an active port and that port is available
 128} FFSoundPulseSinkInfo;
 129
 130// ---------------------------------------------------------------------------
 131// tagstruct
 132// ---------------------------------------------------------------------------
 133
 134static void putTagU32(FFstrbuf* buffer, uint32_t value) {
 135    ffStrbufAppendC(buffer, FF_SOUND_PA_TAG_U32);
 136    value = FF_READ_BE(value);
 137    ffStrbufAppendNS(buffer, sizeof(value), (const char*) &value);
 138}
 139
 140static void putTagString(FFstrbuf* buffer, const char* value) {
 141    if (!value) {
 142        ffStrbufAppendC(buffer, FF_SOUND_PA_TAG_STRING_NULL);
 143        return;
 144    }
 145
 146    ffStrbufAppendC(buffer, FF_SOUND_PA_TAG_STRING);
 147    ffStrbufAppendS(buffer, value);
 148    ffStrbufAppendC(buffer, '\0');
 149}
 150
 151static void putTagArbitrary(FFstrbuf* buffer, const void* value, uint32_t length) {
 152    ffStrbufAppendC(buffer, FF_SOUND_PA_TAG_ARBITRARY);
 153    uint32_t lengthData = FF_READ_BE(length);
 154    ffStrbufAppendNS(buffer, sizeof(lengthData), (const char*) &lengthData);
 155    ffStrbufAppendNS(buffer, length, value);
 156}
 157
 158// A proplist is a series of "string key, u32 length, arbitrary value" triples
 159// terminated by a NULL string. The length is a tagged u32 like any other, only
 160// the length inside the arbitrary value is raw. Because the terminator uses the
 161// same tag as a NULL string, a proplist may only appear at the end of a packet --
 162// which is the case for every packet we send.
 163static void putTagProplist(FFstrbuf* buffer, const char* key, const char* value) {
 164    ffStrbufAppendC(buffer, FF_SOUND_PA_TAG_PROPLIST);
 165    if (key) {
 166        uint32_t length = (uint32_t) strlen(value);
 167        putTagString(buffer, key);
 168        putTagU32(buffer, length);
 169        putTagArbitrary(buffer, value, length);
 170    }
 171    putTagString(buffer, nullptr);
 172}
 173
 174static bool readerEof(const FFSoundPulseReader* reader) {
 175    return reader->offset >= reader->length;
 176}
 177
 178static bool skipBytes(FFSoundPulseReader* reader, uint32_t count) {
 179    if ((uint64_t) reader->offset + count > reader->length) {
 180        reader->error = true;
 181        return false;
 182    }
 183
 184    reader->offset += count;
 185    return true;
 186}
 187
 188static uint8_t readU8(FFSoundPulseReader* reader) {
 189    if (!skipBytes(reader, 1)) {
 190        return 0;
 191    }
 192
 193    return (uint8_t) reader->data[reader->offset - 1];
 194}
 195
 196static uint32_t readU32(FFSoundPulseReader* reader) {
 197    if (!skipBytes(reader, 4)) {
 198        return 0;
 199    }
 200
 201    uint32_t value;
 202    memcpy(&value, reader->data + reader->offset - 4, sizeof(value));
 203    return FF_READ_BE(value);
 204}
 205
 206static bool readU32Value(FFSoundPulseReader* reader, uint32_t* value) {
 207    if (readU8(reader) != FF_SOUND_PA_TAG_U32) {
 208        reader->error = true;
 209        return false;
 210    }
 211
 212    *value = readU32(reader);
 213    return !reader->error;
 214}
 215
 216static bool readU8Value(FFSoundPulseReader* reader, uint8_t* value) {
 217    if (readU8(reader) != FF_SOUND_PA_TAG_U8) {
 218        reader->error = true;
 219        return false;
 220    }
 221
 222    *value = readU8(reader);
 223    return !reader->error;
 224}
 225
 226// Skips a NUL terminated string whose tag has already been consumed.
 227static bool skipString(FFSoundPulseReader* reader) {
 228    while (reader->offset < reader->length && reader->data[reader->offset]) {
 229        reader->offset++;
 230    }
 231
 232    if (reader->offset >= reader->length) {
 233        reader->error = true;
 234        return false;
 235    }
 236
 237    reader->offset++; // trailing NUL
 238    return true;
 239}
 240
 241// Reads a string tag and returns a pointer to the NUL terminated string inside
 242// the frame. A NULL string ('N') is a valid value, not an error. The pointer
 243// stays valid as long as the frame buffer is not reused.
 244static const char* readStringRef(FFSoundPulseReader* reader) {
 245    uint8_t tag = readU8(reader);
 246    if (reader->error) {
 247        return nullptr;
 248    }
 249
 250    if (tag == FF_SOUND_PA_TAG_STRING_NULL) {
 251        return nullptr;
 252    }
 253
 254    if (tag != FF_SOUND_PA_TAG_STRING) {
 255        reader->error = true;
 256        return nullptr;
 257    }
 258
 259    uint32_t start = reader->offset;
 260    if (!skipString(reader)) {
 261        return nullptr;
 262    }
 263
 264    return reader->data + start;
 265}
 266
 267static bool readString(FFSoundPulseReader* reader, FFstrbuf* value) {
 268    const char* string = readStringRef(reader);
 269    if (reader->error) {
 270        return false;
 271    }
 272
 273    if (string) {
 274        ffStrbufSetS(value, string);
 275    } else {
 276        ffStrbufClear(value);
 277    }
 278
 279    return true;
 280}
 281
 282static bool readBoolean(FFSoundPulseReader* reader, bool* value) {
 283    uint8_t tag = readU8(reader);
 284    if (tag == FF_SOUND_PA_TAG_BOOLEAN_TRUE) {
 285        *value = true;
 286        return true;
 287    }
 288
 289    if (tag == FF_SOUND_PA_TAG_BOOLEAN_FALSE) {
 290        *value = false;
 291        return true;
 292    }
 293
 294    reader->error = true;
 295    return false;
 296}
 297
 298// Reads a cvolume tag and returns the volume of its first channel. The channel
 299// count is validated like pa_tagstruct_get_cvolume() does, otherwise a malformed
 300// packet would make us walk out of the payload.
 301static bool readCVolume(FFSoundPulseReader* reader, uint32_t* volume) {
 302    if (readU8(reader) != FF_SOUND_PA_TAG_CVOLUME) {
 303        reader->error = true;
 304        return false;
 305    }
 306
 307    uint8_t channels = readU8(reader);
 308    if (reader->error || channels > FF_SOUND_PA_CHANNELS_MAX) {
 309        reader->error = true;
 310        return false;
 311    }
 312
 313    *volume = 0;
 314    for (uint8_t channel = 0; channel < channels; channel++) {
 315        uint32_t value = readU32(reader);
 316        if (reader->error) {
 317            return false;
 318        }
 319
 320        if (channel == 0) {
 321            *volume = value;
 322        }
 323    }
 324
 325    return true;
 326}
 327
 328// Skips the value that follows the already consumed tag. It dispatches on the
 329// tag it actually reads, so it can not run out of sync even if the layout
 330// assumed for the surrounding packet is wrong.
 331static bool skipValue(FFSoundPulseReader* reader) {
 332    uint8_t tag = readU8(reader);
 333    if (reader->error) {
 334        return false;
 335    }
 336
 337    switch (tag) {
 338        case FF_SOUND_PA_TAG_STRING_NULL:
 339        case FF_SOUND_PA_TAG_BOOLEAN_TRUE:
 340        case FF_SOUND_PA_TAG_BOOLEAN_FALSE:
 341            return true;
 342        case FF_SOUND_PA_TAG_STRING:
 343            return skipString(reader); // the tag is already consumed
 344        case FF_SOUND_PA_TAG_U8:
 345            return skipBytes(reader, 1);
 346        case FF_SOUND_PA_TAG_U32:
 347        case FF_SOUND_PA_TAG_VOLUME:
 348            return skipBytes(reader, 4);
 349        case FF_SOUND_PA_TAG_U64:
 350        case FF_SOUND_PA_TAG_S64:
 351        case FF_SOUND_PA_TAG_USEC:
 352        case FF_SOUND_PA_TAG_TIMEVAL:
 353            return skipBytes(reader, 8);
 354        case FF_SOUND_PA_TAG_SAMPLE_SPEC:
 355            return skipBytes(reader, 6); // format, channels, rate
 356        case FF_SOUND_PA_TAG_ARBITRARY: {
 357            uint32_t length = readU32(reader);
 358            return !reader->error && skipBytes(reader, length);
 359        }
 360        case FF_SOUND_PA_TAG_CHANNEL_MAP: {
 361            uint8_t channels = readU8(reader);
 362            return !reader->error && skipBytes(reader, channels);
 363        }
 364        case FF_SOUND_PA_TAG_CVOLUME: {
 365            uint8_t channels = readU8(reader);
 366            return !reader->error && skipBytes(reader, (uint32_t) channels * 4);
 367        }
 368        case FF_SOUND_PA_TAG_PROPLIST:
 369            while (!reader->error && !readerEof(reader) && reader->data[reader->offset] != FF_SOUND_PA_TAG_STRING_NULL) {
 370                if (!skipValue(reader) || !skipValue(reader) || !skipValue(reader)) { // key, length, value
 371                    return false;
 372                }
 373            }
 374            return skipValue(reader); // the NULL string terminating the list
 375        case FF_SOUND_PA_TAG_FORMAT_INFO:
 376            return skipValue(reader) && skipValue(reader); // encoding, proplist
 377        default:
 378            FF_DEBUG("Unknown pulseaudio tag 0x%02X", tag);
 379            reader->error = true;
 380            return false;
 381    }
 382}
 383
 384// ---------------------------------------------------------------------------
 385// transport
 386// ---------------------------------------------------------------------------
 387
 388static bool writeAll(int fd, const char* data, uint32_t length) {
 389    while (length > 0) {
 390        ssize_t written = send(fd, data, length, FF_SOUND_PA_SEND_FLAGS);
 391        if (written < 0) {
 392            if (errno == EINTR) {
 393                continue;
 394            }
 395
 396            FF_DEBUG("Failed to write to the pulseaudio socket: %s", strerror(errno));
 397            return false;
 398        }
 399
 400        data += written;
 401        length -= (uint32_t) written;
 402    }
 403
 404    return true;
 405}
 406
 407static bool readAll(int fd, char* data, uint32_t length) {
 408    while (length > 0) {
 409        ssize_t received = recv(fd, data, length, 0);
 410        if (received < 0) {
 411            if (errno == EINTR) {
 412                continue;
 413            }
 414
 415            FF_DEBUG("Failed to read from the pulseaudio socket: %s", strerror(errno));
 416            return false;
 417        }
 418
 419        if (received == 0) { // the server closed the connection
 420            FF_DEBUG("The pulseaudio server closed the connection");
 421            return false;
 422        }
 423
 424        data += received;
 425        length -= (uint32_t) received;
 426    }
 427
 428    return true;
 429}
 430
 431static void applyReceiveTimeout(int fd) {
 432    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &(struct timeval){ .tv_sec = FF_SOUND_PA_RECV_TIMEOUT_MS / 1000, .tv_usec = (FF_SOUND_PA_RECV_TIMEOUT_MS % 1000) * 1000 }, sizeof(struct timeval));
 433}
 434
 435static int connectUnixSocket(const char* path) {
 436    struct sockaddr_un address = { .sun_family = AF_UNIX };
 437    size_t length = strlen(path);
 438    if (length >= sizeof(address.sun_path)) {
 439        FF_DEBUG("The pulseaudio socket path is too long: %s", path);
 440        return -1;
 441    }
 442
 443    memcpy(address.sun_path, path, length + 1);
 444
 445    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
 446    if (fd < 0) {
 447        return -1;
 448    }
 449
 450    if (connect(fd, (struct sockaddr*) &address, (socklen_t) (offsetof(struct sockaddr_un, sun_path) + length + 1)) < 0) {
 451        FF_DEBUG("Failed to connect to %s: %s", path, strerror(errno));
 452        close(fd);
 453        return -1;
 454    }
 455
 456    applyReceiveTimeout(fd);
 457    return fd;
 458}
 459
 460static int connectTcpSocket(const char* address, int family) {
 461    // "host", "host:port" or "[host]:port"
 462    FF_STRBUF_AUTO_DESTROY host = ffStrbufCreate();
 463    const char* port = FF_SOUND_PA_DEFAULT_PORT;
 464
 465    if (*address == '[') {
 466        const char* end = strchr(address, ']');
 467        if (!end) {
 468            return -1;
 469        }
 470
 471        ffStrbufSetNS(&host, (uint32_t) (end - address - 1), address + 1);
 472        if (end[1] == ':') {
 473            port = end + 2;
 474        }
 475    } else {
 476        const char* colon = strrchr(address, ':');
 477        if (colon && strchr(address, ':') == colon) { // an unbracketed IPv6 address has several colons
 478            ffStrbufSetNS(&host, (uint32_t) (colon - address), address);
 479            port = colon + 1;
 480        } else {
 481            ffStrbufSetS(&host, address);
 482        }
 483    }
 484
 485    if (host.length == 0) {
 486        ffStrbufSetS(&host, "localhost");
 487    }
 488
 489    if (*port == '\0') {
 490        port = FF_SOUND_PA_DEFAULT_PORT;
 491    }
 492
 493    struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM };
 494    struct addrinfo* result = nullptr;
 495    if (getaddrinfo(host.chars, port, &hints, &result) != 0) {
 496        FF_DEBUG("Failed to resolve the pulseaudio server address %s", address);
 497        return -1;
 498    }
 499
 500    int fd = -1;
 501    for (struct addrinfo* entry = result; entry; entry = entry->ai_next) {
 502        fd = socket(entry->ai_family, entry->ai_socktype, entry->ai_protocol);
 503        if (fd < 0) {
 504            continue;
 505        }
 506
 507        if (connect(fd, entry->ai_addr, entry->ai_addrlen) == 0) {
 508            break;
 509        }
 510
 511        close(fd);
 512        fd = -1;
 513    }
 514
 515    freeaddrinfo(result);
 516
 517    if (fd < 0) {
 518        FF_DEBUG("Failed to connect to the pulseaudio server %s", address);
 519        return -1;
 520    }
 521
 522    applyReceiveTimeout(fd);
 523    return fd;
 524}
 525
 526// $PULSE_SERVER overrides the whole address and accepts "unix:", "tcp:", "tcp4:"
 527// and "tcp6:" prefixes as well as a plain socket path.
 528static int connectServer(const char* server, const FFstrbuf* defaultSocketPath) {
 529    if (ffStrStartsWith(server, "unix:")) {
 530        const char* path = server + strlen("unix:");
 531        return ffStrSet(path) ? connectUnixSocket(path) : connectUnixSocket(defaultSocketPath->chars);
 532    }
 533
 534    if (ffStrStartsWith(server, "tcp4:")) {
 535        return connectTcpSocket(server + strlen("tcp4:"), AF_INET);
 536    }
 537
 538    if (ffStrStartsWith(server, "tcp6:")) {
 539        return connectTcpSocket(server + strlen("tcp6:"), AF_INET6);
 540    }
 541
 542    if (ffStrStartsWith(server, "tcp:")) {
 543        return connectTcpSocket(server + strlen("tcp:"), AF_UNSPEC);
 544    }
 545
 546    return connectUnixSocket(server);
 547}
 548
 549// $PULSE_RUNTIME_PATH is used as is, otherwise $XDG_RUNTIME_DIR/pulse. The socket
 550// is always named "native". The directory is deliberately not created: only the
 551// daemon owns it.
 552static void getDefaultSocketPath(FFstrbuf* path) {
 553    const char* runtimePath = getenv("PULSE_RUNTIME_PATH");
 554    if (ffStrSet(runtimePath)) {
 555        ffStrbufSetF(path, "%s/%s", runtimePath, FF_SOUND_PA_SOCKET_NAME);
 556        return;
 557    }
 558
 559    const char* xdgRuntimeDir = getenv("XDG_RUNTIME_DIR");
 560    if (ffStrSet(xdgRuntimeDir)) {
 561        ffStrbufSetF(path, "%s/pulse/%s", xdgRuntimeDir, FF_SOUND_PA_SOCKET_NAME);
 562        return;
 563    }
 564
 565    const char* home = getenv("HOME");
 566    if (ffStrSet(home)) {
 567        ffStrbufSetF(path, "%s/.pulse/%s", home, FF_SOUND_PA_SOCKET_NAME);
 568        return;
 569    }
 570
 571    ffStrbufClear(path);
 572}
 573
 574// The daemon always compares 256 bytes, so the buffer is sent in full. When no
 575// cookie file is found the daemon still authorizes us by the credentials attached
 576// to the AUTH frame.
 577static void getCookie(char cookie[FF_SOUND_PA_COOKIE_LENGTH]) {
 578    memset(cookie, 0, FF_SOUND_PA_COOKIE_LENGTH);
 579
 580    FF_STRBUF_AUTO_DESTROY paths = ffStrbufCreate();
 581    const char* cookiePath = getenv("PULSE_COOKIE");
 582    if (ffStrSet(cookiePath)) {
 583        ffStrbufSetS(&paths, cookiePath);
 584    } else {
 585        const char* configHome = getenv("XDG_CONFIG_HOME");
 586        const char* home = getenv("HOME");
 587        if (ffStrSet(configHome)) {
 588            ffStrbufAppendF(&paths, "%s/pulse/cookie:", configHome);
 589        }
 590        if (ffStrSet(home)) {
 591            ffStrbufAppendF(&paths, "%s/.config/pulse/cookie:", home);
 592            ffStrbufAppendF(&paths, "%s/.pulse-cookie", home);
 593        }
 594    }
 595
 596    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
 597    for (const char* path = paths.chars; ffStrSet(path);) {
 598        const char* end = strchr(path, ':');
 599        FF_STRBUF_AUTO_DESTROY candidate = ffStrbufCreateNS(end ? (uint32_t) (end - path) : (uint32_t) strlen(path), path);
 600        if (ffReadFileBuffer(candidate.chars, &buffer) && buffer.length > 0) {
 601            memcpy(cookie, buffer.chars, buffer.length < FF_SOUND_PA_COOKIE_LENGTH ? buffer.length : FF_SOUND_PA_COOKIE_LENGTH);
 602            FF_DEBUG("Using the pulseaudio cookie from %s", candidate.chars);
 603            return;
 604        }
 605
 606        path = end ? end + 1 : nullptr;
 607    }
 608
 609    FF_DEBUG("No pulseaudio cookie found, relying on the credentials of the AUTH frame");
 610}
 611
 612// ---------------------------------------------------------------------------
 613// frames
 614// ---------------------------------------------------------------------------
 615
 616// The daemon authorizes same user clients by the credentials of the last packet it
 617// read before AUTH, so they must not be omitted on Unix sockets. They are attached
 618// to the whole frame instead of the header alone: with two packets the daemon can
 619// read the second one without credentials and forget the ones it took from the
 620// first, which then fails AUTH with PA_ERR_ACCESS. Whether it does depends on how
 621// it happens to buffer the two writes, which makes that layout flaky.
 622static bool sendWithCredentials(int fd, const char* data, uint32_t length) {
 623#if FF_SOUND_PA_HAS_CREDENTIALS
 624    struct iovec iov = { .iov_base = (void*) data, .iov_len = length };
 625    char control[CMSG_SPACE(sizeof(struct ucred))];
 626    struct msghdr message = {
 627        .msg_iov = &iov,
 628        .msg_iovlen = 1,
 629        .msg_control = control,
 630        .msg_controllen = sizeof(control),
 631    };
 632
 633    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&message);
 634    cmsg->cmsg_level = SOL_SOCKET;
 635    cmsg->cmsg_type = SCM_CREDENTIALS;
 636    cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
 637    memcpy(CMSG_DATA(cmsg), &(struct ucred){ .pid = getpid(), .uid = getuid(), .gid = getgid() }, sizeof(struct ucred));
 638
 639    ssize_t sent;
 640    do {
 641        sent = sendmsg(fd, &message, FF_SOUND_PA_SEND_FLAGS);
 642    } while (sent < 0 && errno == EINTR);
 643
 644    if (sent < 0) {
 645        FF_DEBUG("Failed to send the pulseaudio frame: %s", strerror(errno));
 646        return false;
 647    }
 648
 649    return writeAll(fd, data + sent, length - (uint32_t) sent); // the remainder carries no credentials
 650#else
 651    return writeAll(fd, data, length);
 652#endif
 653}
 654
 655static bool sendFrame(FFSoundPulseConn* conn, const FFstrbuf* payload, bool withCredentials) {
 656    FF_STRBUF_AUTO_DESTROY frame = ffStrbufCreate();
 657    ffStrbufAppendNC(&frame, FF_SOUND_PA_FRAME_HEADER_SIZE, '\0');
 658    frame.chars[0] = (char) (payload->length >> 24);
 659    frame.chars[1] = (char) (payload->length >> 16);
 660    frame.chars[2] = (char) (payload->length >> 8);
 661    frame.chars[3] = (char) payload->length;
 662    frame.chars[4] = frame.chars[5] = frame.chars[6] = frame.chars[7] = (char) 0xFF; // channel: control packet
 663    ffStrbufAppend(&frame, payload);
 664
 665    if (withCredentials) {
 666        return sendWithCredentials(conn->fd, frame.chars, frame.length);
 667    }
 668
 669    return writeAll(conn->fd, frame.chars, frame.length);
 670}
 671
 672static bool recvFrame(FFSoundPulseConn* conn) {
 673    alignas(uint32_t) char header[FF_SOUND_PA_FRAME_HEADER_SIZE];
 674    if (!readAll(conn->fd, header, sizeof(header))) {
 675        return false;
 676    }
 677
 678    uint32_t length = FF_READ_BE(*(uint32_t*) header);
 679    if (length == 0 || length > FF_SOUND_PA_FRAME_LENGTH_MAX) {
 680        FF_DEBUG("Refusing a pulseaudio frame of %u bytes", length);
 681        return false;
 682    }
 683
 684    ffStrbufEnsureFixedLengthFree(&conn->frame, length);
 685    if (!readAll(conn->fd, conn->frame.chars, length)) {
 686        return false;
 687    }
 688
 689    conn->frame.chars[length] = '\0';
 690    conn->frame.length = length;
 691    return true;
 692}
 693
 694// Waits for the reply to `tag`. On success the reader is positioned after the
 695// command and tag fields.
 696static const char* recvReply(FFSoundPulseConn* conn, uint32_t tag, FFSoundPulseReader* reader) {
 697    while (true) {
 698        if (!recvFrame(conn)) {
 699            FF_DEBUG("Failed to read the pulseaudio reply to tag %u", tag);
 700            return "Failed to read a pulseaudio reply";
 701        }
 702
 703        FFSoundPulseReader frame = { .data = conn->frame.chars, .length = conn->frame.length };
 704        uint32_t command = 0;
 705        uint32_t replyTag = 0;
 706        // Both the command and the tag are tagged u32s, so they must be read as
 707        // values. Reading four raw bytes here would swallow the 'L' of the tag.
 708        if (!readU32Value(&frame, &command) || !readU32Value(&frame, &replyTag)) {
 709            FF_DEBUG("Malformed pulseaudio reply to tag %u: %u payload bytes", tag, frame.length);
 710            return "Malformed pulseaudio reply";
 711        }
 712
 713        if (replyTag != tag) {
 714            // Server initiated commands use the tag (uint32_t) -1 and are only
 715            // sent to subscribed clients. Ignore everything that is not ours.
 716            continue;
 717        }
 718
 719        if (command == FF_SOUND_PA_COMMAND_ERROR) {
 720            uint32_t error = 0;
 721            if (!readU32Value(&frame, &error)) {
 722                FF_DEBUG("Malformed pulseaudio error reply to tag %u: %u payload bytes", tag, frame.length);
 723                return "Malformed pulseaudio error reply";
 724            }
 725
 726            FF_DEBUG("The pulseaudio server returned the error %u", error);
 727            if (error == FF_SOUND_PA_ERROR_ACCESS) {
 728                FF_DEBUG("The pulseaudio server denied access to the AUTH request");
 729                return "Access to the pulseaudio server was denied";
 730            }
 731
 732            if (error == FF_SOUND_PA_ERROR_VERSION) {
 733                FF_DEBUG("The pulseaudio server rejected our protocol version %u", FF_SOUND_PA_VERSION);
 734                return "The pulseaudio server is too old";
 735            }
 736
 737            return "The pulseaudio server refused the request";
 738        }
 739
 740        if (command != FF_SOUND_PA_COMMAND_REPLY) {
 741            FF_DEBUG("Unexpected pulseaudio command %u in the reply to tag %u", command, tag);
 742            return "Unexpected pulseaudio reply";
 743        }
 744
 745        *reader = frame;
 746        return nullptr;
 747    }
 748}
 749
 750// ---------------------------------------------------------------------------
 751// handshake
 752// ---------------------------------------------------------------------------
 753
 754static const char* handshake(FFSoundPulseConn* conn, FFstrbuf* serverName, FFstrbuf* defaultSinkName) {
 755    {
 756        FF_STRBUF_AUTO_DESTROY payload = ffStrbufCreate();
 757        uint32_t tag = conn->nextTag++;
 758        putTagU32(&payload, FF_SOUND_PA_COMMAND_AUTH);
 759        putTagU32(&payload, tag);
 760        putTagU32(&payload, FF_SOUND_PA_VERSION); // neither PA_PROTOCOL_FLAG_SHM nor PA_PROTOCOL_FLAG_MEMFD
 761
 762        char cookie[FF_SOUND_PA_COOKIE_LENGTH];
 763        getCookie(cookie);
 764        putTagArbitrary(&payload, cookie, sizeof(cookie));
 765
 766        if (!sendFrame(conn, &payload, true)) {
 767            FF_DEBUG("Failed to send the pulseaudio AUTH request (tag %u)", tag);
 768            return "Failed to send the pulseaudio AUTH request";
 769        }
 770
 771        FFSoundPulseReader reader;
 772        const char* error = recvReply(conn, tag, &reader);
 773        if (error) {
 774            return error;
 775        }
 776
 777        uint32_t version = 0;
 778        if (!readU32Value(&reader, &version)) {
 779            return "Malformed pulseaudio AUTH reply";
 780        }
 781
 782        conn->version = version & FF_SOUND_PA_VERSION_MASK;
 783        if (conn->version > FF_SOUND_PA_VERSION) {
 784            conn->version = FF_SOUND_PA_VERSION;
 785        }
 786
 787        FF_DEBUG("Negotiated pulseaudio protocol version %u", conn->version);
 788    }
 789
 790    {
 791        FF_STRBUF_AUTO_DESTROY payload = ffStrbufCreate();
 792        uint32_t tag = conn->nextTag++;
 793        putTagU32(&payload, FF_SOUND_PA_COMMAND_SET_CLIENT_NAME);
 794        putTagU32(&payload, tag);
 795
 796        if (conn->version >= 13) {
 797            putTagProplist(&payload, "application.name", "fastfetch");
 798        } else {
 799            putTagString(&payload, "fastfetch");
 800        }
 801
 802        if (!sendFrame(conn, &payload, false)) {
 803            FF_DEBUG("Failed to send the pulseaudio SET_CLIENT_NAME request (tag %u)", tag);
 804            return "Failed to send the pulseaudio SET_CLIENT_NAME request";
 805        }
 806
 807        FFSoundPulseReader reader;
 808        const char* error = recvReply(conn, tag, &reader);
 809        if (error) {
 810            return error;
 811        }
 812    }
 813
 814    {
 815        FF_STRBUF_AUTO_DESTROY payload = ffStrbufCreate();
 816        uint32_t tag = conn->nextTag++;
 817        putTagU32(&payload, FF_SOUND_PA_COMMAND_GET_SERVER_INFO);
 818        putTagU32(&payload, tag);
 819
 820        if (!sendFrame(conn, &payload, false)) {
 821            FF_DEBUG("Failed to send the pulseaudio GET_SERVER_INFO request (tag %u)", tag);
 822            return "Failed to send the pulseaudio GET_SERVER_INFO request";
 823        }
 824
 825        FFSoundPulseReader reader;
 826        const char* error = recvReply(conn, tag, &reader);
 827        if (error) {
 828            return error;
 829        }
 830
 831        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
 832        FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate();
 833        if (!readString(&reader, &name) || !readString(&reader, &version)) {
 834            return "Malformed pulseaudio GET_SERVER_INFO reply";
 835        }
 836
 837        if (!skipValue(&reader) || !skipValue(&reader)) { // user name, host name
 838            return "Malformed pulseaudio GET_SERVER_INFO reply";
 839        }
 840
 841        if (!skipValue(&reader)) { // sample spec
 842            return "Malformed pulseaudio GET_SERVER_INFO reply";
 843        }
 844
 845        if (!readString(&reader, defaultSinkName)) { // default sink name, may be a NULL string
 846            return "Malformed pulseaudio GET_SERVER_INFO reply";
 847        }
 848
 849        FF_DEBUG("Default pulseaudio sink: %s", defaultSinkName->chars);
 850
 851        // pipewire-pulse reports itself as "PulseAudio (on PipeWire x.y.z)"
 852        const char* on = strstr(name.chars, "(on ");
 853        if (on) {
 854            ffStrbufSetS(serverName, on + strlen("(on "));
 855            ffStrbufTrimRight(serverName, ')');
 856        } else {
 857            ffStrbufSetF(serverName, "%s %s", name.chars, version.chars);
 858        }
 859    }
 860
 861    return nullptr;
 862}
 863
 864// ---------------------------------------------------------------------------
 865// sinks
 866// ---------------------------------------------------------------------------
 867
 868// The port that is reported as active decides whether a sink counts as active.
 869// Since its name is sent after the port list, the list is scanned a second time
 870// to look up the availability of that port.
 871static bool isPortAvailable(const FFSoundPulseReader* reader, uint32_t version, uint32_t portCount, uint32_t portsOffset, const char* activePort) {
 872    if (version < 24) {
 873        return true; // ports carry no availability information before v24
 874    }
 875
 876    FFSoundPulseReader scan = *reader;
 877    scan.offset = portsOffset;
 878    scan.error = false;
 879
 880    for (uint32_t port = 0; port < portCount; port++) {
 881        const char* name = readStringRef(&scan);
 882        if (scan.error || !skipValue(&scan) || !skipValue(&scan)) { // description, priority
 883            return true;
 884        }
 885
 886        uint32_t available = 0;
 887        if (!readU32Value(&scan, &available)) {
 888            return true;
 889        }
 890
 891        if (version >= 34) { // availability group, type
 892            if (!skipValue(&scan) || !skipValue(&scan)) {
 893                return true;
 894            }
 895        }
 896
 897        if (name && ffStrEquals(name, activePort)) {
 898            return available != FF_SOUND_PA_PORT_AVAILABLE_NO;
 899        }
 900    }
 901
 902    return true;
 903}
 904
 905// Sink objects are sent in the order of sink_fill_tagstruct(). Everything that is
 906// not needed is skipped; the fields that are read are tag checked so that a
 907// layout mismatch is reported instead of silently shifting the whole list.
 908static bool readSinkInfo(FFSoundPulseReader* reader, const FFSoundPulseConn* conn, const FFstrbuf* defaultSinkName, FFSoundPulseSinkInfo* sink) {
 909    uint32_t version = conn->version;
 910
 911    if (!skipValue(reader)) { // index
 912        return false;
 913    }
 914
 915    sink->name = readStringRef(reader);
 916    if (reader->error || !sink->name) {
 917        return false;
 918    }
 919
 920    sink->description = readStringRef(reader);
 921    if (reader->error) {
 922        return false;
 923    }
 924
 925    if (!skipValue(reader)) { // sample spec
 926        return false;
 927    }
 928
 929    if (!skipValue(reader)) { // channel map
 930        return false;
 931    }
 932
 933    if (!skipValue(reader)) { // owner module
 934        return false;
 935    }
 936
 937    if (!readCVolume(reader, &sink->volume)) {
 938        return false;
 939    }
 940
 941    if (!readBoolean(reader, &sink->mute)) {
 942        return false;
 943    }
 944
 945    if (!skipValue(reader)) { // monitor source
 946        return false;
 947    }
 948
 949    if (!skipValue(reader)) { // monitor source name
 950        return false;
 951    }
 952
 953    if (!skipValue(reader)) { // latency
 954        return false;
 955    }
 956
 957    if (!skipValue(reader)) { // driver
 958        return false;
 959    }
 960
 961    if (!skipValue(reader)) { // flags
 962        return false;
 963    }
 964
 965    if (version >= 13) {
 966        if (!skipValue(reader)) { // proplist
 967            return false;
 968        }
 969
 970        if (!skipValue(reader)) { // configured latency
 971            return false;
 972        }
 973    }
 974
 975    if (version >= 15) {
 976        for (uint32_t field = 0; field < 4; field++) { // base volume, state, volume steps, card
 977            if (!skipValue(reader)) {
 978                return false;
 979            }
 980        }
 981    }
 982
 983    const char* activePort = nullptr;
 984    uint32_t portCount = 0;
 985    uint32_t portsOffset = 0;
 986
 987    if (version >= 16) {
 988        if (!readU32Value(reader, &portCount)) {
 989            return false;
 990        }
 991
 992        portsOffset = reader->offset;
 993        for (uint32_t port = 0; port < portCount; port++) {
 994            if (!skipValue(reader)) { // name
 995                return false;
 996            }
 997
 998            if (!skipValue(reader)) { // description
 999                return false;
1000            }
1001
1002            if (!skipValue(reader)) { // priority
1003                return false;
1004            }
1005
1006            if (version >= 24 && !skipValue(reader)) { // availability
1007                return false;
1008            }
1009
1010            if (version >= 34) {
1011                if (!skipValue(reader) || !skipValue(reader)) { // availability group, type
1012                    return false;
1013                }
1014            }
1015        }
1016
1017        activePort = readStringRef(reader); // the last field of the port section
1018        if (reader->error) {
1019            return false;
1020        }
1021    }
1022
1023    if (version >= 21) {
1024        uint8_t formats = 0;
1025        if (!readU8Value(reader, &formats)) { // number of format infos
1026            return false;
1027        }
1028
1029        for (uint8_t format = 0; format < formats; format++) {
1030            if (!skipValue(reader)) { // format info
1031                return false;
1032            }
1033        }
1034    }
1035
1036    sink->main = ffStrbufEqualS(defaultSinkName, sink->name);
1037    sink->active = activePort && isPortAvailable(reader, version, portCount, portsOffset, activePort);
1038    return true;
1039}
1040
1041static void appendSink(FFlist* devices, const FFSoundPulseSinkInfo* sink, const FFstrbuf* serverName) {
1042    FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
1043    ffStrbufInitS(&device->identifier, sink->name);
1044    ffStrbufTrimRightSpace(&device->identifier);
1045    ffStrbufInitCopy(&device->platformApi, serverName);
1046    ffStrbufInitS(&device->name, sink->description ? sink->description : "");
1047    ffStrbufTrimRightSpace(&device->name);
1048    ffStrbufTrimLeft(&device->name, ' ');
1049    device->volume = sink->mute ? 0 : (uint8_t) (((uint64_t) sink->volume * 100 + FF_SOUND_PA_VOLUME_NORM / 2) / FF_SOUND_PA_VOLUME_NORM);
1050    device->type = (sink->main ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | (sink->active ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE);
1051}
1052
1053static const char* fetchSinkList(FFSoundPulseConn* conn, const FFSoundOptions* options, FFlist* devices, const FFstrbuf* serverName, const FFstrbuf* defaultSinkName) {
1054    FF_STRBUF_AUTO_DESTROY payload = ffStrbufCreate();
1055    uint32_t tag = conn->nextTag++;
1056    putTagU32(&payload, FF_SOUND_PA_COMMAND_GET_SINK_INFO_LIST);
1057    putTagU32(&payload, tag);
1058
1059    if (!sendFrame(conn, &payload, false)) {
1060        FF_DEBUG("Failed to send the pulseaudio GET_SINK_INFO_LIST request (tag %u)", tag);
1061        return "Failed to send the pulseaudio GET_SINK_INFO_LIST request";
1062    }
1063
1064    FFSoundPulseReader reader;
1065    const char* error = recvReply(conn, tag, &reader);
1066    if (error) {
1067        return error;
1068    }
1069
1070    // All sinks are concatenated in a single reply without a separator, a count
1071    // or a terminator: parse until the payload is exhausted.
1072    while (!reader.error && !readerEof(&reader)) {
1073        FFSoundPulseSinkInfo sink = {};
1074        if (!readSinkInfo(&reader, conn, defaultSinkName, &sink)) {
1075            FF_DEBUG("Failed to parse a pulseaudio sink at payload offset %u of %u", reader.offset, reader.length);
1076            return "Failed to parse the pulseaudio sink list";
1077        }
1078
1079        if ((options->soundType & FF_SOUND_TYPE_MAIN) && !sink.main) {
1080            continue;
1081        }
1082
1083        if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !sink.active) {
1084            continue;
1085        }
1086
1087        appendSink(devices, &sink, serverName);
1088    }
1089
1090    if (reader.error) {
1091        FF_DEBUG("The pulseaudio sink list ended at payload offset %u of %u", reader.offset, reader.length);
1092        return "Failed to parse the pulseaudio sink list";
1093    }
1094
1095    FF_DEBUG("Parsed %u pulseaudio sinks", devices->length);
1096    return nullptr;
1097}
1098
1099// ---------------------------------------------------------------------------
1100// the hardware: ALSA
1101// ---------------------------------------------------------------------------
1102
1103// A machine that runs no sound server still has ALSA, and this is what answers for it: the cards
1104// are enumerated from /proc and their volumes read from the control device of each card.
1105//
1106// Nothing is linked and nothing is loaded. The values are behind four ioctls of the kernel's own
1107// control interface, and both the structs and the request numbers are the kernel's uapi, which
1108// every libc ships as <sound/asound.h>. The request numbers are `_IOWR`-style, so they carry the
1109// size of the struct they pass -- a layout that does not match the kernel's is refused as an
1110// unknown ioctl instead of being read as garbage.
1111//
1112// Android is left out: it has no ALSA of the kind this reads ( /dev/snd and /proc/asound are
1113// closed to an app's SELinux domain ) and a session that does have a desktop on top of the device
1114// is running a sound server, which is the branch below. A libc that does not carry the header says
1115// so here, and the fallback is simply not compiled in -- the same shape as the other optional
1116// backends in this tree.
1117#if defined(__linux__) && !defined(__ANDROID__) && __has_include(<sound/asound.h>)
1118    #define FF_SOUND_ALSA 1
1119#endif
1120
1121#ifdef FF_SOUND_ALSA
1122
1123    #include <fcntl.h>
1124    #include <stdio.h>
1125    // <sys/ioctl.h> before <sound/asound.h>, in this order: the uapi header spells its request
1126    // numbers with _IOR and _IOWR and does not define either of them itself.
1127    #include <sys/ioctl.h>
1128    #include <sound/asound.h>
1129
1130// What a mixer control *is* is written down nowhere in the control interface. The kernel describes
1131// a control with a type (integer, boolean, ...) and an access bitmask, and that bitmask has no
1132// volume and no switch bit at all -- the uapi header defines READ, WRITE, VOLATILE, TLV_*, INACTIVE,
1133// LOCK, OWNER and USER, and nothing else. What carries it is the control's name, which a driver
1134// builds by appending a suffix to the name of what is being controlled:
1135//
1136//     "Master Playback Volume"   the output volume of the control named "Master"
1137//     "Capture Volume"           the input volume of a control that has no name of its own
1138//
1139// So the suffix is what is matched, on one condition: it has to be the whole of the name or the
1140// start of a word in it. Without that, "Capture Volume" would end in " Volume" and be read as the
1141// directionless " Volume" -- which is the volume of a card that has only one direction, and so an
1142// output.
1143//
1144// The set of suffixes is the one libasound's simple mixer layer carries; the entries sit one after
1145// another in that library's .rodata. The order below is the order they have to be tried in, the
1146// longest first, or "Master Playback Volume" would be taken for a " Volume".
1147//
1148// One deliberate difference from libasound: it reads "Capture Volume" as a directionless volume,
1149// which is why a desktop mixer shows a "Capture" slider. This module reports output devices, and a
1150// capture gain is not one of them -- so a card that carries nothing else has no output to report.
1151typedef struct FFSoundAlsaSuffix {
1152    const char* suffix;
1153    bool isSwitch; // ... the suffix names a switch rather than a volume
1154    bool playback; // ... and one of the output direction
1155} FFSoundAlsaSuffix;
1156
1157static const FFSoundAlsaSuffix FF_SOUND_ALSA_SUFFIXES[] = {
1158    { "Playback Volume", false, true },
1159    { "Playback Switch", true, true },
1160    { "Playback Enum", false, false },
1161    { "Playback Route", false, false },
1162    { "Capture Volume", false, false },
1163    { "Capture Switch", false, false },
1164    { "Capture Enum", false, false },
1165    { "Capture Route", false, false },
1166    { "Volume", false, true },
1167    { "Switch", true, true },
1168    { "Enum", false, false },
1169    { "Route", false, false },
1170};
1171
1172// Which control is "the volume" of a card. A desktop mixer shows one slider per output and the name
1173// on it is one of these. A card that has none of them -- a USB interface, a virtual card -- still
1174// has exactly one playback volume, and there the first one the kernel enumerates is taken. This list
1175// exists so that the choice does not have to come out of that order, which is the order the driver
1176// registered its controls in and not a ranking at all.
1177static const char* const FF_SOUND_ALSA_MAIN_CONTROLS[] = { "Master", "Master Mono", "PCM", "Speaker", "Headphone", "Headphones", "Front" };
1178
1179enum {
1180    // Ids asked for per ELEM_LIST call. The list is walked in batches rather than copied in one go,
1181    // so that nothing has to be sized after a number the card reports; 64 ids is 4 KiB of stack, and
1182    // the far side of the ioctl is a table lookup either way.
1183    FF_SOUND_ALSA_ID_BATCH = 64,
1184    FF_SOUND_ALSA_PRIORITY_NONE = UINT32_MAX,
1185};
1186
1187// Whether the control name is that of a playback volume or switch, and how much of it the suffix
1188// takes up. Both suffixes that are accepted end in the same six character tail -- "Volume" or
1189// "Switch" -- which is the only thing that pairs a card's volume with its mute; nothing else in the
1190// control interface says the two belong together.
1191static bool stripAlsaNameSuffix(const char* name, uint32_t length, bool* isSwitch, uint32_t* baseLength) {
1192    for (size_t index = 0; index < ARRAY_SIZE(FF_SOUND_ALSA_SUFFIXES); index++) {
1193        const FFSoundAlsaSuffix* suffix = &FF_SOUND_ALSA_SUFFIXES[index];
1194        const uint32_t suffixLength = (uint32_t) strlen(suffix->suffix);
1195        if (length < suffixLength || memcmp(name + length - suffixLength, suffix->suffix, suffixLength) != 0) {
1196            continue;
1197        }
1198
1199        // A suffix is a word of the name: either all of it, or preceded by a space. Anything else
1200        // is a coincidence of letters -- "Capture Volume" ends in " Volume" and is not one.
1201        if (length > suffixLength && name[length - suffixLength - 1] != ' ') {
1202            continue;
1203        }
1204
1205        if (!suffix->playback) {
1206            return false; // a capture or a routing control is not the card's output
1207        }
1208
1209        // The space that separated the base name from the suffix belongs to neither, and a name
1210        // that is nothing but the suffix has no base name at all.
1211        uint32_t base = length - suffixLength;
1212        while (base > 0 && name[base - 1] == ' ') {
1213            base--;
1214        }
1215
1216        *isSwitch = suffix->isSwitch;
1217        *baseLength = base;
1218        return true;
1219    }
1220
1221    return false;
1222}
1223
1224// The rank of a control's base name, or FF_SOUND_ALSA_PRIORITY_NONE when the list does not know it.
1225static uint32_t getAlsaControlPriority(const char* name, uint32_t baseLength) {
1226    for (size_t index = 0; index < ARRAY_SIZE(FF_SOUND_ALSA_MAIN_CONTROLS); index++) {
1227        if (strlen(FF_SOUND_ALSA_MAIN_CONTROLS[index]) == baseLength && memcmp(name, FF_SOUND_ALSA_MAIN_CONTROLS[index], baseLength) == 0) {
1228            return (uint32_t) index;
1229        }
1230    }
1231
1232    return FF_SOUND_ALSA_PRIORITY_NONE;
1233}
1234
1235// Walks the controls of one card. ELEM_LIST holds no state -- every call carries the offset it wants
1236// and the buffer to fill -- so a second walk of the same card is simply a second one of these.
1237typedef struct FFSoundAlsaControls {
1238    int fd;
1239    uint32_t offset;
1240    uint32_t index;
1241    uint32_t batch;
1242    bool done;
1243    struct snd_ctl_elem_id ids[FF_SOUND_ALSA_ID_BATCH];
1244} FFSoundAlsaControls;
1245
1246// The next mixer control of the card, or nullptr at the end. A list that cannot be read ends the
1247// walk rather than failing it: what to do about "this card says nothing" belongs to the caller, and
1248// everything below treats it exactly like a card that has no volume control. The other interfaces a
1249// card carries (PCM, timer, raw MIDI) are skipped here so that no caller has to know about them.
1250static const struct snd_ctl_elem_id* nextAlsaControl(FFSoundAlsaControls* controls) {
1251    for (;;) {
1252        if (controls->index < controls->batch) {
1253            const struct snd_ctl_elem_id* id = &controls->ids[controls->index++];
1254            if (id->iface != SNDRV_CTL_ELEM_IFACE_MIXER) {
1255                continue;
1256            }
1257
1258            return id;
1259        }
1260
1261        if (controls->done) {
1262            return nullptr;
1263        }
1264
1265        struct snd_ctl_elem_list list = {
1266            .offset = controls->offset,
1267            .space = FF_SOUND_ALSA_ID_BATCH,
1268            .pids = controls->ids,
1269        };
1270
1271        if (ioctl(controls->fd, SNDRV_CTL_IOCTL_ELEM_LIST, &list) < 0) {
1272            FF_DEBUG("Listing the controls of an ALSA card failed: %s", strerror(errno));
1273            controls->done = true;
1274            return nullptr;
1275        }
1276
1277        controls->offset += list.used;
1278        controls->index = 0;
1279        controls->batch = list.used;
1280
1281        if (list.used == 0) {
1282            controls->done = true;
1283            return nullptr;
1284        }
1285    }
1286}
1287
1288// A card's volume control and its name without the suffix -- which is what a desktop mixer puts on
1289// the slider, "Master" rather than "Master Playback Volume".
1290typedef struct FFSoundAlsaVolume {
1291    struct snd_ctl_elem_id id;
1292    char base[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
1293    uint32_t baseLength;
1294} FFSoundAlsaVolume;
1295
1296static bool findAlsaVolume(FFSoundAlsaControls* controls, FFSoundAlsaVolume* volume) {
1297    uint32_t priority = FF_SOUND_ALSA_PRIORITY_NONE;
1298    bool found = false;
1299
1300    const struct snd_ctl_elem_id* id = nullptr;
1301    while ((id = nextAlsaControl(controls)) != nullptr) {
1302        // The name is an array of SNDRV_CTL_ELEM_ID_NAME_MAXLEN bytes with the terminator inside
1303        // it, so its length is bounded rather than taken on trust.
1304        const uint32_t length = (uint32_t) strnlen((const char*) id->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
1305        bool isSwitch = false;
1306        uint32_t baseLength = 0;
1307        if (!stripAlsaNameSuffix((const char*) id->name, length, &isSwitch, &baseLength) || isSwitch) {
1308            continue;
1309        }
1310
1311        const uint32_t candidate = getAlsaControlPriority((const char*) id->name, baseLength);
1312        if (found && candidate >= priority) {
1313            continue; // a control the list knows beats one it does not, and the first of a rank wins
1314        }
1315
1316        volume->id = *id;
1317        volume->baseLength = baseLength;
1318        memcpy(volume->base, id->name, baseLength);
1319        volume->base[baseLength] = '\0';
1320        priority = candidate;
1321        found = true;
1322    }
1323
1324    return found;
1325}
1326
1327// The switch that belongs to the volume control: same base name, same index, same device. It takes
1328// a second walk because which base name wins is only known once the first walk is over. A card with
1329// no switch at all leaves the caller with "not muted", which is the answer a control without a mute
1330// state deserves.
1331static bool findAlsaSwitch(FFSoundAlsaControls* controls, const FFSoundAlsaVolume* volume, struct snd_ctl_elem_id* result) {
1332    const struct snd_ctl_elem_id* id = nullptr;
1333    while ((id = nextAlsaControl(controls)) != nullptr) {
1334        if (id->index != volume->id.index || id->device != volume->id.device || id->subdevice != volume->id.subdevice) {
1335            continue;
1336        }
1337
1338        const uint32_t length = (uint32_t) strnlen((const char*) id->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
1339        bool isSwitch = false;
1340        uint32_t baseLength = 0;
1341        if (!stripAlsaNameSuffix((const char*) id->name, length, &isSwitch, &baseLength) || !isSwitch) {
1342            continue;
1343        }
1344
1345        if (baseLength != volume->baseLength || memcmp(id->name, volume->base, baseLength) != 0) {
1346            continue;
1347        }
1348
1349        *result = *id;
1350        return true;
1351    }
1352
1353    return false;
1354}
1355
1356// The output of a card that has no volume control: any playback switch, the first one the kernel
1357// enumerates. HDMI and DP cards are typically this shape, with an "IEC958 Playback Switch" and no
1358// volume to go with it, so the switch is the only thing that says the card plays anything at all.
1359// Returns whether the card has a playback switch, so a caller can tell "no output" from "a switch".
1360static bool findAlsaPlaybackSwitch(FFSoundAlsaControls* controls, FFSoundAlsaVolume* output) {
1361    const struct snd_ctl_elem_id* id = nullptr;
1362    while ((id = nextAlsaControl(controls)) != nullptr) {
1363        const uint32_t length = (uint32_t) strnlen((const char*) id->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
1364        bool isSwitch = false;
1365        uint32_t baseLength = 0;
1366        if (!stripAlsaNameSuffix((const char*) id->name, length, &isSwitch, &baseLength) || !isSwitch) {
1367            continue;
1368        }
1369
1370        output->id = *id;
1371        output->baseLength = baseLength;
1372        memcpy(output->base, id->name, baseLength);
1373        output->base[baseLength] = '\0';
1374        return true;
1375    }
1376
1377    return false;
1378}
1379
1380// One value of a control, and the range that control's own scale puts it in. A switch is a boolean
1381// and shares the same union; a few DSP cards use the 64 bit integer type.
1382typedef struct FFSoundAlsaValue {
1383    long long value;
1384    long long minimum;
1385    long long maximum;
1386} FFSoundAlsaValue;
1387
1388static bool readAlsaValue(int fd, const struct snd_ctl_elem_id* id, FFSoundAlsaValue* result) {
1389    struct snd_ctl_elem_info info = { .id = *id };
1390    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &info) < 0) {
1391        FF_DEBUG("Reading the type of an ALSA control failed: %s", strerror(errno));
1392        return false;
1393    }
1394
1395    struct snd_ctl_elem_value value = { .id = *id };
1396    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_READ, &value) < 0) {
1397        FF_DEBUG("Reading the value of an ALSA control failed: %s", strerror(errno));
1398        return false;
1399    }
1400
1401    // Only the first channel is read, the way the PulseAudio backend reports the first channel of a
1402    // sink's volume. A card whose channels disagree is left to the mixer to sort out.
1403    if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
1404        result->value = value.value.integer64.value[0];
1405        result->minimum = info.value.integer64.min;
1406        result->maximum = info.value.integer64.max;
1407        return true;
1408    }
1409
1410    if (info.type != SNDRV_CTL_ELEM_TYPE_INTEGER && info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN) {
1411        FF_DEBUG("An ALSA control carries the value type %u, which is not a volume", (unsigned) info.type);
1412        return false;
1413    }
1414
1415    result->value = value.value.integer.value[0];
1416    result->minimum = info.value.integer.min;
1417    result->maximum = info.value.integer.max;
1418    return true;
1419}
1420
1421// The control's own scale as a percentage, which is the number `amixer` prints as `[n%]`. A range
1422// that is empty or inverted says nothing at all, and is reported as unknown rather than as 0% --
1423// 0% is a state a control is really in, and it is the muted one.
1424static uint8_t getAlsaPercent(const FFSoundAlsaValue* value) {
1425    if (value->maximum <= value->minimum) {
1426        return FF_SOUND_VOLUME_UNKNOWN;
1427    }
1428
1429    if (value->value <= value->minimum) {
1430        return 0;
1431    }
1432
1433    if (value->value >= value->maximum) {
1434        return 100;
1435    }
1436
1437    return (uint8_t) (((value->value - value->minimum) * 100 + (value->maximum - value->minimum) / 2) / (value->maximum - value->minimum));
1438}
1439
1440// Appends the output of one card. Returns whether the card has an output to report at all, which is
1441// not the same as whether a device was appended: `options` filters what is kept, while the main
1442// device of a machine is the first card that has an output whatever the filter then does with it.
1443//
1444// A card whose control device cannot be opened is reported without a volume rather than dropped.
1445// The devices are root:audio behind an ACL for the seat, so a session over ssh can name a card it
1446// may not read, and the name comes from /proc/asound/cards, which is world readable.
1447//
1448// The reported name is the card's own name from /proc/asound/cards followed by the control's base
1449// name -- "HDA Intel PCH Master" rather than a bare "Master" -- so the list says which card each
1450// volume belongs to. A card that *can* be read and has no playback control at all is dropped
1451// instead, because that is a card with nothing to play through. An HDMI or DP card often has no
1452// volume control but does have an "IEC958 Playback Switch"; the switch alone is still an output, so
1453// such a card is reported, with no percentage to go with it.
1454static bool appendAlsaCard(uint32_t card, const FFstrbuf* cardName, bool main, const FFSoundOptions* options, FFlist* devices) {
1455    // "/dev/snd/controlC", at most ten digits of the card index, and the terminator.
1456    char path[sizeof("/dev/snd/controlC") + 11];
1457    snprintf(path, sizeof(path), "/dev/snd/controlC%u", card);
1458
1459    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateCopy(cardName);
1460    // A card whose control device cannot be opened is not a card without an output, so it still
1461    // gets a device -- without a percentage.
1462    uint8_t volume = FF_SOUND_VOLUME_UNKNOWN;
1463    bool found = false;
1464    bool active = true;
1465
1466    const int fd = open(path, O_RDONLY | O_CLOEXEC);
1467    if (fd < 0) {
1468        // Only read access is asked for: a volume is being read, never written, and read permission
1469        // is the smaller of the two things to need.
1470        FF_DEBUG("Opening \"%s\" failed: %s", path, strerror(errno));
1471        found = true;
1472    } else {
1473        FFSoundAlsaControls controls = { .fd = fd };
1474        FFSoundAlsaVolume control = {};
1475
1476        if (findAlsaVolume(&controls, &control)) {
1477            found = true;
1478            // A control whose name is nothing but the suffix -- a codec that has one volume and no
1479            // name for it registers "Playback Volume" -- leaves the card's own name in place, which
1480            // is the only thing left to call the device.
1481            if (control.baseLength > 0) {
1482                ffStrbufAppendC(&name, ' ');
1483                ffStrbufAppendNS(&name, control.baseLength, control.base);
1484            }
1485
1486            FFSoundAlsaValue value = {};
1487            if (readAlsaValue(fd, &control.id, &value)) {
1488                volume = getAlsaPercent(&value);
1489            }
1490
1491            FFSoundAlsaControls switches = { .fd = fd };
1492            struct snd_ctl_elem_id switchId = {};
1493            FFSoundAlsaValue switchValue = {};
1494            if (findAlsaSwitch(&switches, &control, &switchId) && readAlsaValue(fd, &switchId, &switchValue)) {
1495                active = switchValue.value != 0;
1496                if (!active) {
1497                    // A muted control is reported as 0% rather than as the value it still holds,
1498                    // which is what the PulseAudio backend does with a muted sink.
1499                    volume = 0;
1500                }
1501            }
1502        } else {
1503            // No volume control: an HDMI or DP card is usually this shape, its only output control
1504            // an "IEC958 Playback Switch". The switch alone is still an output to play through, so
1505            // the card is reported -- without a percentage, because there is no volume to read.
1506            FFSoundAlsaControls switches = { .fd = fd };
1507            FFSoundAlsaVolume switchControl = {};
1508            if (findAlsaPlaybackSwitch(&switches, &switchControl)) {
1509                found = true;
1510                if (switchControl.baseLength > 0) {
1511                    ffStrbufAppendC(&name, ' ');
1512                    ffStrbufAppendNS(&name, switchControl.baseLength, switchControl.base);
1513                }
1514
1515                FFSoundAlsaValue switchValue = {};
1516                if (readAlsaValue(fd, &switchControl.id, &switchValue)) {
1517                    active = switchValue.value != 0;
1518                    if (!active) {
1519                        volume = 0;
1520                    }
1521                }
1522            }
1523        }
1524
1525        close(fd);
1526    }
1527
1528    if (!found) {
1529        FF_DEBUG("Card %u has no playback control", card);
1530        return false;
1531    }
1532
1533    const FFSoundType type = (FFSoundType) ((main ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | (active ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE));
1534    if ((options->soundType & FF_SOUND_TYPE_MAIN) && !(type & FF_SOUND_TYPE_MAIN)) {
1535        return true;
1536    }
1537
1538    if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !(type & FF_SOUND_TYPE_ACTIVE)) {
1539        return true;
1540    }
1541
1542    FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
1543    // "hw:0" is how the card is named everywhere else -- `amixer -c 0`, and the name after a colon
1544    // in every ALSA configuration -- so it is what identifies the device here.
1545    ffStrbufInitF(&device->identifier, "hw:%u", card);
1546    ffStrbufInitMove(&device->name, &name);
1547    ffStrbufInitStatic(&device->platformApi, "ALSA");
1548    device->volume = volume;
1549    device->type = type;
1550    FF_DEBUG("Reporting \"%s\" (hw:%u) at %u%%%s", device->name.chars, card, device->volume, active ? "" : ", muted");
1551    return true;
1552}
1553
1554static const char* detectAlsa(FFSoundOptions* options, FFlist* devices) {
1555    FF_STRBUF_AUTO_DESTROY cards = ffStrbufCreate();
1556    if (!ffReadFileBuffer("/proc/asound/cards", &cards)) {
1557        FF_DEBUG("Reading \"/proc/asound/cards\" failed: %s", strerror(errno));
1558        return "Failed to read the ALSA card list";
1559    }
1560
1561    bool main = true; // the machine's main output is the first card that has one
1562    bool output = false;
1563
1564    for (const char* line = cards.chars; *line;) {
1565        const char* newline = strchr(line, '\n');
1566        const uint32_t length = newline ? (uint32_t) (newline - line) : (uint32_t) strlen(line);
1567        FF_STRBUF_AUTO_DESTROY record = ffStrbufCreateNS(length, line);
1568        line = newline ? newline + 1 : line + length;
1569
1570        //      0 [PCH            ]: HDA-Intel - HDA Intel PCH
1571        //                            HDA Intel PCH at 0xdf240000 irq 125
1572        //
1573        // One line per card, with the index in the first column, and the card's long name on the
1574        // line below -- which does not start with a digit, and is what this skips. The name that is
1575        // reported is the one behind the dash: the driver's name for the card, without the
1576        // "HDA-Intel - " in front of it.
1577        char* indexEnd = nullptr;
1578        const long number = strtol(record.chars, &indexEnd, 10);
1579        if (indexEnd == record.chars || number < 0) {
1580            continue;
1581        }
1582
1583        const char* close = strchr(indexEnd, ']');
1584        const char* dash = close ? strstr(close, " - ") : nullptr;
1585        if (dash == nullptr) {
1586            FF_DEBUG("The ALSA card list line \"%s\" is not in the expected form", record.chars);
1587            continue;
1588        }
1589
1590        FF_STRBUF_AUTO_DESTROY cardName = ffStrbufCreateS(dash + strlen(" - "));
1591        ffStrbufTrimRightSpace(&cardName);
1592
1593        if (appendAlsaCard((uint32_t) number, &cardName, main, options, devices)) {
1594            main = false;
1595            output = true;
1596        }
1597    }
1598
1599    if (!output) {
1600        return "No ALSA card with a playback control was found";
1601    }
1602
1603    return nullptr;
1604}
1605
1606#endif // FF_SOUND_ALSA
1607
1608// ---------------------------------------------------------------------------
1609// the server
1610// ---------------------------------------------------------------------------
1611
1612static const char* detectPulseAudio(FFSoundOptions* options, FFlist* devices) {
1613    FF_STRBUF_AUTO_DESTROY defaultSocketPath = ffStrbufCreate();
1614    getDefaultSocketPath(&defaultSocketPath);
1615    FF_DEBUG("Default pulseaudio socket path: %s", defaultSocketPath.chars);
1616
1617    FFSoundPulseConn conn = {
1618        .fd = -1,
1619        .version = FF_SOUND_PA_VERSION,
1620        .nextTag = 0,
1621        .frame = ffStrbufCreate(),
1622    };
1623
1624    const char* serverList = getenv("PULSE_SERVER");
1625    if (ffStrSet(serverList)) {
1626        // $PULSE_SERVER may list several whitespace separated candidates; the
1627        // first one that accepts a connection wins, like libpulse does.
1628        for (const char* cursor = serverList; *cursor && conn.fd < 0;) {
1629            while (*cursor == ' ' || *cursor == '\t') {
1630                cursor++;
1631            }
1632
1633            const char* end = cursor;
1634            while (*end && *end != ' ' && *end != '\t') {
1635                end++;
1636            }
1637
1638            if (end == cursor) {
1639                break;
1640            }
1641
1642            FF_STRBUF_AUTO_DESTROY server = ffStrbufCreateNS((uint32_t) (end - cursor), cursor);
1643            conn.fd = connectServer(server.chars, &defaultSocketPath);
1644            cursor = end;
1645        }
1646    } else if (defaultSocketPath.length > 0) {
1647        conn.fd = connectUnixSocket(defaultSocketPath.chars);
1648    }
1649
1650    if (conn.fd < 0) {
1651        FF_DEBUG("Failed to connect to the pulseaudio server on %s",
1652            defaultSocketPath.length > 0 ? defaultSocketPath.chars : "no known socket path");
1653        ffStrbufDestroy(&conn.frame);
1654        return "Failed to connect to the pulseaudio server";
1655    }
1656
1657    FF_STRBUF_AUTO_DESTROY serverName = ffStrbufCreate();
1658    FF_STRBUF_AUTO_DESTROY defaultSinkName = ffStrbufCreate();
1659    FF_LIST_AUTO_DESTROY parsed = ffListCreate();
1660
1661    const char* error = handshake(&conn, &serverName, &defaultSinkName);
1662    if (!error) {
1663        error = fetchSinkList(&conn, options, &parsed, &serverName, &defaultSinkName);
1664    }
1665
1666    close(conn.fd);
1667    ffStrbufDestroy(&conn.frame);
1668
1669    if (error) {
1670        FF_LIST_FOR_EACH (FFSoundDevice, device, parsed) {
1671            ffStrbufDestroy(&device->identifier);
1672            ffStrbufDestroy(&device->name);
1673            ffStrbufDestroy(&device->platformApi);
1674        }
1675
1676        return error;
1677    }
1678
1679    FF_LIST_FOR_EACH (FFSoundDevice, device, parsed) {
1680        FFSoundDevice* target = FF_LIST_ADD(FFSoundDevice, *devices);
1681        target->identifier = ffStrbufCreateMove(&device->identifier);
1682        target->name = ffStrbufCreateMove(&device->name);
1683        target->platformApi = ffStrbufCreateMove(&device->platformApi);
1684        target->volume = device->volume;
1685        target->type = device->type;
1686    }
1687
1688    return nullptr;
1689}
1690
1691// ---------------------------------------------------------------------------
1692
1693// Android compiles this file next to sound_android.c, which owns ffDetectSound and calls this one
1694// only while the display server is not SurfaceFlinger -- a Termux:X11 or Wayland session runs a real
1695// desktop on top of the device, and PulseAudio is the sound of that desktop. So the entry point has
1696// to step aside there. Same arrangement as wallpaper_linux.c and terminalfont_linux.c.
1697const char*
1698#ifdef __ANDROID__
1699ffDetectSoundLinux
1700#else
1701ffDetectSound
1702#endif
1703    (FFSoundOptions* options, FFlist* devices) {
1704    const char* error = detectPulseAudio(options, devices);
1705    if (error == nullptr) {
1706        return nullptr;
1707    }
1708
1709#ifdef FF_SOUND_ALSA
1710    // No server answered. A machine that runs none still has the hardware, and that is what is
1711    // asked here -- but only ever after the server, never instead of it: on a machine that runs
1712    // one, the server's answer is the desktop's, and the mixer behind it is not the same number.
1713    FF_DEBUG("PulseAudio is not answering (%s), so the ALSA cards are read instead", error);
1714
1715    const uint32_t detected = devices->length;
1716    const char* alsaError = detectAlsa(options, devices);
1717    if (alsaError == nullptr || devices->length > detected) {
1718        // ALSA answered. Its list may still be empty after the filter, which is the same answer
1719        // the server path gives when nothing matches, and not a failure.
1720        return nullptr;
1721    }
1722
1723    FF_DEBUG("Reading the ALSA cards failed as well: %s", alsaError);
1724#endif
1725
1726    return error;
1727}