1.3.4 桌面窗口事件交互流程

描述窗口相关操作的X11 client、X11 server和window manager的事件交互流程

桌面窗口管理器

窗口管理为桌面提供统一的窗口风格、窗口布局、提供基本窗口功能等。

示例代码从base_wm截取。

窗口管理初始化

窗口管理主程序

// 连接X11 Server和获取根窗口 Display* display = XOpenDisplay(NULL); Window root = DefaultRootWindow(display); // 修改根窗口属性,其请求类型为ChangeWindowAttributes,窗口属性掩码值为CWEventMask // 告知X11 server将client的顶层窗口的窗口请求和事件转发过来。 // SubstructureRedirectMask: CirculateRequest, ConfigureRequest, MapRequest // SubstructureNotifyMask  : CirculateNotify, ConfigureNotify, CreateNotify, DestroyNotify, GravityNotify, MapNotify, ReparentNotify, UnmapNotify XSelectInput(display, root, SubstructureRedirectMask | SubstructureNotifyMask); XSync(display, false); // 锁屏,禁止X11 server处理其它client的请求 XGrabServer(display);   Window returned_root, returned_parent; Window* top_level_windows; unsigned int num_top_level_windows; // 发送QueryTree请求,获取X11 server上根窗口的直接子窗口,即client的顶层窗口。 XQueryTree(display, root, &returned_root, &returned_parent, &top_level_windows, &num_top_level_windows); // 给顶层窗口套壳:创建一个新窗口,作为顶层窗口的父窗口,根窗口的子窗口 for (unsigned int i = 0; i < num_top_level_windows; ++i) {   Window w = top_level_windows[i];   Frame(w); } XFree(top_level_windows); // 取消锁屏,恢复处理其它client的请求,一般来说窗口会加个边框 XUngrabServer(display);   // Main event loop. // 1. 转发请求和事件处理,SubstructureRedirectMask和SubstructureNotifyMask对应的请求和事件 // 2. 对自身创建的窗口按键和鼠标处理,包括快捷键,鼠标拖动,最小化/最大化/恢复窗口等操作处理

事件流涉及X11 server和X11 window manager,过程较为简单,不再列出。

窗口套壳

Frame

const unsigned int BORDER_WIDTH = 3; const unsigned long BORDER_COLOR = 0xff0000; const unsigned long BG_COLOR = 0x0000ff;   // 获取窗口属性,主要是尺寸 XWindowAttributes w_attrs; XGetWindowAttributes(display, w, &w_attrs)   // 发送CreateWindow请求,创建一个InputOutput窗口,以根窗口为父窗口 Window frame = XCreateSimpleWindow(display, root, w_attrs.x, w_attrs.y,     w_attrs.width, w_attrs.height, BORDER_WIDTH, BORDER_COLOR, BG_COLOR);   // 告知X11 server将frame直接子窗口的窗口请求和事件转发过来。 XSelectInput(display, frame, SubstructureRedirectMask | SubstructureNotifyMask);   // 发送ChangeSaveSet (SetModeInsert)请求,告知X11 server保存w叠层次序,WM崩溃后,可复原w原有叠层次序 XAddToSaveSet(display, w);   // 发送ReparentWindow请求,将w的父窗口由根窗口改为frame窗口 XReparentWindow(display, w, frame, 0, 0);   // 发送MapWindow请求,显示frame窗口 XMapWindow(display, frame);   // 在客户端窗口上抓取通用窗口管理操作:移动、缩放、关闭和切换窗口 // 分别发送GrabButton和GrabKey请求,捕获w窗口指定的鼠标和键盘事件,X11 server将事件转发给frame // 发送GrabButton请求,ButtonMotionMask响应MotionNotify事件,ButtonPressMask/ButtonPressMask表示响应按键的两个状态(Press, Release)事件 // 发送GrabKey请求,捕获按键请求,响应KeyPress和KeyRelease事件 // 看来Mod1Mask修饰表示Alt,Button1表示left,Button2表示right。两个同时按下,在MotionNotify响应函数时处理   // alt + left按键移动窗口 XGrabButton(display, Button1, Mod1Mask, w, false,     ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,     GrabModeAsync, GrabModeAsync, NULL, NULL);   // alt + right按键缩放窗口 XGrabButton(display, Button3, Mod1Mask, w, false,     ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,     GrabModeAsync, GrabModeAsync, NULL, NULL);   // alt + f4关闭窗口 XGrabKey(display, XKeysymToKeycode(display, XK_F4),     Mod1Mask, w, false, GrabModeAsync, GrabModeAsync);   // alt + tab切换窗口 XGrabKey(display, XKeysymToKeycode(display_, XK_Tab),     Mod1Mask, w, false, GrabModeAsync, GrabModeAsync);

套壳过程涉及X11 server、X11 window manager以及client application。整个过程先由应用程序发起创建窗口请求。

  1. 应用程序发送CreateWindow请求给显示服务器

  2. 显示服务器创建Window对象,初始化窗口属性,操作,尺寸和位置等,发送CreateNotify事件给窗口管理器,然后返回给应用程序

  3. 窗口管理器接收CreateNofity事件,可不做任何处理

  4. 应用程序发送MapWindow请求给显示服务器

  5. 显示服务器将该请求转发给窗口管理器,发送MapRequest事件,并返回给应用程序

  6. 窗口管理器收到事件通知后,创建frame窗口,更改应用窗口父子关系,显示frame窗口,锁定键盘鼠标事件

    1. 发送GetWindowAttributes请求,从显示服务器获取应用窗口属性,窗口尺寸和位置

    2. 发送CreateWindow请求,创建frame窗口

    3. 发送ChangeWindowAttributes请求,告知显示服务器将其直接子窗口的窗口事件和请求转发过来

    4. 发送ChangeSaveSet (SetModeInsert)请求,告知显示服务器保存应用窗口叠层次序

    5. 发送ReparentWindow请求,将应用窗口的父窗口由根窗口改为frame窗口,(显示服务器会发送ReparentNofity事件给窗口管理器)

    6. 发送GrabButton请求,响应鼠标事件

    7. 发送GrabKey请求,响应按键事件

  7. 发送MapWindow请求该显示服务器,显示应用窗口

  8. 显示服务器处理MapWindow请求,并发送MapNotify事件给窗口管理器

  9. 窗口管理器响应MapNotify事件,未做任何处理

注:

  • 应用窗口的子窗口创建和显示流程与次不同,一般来说应用子窗口不转发至窗口管理器处理

  • 所有有回复的请求为同步请求,无回复的请求需调用XSync才会即时发送。任何请求中发送的事件,都在请求完成后处理事件

  • 显示服务器处理MapWindow请求时,对于窗口管理器的请求不会再转发,直接走正常流程,由MaybeDeliverMapRequest函数进行处理,即发送请求的客户端与转发请求的客户端一致时,走正常流程

窗口操作

移动窗口

// 在ButtonPress响应函数中记录光标坐标、窗口坐标,将窗口移到顶层 // 事件中XButtonEvent,记录了光标在根窗口的起始坐标(x_root, y_root) Window returned_root; int x, y; unsigned width, height, border_width, depth;   // 发送GetGeometry请求,获得窗口的几何形状 XGetGeometry(display, frame, &returned_root,     &x, &y, &width, &height, &border_width, &depth);   // 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层 XRaiseWindow(display, frame)   // 在MotionNotify响应函数中处理窗口移动 // 根据XMotionEvent中的光标坐标(x_root, y_root)、点击时的坐标和窗口坐标,计算w窗口在根窗口的坐标 // 由于frame未创建边框,w和frame的起始坐标一致。   // 发送ConfigureWindow请求,移动frame窗口 XMoveWindow(display, frame, frame_x, frame_y);

用户在frame窗口上点击鼠标,并拖动窗口

  1. 显示服务器发送ButtonPress事件给窗口

  2. 发送GetWindowAttributes请求,从显示服务器获取应用窗口几何形状

  3. 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层

    1. 显示服务器响应请求,将窗口移到最上层

    2. 并发送ConfigureNotify事件给窗口管理器(窗口管理器未做处理)

  4. 显示服务器发送MotionNotify事件给窗口服务器

  5. 窗口服务计算新坐标,发送ConfigureWindow请求,移动frame窗口

    1. 显示服务响应请求,移动窗口

    2. 并发送ConfigureNotify事件给窗口管理器(窗口管理器未做处理)

注:移动窗口涉及被覆盖窗口的重绘,显示服务器会发送Expose等事件,在次流程中省略了

缩放窗口

关闭窗口

关闭窗口有两种方式,比较优雅方式是通过WM_PROTOCOLS协议,通知client主动关闭窗口;另一种是发送KillClient请求,直接关闭client从而关闭窗口。对优雅方式进行描述。

切换窗口

EWMH/ICCCM

汇整扩展窗口管理器提示和客户端间通信协定手册中窗口相关内容。

详见官网:ICCCMEWMH

特性

feature

description

feature

description

Shading

阴影窗口,作为窗口最小化图标可选方案,阴影窗口只显示标题栏,无标题栏装饰的窗口隐藏时不能使用阴影窗口

Modality

窗口模式,包括瞬态窗口和模态窗口,由WM_TRANSIENT_FOR提示的瞬态窗口允许应用在结束之前关闭顶层窗口。模态窗口需关闭后才能使用主窗口

Large Desktops

大桌面,尺寸比屏幕大。屏幕作为桌面的视口(viewport),可通过分页(paging)或者滚动条方式显示桌面内容,由窗口管理器实现该功能

Sticky windows

帖窗,窗口在屏幕固定位置,即使移动视口内容,其位置也不改变

Virtual Desktops

虚拟桌面,与工作台类型,应用可在桌面之间切换,由窗口管理实现该功能

Pagers

分页器,如:工作台切换器,或者用缩略图呈现桌面窗口的桌面实用程序

Taskbars

任务栏,另一种窗口管理任务的方式,将客户窗口的标题或图标以按钮方式显示在任务栏中

Animated iconification

动态图标

Window-in-window MDI

嵌套窗口,MDI是微软平台的多文档接口,在顶层窗口中嵌入一个拥有子窗口的工作台,该子窗口可用窗口管理器修饰,窗口操作与顶层窗口一致

Override-redirect windows

重载重定向窗口,忽略窗口管理器,运行合成器将这些窗口绘制屏幕上

Layered stacking order

分层堆叠排序,将堆叠窗口分至不同的分层中

应用窗口功能类型

窗口功能类型存储在atom类型的_NET_WM_WINDOW_TYPE中,窗口类型值也为atom类型,客户端设置窗口类型,一个窗口可由多个窗口类型。这些特性帮助窗口管理器决定窗口的装饰、叠层位置以及其它行为。

  • WM_TRANSIENT_FOR提示表示窗口为谁的瞬态窗口,WM_TRANSIENT_FOR提示的窗口一般为顶层窗口或根窗口。

  • override-redirect窗口,表示该窗口不被窗口管理器管理,但会被合成器使用

functional type

description

functional type

description

具备桌面特性,包含与屏幕尺寸相同的桌面图标的单一窗口,允许桌面环境完全控制桌面,内容一般为桌面背景

具备停靠栏或面板特性,窗口管理器将该窗口放在其它窗口之上,包括在置顶窗口之上

工具栏窗口,被设置为WM_TRANSIENT_FOR提示,可脱离主程序显示

可折叠菜单窗口,被设置为WM_TRANSIENT_FOR提示,可脱离主程序显示

实用工具窗口,如:调色板,工具箱等,在主程序内显示

启动窗口,程序启动时的画面

对话框,被设置为WM_TRANSIENT_FOR提示,在主程序内显示。为具有WM_TRANSIENT_FOR提示的默认窗口

下拉菜单,一般为单击菜单栏时弹出的窗口,一般为override-redirect窗口具有的特性

弹出菜单,为右击弹出菜单窗口,一般为override-redirect窗口具有的特性

提示框,显示提示文本,一般为override-redirect窗口具有的特性

通知窗口,一般为带有信息文本的气泡,一般为override-redirect窗口具有的特性

组合框弹出窗口,如:文本字段下显示的补全窗口,一般为override-redirect窗口具有的特性

被拖拽窗口(拖拽光标),如:一个文件图标窗口被从一个文件管理器拖至另一个文件管理器中,一般为override-redirect窗口具有的特性

被窗口管理器管理的窗口或者override-redirect窗口

应用窗口状态

atom类型的_NET_WM_STATE用于描述窗口状态的提示列表。客户端通过发送_NET_WM_STATE的ClientMessage请求修改窗口状态,窗口管理器持续更新以反映窗口的当时状态。

state

description

state

description

模态对话框,其WM_TRANSIENT_FOR提示为其它顶层窗口,则为该顶层窗口的模特,若为根窗口,则为对话框所在的窗口组的模态

告示贴,窗口管理器将该窗口固定在屏幕上,即使滚动屏幕,其位置也不会变化

窗口水平方向最大化,即宽度已最大化

窗口垂直方向最大化,即高度已最大化

窗口阴影

窗口图标不显示在任务栏上,由客户端设置

窗口不显示在虚拟桌面上,由客户端设置

窗口隐藏

全屏,没有窗口装饰,窗口管理器需保存窗口先前的几何形状,取消全屏时还原成原有几何形状窗口

置顶

置底

与窗口或窗口有关的事件发生,这些事件一般被窗口管理器拒绝,窗口管理器设置该状态,或者应用程序完成了某些操作,设置该状态

窗口装饰是否在活动状态下绘制。

WM_NORMAL_HINTS

设置窗口几何形状的约束。应用程序设置其值,窗口管理器读取该值,根据约束确定窗口显示位置和显示尺寸,比如:最大化,若不满足宽高比,不满足最大尺寸要求,窗口管理器需计算出一个合适尺寸。

窗口管理器需要支持和满足该约束,防止窗口不能正常显示和响应。若应用程序未显示设置,约束无法生效。