wayland窗口操作

Wayland窗口操作包括窗口切换,窗口移动,窗口缩放等,同时支持X11和wayland窗口。窗口切换由快捷键触发;窗口移动由鼠标持续按压窗口管理器的窗口标题栏拖动触发;窗口缩放由鼠标持续按压窗口管理器的窗口边框拖动触发。本文针对mutter源码的实现进行描述

杂记

窗口事件

mutter实现了wayland compositor,X window manager等功能,其对X11的窗口掩码进行了修改,包括xwayland的根窗口,顶层窗口和窗口管理器的页框窗口。

  • 根窗口事件掩码:

    • 核心事件:SubstructureRedirectMask | SubstructureNotifyMask | StructureNotifyMask | ColormapChangeMask | PropertyChangeMask

    • 扩展事件:XI_Enter | XI_Leave | XI_FocusIn | XI_FocusOut | XI_BarrierHit | XI_BarrierLeave | XFixesDisplayCursorNotifyMask

  • 顶级窗口掩码:增加

    • 核心事件:PropertyChangeMask (and StructureNotifyMask if override redirect window)

    • 扩展事件:XI_Enter | XI_Leave | XI_FocusIn | XI_FocusOut | ShapeNotifyMask

  • 页框窗口掩码:

    • 核心事件:SubstructureRedirectMask | SubstructureNotifyMask | StructureNotifyMask | ExposureMask | FocusChangeMask

event mask

event type

description

event mask

event type

description

SubstructureRedirectMask

CirculateRequest

 

ConfigureRequest

 

MapRequest

 

StructureNotifyMask

 

SubstructureNotifyMask

CirculateNotify

 

ConfigureNotify

修改窗口显示属性通知

DestroyNotify

销毁窗口通知

GravityNotify

 

MapNotify

 

ReparentNotify

 

UnmapNotify

 

SubstructureNotifyMask

CreateNotify

创建窗口通知

ColormapChangeMask

ColormapNotify

修改色彩映射表通知

PropertyChangeMask

PropertyNotify

修改窗口资源属性通知

ExposureMask

Expose

暴露窗口通知

FocusChangeMask

FocusIn

焦点进入通知

FocusOut

焦点离开通知

ShapeNotifyMask

ShapeNotify

 

XFixesDisplayCursorNotifyMask

XFixesDisplayCursorNotify

 

特殊窗口

特殊窗口在mutter创建MetaX11Display时调用meta_x11_display_new和meta_display_init_x11_finish初始化,针对X11 server的特殊窗口,这些窗口都有override redirect属性

name

geometry

mask

description

name

geometry

mask

description

composite_overlay_window

(0, 0, width, heigth)

NoEventMask

调用XCompositeGetOverlayWindow创建,未使用

leader_window

(-100, -100, 1, 1)

NoEventMask

X11窗口的默认组长,用于窗口分组

timestamp_pinging_window

(-100, -100, 1, 1)

PropertyChangeMask

获取X11 server时间戳

wm_sn_selection_window

(-100, -100, 1, 1)

NoEventMask

标记client为窗口管理器的窗口,

no_focus_window

(-100, -100, 1, 1)

FocusChangeMask | KeyPressMask | KeyReleaseMask

取消X11 display焦点时,焦点设在no_focus_window上

guard_window

(0, 0, width, heigth)

NoEventMask

对最小化窗口的优化处理,将其隐藏在guard窗口后面

wm_cm_selection_window

(-100, -100, 1, 1)

NoEventMask

标记client为compositor的窗口

The guard window allows us to leave minimized windows mapped so that compositor code may provide live previews of them. Instead of being unmapped/withdrawn, they get pushed underneath the guard window. We also select events on the guard window, which should effectively be forwarded to events on the background actor, providing that the scene graph is set up correctly.

注:composite_overlay_window在使用MetaCompositorX11时创建,而在MetaWaylandCompositor时不创建该窗口,xwayland非根模式使用的时MetaWaylandCompositor和MetaCompositorServer。

X11窗口树

X11 server以属性结构管理窗口,每个屏幕有一个根窗口,其它创建的窗口都为根窗口子孙窗口。其中有几个约束:

  1. 子窗口不能超过父窗口可显示区域显示,所有子窗口显示在父窗口上面

  2. 只能兄弟窗口间切换窗口,兄弟窗口有堆叠层次。若切换的窗口所在的顶层窗口不在顶层显示,则涉及顶层窗口间的切换

  3. 父窗口使用列表管理子窗口及其堆叠层次,排在列表前的子窗口显示在上层,也即是排在前的子窗口可能会遮挡排在后的子窗口。

以堆叠层次切换为例。X11 server收到ConfigureWindow中CWStackMode请求,X11 server对窗口树进行重排。

  • 更新窗口树结构:确定pWin位置并更新子窗口列表(MoveWindowInStack)

  • 识别重绘区域:父窗口原可见区域clipList,包含边框区域boardClip,窗口移动时,影响的可绘制区域,这些区域限制在父窗口的winSize区域内。(MoveWindowInStack)

  • 计算子窗口的重绘区域:按照深度优先顺序计算子窗口的重绘区域,生成exposed和borderExposed区域 (miMarkOverlappedWindows / miValidateTree)

  • 重绘各子窗口的exposed和borderExposed区域:在server端绘制窗口边框和背景,发送Expose事件由client绘制窗口内容(miHandleValidateExposures)

// pWin为移动的窗口,pSib为pWin移动在该窗口之上。若pSib为空,则pWin窗口在最上层 static void ReflectStackChange(WindowPtr pWin, WindowPtr pSib, VTKind kind){     Bool WasViewable = (Bool) pWin->viewable;     Bool anyMarked;     WindowPtr pFirstChange;     WindowPtr pLayerWin;     ScreenPtr pScreen = pWin->drawable.pScreen;     if (!pWin->parent)         return; // if this is a root window, can't be restacked     // pWin为移动窗口,pSib为空,pWin置底;若pWin下一个相邻窗口为pSib,栈层次未变,则什么都不做     // pFirstChange:表示第一个被暴露的窗口     // pWin向下移,则pFirstChange为pWin相邻的下一个窗口。pWin与pSib中间有兄弟窗口,当pWin在pSib上方时,需下移。     // pWin向上移,则pFirstChange为pWin本身     pFirstChange = MoveWindowInStack(pWin, pSib);     if (WasViewable) {         // miMarkOverlappedWindows,标记与pWin窗口重叠的兄弟窗口及其子窗口所在位置,这些窗口可能需要重绘         // 若pFirstChange为pWin(窗口上移),则标记pWin及其子窗口,同时标记与pWin窗口(包括边框)重叠的兄弟窗口以及其子窗口         // 若pFirstChange不为pWin(窗口下移),标记与pWin窗口重叠的兄弟窗口以及其子窗口         anyMarked = (*pScreen->MarkOverlappedWindows) (pWin, pFirstChange, &pLayerWin);         // pLayerWin初始化为pWin,不清楚这里为什么要判断         if (pLayerWin != pWin)             pFirstChange = pLayerWin;         if (anyMarked) {             // 有窗口重叠。若无窗口则不需重绘,修改窗口树结构即可。此场景必有重叠,pWin窗口与其子窗口重叠。             // miValidateTree,计算父窗口以及所有子窗口的可剪接区域(boarderClip和clipList),以及exposed和borderExposed区域             (*pScreen->ValidateTree) (pLayerWin->parent, pFirstChange, kind);             // miHandleValidateExposures,深度优先,先绘制边框,再绘制exposed区域的背景,然后发送Expose事件给客户端绘制             // 边框和背景的绘制在server端完成,exposed区域的内容通过发送Expose事件由client发起绘制完成             (*pScreen->HandleExposures) (pLayerWin->parent);             // 一般为空,只有当视频窗口时,xv扩展才需处理             if (pWin->drawable.pScreen->PostValidateTree)                 (*pScreen->PostValidateTree) (pLayerWin->parent, pFirstChange, kind);         }     }     if (pWin->realized)         // 处理焦点和光标         WindowsRestructured(); }

窗口栈管理

wayland对窗口栈管理分为组(group),层(layer),栈(stack),由MetaStack和MetaWindow管理实现。MetaStack处理3个信号事件:"changed", "window-added", "window-removed",这3个事件都涉及窗口栈的更新。

一般来说,顶层窗口作为组长,与子孙窗口分为一组。client可通过EWMH指定窗口为特定的组(WindowGroupHint)。一般只有X11窗口有该属性。MetaWindow::group保存组关系

struct _MetaGroup {   int refcount;   MetaX11Display *x11_display;   GSList *windows;       // 窗口列表   Window group_leader;  // 组长   char *startup_id;   char *wm_client_machine; };

Wayland根据窗口类型来划分层的,对应关系如下,MetaWindow::layer保存窗口层次关系。在某个窗口组中的窗口来说,以其窗口组中层次最大的层划分。由MetaStackLayer定义。Wayland窗口只有META_LAYER_NORMAL、META_LAYER_TOP和META_LAYER_BOTTOM 3种类型

layer

enum

window type

description

layer

enum

window type

description

META_LAYER_DESKTOP

0

META_WINDOW_DESKTOP

桌面层

META_LAYER_BOTTOM

1

wm_state_below

底层,由客户应用程序使用EWMH指定窗口为 wm_state_below(_NET_WM_STATE_BELOW)

META_LAYER_NORMAL

2

META_WINDOW_NORMAL or other

中间层

META_LAYER_TOP

4

wm_state_above and not maximized

顶层,可由客户应用程序使用EWMH指定窗口为 wm_state_above(_NET_WM_STATE_ABOVE)

META_LAYER_DOCK

4

META_WINDOW_DOCK and not wm_state_below

停靠层,与顶层同层

META_LAYER_OVERRIDE_REDIRECT

7

META_WINDOW_DROPDOWN_MENU

META_WINDOW_POPUP_MENU

META_WINDOW_TOOLTIP

META_WINDOW_NOTIFICATION

META_WINDOW_COMBO

META_WINDOW_OVERRIDE_OTHER

重载重定向层,一般为禁止窗口管理器管理的窗口,显示在最上层

Wayland使用MetaStack管理所有的MetaWindow窗口,wayland窗口由MetaWindowWayland表示,xwayland窗口由MetaWindowXwayland表示,均为MetaWindow派生类。MetaWindow::stack_position记录窗口在MetaStack中的位置,越大越显示在上层,后续操作均按该栈序处理。窗口的层次切换后,也需保持相对位置。

使用如下两个函数来增加和删除窗口:

  1. meta_stack_add增加窗口:初始化窗口的stack_position值,为列表大小,发送"window-added"同步信号。新增窗口都显示在最上面。只能增加可堆叠窗口,处理完成后再发送"changed"同步信号

    1. meta_window_x11_is_stackable:非override redirect窗口

    2. meta_window_wayland_is_stackable:可显示窗口,MetaWaylandBuffer不为空

  2. meta_stack_remove删除窗口:发送"window-removed"同步信号,从栈中删除窗口,处理完成后再发送"changed"同步信号

MetaStack响应”changed”信号,信号处理函数为on_stack_changed,重新对wayland和X11的顶层窗口堆栈排序。

注:使用MetaX11Stack管理X11窗口栈备份,为XID列表。MetaX11Stack响应这三个信号,响应函数在src/x11/meta-x11-stack.c文件中

  •  "window-added":stack_window_added_cb将window加入x11_stack->added临时列表中

  • "window-removed":stack_window_removed_cb将window加入x11_stack->removed临时列表,window的页框窗口也加入临时页表中

  • "changed": stack_changed_cb,

    • 处理added和removed两个临时列表,整理至x11_stack->xwindows列表中

    • 调用XChangeProperty同步"_NET_CLIENT_LIST"和"_NET_CLIENT_LIST_STACKING"两个根窗口的EWMH属性,前者值为xwindow列表,后者值为从MetaStack中过滤出来的X11窗口栈

struct _MetaStack{   GObject parent;   MetaDisplay *display;  // The MetaDisplay containing this stack.   GList *sorted;         // The MetaWindows of the windows we manage, sorted in order.     // If this is zero, the local stack oughtn't to be brought up to date with the X server's stack, because it is in the middle of being updated.   // If it is positive, the local stack is said to be "frozen", and will need to be thawed that many times before the stack can be brought up to date again.   // You may freeze the stack with meta_stack_freeze() and thaw it with meta_stack_thaw().   int freeze_count;    // 计数可实现嵌套freeze      // The last-known stack of all windows, bottom to top.  We cache it here so that subsequent times we'll be able to do incremental moves.   GArray *last_all_root_children_stacked;   gint n_positions; // Number of stack positions; same as the length of added, but kept for quick reference.     unsigned int need_resort : 1;    // Is the stack in need of re-sorting?   unsigned int need_relayer : 1;   // Are the windows in the stack in need of having their layers recalculated?   unsigned int need_constrain : 1; // Are the windows in the stack in need of having their positions recalculated with respect to transiency (parent and child windows)? };   // 窗口排序需考虑窗口的层次,窗口显示的约束等,先按层次排序,再按位置排序。 static void stack_ensure_sorted (MetaStack *stack) {   // 对stack中的窗口分层,若层次有变化,则需重新计算窗口约束和重新排序。窗口的层次为窗口组中的最高层次   stack_do_relayer (stack);   // 瞬态窗口须在指定窗口之上(MetaWindow::transient_for指定窗口或者所在窗口组的所有窗口),其它类型窗口无此约束   stack_do_constrain (stack);   // 重新排序,先按层,再按位置。重排完成后,延后调用check_fullscreen_func处理全屏窗口   stack_do_resort (stack); }   // 临时窗口类型(瞬态窗口) gboolean meta_window_has_transient_type (MetaWindow *window) {   return (window->type == META_WINDOW_DIALOG ||           window->type == META_WINDOW_MODAL_DIALOG ||           window->type == META_WINDOW_TOOLBAR ||           window->type == META_WINDOW_MENU ||           window->type == META_WINDOW_UTILITY); }

堆跟踪器

MetaStackTracker,跟踪所有窗口,包括unmanaging窗口。窗口用窗口ID表示,X11窗口为32位ID,Wayland窗口ID为64位,其值都大于(1<<32)。Wayland窗口的序列号为0。

unmanaging窗口:the window of withdrawn, destroyed, attaches, detaches, or changes attached parents.

  • 调用stack_tracker_apply_prediction会将window加入unverified列表中,一般有4个操作:record_add, record_remove, raise_above, lower_below。

  • 调用stack_tracker_event_received将unverified列表中的窗口加入verified列表中,调用该函数的有3种情况,Wayland收到X11的事件通知时,共3个通知:CreateNotify, ReparentNotify和ConfigureNotify。(X11窗口的序列号一般为处理时生成的递增序号,用来表示时序)

窗口切换

wayland server在启动时注册了窗口切换快捷键,在init_builtin_key_bindings函数中绑定了默认的快捷键,包含“switch-windows”和“switch-windows-backward”两个事件,由handle_switch函数实现,该函数可响应多个事件,还包括窗口组切换,应用程序切换,面板切换等,分别为"switch-group", "switch-applications", "switch-windows", "switch-panels"等正向切换事件,都对应有反向切换的事件。本次只描述窗口正向切换流程。

  1. meta_display_get_tab_next:从工作台中获取切换窗口

  2. meta_window_activate:激活切换窗口

    1. meta_window_raise:将切换窗口放置在顶层,更新MetaStack窗口栈顺序,同步至MetaStackTracker窗口顺序

      1. meta_window_set_stack_position_no_sync:移动切换窗口,更新MetaStack窗口栈顺序

      2. meta_stack_changed:同步tracker、compositor和X11 server栈信息

        1. 调整MetaStackTracer窗口栈顺序

        2. 发送XConfigureWindow给X11 Server修改server上栈信息,根据信息调整栈位置,更新窗口显示

        3. 同步compositor的窗口栈,即MetaWindowActor栈

      3. meta_stack_update_window_tile_matches:调整窗口平铺位置

    2. meta_window_focus:窗口获取焦点,包括键盘输入焦点和光标

      1. 显示窗口,包括隐藏窗口,第一次显示的窗口,未放置窗口和图标窗口

      2. 发送SetInputFocus请求给X11 server,同步X11 server的焦点信息,由X11 Server处理焦点

      3. 关联输入设备与切换窗口

获取窗口

有几个地方维护了窗口列表,

  • MetaWindow:维护两类窗口列表,一类为原生Wayland窗口列表(wayland_windows);另一类为X11对应的Wayland窗口列表(x11_display->xids)

  • MetaWorkspace:维护mru_list窗口列表,属于该工作台中的窗口列表,按照MRU(most recently used)排序,最近访问的窗口排在前面

从X11和wayland server中找到所有在workspace中的MetaWindow窗口。

  1. 获取所有窗口列表:从MetaDisplay中获取所有的X11和Wayland窗口(不包括override redirect窗口)

  2. 初始化候选窗口列表:获取工作台(workspace)中类型为META_TAB_LIST_NORMAL的窗口列表,非最小化窗口在前,最小化窗口在后,每组列表保序(MRU序),拼接成一个链表

  3. 向前追加状态为demands_attention的窗口值候选窗口列表中:该状态窗口不在工作台中,类型为META_TAB_LIST_NORMAL,需要额外追加

  4. 候选窗口列表中第一个非焦点窗口,即为候选窗口

注:获取所有窗口列表时,列表并未按照MRU排序,在追加demands_attention窗口时,窗口创建越迟越优先。

激活窗口

根据窗口类型分别处理:

  • 普通窗口,将该窗口切换至工作台,并在顶层显示

  • 具备transient_for属性的窗口,将该窗口中脱离父子关系的窗口都切换至同一个工作台中,先将与该窗口脱离关系的祖先窗口显示在顶层,再将该窗口显示在顶层

  • shaded或minimized窗口,需要先做unshade或unminimize

meta_window_activate

meta_window_raise

显示窗口,对于普通窗口只显示自身窗口,对于具有transient_for属性的窗口,先显示祖先窗口,再显示自身窗口

on_stack_changed

meta_stack_tracker_restack_managed

stack_tracker_sync_stack_later

meta_window_focus

update_window_visibilities

显示或隐藏窗口

meta_window_show

meta_window_show主要对四类窗口进行显示,最终调用meta_compositor_show_window显示

  • 隐藏窗口(hidden),显示后的窗口被隐藏了

  • 第一次显示的窗口,窗口已map,但未显示过

  • 未放置窗口:从未计算过窗口的放置位置,也就是从未显示过的窗口

  • 图标窗口:窗口已最小化,使用图标显示的窗口

meta_window_x11_focus

meta_x11_display_set_input_focus
meta_display_update_focus_window