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}