弹跳窗口管理器

前几天在啁啾会馆上看到 Julia Evans 发起了一个编程挑战——写一个「弹跳窗口管理器」,让窗口动起来,效果大概是这样的:

这种弹跳效果在游戏里很常用,可以这样实现:每次移动(或者每一帧)检查一下物体有没有碰到墙壁,如果碰到墙壁,则将物体与墙壁垂直的方向的速度取反(不计能量损失),保持物体与墙壁平行的方向的速度不变。

读了一遍 Xlib 的文档之后,我写了这样的运行逻辑:

while (!stop) {
    // next position
    x_pos = MAX(MIN(x_pos + x_speed, display_width - win_width), 0);
    y_pos = MAX(MIN(y_pos + y_speed, display_height - win_height), 0);

    // collision detection
    if (x_pos == 0 || (x_pos + win_width) == display_width) {
        x_speed = -x_speed;
    }
    if (y_pos == 0 || (y_pos + win_height) == display_height) {
        y_speed = -y_speed;
    }

    // move subwindow
    XMoveWindow(dpy, sub_win, x_pos, y_pos);

    // sleep for 90 ms
    usleep(90000);
}

运行程序之后发现窗口不会动,我一开始还以为是 usleep 函数会导致 XMoveWindow 函数失效,另外写了一个计时器来控制时间,结果还是不行。后来发邮件和 Julia 交流,她说她也遇到了一模一样的问题,最后通过 XSync 函数解决了。我猜窗口不动的原因可能是 XMoveWindow 函数会把移动窗口的请求写入缓冲区,但是 usleep 函数让操作系统切换进程,导致缓冲区的内容被清除了。解决的办法是在调用 usleep 函数之前先调用 XSync 函数清空缓冲区,在切换进程之前处理移动窗口的请求:

while (!stop) {
    // next position
    x_pos = MAX(MIN(x_pos + x_speed, display_width - win_width), 0);
    y_pos = MAX(MIN(y_pos + y_speed, display_height - win_height), 0);

    // collision detection
    if (x_pos == 0 || (x_pos + win_width) == display_width) {
        x_speed = -x_speed;
    }
    if (y_pos == 0 || (y_pos + win_height) == display_height) {
        y_speed = -y_speed;
    }

    // move subwindow
    XMoveWindow(dpy, sub_win, x_pos, y_pos);

    // ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️

    // flush the output buffer
    XSync(dpy, False);

    // ⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️

    // sleep for 90 ms
    usleep(90000);
}

最终的效果如下:

完整代码可见 GitHub。更多方案可见 Julia 的另一篇博客 Solutions to the tiny window manager challenge

Updated: