![Xlib。如何让XGrabButton不消耗点击?](https://linux22.com/image/210619/Xlib%E3%80%82%E5%A6%82%E4%BD%95%E8%AE%A9XGrabButton%E4%B8%8D%E6%B6%88%E8%80%97%E7%82%B9%E5%87%BB%EF%BC%9F.png)
第一个问题,如果我忽略了这里的要求,请原谅我。
我正在尝试为 arch linux 构建一个窗口管理器。目前,我在映射窗口之前添加抓取事件。
我有以下内容:
XGrabButton(display, Button1, 0, window, false, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
当按下 Button1 时,我运行以下命令:
XRaiseWindow(display, frame);
现在的问题是我无法单击窗口中的任何内容。仅正在运行 ButtonPressed 回调函数。我如何才能(1)确保窗口也接收到 ButtonPress 事件,以及(2)如果窗口当前不是顶部窗口(我假设此处可以使用 stack_mode),则仅运行 XRaiseWindow 函数?
答案1
我正在尝试为 arch linux 构建一个窗口管理器。
这是一个相当大的工程。在你的位置,我会首先查看现有窗口管理器的代码。
目前,我在映射窗口之前添加抓取事件。
IIRC,他们不是这么做的。相反,他们通过“重定向”注册事件,例如使用SubstructureRedirectMask
.例如看看这文章系列。
现在的问题是我无法单击窗口中的任何内容。
嗯,是的,您抓取了按钮事件,因此现在每个按钮事件都会传递到窗口管理器而不是窗口(这就是窗口管理器通常不进行抓取的原因)。如果你坚持这样做(我不会),你必须决定每个按钮事件是否应该传递到窗口,并且你必须取消抓取,产生第二个合成事件,并重新抓取应传递到窗口的每个按钮事件。这不是一个很愚蠢的方法。
仅当窗口当前不是顶部窗口时才运行 XRaiseWindow 函数
那么这与焦点策略(“点击加注”)有关吗?我实际上不确定这是如何实现的,正如我上面所写的,我会阅读现有窗口管理器的代码来找出答案。也可能是您可以在 X 本身中配置的东西。
答案2
我已经用以下方法解决了(1)(在这里找到)。
将pointer_mode更改为GrabModeSync并使用
XAllowEvents(display, ReplayPointer, event.time);
XSync(display, 0);
在 ButtonPressed 回调中传递单击事件。
仍在寻找(2),尽管我发现仅调用 XRaiseWindow 而不检查是否有必要并不是问题。需要明确的是,我没有介绍任何内容,这只是我的个人经历。
答案3
至于 (2),我可以分享到目前为止我在自己的 WM 项目中学到的知识,尽管我来这里是为了自己尝试更多地了解其中一些技术。也许这会对别人有所帮助,至少我可以对自己说一下。
首先,您的 WM 中可能已经有某种变量,它可以让您知道而不需要询问 X 哪个窗口是“活动的”。请注意,哪个窗口位于堆栈顶部,哪个窗口处于焦点状态(接收键盘输入)之间存在差异。
在我们熟悉的大多数桌面中,它们几乎总是相同的东西,但 AFAICT X 并不真正关心这一点,因此实际上取决于您编写一些内容,通过发送 SetInputFocus 请求(XSetInputFocus())来按照用户期望的方式移动焦点用 Xlib 的说法)。 XRaiseWindow() 本身仅有的升起窗口,它根本不会改变焦点。
无论如何,您可能会自己管理输入焦点,因此内部应该已经有某种方法来知道窗口是否是活动窗口。您将需要这个来更新大多数寻呼机/任务栏读取的根窗口上的 _NET_ACTIVE_WINDOW 属性,并且您可能还会发现一些需要自己重新设置焦点的边缘情况,例如,如果顶级,聚焦窗口被破坏,然后 IME X 倾向于将键盘焦点转回根窗口,而在典型的桌面环境中您可能真正想要的是将焦点返回到最近聚焦的窗口,而不是刚刚聚焦的窗口。关闭。
在我的 WM 中,我一直在通过将所有客户端/容器/装饰窗口/无论您特定的 WM 调用它们保留在链接列表中来相当轻松地找出这种查找下一个焦点的场景,并且每次窗口被抬起/聚焦,我只需将指向它的结构的指针移动到客户端列表的末尾,这是一个非常便宜的操作,因为它只是一个链接列表删除+插入。然后,如果我获得根窗口的 FocusIn 或以其他方式确定需要 WM 的帮助来更新焦点,我可以只获取列表中的最后一个窗口并将焦点放在该窗口上。
我要提到的另一件事是,正如您也发现的那样,我查看了几个 WM 的源代码,以使用 GrabButton 和 AllowEvents 来检测“单击焦点”,以便在单击窗口,而不仅仅是标题栏/边框,因为您不拥有客户区。但这里的技巧有点多:您应该只抓取当前未聚焦的窗口上的按钮。一旦用户点击其中一个并且它被抬起+聚焦,我们就释放抓取。实际上,我在每当我们想要改变焦点时使用的“SetFocus”函数中执行此操作。这是有道理的,因为它解决了我在学习这项技术时最初的反对意见——我们并不真的希望每次鼠标点击总是被拦截,弹回 WM,然后重新广播回来——这是非常低效的,如果用户正在玩超激烈的 FPS 游戏或其他游戏,额外的延迟可能不仅不雅观,而且可能实际上很重要?但是,通过根据窗口的焦点添加和删除抓取,我们仅拦截完成所需功能所需的内容,并且大多数点击不会发生双重弹跳。
我还没有完全弄清楚的唯一部分是,如果您有一个活动的 GrabButton 并且在单击后不调用AllowEvents,那么 X 不会向您发送任何进一步的按钮事件。我还不完全确定为什么会这样,但请注意您可能会遇到这种情况。每当我在具有抓取功能的窗口上收到带有按钮 1 的 ButtonPress(与我的 GrabButton 条件相同)时,我都会发送AllowEvents。但这意味着您可能希望能够区分原始事件和重播事件 - 否则,如果他们说单击标题栏(*)而不是客户区,您将获得两个 ButtonPress 事件而无需干预ButtonRelease,这可能会或可能不会导致微妙的问题,具体取决于您编码 WM 的方式。
如果需要区分它们,到目前为止我发现的最好方法是使用事件的“时间”字段,该字段通常会不断增加,但当重播事件返回时将与抓取的版本相同。因此,您可以记录最后一次单击的时间字段,该按钮+状态将有资格在该窗口/客户端的成员变量中进行抓取,例如,忽略时间字段等于前一个合格 ButtonPress 的抓取限定 ButtonPress在那扇窗户上。
(*) 我正在抓取我创建的 CONTAINER/DECOR 窗口上的按钮,并将客户端重新设置为客户端的父级,而不是原始客户端窗口,该窗口仍然捕获对客户端区域的点击,因为客户端已被重新设置为我的子级。容器。您可能可以通过抓取客户端窗口来避免点击窗口的装饰部分而导致双重事件,但我不喜欢与客户端窗口进行超出我需要的交互,因为我不拥有它们,客户端可以随时与我的代码异步销毁它们,这会创建各种竞争条件,您必须小心地计划这些条件,或者通过在关键部分使用 XGrabServer 来确保,例如重新设置父级,然后向服务器进行查询以确保该窗口在 GrabServer 之后仍然存在,或者您可以执行一些最小的 WM(例如 dwm)所做的操作,并捕获并忽略由于此类竞争而可能从服务器返回的任何错误。但我会尽可能地减少此类事情,这就是我目前正在抢占容器的原因。
但是,如果您在客户端窗口上执行 GrabButton,也许您可以只执行 SelectInput,而不需要首先进行抓取。