目录

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

桌面窗口管理器

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

示例代码从base_wm截取。

窗口管理初始化

窗口管理主程序

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

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

窗口套壳

Frame

1const unsigned int BORDER_WIDTH = 3; 2const unsigned long BORDER_COLOR = 0xff0000; 3const unsigned long BG_COLOR = 0x0000ff; 4  5// 获取窗口属性,主要是尺寸 6XWindowAttributes w_attrs; 7XGetWindowAttributes(display, w, &w_attrs) 8  9// 发送CreateWindow请求,创建一个InputOutput窗口,以根窗口为父窗口 10Window frame = XCreateSimpleWindow(display, root, w_attrs.x, w_attrs.y, 11    w_attrs.width, w_attrs.height, BORDER_WIDTH, BORDER_COLOR, BG_COLOR); 12  13// 告知X11 server将frame直接子窗口的窗口请求和事件转发过来。 14XSelectInput(display, frame, SubstructureRedirectMask | SubstructureNotifyMask); 15  16// 发送ChangeSaveSet (SetModeInsert)请求,告知X11 server保存w叠层次序,WM崩溃后,可复原w原有叠层次序 17XAddToSaveSet(display, w); 18  19// 发送ReparentWindow请求,将w的父窗口由根窗口改为frame窗口 20XReparentWindow(display, w, frame, 0, 0); 21  22// 发送MapWindow请求,显示frame窗口 23XMapWindow(display, frame); 24  25// 在客户端窗口上抓取通用窗口管理操作:移动、缩放、关闭和切换窗口 26// 分别发送GrabButton和GrabKey请求,捕获w窗口指定的鼠标和键盘事件,X11 server将事件转发给frame 27// 发送GrabButton请求,ButtonMotionMask响应MotionNotify事件,ButtonPressMask/ButtonPressMask表示响应按键的两个状态(Press, Release)事件 28// 发送GrabKey请求,捕获按键请求,响应KeyPress和KeyRelease事件 29// 看来Mod1Mask修饰表示Alt,Button1表示left,Button2表示right。两个同时按下,在MotionNotify响应函数时处理 30  31// alt + left按键移动窗口 32XGrabButton(display, Button1, Mod1Mask, w, false, 33    ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, 34    GrabModeAsync, GrabModeAsync, NULL, NULL); 35  36// alt + right按键缩放窗口 37XGrabButton(display, Button3, Mod1Mask, w, false, 38    ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, 39    GrabModeAsync, GrabModeAsync, NULL, NULL); 40  41// alt + f4关闭窗口 42XGrabKey(display, XKeysymToKeycode(display, XK_F4), 43    Mod1Mask, w, false, GrabModeAsync, GrabModeAsync); 44  45// alt + tab切换窗口 46XGrabKey(display, XKeysymToKeycode(display_, XK_Tab), 47    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函数进行处理,即发送请求的客户端与转发请求的客户端一致时,走正常流程

窗口操作

移动窗口

1// 在ButtonPress响应函数中记录光标坐标、窗口坐标,将窗口移到顶层 2// 事件中XButtonEvent,记录了光标在根窗口的起始坐标(x_root, y_root) 3Window returned_root; 4int x, y; 5unsigned width, height, border_width, depth; 6  7// 发送GetGeometry请求,获得窗口的几何形状 8XGetGeometry(display, frame, &returned_root, 9    &x, &y, &width, &height, &border_width, &depth); 10  11// 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层 12XRaiseWindow(display, frame) 13  14// 在MotionNotify响应函数中处理窗口移动 15// 根据XMotionEvent中的光标坐标(x_root, y_root)、点击时的坐标和窗口坐标,计算w窗口在根窗口的坐标 16// 由于frame未创建边框,w和frame的起始坐标一致。 17  18// 发送ConfigureWindow请求,移动frame窗口 19XMoveWindow(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等事件,在次流程中省略了

缩放窗口

1// 在ButtonPress响应函数中记录窗口尺寸,将窗口移到顶层 2Window returned_root; 3int x, y; 4unsigned width, height, border_width, depth; 5  6// 发送GetGeometry请求,获得窗口的几何形状 7XGetGeometry(display, frame, &returned_root, 8    &x, &y, &width, &height, &border_width, &depth); 9  10// 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层 11XRaiseWindow(display, frame) 12  13// 在MotionNotify响应函数中处理窗口缩放 14// 发送ConfigureWindow请求,掩码值为CWWidth|CWHeight,缩放窗口 15XResizeWindow(display, frame, width, depth); 16// 缩放客户端窗口 17XResizeWindow(display, w, width, depth);

关闭窗口

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

1// 在KeyPress响应函数中优雅关闭窗口 2   3Atom* supported; 4int num_supported; 5// 发送InternAtom请求,获取名为"WM_PROTOCOLS"的atom,supported中存在WM_DELETE_WINDOW 6XGetWMProtocols(display, w, &supported, &num_supported); 7  8XEvent msg; 9memset(&msg, 0, sizeof(msg)); 10msg.xclient.type = ClientMessage; 11msg.xclient.message_type = WM_PROTOCOLS; 12msg.xclient.window = e.window; 13msg.xclient.format = 32; 14msg.xclient.data.l[0] = WM_DELETE_WINDOW; 15// 发送SendEvent请求给X11 server,server发送ClientMessage给client,client根据WM_PROTOCOLS协议中WM_DELETE_WINDOW值销毁窗口 16XSendEvent(display, w, false, 0, &msg)

切换窗口

1// 在KeyPress响应函数中切换窗口 2  3// 先从窗口管理器中找到w窗口下一层next_w窗口,然后将该窗口移到最上层。窗口管理器管理所有client顶层窗口,未在此例中列出   4  5// 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层 6XRaiseWindow(display, next_frame); 7// 发送SetInputFocus请求,窗口获取输入焦点。RevertToPointerRoot表示窗口不可见时,将输入焦点回退至根窗口 8XSetInputFocus(display, next_w, RevertToPointerRoot, CurrentTime);

EWMH/ICCCM

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

详见官网:ICCCMEWMH

特性

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

1_NET_WM_WINDOW_TYPE_DESKTOP

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

1_NET_WM_WINDOW_TYPE_DOCK

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

1_NET_WM_WINDOW_TYPE_TOOLBAR

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

1_NET_WM_WINDOW_TYPE_MENU

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

1_NET_WM_WINDOW_TYPE_UTILITY

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

1_NET_WM_WINDOW_TYPE_SPLASH

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

1_NET_WM_WINDOW_TYPE_DIALOG

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

1_NET_WM_WINDOW_TYPE_DROPDOWN_MENU

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

1_NET_WM_WINDOW_TYPE_POPUP_MENU

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

1_NET_WM_WINDOW_TYPE_TOOLTIP

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

1_NET_WM_WINDOW_TYPE_NOTIFICATION

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

1_NET_WM_WINDOW_TYPE_COMBO

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

1_NET_WM_WINDOW_TYPE_DND

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

1_NET_WM_WINDOW_TYPE_NORMAL

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

应用窗口状态

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

state

description

1_NET_WM_STATE_MODAL

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

1_NET_WM_STATE_STICKY

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

1_NET_WM_STATE_MAXIMIZED_VERT

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

1_NET_WM_STATE_MAXIMIZED_HORZ

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

1_NET_WM_STATE_SHADED

窗口阴影

1_NET_WM_STATE_SKIP_TASKBAR

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

1_NET_WM_STATE_SKIP_PAGER

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

1_NET_WM_STATE_HIDDEN

窗口隐藏

1_NET_WM_STATE_FULLSCREEN

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

1_NET_WM_STATE_ABOVE

置顶

1_NET_WM_STATE_BELOW

置底

1_NET_WM_STATE_DEMANDS_ATTENTION

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

1_NET_WM_STATE_FOCUSED

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

WM_NORMAL_HINTS

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

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

1typedef struct { 2    long flags;         /* marks which fields in this structure are defined */ 3    // x, y, width, height由用户设置指定,已废弃使用 4    int x, y;           /* Obsolete */ 5    int width, height;  /* Obsolete */ 6    // 窗口最小的宽度和高度 7    int min_width, min_height; 8    // 窗口最大的宽度和高度 9    int max_width, max_height; 10    // 缩放窗口的最小步幅 11    int width_inc, height_inc; 12    // 最大和最小的屏幕宽高比。x为宽,y为高 13    struct { 14           int x;           /* numerator */ 15           int y;           /* denominator */ 16    } min_aspect, max_aspect; 17    // 最适合的宽度和高度,未设置时,以最小宽度和高度为准 18    int base_width, base_height; 19    // 指定参考坐标位置,默认为NorthWest 20    int win_gravity; 21    /* this structure may be extended in the future */ 22} XSizeHints; 23  24// 设置几何形状约束函数 25void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 26// 获取几何形状约束函数 27Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints_return, long *supplied_return);