22#include "common/io.h"
33#include "common/stringUtils.h"
44
5- static void addDevice ( FFlist * devices , uint64_t * flags , uint32_t index , FFstrbuf * path )
5+ static bool isRealKeyboard ( uint32_t index , FFstrbuf * path )
66{
7- if (index >= 64 || (* flags & (1ULL << index )))
8- return ;
9-
10- ffStrbufSetF (path , "/sys/class/input/event%u/device/name" , (unsigned ) index );
11-
12- FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate ();
13- if (ffAppendFileBuffer (path -> chars , & name ))
7+ // Check EV_REP (auto-repeat, bit 20) to filter pseudo-keyboards (Power Button, PC Speaker).
8+ ffStrbufSetF (path , "/sys/class/input/event%u/device/capabilities/ev" , (unsigned ) index );
149 {
15- * flags |= (1ULL << index );
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 );
10+ FF_STRBUF_AUTO_DESTROY caps = ffStrbufCreate ();
11+ if (!ffReadFileBuffer (path -> chars , & caps ))
12+ return false;
13+
14+ ffStrbufTrimRightSpace (& caps );
15+ unsigned long val = strtoul (caps .chars , NULL , 16 );
16+ if (!(val & (1UL << 20 ))) // EV_REP
17+ return false;
2618 }
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;
3419
35- struct dirent * entry ;
36- while ((entry = readdir (dirp )) != NULL )
20+ // Check KEY_A (bit 30) to filter media remotes and headset controls.
21+ // The key capability bitmap is space-separated hex longs, MSB first;
22+ // KEY_A falls in the last (least significant) word on all architectures.
23+ ffStrbufSetF (path , "/sys/class/input/event%u/device/capabilities/key" , (unsigned ) index );
3724 {
38- if (!ffStrEndsWith (entry -> d_name , "-event-kbd" ))
39- continue ;
25+ FF_STRBUF_AUTO_DESTROY caps = ffStrbufCreate ();
26+ if (!ffReadFileBuffer (path -> chars , & caps ))
27+ return false;
4028
41- char buffer [32 ]; // `../eventXX`
42- ssize_t len = readlinkat (dirfd (dirp ), entry -> d_name , buffer , ARRAY_SIZE (buffer ) - 1 );
43- if (len <= (ssize_t ) strlen ("../event" ) || !ffStrStartsWith (buffer , "../event" )) continue ;
44- buffer [len ] = 0 ;
29+ ffStrbufTrimRightSpace (& caps );
30+ const char * lastWord = strrchr (caps .chars , ' ' );
31+ lastWord = lastWord ? lastWord + 1 : caps .chars ;
4532
46- char * pend = NULL ;
47- uint32_t index = (uint32_t ) strtoul (buffer + strlen ("../event" ), & pend , 10 );
48- if (pend == buffer + strlen ("../event" )) continue ;
49-
50- addDevice (devices , flags , index , path );
33+ unsigned long val = strtoul (lastWord , NULL , 16 );
34+ if (!(val & (1UL << 30 ))) // KEY_A
35+ return false;
5136 }
5237
5338 return true;
5439}
5540
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 );
62-
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 )
41+ const char * ffDetectKeyboard (FFlist * devices /* List of FFKeyboardDevice */ )
7342{
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 .
43+ // Parse /proc/bus/input/devices to find keyboards with a "kbd" handler .
44+ // This detects both wired and Bluetooth keyboards uniformly .
7645 FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate ();
7746 if (!ffAppendFileBuffer ("/proc/bus/input/devices" , & content ))
78- return false;
47+ return "ffAppendFileBuffer(\"/proc/bus/input/devices\") == NULL" ;
48+
49+ uint64_t flags = 0 ;
50+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate ();
7951
8052 const char * line = content .chars ;
8153 while (line && * line )
@@ -102,36 +74,41 @@ static bool detectFromProc(FFlist* devices, uint64_t* flags, FFstrbuf* path)
10274 else if (wordLen > strlen ("event" ) && memcmp (wordStart , "event" , strlen ("event" )) == 0 )
10375 {
10476 char * pend = NULL ;
105- eventIndex = (uint32_t ) strtoul (wordStart + 5 , & pend , 10 );
106- if (pend == wordStart + 5 ) eventIndex = UINT32_MAX ;
77+ eventIndex = (uint32_t ) strtoul (wordStart + strlen ( "event" ) , & pend , 10 );
78+ if (pend == wordStart + strlen ( "event" ) ) eventIndex = UINT32_MAX ;
10779 }
10880 }
10981
110- if (hasKbd && eventIndex != UINT32_MAX && hasAutoRepeat (eventIndex , path ))
111- addDevice (devices , flags , eventIndex , path );
112- }
82+ // Skip duplicates (dedup bitmap covers indices 0-63; higher indices are not deduped)
83+ bool seen = eventIndex < 64 && (flags & (1ULL << eventIndex ));
11384
114- const char * next = strchr ( line , '\n' );
115- line = next ? next + 1 : NULL ;
116- }
85+ if ( hasKbd && eventIndex != UINT32_MAX && ! seen && isRealKeyboard ( eventIndex , & path ))
86+ {
87+ ffStrbufSetF ( & path , "/sys/class/input/event%u/device/name" , ( unsigned ) eventIndex );
11788
118- return true;
119- }
89+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate ();
90+ if (ffAppendFileBuffer (path .chars , & name ))
91+ {
92+ if (eventIndex < 64 )
93+ flags |= (1ULL << eventIndex );
12094
121- const char * ffDetectKeyboard (FFlist * devices /* List of FFKeyboardDevice */ )
122- {
123- uint64_t flags = 0 ;
124- FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate ();
95+ ffStrbufTrimRightSpace (& name );
96+ ffStrbufSubstrBefore (& path , path .length - (uint32_t ) strlen ("name" ));
12597
126- // Prefer /dev/input/by-path/ for wired/USB keyboards
127- bool byPathOk = detectFromByPath (devices , & flags , & path );
98+ FFKeyboardDevice * device = (FFKeyboardDevice * ) ffListAdd (devices );
99+ ffStrbufInitMove (& device -> name , & name );
100+ ffStrbufInit (& device -> serial );
128101
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 );
102+ ffStrbufAppendS (& path , "uniq" );
103+ if (ffAppendFileBuffer (path .chars , & device -> serial ))
104+ ffStrbufTrimRightSpace (& device -> serial );
105+ }
106+ }
107+ }
132108
133- if (!byPathOk && !procOk )
134- return "Failed to read both /dev/input/by-path/ and /proc/bus/input/devices" ;
109+ const char * next = strchr (line , '\n' );
110+ line = next ? next + 1 : NULL ;
111+ }
135112
136113 return NULL ;
137114}
0 commit comments