版本比较

密钥

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。
目录
stylenone

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

桌面窗口管理器

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

示例代码从base_wm截取。

窗口管理初始化

窗口管理主程序

代码块
languagejava
// 连接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

代码块
languagejava
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函数进行处理,即发送请求的客户端与转发请求的客户端一致时,走正常流程

窗口操作

移动窗口

代码块
languagejava
// 在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等事件,在次流程中省略了

缩放窗口

代码块
languagejava
// 在ButtonPress响应函数中记录窗口尺寸,将窗口移到顶层
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响应函数中处理窗口缩放
// 发送ConfigureWindow请求,掩码值为CWWidth|CWHeight,缩放窗口
XResizeWindow(display, frame, width, depth);
// 缩放客户端窗口
XResizeWindow(display, w, width, depth);

关闭窗口

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

代码块
languagejava
// 在KeyPress响应函数中优雅关闭窗口
  
Atom* supported;
int num_supported;
// 发送InternAtom请求,获取名为"WM_PROTOCOLS"的atom,supported中存在WM_DELETE_WINDOW
XGetWMProtocols(display, w, &supported, &num_supported);
 
XEvent msg;
memset(&msg, 0, sizeof(msg));
msg.xclient.type = ClientMessage;
msg.xclient.message_type = WM_PROTOCOLS;
msg.xclient.window = e.window;
msg.xclient.format = 32;
msg.xclient.data.l[0] = WM_DELETE_WINDOW;
// 发送SendEvent请求给X11 server,server发送ClientMessage给client,client根据WM_PROTOCOLS协议中WM_DELETE_WINDOW值销毁窗口
XSendEvent(display, w, false, 0, &msg)

切换窗口

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_DESKTOP

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_DOCK

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_TOOLBAR

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_MENU

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_UTILITY

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_SPLASH

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_DIALOG

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_DROPDOWN_MENU

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_POPUP_MENU

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_TOOLTIP

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_NOTIFICATION

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_COMBO

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_DND

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

代码块
languagejava
_NET_WM_WINDOW_TYPE_NORMAL

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

应用窗口状态

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

state

description

代码块
_NET_WM_STATE_MODAL

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

代码块
_NET_WM_STATE_STICKY

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

代码块
_NET_WM_STATE_MAXIMIZED_VERT

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

代码块
_NET_WM_STATE_MAXIMIZED_HORZ

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

代码块
_NET_WM_STATE_SHADED

窗口阴影

代码块
_NET_WM_STATE_SKIP_TASKBAR

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

代码块
_NET_WM_STATE_SKIP_PAGER

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

代码块
_NET_WM_STATE_HIDDEN

窗口隐藏

代码块
_NET_WM_STATE_FULLSCREEN

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

代码块
_NET_WM_STATE_ABOVE

置顶

代码块
_NET_WM_STATE_BELOW

置底

代码块
_NET_WM_STATE_DEMANDS_ATTENTION

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

代码块
_NET_WM_STATE_FOCUSED

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

WM_NORMAL_HINTS

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

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

代码块
languagejava
typedef struct {
    long flags;         /* marks which fields in this structure are defined */
    // x, y, width, height由用户设置指定,已废弃使用
    int x, y;           /* Obsolete */
    int width, height;  /* Obsolete */
    // 窗口最小的宽度和高度
    int min_width, min_height;
    // 窗口最大的宽度和高度
    int max_width, max_height;
    // 缩放窗口的最小步幅
    int width_inc, height_inc;
    // 最大和最小的屏幕宽高比。x为宽,y为高
    struct {
           int x;           /* numerator */
           int y;           /* denominator */
    } min_aspect, max_aspect;
    // 最适合的宽度和高度,未设置时,以最小宽度和高度为准
    int base_width, base_height;
    // 指定参考坐标位置,默认为NorthWest
    int win_gravity;
    /* this structure may be extended in the future */
} XSizeHints;
 
// 设置几何形状约束函数
void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
// 获取几何形状约束函数
Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints_return, long *supplied_return);