Linux 上的 C++:在 运行 作为系统服务时监听键盘输入
C++ on Linux: Listening to keyboard input while running as systemd service
所以有一些关于这个主题的问题,但没有真正高质量的答案。
我的问题如下:我在 Raspberry Pi 上有一个嵌入式应用程序 运行,它是通过 init.d 启动的。整个设置没有屏幕,通常应该在生产使用中禁用网络的情况下工作(因为它将进入 WiFi / 蓝牙干扰可能会出现问题的环境)。
我想使用通用 USB 键盘/数字键盘作为输入设备进行配置和故障排除。当然,我不能只从 cin 读取,因为我的程序不在终端上 运行(事实上,没有终端)。
我该怎么做,最好是在我不必担心键盘布局细节和/或插入的输入设备数量的水平上?
edit 我可能的解决方法是涉及自动登录和 .profile 脚本。尽管如此,如果有人对此有解决方案,我们将不胜感激。
@meuh 似乎赢得了 cookie 的最佳建议:libevdev
完全正确。
我发现this answer解决了绑定按键(ALT-X)自动启动程序的相关问题,整体结构很容易适配(我修改后的代码如下)。
在你得到的每个 struct input_event
中,你寻找 ev.type == EV_KEY
来选择键盘事件(而不是鼠标或其他事件),ev.code
包含键的代码( KEY_UP
、KEY_0
、KEY_KP5
、KEY_BACKSPACE
等)。我只用一个数字键盘进行测试,所以我看不到 shifts 或 alt 之类的东西,但我怀疑这很简单。
你也看看ev.value
,可以是:
EV_KEY
- 向下键
EV_REL
- 键重复值(可选,可以多个)
EV_SYN
- 向上键
我想对于某些应用程序,您可以忽略除 EV_SYN
以外的所有应用程序以捕获按键事件;这就是我要做的。
$ sudo ./evtest
Device /dev/input/event1 is open and associated w/ libevent
KEY: Value=EV_KEY; Code=KEY_KP7 <-- KP = keypad
KEY: Value=EV_SYN; Code=KEY_KP7
KEY: Value=EV_KEY; Code=KEY_KP8
KEY: Value=EV_SYN; Code=KEY_KP8
KEY: Value=EV_KEY; Code=KEY_KP9
KEY: Value=EV_SYN; Code=KEY_KP9
请注意,键值不是 ASCII,也不是传统的键盘扫描码 - 这些是一个全新的命名空间,可能还有一些其他抽象层可以转换为常规 ASCII,但我没有寻找它因为 KEY_* 代码适合我的应用程序。但它比我之前使用的糟糕 /dev/hidraw0
机制要好得多。
它确实需要 root 权限,这是有道理的,否则您的用户模式程序可能会等待超级用户登录控制台,获取他们的密码。对于嵌入式应用程序,我相信这不会成为问题。
谢谢 @meuh 的好提示。而且我什至不必编写设备驱动程序!
下面的代码在 BeagleBone 运行 Debian Buster 上运行。
// hack test for working with events
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <libevdev-1.0/libevdev/libevdev.h>
#define COUNTOF(x) (int) ( ( sizeof(x) / sizeof((x)[0]) ) )
static void setupKeyCodes(void);
static const char *printableEventType(int t);
static const char *keycodes[64 * 1024] = { 0 }; // hack
int main(void) {
setupKeyCodes();
const char *eventDevice = "/dev/input/event1";
const int fd = open(eventDevice, O_RDONLY | O_NONBLOCK);
if (fd < 0) errx(EXIT_FAILURE, "ERROR: cannot open device %s [%s]", eventDevice, strerror(errno));
struct libevdev *dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) errx(EXIT_FAILURE, "ERROR: cannot associate event device [%s]", strerror(-err));
printf("Device %s is open and associated w/ libevent\n", eventDevice);
do {
struct input_event ev;
err = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (err == 0 && ev.type == EV_KEY)
{
printf("KEY: Value=%s; Code=%s\n",
printableEventType(ev.value),
keycodes[ev.code]);
}
} while (err == 1 || err == 0 || err == -EAGAIN);
return 0;
}
// HACK: populate the whole array of possible keycodes with their strings
// so we can see what we're reading.
static void setupKeyCodes(void)
{
for (int i = 0; i < COUNTOF(keycodes); i++)
keycodes[i] = "-unknown-";
// these from /usr/include/linux/input-event-codes.h
keycodes[KEY_RESERVED] = "KEY_RESERVED";
keycodes[KEY_ESC] = "KEY_ESC";
keycodes[KEY_1] = "KEY_1";
keycodes[KEY_2] = "KEY_2";
keycodes[KEY_3] = "KEY_3";
keycodes[KEY_4] = "KEY_4";
keycodes[KEY_5] = "KEY_5";
keycodes[KEY_6] = "KEY_6";
keycodes[KEY_7] = "KEY_7";
keycodes[KEY_8] = "KEY_8";
keycodes[KEY_9] = "KEY_9";
keycodes[KEY_0] = "KEY_0";
// ... many many more
keycodes[KEY_STOP_RECORD] = "KEY_STOP_RECORD";
keycodes[KEY_PAUSE_RECORD] = "KEY_PAUSE_RECORD";
keycodes[KEY_VOD] = "KEY_VOD";
keycodes[KEY_UNMUTE] = "KEY_UNMUTE";
keycodes[KEY_FASTREVERSE] = "KEY_FASTREVERSE";
keycodes[KEY_SLOWREVERSE] = "KEY_SLOWREVERSE";
}
#define STRCASE(x) case x: return #x
static const char *printableEventType(int t)
{
switch (t)
{
STRCASE(EV_SYN);
STRCASE(EV_KEY);
STRCASE(EV_REL);
STRCASE(EV_ABS);
STRCASE(EV_MSC);
STRCASE(EV_SW);
STRCASE(EV_LED);
STRCASE(EV_SND);
STRCASE(EV_REP);
STRCASE(EV_FF);
STRCASE(EV_PWR);
STRCASE(EV_FF_STATUS);
default: return "-?-";
}
}
所以有一些关于这个主题的问题,但没有真正高质量的答案。
我的问题如下:我在 Raspberry Pi 上有一个嵌入式应用程序 运行,它是通过 init.d 启动的。整个设置没有屏幕,通常应该在生产使用中禁用网络的情况下工作(因为它将进入 WiFi / 蓝牙干扰可能会出现问题的环境)。
我想使用通用 USB 键盘/数字键盘作为输入设备进行配置和故障排除。当然,我不能只从 cin 读取,因为我的程序不在终端上 运行(事实上,没有终端)。
我该怎么做,最好是在我不必担心键盘布局细节和/或插入的输入设备数量的水平上?
edit 我可能的解决方法是涉及自动登录和 .profile 脚本。尽管如此,如果有人对此有解决方案,我们将不胜感激。
@meuh 似乎赢得了 cookie 的最佳建议:libevdev
完全正确。
我发现this answer解决了绑定按键(ALT-X)自动启动程序的相关问题,整体结构很容易适配(我修改后的代码如下)。
在你得到的每个 struct input_event
中,你寻找 ev.type == EV_KEY
来选择键盘事件(而不是鼠标或其他事件),ev.code
包含键的代码( KEY_UP
、KEY_0
、KEY_KP5
、KEY_BACKSPACE
等)。我只用一个数字键盘进行测试,所以我看不到 shifts 或 alt 之类的东西,但我怀疑这很简单。
你也看看ev.value
,可以是:
EV_KEY
- 向下键EV_REL
- 键重复值(可选,可以多个)EV_SYN
- 向上键
我想对于某些应用程序,您可以忽略除 EV_SYN
以外的所有应用程序以捕获按键事件;这就是我要做的。
$ sudo ./evtest
Device /dev/input/event1 is open and associated w/ libevent
KEY: Value=EV_KEY; Code=KEY_KP7 <-- KP = keypad
KEY: Value=EV_SYN; Code=KEY_KP7
KEY: Value=EV_KEY; Code=KEY_KP8
KEY: Value=EV_SYN; Code=KEY_KP8
KEY: Value=EV_KEY; Code=KEY_KP9
KEY: Value=EV_SYN; Code=KEY_KP9
请注意,键值不是 ASCII,也不是传统的键盘扫描码 - 这些是一个全新的命名空间,可能还有一些其他抽象层可以转换为常规 ASCII,但我没有寻找它因为 KEY_* 代码适合我的应用程序。但它比我之前使用的糟糕 /dev/hidraw0
机制要好得多。
它确实需要 root 权限,这是有道理的,否则您的用户模式程序可能会等待超级用户登录控制台,获取他们的密码。对于嵌入式应用程序,我相信这不会成为问题。
谢谢 @meuh 的好提示。而且我什至不必编写设备驱动程序!
下面的代码在 BeagleBone 运行 Debian Buster 上运行。
// hack test for working with events
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <libevdev-1.0/libevdev/libevdev.h>
#define COUNTOF(x) (int) ( ( sizeof(x) / sizeof((x)[0]) ) )
static void setupKeyCodes(void);
static const char *printableEventType(int t);
static const char *keycodes[64 * 1024] = { 0 }; // hack
int main(void) {
setupKeyCodes();
const char *eventDevice = "/dev/input/event1";
const int fd = open(eventDevice, O_RDONLY | O_NONBLOCK);
if (fd < 0) errx(EXIT_FAILURE, "ERROR: cannot open device %s [%s]", eventDevice, strerror(errno));
struct libevdev *dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) errx(EXIT_FAILURE, "ERROR: cannot associate event device [%s]", strerror(-err));
printf("Device %s is open and associated w/ libevent\n", eventDevice);
do {
struct input_event ev;
err = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (err == 0 && ev.type == EV_KEY)
{
printf("KEY: Value=%s; Code=%s\n",
printableEventType(ev.value),
keycodes[ev.code]);
}
} while (err == 1 || err == 0 || err == -EAGAIN);
return 0;
}
// HACK: populate the whole array of possible keycodes with their strings
// so we can see what we're reading.
static void setupKeyCodes(void)
{
for (int i = 0; i < COUNTOF(keycodes); i++)
keycodes[i] = "-unknown-";
// these from /usr/include/linux/input-event-codes.h
keycodes[KEY_RESERVED] = "KEY_RESERVED";
keycodes[KEY_ESC] = "KEY_ESC";
keycodes[KEY_1] = "KEY_1";
keycodes[KEY_2] = "KEY_2";
keycodes[KEY_3] = "KEY_3";
keycodes[KEY_4] = "KEY_4";
keycodes[KEY_5] = "KEY_5";
keycodes[KEY_6] = "KEY_6";
keycodes[KEY_7] = "KEY_7";
keycodes[KEY_8] = "KEY_8";
keycodes[KEY_9] = "KEY_9";
keycodes[KEY_0] = "KEY_0";
// ... many many more
keycodes[KEY_STOP_RECORD] = "KEY_STOP_RECORD";
keycodes[KEY_PAUSE_RECORD] = "KEY_PAUSE_RECORD";
keycodes[KEY_VOD] = "KEY_VOD";
keycodes[KEY_UNMUTE] = "KEY_UNMUTE";
keycodes[KEY_FASTREVERSE] = "KEY_FASTREVERSE";
keycodes[KEY_SLOWREVERSE] = "KEY_SLOWREVERSE";
}
#define STRCASE(x) case x: return #x
static const char *printableEventType(int t)
{
switch (t)
{
STRCASE(EV_SYN);
STRCASE(EV_KEY);
STRCASE(EV_REL);
STRCASE(EV_ABS);
STRCASE(EV_MSC);
STRCASE(EV_SW);
STRCASE(EV_LED);
STRCASE(EV_SND);
STRCASE(EV_REP);
STRCASE(EV_FF);
STRCASE(EV_PWR);
STRCASE(EV_FF_STATUS);
default: return "-?-";
}
}