Gtk窗口在某些情况下无法聚焦窗口的分析与解决办法

(Gtk window focus failed when opened more than one times in one thread)

 

一. 背景:

 

在做Linux取词翻译软件的时候,UI编程使用的是Gtk,翻译界面的逻辑代码被放置于某个线程内,途中会进行多次的创建与销毁,Gtk窗口除了第一次能够成功聚焦,之后就再也无法成功,写了短小的测试用例,发现每次进行进程创建可以保证窗口聚焦。

但是考虑到各个线程中众多的共享数据,另开进程进行数据通信的代价太过于大,主要是进程有独立的地址空间,彼此间通信要借助IPC的方式,而且无论是代码的重构还是之后的调试都将耗费巨大的精力。

 

二. 解决过程:

 

而后开始寻找解决办法,尝试了好几个小时在Gtk内部查找问题,换了或增加了各种各样的窗口聚焦相关代码,仍旧不可行。

接着想到了另一个突破口,平时Alt-tab键可以进行窗口切换,本质就是聚焦窗口的改变,巡着这条路想起xdotool好像有这个功能可以激活特定窗口(亦即聚焦窗口)。

然后在终端尝试了一下,果然,用 xdotool windowactivate <窗口id>可以激活窗口!马上阅读源码,很快找到了相关实现的函数,在里面添加了些log的打印进行进一步定位,或者注释掉一些可能实际起作用的代码后,程序窗口激活功能即刻失效,由此得到几行关键代码:

 

观察后不难发现,其中最后几句中的XSendEvent基本可以被确定是核心代码。

问题是其中的参数应该如何获取?

 

1.如果了解一点点X11,应该可以猜出xdo->xdpy就是来自XOpenDisplay(), 当然这个可以回溯找到,这里就不找了。

2.wattr.screen->root来自XWindowAttributes结构体,此结构体的相关属性由以下函数获取:

这里的wid由函数参数可以看到是一个窗口id值, 来自Window变量。

3.位掩码大概率是宏定义,不用获取和更改,剩下xev由上文可知是一个XEvent结构体,相关属性的赋值可以不用修改,在代码中很清晰。

 

如此,我们需要的信息都已经明确,需要在Gtk编程中使用XOpenDisplay()连接到X服务,获取一个xdpy,然后定义两个结构体 —- XWindowAttributes, XEvent 进行属性赋值或者获取,最后调用如下函数使我们的窗口处于聚焦状态:

但是我们的窗口是Gtk,而不是X11,Window id如何获取是个问题,带着问题去网上搜索如何将Gtk转化为X11的Window id,很快找到了解决方案,如下:

 

最后附上项目中的代码:

 

要解决聚焦问题请继续往下看,以上代码只是问题解决的一环,还差一环。

 

三. 窗口无法聚焦的原因分析尝试:

 

在使用该函数进行窗口聚焦的时候,发现虽然返回值显示窗口聚焦请求成功,但窗口依旧处于失焦状态,想到很有可能聚焦请求被其他程序所抢占。

程序在同一个线程第二次运行的时候,同样靠键盘输入调出界面。这时接收到键盘信号的终端或者其他应用,与翻译软件一同发送了聚焦请求,使翻译软件的请求被抢占,即使我们的程序聚焦成功过,但很快又被切换到的其他应用打断,看上去就是聚焦失败的效果。

既然我们可以后期请求聚焦,那这个就好办了,直接在运行后注册信号超时回调函数,多执行几次聚焦请求便可解决。

 

注意以上只是作者项目中的部分代码,并不完整,照搬是无用的,只是提供一个思路。