Ubuntu下如何修改X11应用代码实现全局键盘输入捕获?
如何修改Ubuntu下的键盘捕获代码以实现全局键盘监听
现有一款基于X11库开发、在Ubuntu环境测试的应用,可接收简单键盘输入并输出对应值,但仅在应用窗口(Ubuntu终端)处于活跃状态时正常工作。实际需求是捕获操作系统内所有应用的键盘输入并进行后续处理。
现有代码
linux-kbhit.h
#define ESC "\033" #define UP "\033[A" #define DOWN "\033[B" #define LEFT "\033[D" #define RIGHT "\033[C" #define A "a" void term_setup(void (*sighandler)(int)); void term_restore(); bool kbhit(); bool keydown(const char* key);
linux-kbhit.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <poll.h> #include <signal.h> #include <termios.h> #include <sys/ioctl.h> static struct termios oldtio; static struct termios curtio; void term_setup(void (*sighandler)(int)){ struct sigaction sa; /* Save stdin terminal attributes */ tcgetattr(0, &oldtio); if(sighandler){ /* Make sure we exit cleanly */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sighandler; sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); } /* This is needed to be able to tcsetattr() after a hangup (Ctrl-C) * see tcsetattr() on POSIX */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = SIG_IGN; sigaction(SIGTTOU, &sa, NULL); /* Set non-canonical no-echo for stdin */ tcgetattr(0, &curtio); curtio.c_lflag &= ~(ICANON | ECHO); tcsetattr(0, TCSANOW, &curtio); } void term_restore(){ /* restore terminal attributes */ tcsetattr(0, TCSANOW, &oldtio); } static char get[4]= {0}; bool kbhit(){ struct pollfd pfds[1]; int ret; memset(get, 0, sizeof(char) * 4); /* See if there is data available */ pfds[0].fd = 0; pfds[0].events = POLLIN; ret = poll(pfds, 1, 0); /* Consume data */ if (ret > 0) { read(0, get, 3); return strlen(get) > 0; } return false; } bool keydown(const char* key){ return !strcmp(get, key); }
main.c
#include "linux-kbhit.h" #include "stdio.h" #include <signal.h> static sig_atomic_t end = 0; static void sighandler(int signo) { end = 1; printf("good beye!\n"); } int main() { term_setup(sighandler); while (!end) { if (kbhit()) { if (keydown(ESC)) printf("This is \"ESC\" button!\n"); if (keydown(UP)) printf("This is \"UP\" button!\n"); if (keydown(DOWN)) printf("This is \"DOWN\" button!\n"); if (keydown(LEFT)) printf("This is \"LEFT\" button!\n"); if (keydown(RIGHT)) printf("This is \"RIGHT\" button!\n"); if (keydown(A)) printf("This is \"A\" button!\n"); } } term_restore(); return 0; }
全局键盘监听修改方案
原代码仅监听当前终端的标准输入,无法捕获全局事件。要实现全局监听,需借助X11的XRecord扩展(适用于Xorg环境),它能捕获X服务器上的所有输入事件。以下是具体修改步骤:
1. 更新头文件(linux-kbhit.h)
移除终端相关定义,添加X11全局监听的声明:
#ifndef LINUX_KBHIT_H #define LINUX_KBHIT_H // 键盘按键对应的keycode(可通过xev工具查看) #define KEY_ESC 9 #define KEY_UP 111 #define KEY_DOWN 116 #define KEY_LEFT 113 #define KEY_RIGHT 114 #define KEY_A 38 void setup_global_key_listener(void (*sighandler)(int)); void cleanup_global_key_listener(); // 按键事件回调函数类型 typedef void (*KeyEventCallback)(int keycode, bool is_pressed); void set_key_event_callback(KeyEventCallback callback); #endif
2. 重写全局监听逻辑(linux-kbhit.c)
替换原有终端处理代码,使用XRecord实现全局键盘捕获:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <X11/Xlib.h> #include <X11/extensions/Xrecord.h> static Display *dpy = NULL; static XRecordContext record_context = 0; static KeyEventCallback key_callback = NULL; static sig_atomic_t end = 0; static void sighandler(int signo) { end = 1; } // XRecord事件处理回调 static void record_callback(XPointer ptr, XRecordInterceptData *data) { if (end) { XRecordFreeData(data); return; } if (data->category == XRecordFromServer) { xEvent *event = (xEvent *)data->data; // 仅处理键盘按下/释放事件 if (event->u.u.type == KeyPress || event->u.u.type == KeyRelease) { int keycode = event->u.keyButtonPointer.detail; bool is_pressed = (event->u.u.type == KeyPress); if (key_callback != NULL) { key_callback(keycode, is_pressed); } } } XRecordFreeData(data); } void set_key_event_callback(KeyEventCallback callback) { key_callback = callback; } void setup_global_key_listener(void (*sighandler)(int)) { if (sighandler) { signal(SIGINT, sighandler); signal(SIGQUIT, sighandler); signal(SIGTERM, sighandler); } // 打开X显示 dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "无法打开X显示\n"); exit(EXIT_FAILURE); } // 配置XRecord监听范围 XRecordClientSpec clients = XRecordAllClients; XRecordRange *range = XRecordAllocRange(); if (!range) { fprintf(stderr, "无法分配XRecord范围\n"); XCloseDisplay(dpy); exit(EXIT_FAILURE); } memset(range, 0, sizeof(XRecordRange)); range->device_events.first = KeyPress; range->device_events.last = KeyRelease; // 创建XRecord上下文 record_context = XRecordCreateContext(dpy, 0, &clients, 1, &range, 1); XFree(range); if (!record_context) { fprintf(stderr, "无法创建XRecord上下文\n"); XCloseDisplay(dpy); exit(EXIT_FAILURE); } // 启动异步监听 if (!XRecordEnableContextAsync(dpy, record_context, record_callback, NULL)) { fprintf(stderr, "无法启用XRecord上下文\n"); XRecordFreeContext(dpy, record_context); XCloseDisplay(dpy); exit(EXIT_FAILURE); } } void cleanup_global_key_listener() { if (dpy && record_context) { XRecordDisableContext(dpy, record_context); XRecordFreeContext(dpy, record_context); XCloseDisplay(dpy); } }
3. 修改main.c适配全局监听
更新主函数,设置按键回调并运行X事件循环:
#include "linux-kbhit.h" #include <stdio.h> #include <signal.h> static sig_atomic_t end = 0; static void sighandler(int signo) { end = 1; printf("再见!\n"); } // 处理捕获到的按键事件 static void handle_key_event(int keycode, bool is_pressed) { if (!is_pressed) { return; // 仅处理按键按下事件,可按需调整为处理释放事件 } switch (keycode) { case KEY_ESC: printf("这是\"ESC\"按键!\n"); break; case KEY_UP: printf("这是\"UP\"按键!\n"); break; case KEY_DOWN: printf("这是\"DOWN\"按键!\n"); break; case KEY_LEFT: printf("这是\"LEFT\"按键!\n"); break; case KEY_RIGHT: printf("这是\"RIGHT\"按键!\n"); break; case KEY_A: printf("这是\"A\"按键!\n"); break; default: // printf("捕获到按键: %d\n", keycode); // 可开启查看所有按键的keycode break; } } int main() { setup_global_key_listener(sighandler); set_key_event_callback(handle_key_event); // 事件循环,保持程序运行并处理X事件 while (!end) { XEvent event; XNextEvent(dpy, &event); } cleanup_global_key_listener(); return 0; }
4. 编译与运行
编译时需要链接X11和XRecord相关库:
gcc main.c linux-kbhit.c -o global-key-listener -lX11 -lXrecord
运行程序:
./global-key-listener
若遇到权限问题,可尝试使用sudo运行。
关键注意事项
- 环境适配:该方案仅支持Xorg显示服务器。如果你的Ubuntu使用Wayland,需改用其他方案(如监听
/dev/input/event*设备,需要root权限)。 - 按键码调整:不同键盘布局的按键码可能不同,可使用
xev工具查看目标按键的实际keycode,再修改头文件中的定义。 - 权限问题:全局输入监听需要较高权限,部分系统可能需要配置X访问权限或使用sudo运行。
内容的提问来源于stack exchange,提问作者Jack3n
相关产品推荐
相关产品推荐

