7.1.12 最大化/最小化/关闭窗口

本节我们讨论最大化、最小化及关闭窗口的相关知识。

1.最小化窗口

最小化窗口本质上就是取消窗口的显示,Xlib为此提供了相应的函数XUnmapWindow。当取消某个窗口的显示时,同时也需要取消其临时窗口的显示,代码如下:

7.1.12 最大化/最小化/关闭窗口 - 图1

如果取消了一个窗口的显示,那么如何再恢复它的显示呢?在7.2节,我们再来讨论这个问题。

2.最大化/恢复窗口

所谓的最大化/恢复窗口,本质上就是使用Xlib的类似如XMoveResizeWindow的函数调整窗口位置和大小,winman中代码中对应的实现是maximize_window函数。

唯一需要指出的就是,winman自定义了属性_CUSTOM_WM_RESTORE_GEOMETRY,在最大化之前将窗口的几何信息,包括位置、高度和宽度,都记录到窗口的属性中。为什么要在窗口的属性中记录,不是都已经记录到窗口对象中了吗?试想一下,如果不在窗口的属性中记录,而只是记录在窗口管理器中,一旦窗口管理器异常退出,那么一切状态信息将随着窗口管理器灰飞烟灭。为了窗口管理器再次启动时能获得这些信息,将这些信息保存在窗口中是一个合理的办法。

类似地,在最大化/恢复窗口时,winman也更新了窗口的另外两个属性_NET_WM_STATE_MAXIMIZED_VERT和_NET_WM_STATE_MAXIMIZED_HORZ。

3.关闭窗口

ICCCM规范规定,当关闭窗口时,窗口管理器应该发送消息WM_DELETE_WINDOW给应用,而不是越俎代庖地请求X服务器去销毁应用的窗口。因为应用收到消息WM_DELETE_WINDOW后,可以做一些善后处理,然后在请求X服务器关闭窗口。

当然,有些应用程序不是很守规矩,尤其是早期使用Xlib编写的程序,它们不处理消息WM_DELETE_WINDOW。对于这类窗口,也只能采用简单粗暴的方法了,直接使用Xlib提供的函数XKillClient断开应用程序到X服务器的连接,这也就意味着整个X应用彻底退出执行。

那么窗口管理器如何得知应用是否处理了事件WM_DELETE_WINDOW?ICCCM规范规定,如果窗口自己负责销毁,其应该在窗口的属性WM_PROTOCOLS中设置属性WM_DELETE_WINDOW。属性WM_PROTOCOLS的值是个Atom数组,其中包括多个属性。

我们来看一下winman中的相关代码。当用户点击关闭按钮时,winman将调用函数icccm_delete_window,代码如下:

7.1.12 最大化/最小化/关闭窗口 - 图2

函数icccm_delete_window检查窗口的属性WM_PROTOCOLS,若其中包含属性WM_DELETE_WINDOW,那么就发消息WM_DELETE_WINDOW给窗口,否则调用Xlib的函数XKillClient直接切断应用和X服务器的连接。

无论是采用哪种方式,最终窗口一定会离我们而去的。虽然它们“轻轻的走了,不带走一片云彩”,但是winman还是要做一些必要的善后处理的,最起码,要把代表窗口的对象释放了吧。X服务器在销毁窗口后将会发送通知UnmapNotify给窗口管理器,winman在这个事件的处理函数中为离去的窗口“销户”。以标准窗口为例,其对应的“销户”函数如下:

7.1.12 最大化/最小化/关闭窗口 - 图3

7.1.12 最大化/最小化/关闭窗口 - 图4

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

1)首先从窗口栈中将窗口对应的窗口对象移除。

2)让根窗口收容应用的窗口。为什么要做这么一件事呢?因为有些窗口只是暂时取消显示,比如我们经常进行的最小化窗口。所以如果不将应用的窗口从Frame窗口拆除,那么接下来销毁Frame时,应用的窗口作为Frame窗口的子窗口,也将一并被销毁。

3)安全的将应用的窗口从Frame窗口脱离后,normal_client_remove调用Xlib的函数XDestroyWindow销毁了Frame等装饰窗口。

4)如果销毁的窗口是当前的活动窗口,winman将在窗口栈中试图找到下一个非临时窗口作为当前活动的窗口。如果找到了,那么余下要做的就和切换窗口类似了。

5)当前活动窗口变化了,窗口列表也变了。函数normal_client_remove调用相应的函数更新根窗口的属性。这方面的内容前面已多次见过,这里就不再讨论了。