You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何移除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

这里的“正常工作”指可以拖拽窗口。


问题原因与修正方案

核心原因

  1. 事件队列处理逻辑错误:进入MotionNotify分支时,当前的MotionNotify事件已经被XNextEvent从队列取出并存入ev。如果直接调用XCheckTypedEvent,它会去队列找下一个MotionNotify事件——但此时队列可能没有更多同类型事件,导致返回false,循环直接跳过,拖拽代码根本没机会执行。
  2. XPeekEvent的歪打正着:添加XPeekEvent后,它会把队列头部的事件(如果存在)复制到ev但不移除,此时XCheckTypedEvent能检测到这个事件并移除,循环得以执行。但这是错误的做法,因为你在循环里错误地用ev.xbutton访问MotionNotify事件的数据(应该用ev.xmotion)。
  3. 未初始化的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 01:21:01