master ohmu/fastfetch-openbsd / src / detection / media / media.c
ohmu  ·  2026-10-15
 1#include "media.h"
 2#include "common/FFcache.h"
 3#include "common/io.h"
 4
 5void ffDetectMediaImpl(FFMediaResult* media, bool saveCover);
 6
 7static FFMediaResult result;
 8
 9// The cover file of this run, if there is one. It is tracked here rather than in `result` because the
10// cached result is dropped between `--dynamic-interval` rounds, and `result.cover` — the only other
11// copy of the path — is destroyed with it. The file itself has to outlive that, since the
12// `kitty-direct` logo type hands its path to the terminal, so it is kept for the whole run and
13// removed at exit.
14static FFstrbuf coverFile;
15static bool coverFileRegistered = false;
16
17static void removeMediaCoverFile(void) {
18    if (coverFile.length > 0) {
19        ffRemoveFile(coverFile.chars);
20        ffStrbufDestroy(&coverFile);
21    }
22}
23
24static void destroyMediaResult(void* storage) {
25    FFMediaResult* media = storage;
26
27    ffStrbufDestroy(&media->error);
28    ffStrbufDestroy(&media->playerId);
29    ffStrbufDestroy(&media->player);
30    ffStrbufDestroy(&media->song);
31    ffStrbufDestroy(&media->artist);
32    ffStrbufDestroy(&media->album);
33    ffStrbufDestroy(&media->url);
34    ffStrbufDestroy(&media->status);
35    ffStrbufDestroy(&media->cover);
36    media->length = 0;
37    media->position = 0;
38    media->removeCoverAfterUse = false;
39}
40
41// `saveCover` asks for a side effect and is not part of the cache identity, so the entry cannot be
42// initialized through `init(void*)`: the first caller of a generation decides, and the callers that
43// follow reuse its result, as they did when the result was cached for the whole run.
44static FFcacheEntry ffCacheEntryMedia = {
45    .name = "media",
46    .storage = &result,
47    .destroy = destroyMediaResult,
48};
49
50const FFMediaResult* ffDetectMedia(bool saveCover) {
51    if (ffCacheBeginInit(&ffCacheEntryMedia)) {
52        ffStrbufInit(&result.error);
53        ffStrbufInit(&result.playerId);
54        ffStrbufInit(&result.player);
55        ffStrbufInit(&result.song);
56        ffStrbufInit(&result.artist);
57        ffStrbufInit(&result.album);
58        ffStrbufInit(&result.url);
59        ffStrbufInit(&result.status);
60        ffStrbufInit(&result.cover);
61        result.length = 0;
62        result.position = 0;
63        result.removeCoverAfterUse = false;
64        ffDetectMediaImpl(&result, saveCover);
65
66        if (result.song.length == 0 && result.error.length == 0) {
67            ffStrbufAppendS(&result.error, "No media found");
68        }
69        ffStrbufTrimRightSpace(&result.song);
70        ffStrbufTrimRightSpace(&result.artist);
71        ffStrbufTrimRightSpace(&result.album);
72        ffStrbufTrimRightSpace(&result.player);
73
74        if (saveCover && result.removeCoverAfterUse) {
75            // A new round can produce a new cover while the previous file is still on disk, and
76            // `coverFile` is about to forget its path. Remove it here, otherwise only the last one
77            // would ever be cleaned up by `atexit`.
78            if (coverFile.length > 0 && !ffStrbufEqual(&coverFile, &result.cover)) {
79                ffRemoveFile(coverFile.chars);
80            }
81            ffStrbufSet(&coverFile, &result.cover);
82            if (!coverFileRegistered) {
83                coverFileRegistered = true;
84                atexit(removeMediaCoverFile);
85            }
86        }
87    }
88
89    return &result;
90}