Skip to content

Commit 89be1b9

Browse files
committed
Keyboard (Linux) Bluetooth support
1 parent 724ab6b commit 89be1b9

1 file changed

Lines changed: 106 additions & 26 deletions

File tree

src/detection/keyboard/keyboard_linux.c

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,35 @@
22
#include "common/io.h"
33
#include "common/stringUtils.h"
44

5-
const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)
5+
static void addDevice(FFlist* devices, uint64_t* flags, uint32_t index, FFstrbuf* path)
66
{
7-
// There is no /sys/class/input/kbd* on Linux
8-
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/dev/input/by-path/");
9-
if (dirp == NULL)
10-
return "opendir(\"/dev/input/by-path/\") == NULL";
7+
if (index >= 64 || (*flags & (1UL << index)))
8+
return;
9+
*flags |= (1UL << index);
1110

12-
uint64_t flags = 0;
11+
ffStrbufSetF(path, "/sys/class/input/event%u/device/name", (unsigned) index);
1312

14-
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
13+
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
14+
if (ffAppendFileBuffer(path->chars, &name))
15+
{
16+
ffStrbufTrimRightSpace(&name);
17+
ffStrbufSubstrBefore(path, path->length - (uint32_t) strlen("name"));
18+
19+
FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices);
20+
ffStrbufInitMove(&device->name, &name);
21+
ffStrbufInit(&device->serial);
22+
23+
ffStrbufAppendS(path, "uniq");
24+
if (ffAppendFileBuffer(path->chars, &device->serial))
25+
ffStrbufTrimRightSpace(&device->serial);
26+
}
27+
}
28+
29+
static bool detectFromByPath(FFlist* devices, uint64_t* flags, FFstrbuf* path)
30+
{
31+
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/dev/input/by-path/");
32+
if (dirp == NULL)
33+
return false;
1534

1635
struct dirent* entry;
1736
while ((entry = readdir(dirp)) != NULL)
@@ -24,34 +43,95 @@ const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)
2443
if (len <= (ssize_t) strlen("../event") || !ffStrStartsWith(buffer, "../event")) continue;
2544
buffer[len] = 0;
2645

27-
const char* eventid = buffer + strlen("../event");
28-
2946
char* pend = NULL;
30-
uint32_t index = (uint32_t) strtoul(eventid, &pend, 10);
31-
if (pend == eventid) continue;
47+
uint32_t index = (uint32_t) strtoul(buffer + strlen("../event"), &pend, 10);
48+
if (pend == buffer + strlen("../event")) continue;
3249

33-
// Ignore duplicate entries (flags supports up to 64 event indices)
34-
if (index >= 64 || (flags & (1UL << index)))
35-
continue;
36-
flags |= (1UL << index);
50+
addDevice(devices, flags, index, path);
51+
}
3752

38-
ffStrbufSetF(&path, "/sys/class/input/event%s/device/name", eventid);
53+
return true;
54+
}
55+
56+
static bool hasAutoRepeat(uint32_t index, FFstrbuf* path)
57+
{
58+
// Filter out pseudo-keyboards (Power Button, PC Speaker) by checking for
59+
// EV_REP (auto-repeat, bit 20) in the device's event capabilities.
60+
// Real keyboards and macro pads support auto-repeat; system buttons don't.
61+
ffStrbufSetF(path, "/sys/class/input/event%u/device/capabilities/ev", (unsigned) index);
3962

40-
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
41-
if (ffAppendFileBuffer(path.chars, &name))
63+
FF_STRBUF_AUTO_DESTROY caps = ffStrbufCreate();
64+
if (!ffReadFileBuffer(path->chars, &caps))
65+
return false;
66+
67+
ffStrbufTrimRightSpace(&caps);
68+
unsigned long val = strtoul(caps.chars, NULL, 16);
69+
return (val & (1UL << 20)) != 0; // EV_REP
70+
}
71+
72+
static bool detectFromProc(FFlist* devices, uint64_t* flags, FFstrbuf* path)
73+
{
74+
// Bluetooth and other virtual keyboards may not appear in /dev/input/by-path/.
75+
// Parse /proc/bus/input/devices to find any keyboard with a "kbd" handler.
76+
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
77+
if (!ffAppendFileBuffer("/proc/bus/input/devices", &content))
78+
return false;
79+
80+
const char* line = content.chars;
81+
while (line && *line)
82+
{
83+
if (ffStrStartsWith(line, "H: Handlers="))
4284
{
43-
ffStrbufTrimRightSpace(&name);
44-
ffStrbufSubstrBefore(&path, path.length - (uint32_t) strlen("name"));
85+
const char* handlers = line + strlen("H: Handlers=");
86+
bool hasKbd = false;
87+
uint32_t eventIndex = UINT32_MAX;
88+
89+
// Parse space-separated handler names
90+
const char* p = handlers;
91+
while (*p && *p != '\n')
92+
{
93+
while (*p == ' ') p++;
94+
if (*p == '\n' || *p == '\0') break;
95+
96+
const char* wordStart = p;
97+
while (*p && *p != ' ' && *p != '\n') p++;
98+
uint32_t wordLen = (uint32_t)(p - wordStart);
4599

46-
FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices);
47-
ffStrbufInitMove(&device->name, &name);
48-
ffStrbufInit(&device->serial);
100+
if (wordLen == 3 && memcmp(wordStart, "kbd", 3) == 0)
101+
hasKbd = true;
102+
else if (wordLen > strlen("event") && memcmp(wordStart, "event", strlen("event")) == 0)
103+
{
104+
char* pend = NULL;
105+
eventIndex = (uint32_t) strtoul(wordStart + 5, &pend, 10);
106+
if (pend == wordStart + 5) eventIndex = UINT32_MAX;
107+
}
108+
}
49109

50-
ffStrbufAppendS(&path, "uniq");
51-
if (ffAppendFileBuffer(path.chars, &device->serial))
52-
ffStrbufTrimRightSpace(&device->serial);
110+
if (hasKbd && eventIndex != UINT32_MAX && hasAutoRepeat(eventIndex, path))
111+
addDevice(devices, flags, eventIndex, path);
53112
}
113+
114+
const char* next = strchr(line, '\n');
115+
line = next ? next + 1 : NULL;
54116
}
55117

118+
return true;
119+
}
120+
121+
const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)
122+
{
123+
uint64_t flags = 0;
124+
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
125+
126+
// Prefer /dev/input/by-path/ for wired/USB keyboards
127+
bool byPathOk = detectFromByPath(devices, &flags, &path);
128+
129+
// Fall back to /proc/bus/input/devices for Bluetooth and other keyboards
130+
// that don't appear in by-path. The flags bitmap prevents duplicates.
131+
bool procOk = detectFromProc(devices, &flags, &path);
132+
133+
if (!byPathOk && !procOk)
134+
return "Failed to read both /dev/input/by-path/ and /proc/bus/input/devices";
135+
56136
return NULL;
57137
}

0 commit comments

Comments
 (0)