XInput2使用RawMotion触发BadValue错误:XISelectEvents事件值过高问题
我正在编写纯Xlib应用,希望借助XInput2扩展获取RawMotion事件。调用XISelectEvents时,若掩码中设置任何高于XI_PropertyEvent的XI2事件(例如使用XISetMask(mask, XI_RawMotion))就会报错,设置XI_Motion时则正常运行。该错误与传入的deviceid及掩码中设置的其他事件类型无关,请问这是什么原因?
代码示例
#include <X11/Xlib.h> #include <X11/extensions/XInput2.h> int main() { Display* display = XOpenDisplay(nullptr); int screen_num = DefaultScreen(display); Window window = XCreateSimpleWindow( display, RootWindow(display, screen_num), 0, 0, 640, 480, 4, 16711680, WhitePixel(display, screen_num) ); XSelectInput(display, window, KeyPressMask); int major_opcode, first_event, first_error; XQueryExtension(display, "XInputExtension", &major_opcode, &first_event, &first_error); unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0, }; XISetMask(mask, XI_RawMotion); XIEventMask event_mask; event_mask.deviceid = XIAllDevices; event_mask.mask_len = sizeof(mask); event_mask.mask = mask; XISelectEvents(display, window, &event_mask, 1); XMapWindow(display, window); XFlush(display); while (true) { XEvent ev; XNextEvent(display, &ev); if (ev.type == KeyPress && ev.xkey.keycode == 9) { break; } } XUnmapWindow(display, window); XFlush(display); XDestroyWindow(display, window); XCloseDisplay(display); return 0; }
报错信息
X Error of failed request: BadValue (integer parameter out of range for operation)
Major opcode of failed request: 131 (XInputExtension)
Minor opcode of failed request: 46 ()
Value in failed request: 0xd
Serial number of failed request: 16
Current serial number in output stream: 17
核心原因
只调用XQueryExtension确认XInput扩展存在,但未检查XInput2的版本支持。RawMotion这类事件是XInput 2.1及以上版本才新增的特性,若X Server或Xlib只支持更低版本的XInput2,尝试选择高版本事件就会触发BadValue错误。
另外,XIMaskLen(XI_LASTEVENT)计算的是所有XI事件的掩码长度,但低版本XInput中XI_LASTEVENT的值更小,导致传入的掩码长度超出服务器预期范围,这是触发错误的直接原因。
解决步骤
检查并获取XInput2版本
调用XIQueryVersion替代XQueryExtension,明确获取服务器支持的XInput2版本,同时确保客户端请求的版本不低于2.1:int major = 2, minor = 1; if (XIQueryVersion(display, &major, &minor) != Success) { fprintf(stderr, "XInput 2.1 or higher is required\n"); XCloseDisplay(display); return 1; }这一步会确保服务器支持RawMotion等事件。
修正掩码长度计算
使用服务器实际支持的最高事件类型计算掩码长度,而非直接用XI_LASTEVENT:int max_event; int major = 2, minor = 1; XIQueryVersion(display, &major, &minor); if (major >= 2 && minor >= 1) { max_event = XI_LASTEVENT; } else { max_event = XI_PropertyEvent; // 低版本最大事件 } unsigned char mask[XIMaskLen(max_event)] = {0, };选择正确的设备范围
RawMotion事件属于主设备(如鼠标)的原始输入事件,建议使用XIAllMasterDevices替代XIAllDevices,避免选择不支持Raw事件的从设备:event_mask.deviceid = XIAllMasterDevices;
修正后的完整代码示例
#include <X11/Xlib.h> #include <X11/extensions/XInput2.h> #include <stdio.h> int main() { Display* display = XOpenDisplay(nullptr); if (!display) { fprintf(stderr, "Failed to open display\n"); return 1; } int screen_num = DefaultScreen(display); Window window = XCreateSimpleWindow( display, RootWindow(display, screen_num), 0, 0, 640, 480, 4, 16711680, WhitePixel(display, screen_num) ); XSelectInput(display, window, KeyPressMask); // 检查XInput2版本,要求至少2.1 int major = 2, minor = 1; if (XIQueryVersion(display, &major, &minor) != Success) { fprintf(stderr, "XInput 2.1 or higher is required (server supports %d.%d)\n", major, minor); XCloseDisplay(display); return 1; } // 初始化掩码,选择RawMotion事件 unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0, }; XISetMask(mask, XI_RawMotion); XIEventMask event_mask; event_mask.deviceid = XIAllMasterDevices; event_mask.mask_len = sizeof(mask); event_mask.mask = mask; XISelectEvents(display, window, &event_mask, 1); XMapWindow(display, window); XFlush(display); while (true) { XEvent ev; XNextEvent(display, &ev); if (ev.type == KeyPress && ev.xkey.keycode == 9) { break; } // 处理RawMotion事件 if (ev.type == GenericEvent && ev.xgeneric.extension == major) { XIDeviceEvent* xi_ev = (XIDeviceEvent*)&ev; if (xi_ev->evtype == XI_RawMotion) { printf("RawMotion detected: dx=%d, dy=%d\n", xi_ev->rawmotion.dx, xi_ev->rawmotion.dy); } } } XUnmapWindow(display, window); XFlush(display); XDestroyWindow(display, window); XCloseDisplay(display); return 0; }
内容的提问来源于stack exchange,提问作者Milana

