7.1.5 为窗口“落户”

一旦收到X服务器转发来的MapRequest,就说明有应用的顶层窗口请求显示了,显然,这个时机是窗口管理器切入的最佳时机。winman首先遍历窗口栈确认窗口是否已经被管理了,如果请求映射的窗口尚未被管理,则调用wm_new_client开始管理窗口,函数wm_new_client的代码如下:

7.1.5 为窗口“落户” - 图1

该函数执行的主要操作如下:

1)如同我们每个人要有一个户口,在落户时需要提供各种自然人信息一样,窗口管理器也要收集窗口的各种“自然人”信息,为窗口在窗口管理器中“落户”。

2)绘制窗口装饰。

3)一切准备妥当后,申请X服务器显示应用的窗口,当然也包括窗口管理器附加的装饰。

这一节,我们先来讨论为窗口“落户”这一过程。

如前所述,在一个典型的X环境中,可能有多种类型的X应用程序,比如构成桌面环境的任务条等组件,以及普通的应用程序。即使是普通的应用的窗口,也可分为标准的窗口以及对话框等。显然,不同的类型的窗口需要区别对待,我们不能给任务条也加个标题栏,那样就会闹出笑话。

EWMH规定窗口需要设置属性_NET_WM_WINDOW_TYPE来表明自己的类型,函数wm_new_client依据的就是EWMH这个规定来判别窗口的类型。因此,函数wm_new_client调用Xlib的函数XGetWindowProperty获取窗口的属性_NET_WM_WINDOW_TYPE的值,根据窗口的不同类型,创建不同类型的窗口对象。

下面,我们以标准窗口为例,讨论其“落户”过程。

7.1.5 为窗口“落户” - 图2

7.1.5 为窗口“落户” - 图3

下面介绍函数normal_client_new执行的主要操作。

(1)设置窗口边框

通常,窗口可以请求X服务器绘制边框。但是为了统一,我们调用XSetWindowBorderWidth人为地将窗口的自身的边框设置为0,而是在Frame窗口上为被管理的窗口绘制统一的边框。在winman中,为简单起见,边框的宽度采用了一个固定的值。但是窗口管理器可以尊重窗口的诉求,在绘制窗口边框前,读取窗口属性中设定的边框宽度。

(2)获取窗口几何尺寸

接下来,我们读取窗口的几何尺寸,包括位置、宽度和高度,以及窗口所允许的最小的尺寸。这里我们分别使用了Xlib的函数XGetWindowAttributes及XGetWMNormalHints,主要是因为Xlib不推荐通过XGetWMNormalHints获取的窗口的位置和大小,但是通过XGetWindowAttributes又不能获取窗口的最小宽度和最小高度。

(3)读取窗口状态

在EWMH中,规定了窗口的状态属性_NET_WM_STATE包括_NET_WM_STATE_MODAL、_NET_WM_STATE_MAXIMIZED_VERT、_NET_WM_STATE_MAXIMIZED_HORZ、_NET_WM_STATE_FULLSCREEN等。

为简单起见,winman中只示例处理了窗口最大化的状态。函数ewmh_get_net_wm_state调用Xlib的接口XGetWindowProperty读取窗口的属性_NET_WM_STATE。如果属性中包含_NET_WM_STATE_MAXIMIZED_VERT和_NET_WM_STATE_MAXIMIZED_HORZ,那么就说明窗口是处于最大化状态,则winman尝试读取窗口中的标准状态(Restore状态)下窗口的位置和尺寸信息,以便窗口从最大化切换到标准状态时使用。

读者可能会问,结构体Client中数据项restore_x、restore_y、restore_w、restore_h不是记录了窗口在最大化之前的位置和尺寸吗?但是设想这样一个场景:当窗口处于最大化时,窗口管理器异常退出了,那么当窗口管理器再次启动时,这个数据如何初始化?这就是winman为窗口自定义属性_CUSTOM_WM_RESTORE_GEOMETRY的目的,winman将这些信息保存在窗口中,只要窗口在,这些信息就可以从窗口中读出来,可谓是“人在阵地就在”。

(4)捕捉“旧”窗口

当新的窗口出现后,如果这个新窗口不是一个临时窗口,其将成为当前活动的窗口,而上一个活动窗口将退居二线。因此,winman需要捕捉这个退居二线的窗口,以便可以将其顺利切换回来。而且,这个退居二线的窗口可能还有临时窗口,而且临时窗口可能还有临时窗口,因此,函数normal_client_get_transients遍历窗口栈,返回这个退居二线窗口的临时窗口组成的链,捕捉这个链上的所有窗口。

(5)设置窗口对象的函数指针

创建了窗口对象后,显然需要设置操作窗口的函数指针。根据不同的窗口类型,设置这些指针指向不同的函数实现。对于标准窗口,设置这些指针指向标准窗口的实现。

(6)更新根窗口的属性

新窗口的“自然人”信息收集完毕后,就可以给其“落户”了。函数normal_client_new调用stack_append_top将新创建的窗口对象压入winman自己维护的窗口栈。

为了让其他应用知晓又有新成员加入了,当然需要更新一些状态信息,比如任务条就时刻关注着系统中应用的变化情况。一个是记录当前活动窗口的属性_NET_ACTIVE_WINDOW;另外一个是记录X服务器中所有窗口的列表的属性_NET_CLIENT_LIST_STACKING。这两个属性都是EWMH标准规定的,它们都是根窗口的属性。函数normal_client_new中调用的两个子函数ewmh_set_net_active_window和ewmh_update_net_client_list_stacking目的就是分别更新这两个属性。