在 Linux 中窃取 Firefox 的焦点
此文档之前位于 wiki 上
此页面描述了 Linux 上原生事件实现的一个重要组成部分 - 焦点保持。为了在 Firefox 中处理原生事件,它必须始终保持焦点。如果用户决定切换到另一个窗口(这可以理解),Firefox 必须不知道它失去了焦点。
解决方案概述
基本思路
基本思路是在 XLib(X-Windows 客户端库)层和应用程序之间进行干预。X-Windows 通过异步事件通知应用程序事件(用户输入、窗口被销毁、鼠标移动)。指示失去焦点的事件 FocusOut 被丢弃。这个思路基于 Jordan Sissel 实现的一个预加载库,该库覆盖了 XNextEvent - 请参阅 http://www.semicomplete.com/blog/geekery/xsendevent-xdotool-and-ld_preload.html。
扩展
只要只有一个浏览器窗口,这个简单的实现就可以很好地工作。当涉及多个窗口时,会出现几个挑战
- 即使可能会打开新窗口,原生事件也必须继续流向活动窗口。然而,大多数窗口管理器会将焦点给予新打开的窗口。
- 窗口切换:当希望切换到另一个窗口时,必须移动焦点。这需要 WebDriver 的 Firefox 扩展和此组件之间的合作。
- 关闭窗口:当关闭窗口时,焦点必须移动到另一个窗口。按照设计,如果关闭了活动窗口,WebDriver 不会保证任何事情 - 直到切换到新窗口为止。在这种情况下,必须特别小心。
与其他组件的交互
基本思路不需要与 WebDriver 的其他组件进行交互。但是,当涉及多个窗口 - 创建、切换或销毁时,此组件应该意识到这一点。无法跟踪新窗口的创建 - 因为它可能是许多操作的副作用。可以跟踪切换和关闭。
涉及的技术
要理解这个解决方案,应该熟悉 X-Windows 及其事件。了解 GDK 事件处理循环也很有用。
实现细节
所有这些都描述了 firefox/src/cpp/linux-specific/x_ignore_nofocus.c
中的代码。
共享库
劫持事件是通过覆盖 XNextEvent 来完成的。使用 LD_PRELOAD
加载包含 XNextEvent
修改实现的共享库。修改后的函数打开 /usr/lib/libX11.so.6
并调用真正的函数。然后检查真正的函数返回的事件(即真正的事件)。
识别事件
在基本思路下,FocusOut
事件将被简单地丢弃。但是,窗口切换使事情复杂化。
数据结构
有一个全局数据结构,它记住以下信息
- 活动窗口 ID(如果此时有这样的 ID)
- 正在创建的新窗口的 ID(同样,如果存在)
- 窗口切换是否正在进行中。
- 窗口关闭是否正在进行中。
- 焦点是否已给予另一个窗口,是否应将其窃取回活动窗口?
- 活动窗口是否已收到
FocusIn
事件? - 我们是否将当前活动窗口设置为关闭操作的结果?
Firefox 启动
FocusIn
事件到达,活动窗口 ID 为 0。设置新的活动窗口。请注意,在创建主窗口期间,会创建另一个子窗口,并且会向活动窗口发送 FocusOut
事件。幸运的是,此 FocusOut
事件表明焦点将要移动到子窗口(由 NotifyInferior
标识),因此它是允许的。
用户已切换到另一个窗口
这由一个 FocusOut
事件表示,该事件的 detail 字段既不是 NotifyAncestor
也不是 NotifyInferior
。此事件被简单地丢弃,并替换为 KeymapNotify
事件,该事件会被 GDK 立即丢弃。
正在创建新窗口
此条件由 ReparentNotify
事件标识。发生这种情况时,new_window 字段将被设置为新创建的窗口的 ID。将允许后续的 FocusOut
事件 - 在新窗口创建期间,事件将像往常一样流动(来自活动窗口的 FocusOut
事件,到新窗口的 FocusIn
事件,到新窗口的 FocusOut
事件以及到新窗口的子窗口的 FocusIn
事件)。在新窗口的子窗口收到 FocusIn
后,将发出对 XSetInputFocus
的调用,以将焦点返回到活动窗口。
发生窗口切换
在窗口切换期间,事件将正常流动。当窗口的子窗口收到 FocusIn
事件时,窗口切换被认为完成。窗口切换从标识文件 /tmp/switch_window_started
开始。在此文件中,将写入 switch:
字符串,后跟窗口 ID(该 ID 仅用于调试目的)。这会将活动窗口 ID 更改为 0,并将状态更改为“正在切换”。在切换期间(或当没有活动窗口时)不会丢弃任何事件。
正在关闭窗口
与窗口切换非常相似(也通过读取文件来识别)。但是,它表明正在关闭窗口 - 如果已关闭,则不会发生焦点窃取。此外,还标识了 DestroyNotify
事件,以查找活动窗口何时被关闭(由用户显式关闭,或由其他操作隐式关闭,而不是显式调用关闭)。在这种情况下,活动窗口 ID 也将设置为 0。
重要链接
- Jordan Sissel 的原始 XSendEvent 黑客
- XLib 事件 和 XLib 编程手册
- X 编程手册/规范