XServer为何发送两次FocusOut通知?软件KVM焦点异常排查
分析XServer发送两次FocusOut事件的原因及解决方案
这是个典型的X11窗口系统与平铺窗口管理器(Awesome WM)交互时的特性问题,咱们一步步拆解:
为什么会收到两次FocusOut事件?
首先要明确:X11的FocusOut事件本身就带有不同的触发场景,通过事件的detail字段区分(比如NotifyNormal、NotifyGrab、NotifyUngrab等)。在Awesome WM这类平铺WM中,窗口状态的变更(比如位置移动、窗口类型属性设置)会触发WM的额外逻辑,进而导致XServer发送两次FocusOut:
- 第一次FocusOut:通常是WM在处理虚拟窗口的状态变化时触发的——比如你调用
XMoveWindow把窗口移到目标位置后,Awesome WM会重新评估窗口的布局和焦点优先级,这时候会发送一次非用户主动触发的FocusOut(可能带有NotifyGrab或其他系统级的detail值)。 - 第二次FocusOut:这才是真正的用户操作导致的焦点丢失(比如你切换到其他窗口/机器),对应的
detail是NotifyNormal,这是你需要处理的有效事件。
你的代码里的xevent_handler只判断了事件类型和窗口,没有区分触发场景,所以会把两次事件都当成有效事件,必须调用两次XIfEvent才能等到真正需要的那一次。
这是Awesome WM的Bug吗?
不是Bug,而是平铺WM的特性。Awesome WM对窗口的管理逻辑非常严格,会在窗口的位置、类型、可见性等属性变化时,主动同步XServer的状态,确保WM的内部状态和实际窗口行为一致。这种额外的事件发送是为了避免焦点状态出现不一致,属于设计上的严谨性体现。
如何修复代码?
不需要手动调用两次XIfEvent,只需要修改事件处理器,过滤掉非用户触发的FocusOut事件即可。修改你的xevent_handler函数:
Bool xevent_handler(Display *dpy, XEvent *ev, XPointer arg) { wait_arg *a = (wait_arg *) arg; if (ev->type == a->notify) { if (ev->type == FocusOut) { // 只处理用户主动切换导致的焦点丢失(排除Grab/Ungrab等系统触发的场景) XFocusChangeEvent *focus_ev = &ev->xfocus; return (focus_ev->window == a->win) && (focus_ev->detail != NotifyGrab) && (focus_ev->detail != NotifyUngrab); } else if (ev->type == VisibilityNotify) { return (ev->xvisibility.window == a->win); } } return False; }
然后去掉代码中第二次XIfEvent调用,只保留一次:
// Wait until focus lost arg.win = win; arg.notify = FocusOut; XIfEvent(dpy, &ev, &xevent_handler, (XPointer) &arg); // Restore focus XSetInputFocus(dpy, focused, revert_to, CurrentTime); XSync(dpy, False);
这样就能精准捕获到真正的焦点丢失事件,恢复焦点时也会正确回到原来的终端窗口。
内容的提问来源于stack exchange,提问作者Kirill Bugaev
相关产品推荐
相关产品推荐

