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