7.1.11 切换窗口
我们以图7-12所示的场景为例来讨论窗口之间的切换。应用A和应用B分别为两个X应用,图中使用虚线标识的是应用创建的窗口,实线标出的是窗口管理器创建的窗口。A1是应用A的标准类型的顶层窗口,A2是对话框类型的顶层窗口,且A2是窗口A1的临时窗口。B1是应用B的标准类型的顶层窗口,B2是对话框类型的顶层窗口,且B2是窗口B1的临时窗口。初始状态X时,应用A是当前活动的应用;在状态为Y时,应用B被切换为当前的活动应用。
图 7-12 切换窗口
当将应用B切换为当前应用时,窗口管理器需要考虑以下两点:
❑窗口管理器不应限制用户只有点击在窗口管理器完全控制的装饰窗口上才可以切换,即使鼠标指针落在应用自己创建的窗口上,如B1窗口、B2窗口,窗口管理器也应将应用B切换为当前活动应用。
❑窗口管理器应以整个应用为单位进行切换,即将应用B的所有窗口都移动到X服务器窗口栈的顶端。切换完成后,窗口的栈序应该为B2→B1→A2→A1,而不是类似如B2→A2→A1→B1。
理解了上面两点后,我们来看具体的切换实现,代码如下:
函数wm_handle_button_press中与切换相关的主要操作如下:
1)winman首先在窗口栈中寻找鼠标点击事件发生的窗口对象。winman既考虑了鼠标可能点击在窗口管理器创建的装饰窗口,也考虑了鼠标可能点击在应用创建的窗口的情况。
2)如果鼠标点击的窗口不是临时窗口,并且也不是当前活动的窗口,则调用函数指针activate指向的函数进行切换。
3)但是如果用户点击在了临时窗口上,winman调用函数transient_get_topmost一直找到非临时窗口,如果这个非临时窗口不是当前活动的窗口,那么调用函数指针activate指向的函数进行切换。
以标准窗口为例,函数指针activate指向函数为normal_client_activate,代码如下:
该函数normal_client_activate执行的主要操作如下:
1)首先要调整窗口栈序,将准备激活的窗口放到窗口栈的最顶端。
2)如果窗口还有临时窗口,那么也要将临时窗口移动到栈的最顶端。因为临时窗口可能还有临时窗口,这就是代码中for循环的目的。
3)安排好窗口的栈序后,函数normal_client_activate调用函数wm_restack_clients请求X服务器重新调整窗口栈序。函数wm_restack_clients就是将winman的窗口栈中的窗口按照Xlib的函数XRestackWindows的参数格式组织好,然后调用Xlib的函数XRestackWindows向X服务器发出调整请求。从这里我们再次深刻地体会到X策略和机制的分离哲学:X服务器负责具体的切换窗口的动作,但是,各个窗口的前后顺序如何排列这个策略由窗口管理器来负责。
4)切换窗口后,winman还有一件事情要做,那就是取消捕捉刚刚晋升的活动窗口。捕捉的目的是为了接收非当前活动窗口的鼠标事件,而对于当前活动的窗口显然没有必要继续捕捉了。而且如果不取消捕捉窗口的鼠标事件,那么每次点击窗口时,鼠标事件都会送给winman,这显然是没有必要的,而且徒增X服务器和窗口管理器之间的通信量。
5)接下来,函数normal_client_activate调用Xlib的函数XAllowEvents放行捕捉的鼠标事件。这个事件就像一个接力棒一样,在winman中逗留了一圈,又回到其真正属于的应用,不至于造成事件丢失。但是前提是捕捉时必须使用同步模式。
6)有放就要有抓,这样才能张弛有度。winman需要捕捉上一个活动但是马上就变为非活动的窗口,包括其临时窗口。可见winman不仅只“见新人笑”,也“闻旧人哭”。
7)最后,winman更新了根窗口的属性_NET_ACTIVE_WINDOW。因为有些应用可能会通过根窗口的这个属性获取当前的活动窗口,比如后面的窗口组件任务条。