master ohmu/fastfetch-openbsd / src / detection / keyboard / keyboard_linux.c
ohmu  ·  2026-10-15
  1#include "keyboard.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/strutil.h"
  5
  6#include <linux/input-event-codes.h>
  7
  8const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) {
  9    // Parse /proc/bus/input/devices to find keyboards with a "kbd" handler.
 10    // This detects both wired and Bluetooth keyboards uniformly.
 11    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
 12    if (!ffAppendFileBuffer("/proc/bus/input/devices", &content)) {
 13        FF_DEBUG("ffAppendFileBuffer(\"/proc/bus/input/devices\") failed");
 14        return "ffAppendFileBuffer(\"/proc/bus/input/devices\") == nullptr";
 15    }
 16
 17    FFstrbuf kbd = ffStrbufCreateStatic("kbd");
 18
 19    FFKeyboardDevice device = {
 20        .name = ffStrbufCreate(),
 21        .serial = ffStrbufCreate()
 22    };
 23
 24    char* line = nullptr;
 25    size_t len = 0;
 26    while (ffStrbufGetline(&line, &len, &content)) {
 27        switch (line[0]) {
 28            case 'N': {
 29                const uint32_t prefixLen = strlen("N: Name=");
 30                if (__builtin_expect(len <= prefixLen, false)) {
 31                    continue;
 32                }
 33                const char* name = line + prefixLen;
 34                const uint32_t nameLen = (uint32_t) len - prefixLen;
 35                ffStrbufSetNS(&device.name, nameLen, name);
 36                ffStrbufTrim(&device.name, '"');
 37                continue;
 38            }
 39            case 'H': {
 40                const uint32_t prefixLen = strlen("H: Handlers=");
 41                if (__builtin_expect(len <= prefixLen, false)) {
 42                    continue;
 43                }
 44                const char* handlers = line + prefixLen;
 45                const uint32_t handlersLen = (uint32_t) len - prefixLen;
 46                if (!ffStrbufMatchSeparatedNS(&kbd, handlersLen, handlers, ' ')) {
 47                    goto skipDevice;
 48                }
 49                continue;
 50            }
 51            case 'B': {
 52                const char* bits = line + strlen("B: ");
 53                if (ffStrStartsWith(bits, "EV=")) {
 54                    // Check EV_REP (auto-repeat, bit 20) to filter pseudo-keyboards (Power Button, PC Speaker).
 55                    const char* evBits = bits + strlen("EV=");
 56                    uint64_t val = strtoull(evBits, nullptr, 16);
 57                    if (!(val & (1ULL << EV_REP))) {
 58                        goto skipDevice;
 59                    }
 60                } else if (ffStrStartsWith(bits, "KEY=")) {
 61                    // Check KEY_A (bit 30) to filter media remotes and headset controls.
 62                    // The key capability bitmap is space-separated hex longs, MSB first;
 63                    // KEY_A falls in the last (least significant) word on all architectures.
 64                    const char* keyBits = bits + strlen("KEY=");
 65                    const char* lastWord = memrchr(keyBits, ' ', len - (size_t) (keyBits - line));
 66                    lastWord = lastWord ? lastWord + 1 : keyBits;
 67
 68                    uint64_t val = strtoull(lastWord, nullptr, 16);
 69                    if (!(val & (1ULL << KEY_A))) {
 70                        goto skipDevice;
 71                    }
 72                }
 73                continue;
 74            }
 75            case 'U': {
 76                const uint32_t prefixLen = strlen("U: Uniq=");
 77                if (__builtin_expect(len <= prefixLen, false)) {
 78                    continue;
 79                }
 80                const char* uniq = line + prefixLen;
 81                const uint32_t uniqLen = (uint32_t) len - prefixLen;
 82                ffStrbufSetNS(&device.serial, uniqLen, uniq);
 83                continue;
 84            }
 85            case '\0':
 86                // End of device entry; add to list if it has a name.
 87                if (device.name.length > 0) {
 88                    FFKeyboardDevice* added = FF_LIST_ADD(FFKeyboardDevice, *devices);
 89                    ffStrbufInitMove(&added->name, &device.name);
 90                    ffStrbufInitMove(&added->serial, &device.serial);
 91                }
 92                continue;
 93            default:
 94                continue;
 95        }
 96
 97    skipDevice:
 98        // Skip to the end of the current device entry.
 99        while (line[0] != '\0' && ffStrbufGetline(&line, &len, &content));
100        // Despite the fn name, it resets the string buffer to initial state
101        ffStrbufDestroy(&device.name);
102        ffStrbufDestroy(&device.serial);
103    }
104
105    return nullptr;
106}