为何移除XPeekEvent调用后while循环无法正常工作?
为什么移除XPeekEvent后窗口拖拽功能失效?
我遇到一个问题:在while XCheckTypedEvent语句前调用XPeekEvent时,代码能正常实现窗口拖拽;但删掉这个调用后,XCheckTypedEvent的条件判断一直为false,根本没法执行拖拽操作,完全搞不懂原因。
相关代码
#include <X11/Xlib.h> #define MAX(a, b) ((a) > (b)? (a) : (b)) #define false 0 #define true 1 int main(void) { Display* dpy; XWindowAttributes attr; // state of cursor at the start of move/resize XButtonEvent cur_start; // return failure status if we can't connect if (!(dpy = XOpenDisplay(0x0))) return 1; char* keys_to_grab[] = {"F1"}; for (size_t i = 0; i < sizeof(keys_to_grab) / sizeof(keys_to_grab[0]); i++) { XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym(keys_to_grab[i])), Mod1Mask, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync); } XGrabButton(dpy, 1, Mod1Mask, DefaultRootWindow(dpy), True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); XGrabButton(dpy, 3, Mod1Mask, DefaultRootWindow(dpy), True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); cur_start.subwindow = None; for(;;) { XNextEvent(dpy, &ev); switch(ev.type) { case KeyPress: if (ev.xkey.subwindow != None) XRaiseWindow(dpy, ev.xkey.subwindow); break; case ButtonPress: if (ev.xbutton.subwindow != None) cur_start = ev.xbutton; break; case MotionNotify: if (cur_start.subwindow != None) { XPeekEvent(dpy, &ev); while(XCheckTypedEvent(dpy, MotionNotify, &ev)) { int xdiff = ev.xbutton.x_root - cur_start.x_root; int ydiff = ev.xbutton.y_root - cur_start.y_root; XMoveWindow(dpy, cur_start.subwindow, attr.x + (cur_start.button==1 ? xdiff : 0), attr.y + (cur_start.button==1 ? ydiff : 0)); } } break; case ButtonRelease: cur_start.subwindow = None; break; default: break; } } }
运行方式
使用以下命令启动:
xinit ./xinitrc -- /usr/bin/Xephyr :100 -ac -screen 500x500 -host-cursor
其中xinitrc的内容为:
xclock & xeyes & sleep 2 && xterm & exec ./a.out
这里的“正常工作”指可以拖拽窗口。
问题原因与修正方案
核心原因
- 事件队列处理逻辑错误:进入
MotionNotify分支时,当前的MotionNotify事件已经被XNextEvent从队列取出并存入ev。如果直接调用XCheckTypedEvent,它会去队列找下一个MotionNotify事件——但此时队列可能没有更多同类型事件,导致返回false,循环直接跳过,拖拽代码根本没机会执行。 XPeekEvent的歪打正着:添加XPeekEvent后,它会把队列头部的事件(如果存在)复制到ev但不移除,此时XCheckTypedEvent能检测到这个事件并移除,循环得以执行。但这是错误的做法,因为你在循环里错误地用ev.xbutton访问MotionNotify事件的数据(应该用ev.xmotion)。- 未初始化的
attr变量:代码从未调用XGetWindowAttributes获取窗口属性,attr.x和attr.y是垃圾值,拖拽时窗口位置会混乱。
修正后的代码片段
case MotionNotify: if (cur_start.subwindow != None) { // 先处理当前已取出的MotionNotify事件 int xdiff = ev.xmotion.x_root - cur_start.x_root; int ydiff = ev.xmotion.y_root - cur_start.y_root; // 必须先获取目标窗口的当前属性 XGetWindowAttributes(dpy, cur_start.subwindow, &attr); XMoveWindow(dpy, cur_start.subwindow, attr.x + (cur_start.button == 1 ? xdiff : 0), attr.y + (cur_start.button == 1 ? ydiff : 0)); // 处理队列中剩余的MotionNotify事件,避免拖拽滞后 while (XCheckTypedEvent(dpy, MotionNotify, &ev)) { xdiff = ev.xmotion.x_root - cur_start.x_root; ydiff = ev.xmotion.y_root - cur_start.y_root; XGetWindowAttributes(dpy, cur_start.subwindow, &attr); XMoveWindow(dpy, cur_start.subwindow, attr.x + (cur_start.button == 1 ? xdiff : 0), attr.y + (cur_start.button == 1 ? ydiff : 0)); } // 更新起始坐标,避免累积误差 cur_start.x_root = ev.xmotion.x_root; cur_start.y_root = ev.xmotion.y_root; } break;
关键修正点
- 移除多余的
XPeekEvent,先处理当前已获取的MotionNotify事件,再用XCheckTypedEvent清理队列剩余的同类型事件。 - 用
ev.xmotion访问MotionNotify事件的坐标数据,替换错误的ev.xbutton。 - 每次移动前调用
XGetWindowAttributes获取窗口当前位置,确保坐标计算正确。 - 更新
cur_start的坐标,避免多次移动后出现位置偏移误差。
内容的提问来源于stack exchange,提问作者Neat
相关产品推荐
相关产品推荐

