描述窗口相关操作的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。整个过程先由应用程序发起创建窗口请求。
应用程序发送CreateWindow请求给显示服务器
显示服务器创建Window对象,初始化窗口属性,操作,尺寸和位置等,发送CreateNotify事件给窗口管理器,然后返回给应用程序
窗口管理器接收CreateNofity事件,可不做任何处理
应用程序发送MapWindow请求给显示服务器
显示服务器将该请求转发给窗口管理器,发送MapRequest事件,并返回给应用程序
窗口管理器收到事件通知后,创建frame窗口,更改应用窗口父子关系,显示frame窗口,锁定键盘鼠标事件
发送GetWindowAttributes请求,从显示服务器获取应用窗口属性,窗口尺寸和位置
发送CreateWindow请求,创建frame窗口
发送ChangeWindowAttributes请求,告知显示服务器将其直接子窗口的窗口事件和请求转发过来
发送ChangeSaveSet (SetModeInsert)请求,告知显示服务器保存应用窗口叠层次序
发送ReparentWindow请求,将应用窗口的父窗口由根窗口改为frame窗口,(显示服务器会发送ReparentNofity事件给窗口管理器)
发送GrabButton请求,响应鼠标事件
发送GrabKey请求,响应按键事件
发送MapWindow请求该显示服务器,显示应用窗口
显示服务器处理MapWindow请求,并发送MapNotify事件给窗口管理器
窗口管理器响应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窗口上点击鼠标,并拖动窗口
显示服务器发送ButtonPress事件给窗口
发送GetWindowAttributes请求,从显示服务器获取应用窗口几何形状
发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层
显示服务器响应请求,将窗口移到最上层
并发送ConfigureNotify事件给窗口管理器(窗口管理器未做处理)
显示服务器发送MotionNotify事件给窗口服务器
窗口服务计算新坐标,发送ConfigureWindow请求,移动frame窗口
显示服务响应请求,移动窗口
并发送ConfigureNotify事件给窗口管理器(窗口管理器未做处理)
注:移动窗口涉及被覆盖窗口的重绘,显示服务器会发送Expose等事件,在次流程中省略了
缩放窗口
// 在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从而关闭窗口。对优雅方式进行描述。
// 在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) |
切换窗口
// 在KeyPress响应函数中切换窗口
// 先从窗口管理器中找到w窗口下一层next_w窗口,然后将该窗口移到最上层。窗口管理器管理所有client顶层窗口,未在此例中列出
// 发送ConfigureWindow请求,掩码为CWStackMode,值为Above,将窗口移到最上层
XRaiseWindow(display, next_frame);
// 发送SetInputFocus请求,窗口获取输入焦点。RevertToPointerRoot表示窗口不可见时,将输入焦点回退至根窗口
XSetInputFocus(display, next_w, RevertToPointerRoot, CurrentTime); |
EWMH/ICCCM
汇整扩展窗口管理器提示和客户端间通信协定手册中窗口相关内容。
特性
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 |
---|---|
_NET_WM_WINDOW_TYPE_DESKTOP | 具备桌面特性,包含与屏幕尺寸相同的桌面图标的单一窗口,允许桌面环境完全控制桌面,内容一般为桌面背景 |
_NET_WM_WINDOW_TYPE_DOCK | 具备停靠栏或面板特性,窗口管理器将该窗口放在其它窗口之上,包括在置顶窗口之上 |
_NET_WM_WINDOW_TYPE_TOOLBAR | 工具栏窗口,被设置为WM_TRANSIENT_FOR提示,可脱离主程序显示 |
_NET_WM_WINDOW_TYPE_MENU | 可折叠菜单窗口,被设置为WM_TRANSIENT_FOR提示,可脱离主程序显示 |
_NET_WM_WINDOW_TYPE_UTILITY | 实用工具窗口,如:调色板,工具箱等,在主程序内显示 |
_NET_WM_WINDOW_TYPE_SPLASH | 启动窗口,程序启动时的画面 |
_NET_WM_WINDOW_TYPE_DIALOG | 对话框,被设置为WM_TRANSIENT_FOR提示,在主程序内显示。为具有WM_TRANSIENT_FOR提示的默认窗口 |
_NET_WM_WINDOW_TYPE_DROPDOWN_MENU | 下拉菜单,一般为单击菜单栏时弹出的窗口,一般为override-redirect窗口具有的特性 |
_NET_WM_WINDOW_TYPE_POPUP_MENU | 弹出菜单,为右击弹出菜单窗口,一般为override-redirect窗口具有的特性 |
_NET_WM_WINDOW_TYPE_TOOLTIP | 提示框,显示提示文本,一般为override-redirect窗口具有的特性 |
_NET_WM_WINDOW_TYPE_NOTIFICATION | 通知窗口,一般为带有信息文本的气泡,一般为override-redirect窗口具有的特性 |
_NET_WM_WINDOW_TYPE_COMBO | 组合框弹出窗口,如:文本字段下显示的补全窗口,一般为override-redirect窗口具有的特性 |
_NET_WM_WINDOW_TYPE_DND | 被拖拽窗口(拖拽光标),如:一个文件图标窗口被从一个文件管理器拖至另一个文件管理器中,一般为override-redirect窗口具有的特性 |
_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
设置窗口几何形状的约束。应用程序设置其值,窗口管理器读取该值,根据约束确定窗口显示位置和显示尺寸,比如:最大化,若不满足宽高比,不满足最大尺寸要求,窗口管理器需计算出一个合适尺寸。
窗口管理器需要支持和满足该约束,防止窗口不能正常显示和响应。若应用程序未显示设置,约束无法生效。
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); |