C++跨平台获取NumLock状态的Unix端代码报错问题
跨平台C/C++获取NumLock状态Unix端报错修复
问题背景
- 开发目标:实现跨平台C/C++接口获取NumLock键状态,Windows端基于
GetKeyState配合条件宏的实现已正常运行,Unix端运行异常 - 故障表现:代码运行时抛出错误码22对应的
Invalid Argument错误,先后尝试KDGETLED、KDGKBLED、KDGKBMETA等ioctl请求参数均未成功 - 现有情况:
- 代码拆分为两个函数是前期调试需要,用于尝试打开
/dev/路径下的不同设备文件 - 将运行用户加入
input用户组可解决设备文件打开权限问题,但希望避免要求用户执行加组的前置操作
- 代码拆分为两个函数是前期调试需要,用于尝试打开
- 原异常代码:
// 此处宏写法错误,应为#ifdef _WIN32 #defined (_WIN32) // Windows端代码可正常运行 #else #include <sys/ioctl.h> #include <sys/kd.h> #include <sys/vt.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <sstream> int fdKeybd; int tryStrForInput(std::string &inStr, int *pNumLed) { int testVal = -1; std::string kybdStr = inStr; printf("\nTry: %s", kybdStr.c_str()); fdKeybd = open(kybdStr.c_str(), O_RDONLY | O_NONBLOCK); if ( fdKeybd < 0 ) { printf(" Opening file : Failed. "); printf ("Error no is : %d ", errno); printf("Error description is : %s",strerror(errno)); } else { testVal = ioctl(fdKeybd, KDGKBMETA, pNumLed); if(testVal != -1) printf(" Success"); else { printf(" Getting NumLock : Failed. "); printf ("Error no is : %d ", errno); printf("Error description is : %s",strerror(errno)); } } return testVal; } bool isNumlockActivated(void) { int NUM_LED = 0x00; bool bNumLockOn = false; int testVal = -1; std::string kybdStr = "/dev/input/by-path/platform-i8042-serio-0-event-kbd"; testVal = tryStrForInput(kybdStr, &NUM_LED); if(testVal != -1) { if((NUM_LED == 0x02) || (NUM_LED == 0x03) || (NUM_LED == 0x06) || (NUM_LED == 0x07)) { bNumLockOn = true; } } close(fdKeybd); return bNumLockOn; } #endif
故障根因
- 条件编译宏写法错误:开头的
#defined (_WIN32)为非法语法,会导致整个条件编译分支判断失效 - ioctl请求与打开的设备类型不匹配:
KDGETLED/KDGKBLED/KDGKBMETA属于虚拟控制台(tty)设备专属的ioctl命令,仅对tty类设备文件生效;原代码打开的是/dev/input/by-path/下的evdev输入设备,对这类设备发送tty专属ioctl请求时,内核会直接返回EINVAL(错误码22,参数无效) - ioctl命令用错:
KDGKBMETA的作用是查询Alt元键的处理模式,和键盘LED状态、NumLock状态完全无关 - 权限问题来源:如果直接访问evdev设备,默认需要root权限或
input组权限,而访问当前用户关联的tty设备不需要特殊权限
修复方案
无需依赖evdev设备、无需用户加特殊组,直接通过当前进程关联的控制tty获取LED状态即可,普通用户默认拥有对应访问权限。
修复后代码
#ifdef _WIN32 // 原有Windows端GetKeyState实现保持不变 #else #include <sys/ioctl.h> #include <sys/kd.h> #include <unistd.h> #include <fcntl.h> bool isNumlockActivated(void) { bool numlock_on = false; // 打开当前进程绑定的控制tty,普通用户默认有权限 int tty_fd = open("/dev/tty", O_RDONLY); if (tty_fd < 0) { // fallback 打开当前虚拟控制台 tty_fd = open("/dev/tty0", O_RDONLY); } if (tty_fd < 0) { return false; } char led_state = 0; // 用正确的KDGKBLED命令获取tty关联键盘的LED状态 if (ioctl(tty_fd, KDGKBLED, &led_state) == 0) { // NumLock对应的LED状态位掩码为0x02,直接位运算判断即可 numlock_on = static_cast<bool>(led_state & 0x02); } close(tty_fd); return numlock_on; } #endif
注意事项
- 不要硬编码
/dev/input/下的设备路径,不同硬件、不同发行版的event设备路径不固定,兼容性差 - LED状态判断用位与运算即可,不需要枚举所有可能的数值组合,避免CapsLock、ScrollLock同时开启时判断逻辑失效
- 该方案在X11、Wayland图形会话和纯文本控制台场景下均生效,不需要额外链接图形界面相关的依赖库
内容的提问来源于stack exchange,提问作者Fergus Crawford
相关产品推荐
相关产品推荐

