1.3.5 Configure Window

client通过发送ConfigureWindow请求配置窗口,配置窗口的内容包括窗口位置、窗口尺寸、窗口栈的调整、窗口边框大小等。X11 Server通过ProcConfigureWindow请求,调用ConfigureWindow实现。

  1. 根据掩码获取窗口的新坐标,宽高尺寸(CWX | CWY | CWWidth | CWHeight)等。涉及窗口移动和缩放

  2. 根据掩码获取窗口边框大小,窗口栈调整位置等

    1. CWBorderWidth:调整窗口边框

    2. CWStackMode:调整窗口在栈中的位置,包括:TopIf、BottomIf、Opposite、Above、Below等;其中CWSibling可指定相对哪个兄弟窗口的栈位置变化

    3. 重定向ConfigureWindow请求至管理客户端,如:窗口管理器等,发送ConfigureRequest事件

    4. 重定向窗口缩放至管理客户端,发送ResizeRequest事件

    5. 通过回调ConfigNotify函数告知服务端的X11扩展协议窗口配置变化,如:composite等

    6. 通过发送ConfigureNotify事件,通知感兴趣的client窗口配置变化

    7. 回调ChangeBorderWidth处理边框

    8. 回调MoveWindow处理窗口移动

    9. 回调ResizeWindow处理窗口缩放

    10. 调用ReflectStackChange处理窗口在窗口栈中的位置变化

    11. 调用CheckCursorConfinement处理在窗口以及子窗口上的光标

ReflectStackChange

对pWin窗口进行重排,新暴露的窗口的可见区域需要进行重绘。

// 将pWin重排在pSib之上,若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; // 更新pWin在窗口栈的位置,并计算第一个在栈中重新暴露的窗口: // pWin在pNextSib之上(窗口下移),则为pWin->nextSib // pWin在pNextSib之下(窗口上移),则为pWin pFirstChange = MoveWindowInStack(pWin, pSib); if (WasViewable) { // 调用miMarkOverlappedWindows,从pFirstChange开始向后遍历其兄弟窗口,标记被pWin重叠的窗口,若存在返回true anyMarked = (*pScreen->MarkOverlappedWindows) (pWin, pFirstChange, &pLayerWin); if (pLayerWin != pWin) pFirstChange = pLayerWin; if (anyMarked) { // 调用miValidateTree重新计算pWin父窗口及其子窗口的可见区域,并计算待重新绘制区域(需重新显示的区域) (*pScreen->ValidateTree) (pLayerWin->parent, pFirstChange, kind); // 调用miHandleValidateExposures,深度优先,先绘制边框,再绘制背景,然后发送Expose事件给客户端绘制 (*pScreen->HandleExposures) (pLayerWin->parent); if (pWin->drawable.pScreen->PostValidateTree) (*pScreen->PostValidateTree) (pLayerWin->parent, pFirstChange, kind); } } if (pWin->realized) WindowsRestructured(); }

MarkOverlappedWindows

miMarkOverlappedWindows实现了MarkOverlappedWindows回调功能。该函数主要时过滤一批确定不需要重绘的窗口,识别有可能会需重新绘制的窗口,从而减少后续的计算量。在pWin窗口之上兄弟窗口不存在重绘的需求,可以过滤;在pWin窗口之下的窗口有可能会要求重新绘制,如:pWin窗口缩小了,pWin窗口的边框改变了,pWin窗口移动了等。

// pWin为需重排的窗口,pFirst为第一个改变显示的窗口,pWin和pFirst为同一层窗口 // pWin窗口向下移,pFirst为窗口原堆叠层的pWin的下一个相邻窗口(pWin->nextSib),标记与pWin重叠的窗口 // pWin窗口向上移,pFirst为pWin本身窗口,标记被pWin遮挡的窗口 Bool miMarkOverlappedWindows(WindowPtr pWin, WindowPtr pFirst, WindowPtr *ppLayerWin){ BoxPtr box; WindowPtr pChild, pLast; Bool anyMarked = FALSE; MarkWindowProcPtr MarkWindow = pWin->drawable.pScreen->MarkWindow; if (ppLayerWin) *ppLayerWin = pWin; if (pWin == pFirst) { // Blindly mark pWin and all of its inferiors. This is a slight overkill // if there are mapped windows that outside pWin's border, // but it's better than wasting time on RectIn checks. pChild = pWin; // 深度优先遍历,初始化该窗口及其子窗口的窗口和边框区域 // 子窗口的区域限制在pWin的窗口(winSize)区域内 while (1) { if (pChild->viewable) { // 根据窗口的drawable构建winSize区域,该区域限制在父窗口的winSize范围内 if (RegionBroken(&pChild->winSize)) SetWinSize(pChild); // 包括窗口边框的区域,该区域限制在父窗口的winSize范围内 if (RegionBroken(&pChild->borderSize)) SetBorderSize(pChild); // miMarkWindow, 构建Validate对象,记录起始坐标 (*MarkWindow) (pChild); if (pChild->firstChild) { pChild = pChild->firstChild; continue; } } while (!pChild->nextSib && (pChild != pWin)) pChild = pChild->parent; // pWin的子窗口遍历完成,退出循环 if (pChild == pWin) break; pChild = pChild->nextSib; } // pWin窗口向上移,pWin为第一个暴露的窗口,可能需要重绘,因此需标记 anyMarked = TRUE; // 下一个相邻窗口 pFirst = pFirst->nextSib; } if ((pChild = pFirst)) { // pWin区间,窗口向上移,该区域会遮挡相邻窗口,向下移,则会暴露相邻窗口 box = RegionExtents(&pWin->borderSize); pLast = pChild->parent->lastChild; while (1) { if (pChild->viewable) { if (RegionBroken(&pChild->winSize)) SetWinSize(pChild); if (RegionBroken(&pChild->borderSize)) SetBorderSize(pChild); // 区域有重叠,这标记该窗口,可能被遮挡,也可能会被暴露 if (RegionContainsRect(&pChild->borderSize, box)) { // 调用miMarkWindow (*MarkWindow) (pChild); anyMarked = TRUE; if (pChild->firstChild) { pChild = pChild->firstChild; continue; } } } while (!pChild->nextSib && (pChild != pLast)) pChild = pChild->parent; if (pChild == pLast) break; pChild = pChild->nextSib; } } if (anyMarked) (*MarkWindow) (pWin->parent); return anyMarked; }

ValidateTree

计算窗口及其子窗口的clipList,boardList,exposed区域。

  1. 计算可能变化的可见区域(totalClip),为减少重绘开销。有两种方式:

    1. 父窗口的borderClip区域减去pChild之上的子窗口区域(boarderSize),这些子窗口区域的显示内容不会改变

    2. 所有与移动窗口有重叠的子窗口的可见区域之和,为父窗口的最大可见区域,这些区域的内容会发生变化

  2. 过滤优化:在可见区域(totalClip)内,计算子窗口间是否重叠,若无,则不需重绘

  3. 子窗口的clipList,boardList,exposed区域,按照深度优先递归计算窗口的可见区域

    1. 窗口当可见性发生变化时(Unobscured, PartiallyObscured, FullyObscured),可能发送VisibilityNotify事件

    2. 当可见区域发生变化时,回调ClipNotify函数(present_clip_notify/compClipNotify/xf86XVClipNotify)

  4. 计算父窗口的可见区域,需重绘区域(exposed)等

int miValidateTree(WindowPtr pParent, WindowPtr pChild, VTKind kind) { // Total clipping region available to the marked children. // pParent's clipList merged with the borderClips of all the marked children. RegionRec totalClip; RegionRec childClip; /* The new borderClip for the current child */ RegionRec childUnion; /* the space covered by borderSize for all marked children */ RegionRec exposed; /* For intermediate calculations */ WindowPtr pWin; Bool overlap; int viewvals; Bool forward; ScreenPtr pScreen = pParent->drawable.pScreen; if (pChild == NullWindow) pChild = pParent->firstChild; RegionNull(&childClip); RegionNull(&exposed); // compute the area of the parent window occupied by the marked children + the parent itself. // This is the area which can be divied up among the marked children in their new configuration. RegionNull(&totalClip); viewvals = 0; // 计算可能变化的可见区域(totalClip),有两种方法: // 1. 父窗口的borderClip区域减去pChild之上的子窗口区域(boarderSize),这些子窗口区域的显示内容不会改变 // 2. 所有与移动窗口有重叠的子窗口的可见区域之和,为父窗口的最大可见区域,这些区域的内容会发生变化 if (RegionBroken(&pParent->clipList) && !RegionBroken(&pParent->borderClip)) { // RegionBroken表示数据空间为空,一般来说根窗口的clipList置为broken,因为根窗口时常变化。 kind = VTBroken; // When rebuilding clip lists after out of memory, assume everything is busted. forward = TRUE; RegionCopy(&totalClip, &pParent->borderClip); RegionIntersect(&totalClip, &totalClip, &pParent->winSize); // 在pChild之上的相邻窗口,遮挡了pParent,减去pWin的窗口大小(加上边框) // 如果pWin为compositor client管理,可能需要透明化,因此不能将其区域去掉 for (pWin = pParent->firstChild; pWin != pChild; pWin = pWin->nextSib) { if (pWin->viewable && !TreatAsTransparent(pWin)) RegionSubtract(&totalClip, &totalClip, &pWin->borderSize); } for (pWin = pChild; pWin; pWin = pWin->nextSib) if (pWin->valdata && pWin->viewable) viewvals++; RegionEmpty(&pParent->clipList); } else { // pChild后续相邻窗口的可见区域之和,即为pWin最大可见区域 if ((pChild->drawable.y < pParent->lastChild->drawable.y) || ((pChild->drawable.y == pParent->lastChild->drawable.y) && (pChild->drawable.x < pParent->lastChild->drawable.x))) { forward = TRUE; for (pWin = pChild; pWin; pWin = pWin->nextSib) { if (pWin->valdata) { // 与移动窗口有重叠 RegionAppend(&totalClip, getBorderClip(pWin)); if (pWin->viewable) viewvals++; } } } else { forward = FALSE; pWin = pParent->lastChild; while (1) { if (pWin->valdata) { // 与移动窗口有重叠 RegionAppend(&totalClip, getBorderClip(pWin)); if (pWin->viewable) viewvals++; } if (pWin == pChild) break; pWin = pWin->prevSib; } } RegionValidate(&totalClip, &overlap); } // Now go through the children of the root and figure their new borderClips from the totalClip, // passing that off to miComputeClips to handle recursively. // Once that's done, we remove the child from the totalClip to clip any siblings below it. overlap = TRUE; // 在可见区域(totalClip)内,计算子窗口间是否重叠,若无,则不需重绘 if (kind != VTStack) { // 子窗口的可见区域加上父窗口的可见区域,为父窗口整个可见区域 RegionUnion(&totalClip, &totalClip, &pParent->clipList); if (viewvals > 1) { // precompute childUnion to discover whether any of them overlap. // This seems redundant, but performance studies have demonstrated // that the cost of this loop is lower than the cost of multiple Subtracts in the loop below. RegionNull(&childUnion); if (forward) { for (pWin = pChild; pWin; pWin = pWin->nextSib) if (pWin->valdata && pWin->viewable && !TreatAsTransparent(pWin)) RegionAppend(&childUnion, &pWin->borderSize); } else { pWin = pParent->lastChild; while (1) { if (pWin->valdata && pWin->viewable && !TreatAsTransparent(pWin)) RegionAppend(&childUnion, &pWin->borderSize); if (pWin == pChild) break; pWin = pWin->prevSib; } } // 窗口之间是否有重叠,若无重叠,不需要重绘。 RegionValidate(&childUnion, &overlap); if (overlap) RegionUninit(&childUnion); } } for (pWin = pChild; pWin != NullWindow; pWin = pWin->nextSib) { if (pWin->viewable) { if (pWin->valdata) { RegionIntersect(&childClip, &totalClip, &pWin->borderSize); // 计算pWin及其子窗口的clipList,boardList,exposed区域 miComputeClips(pWin, pScreen, &childClip, kind, &exposed); if (overlap && !TreatAsTransparent(pWin)) { // pWin遮挡了下层的窗口,下层窗口对boarderSize区域不可见,因此可以去掉该区域 RegionSubtract(&totalClip, &totalClip, &pWin->borderSize); } } else if (pWin->visibility == VisibilityNotViewable) { miTreeObscured(pWin); } } else { // 不可显示,则清空clipList,删除临时数据 if (pWin->valdata) { RegionEmpty(&pWin->clipList); if (pScreen->ClipNotify) (*pScreen->ClipNotify) (pWin, 0, 0); RegionEmpty(&pWin->borderClip); pWin->valdata = NULL; } } } RegionUninit(&childClip); // 计算父窗口的可剪接区域,需重绘区域等 if (!overlap) { // 没重叠,减去子窗口的可见区域,即为父窗口的可见区域 RegionSubtract(&totalClip, &totalClip, &childUnion); RegionUninit(&childUnion); } RegionNull(&pParent->valdata->after.exposed); RegionNull(&pParent->valdata->after.borderExposed); // each case below is responsible for updating the clipList and serial number for the parent window switch (kind) { case VTStack: // 子窗口间的堆叠层次变化,不影响父窗口的clipList区域 break; default: // totalClip contains the new clipList for the parent. // Figure out exposures and obscures as per miComputeClips and reset the parent's clipList. // 只重绘未绘制区域。clipList中的区域已绘制。边框不需要重绘,因为子窗口从未覆盖边框 RegionSubtract(&pParent->valdata->after.exposed, &totalClip, &pParent->clipList); /* fall through */ case VTMap: // 更新clipList为新的可见区域 RegionCopy(&pParent->clipList, &totalClip); pParent->drawable.serialNumber = NEXT_SERIAL_NUMBER; break; } RegionUninit(&totalClip); RegionUninit(&exposed); if (pScreen->ClipNotify) (*pScreen->ClipNotify) (pParent, 0, 0); return 1; }

HandleValidateExposures

发送重绘(Expose)事件,采用深度优先遍历窗口树

  1. 重绘父窗口图形

    1. Server调用PaintWindow绘制边框

    2. 调用WindowExposures

      1. Server调用PaintWindow绘制窗口背景

      2. 发送重绘事件给客户端

  2. 重绘子窗口图形,流程与上类似

 

WindowsRestructured

处理光标和键盘焦点,并重构光标窗口:

  1. 计算获得当前焦点坐标所在窗口(最低级窗口)

  2. 若当前焦点窗口与原窗口不同,则将焦点离开原窗口,进入新窗口

  3. 如果是光标设备,则重绘光标窗口,根据光标所在窗口特性,可绘制不同的光标图形,一般由miPointerDisplayCursor函数实现,动画光标由AnimCurDisplayCursor实现

ConfigNotify

ConfigNotify回调通知X11扩展窗口配置发送变化,涉及一个回调函数链,其调用顺序为:

  1. present_config_notify:发送PresentConfigureNotify扩展事件给客户端

  2. compConfigNotify:针对拥有compWindow的窗口进行处理,若窗口尺寸有变化(ResizeWindow),则分配新的pixmap

  3. DRI2ConfigNotify:失效原有的显存内存

present_config_notify

发送PresentConfigureNotify扩展事件给客户端。

compConfigNotify

针对拥有compWindow的窗口进行处理,若窗口尺寸有变化,则分配新的pixmap,并将父窗口pixmap中新区域的窗口内容拷贝至新的pixmap中,若窗口的色彩深度与父窗口不同,则内容需要进行合成,旧的pixmap缓冲在cw->pOldPixmap中

MoveWindow

移动窗口涉及窗口原位置遮挡的兄弟窗口需要显示内容,窗口移动后须在新位置显示窗口内容。其实现涉及一个回调函数链,包括:xwayland、composite、machine independent的回调函数。其调用顺序为:

  1. xwl_move_window:更新顶层窗口的视口(viewport)

  2. compMoveWindow:处理compWindow

  3. miMoveWindow:移动窗口,重新计算子窗口的位置和尺寸,显示窗口内容等

xwl_move_window

在非根模式下,额外处理顶级窗口与视口之间的关系,窗口内容在Wayland compositor视口中显示,可能只显示一部分内容。

compMoveWindow

回收compWindow中旧的pixmap资源

miMoveWindow

实现窗口移动功能,窗口移动涉及原被遮挡窗口内容显示,窗口新位置内容显示,窗口栈的更新等,同时其子窗口也随之移动。

  1. 标记在父窗口树中被pWin覆盖的窗口和子窗口,包括pWin本身以及子窗口

  2. 更新pWin窗口尺寸和坐标,坐标平移,截取超过父窗口边界的区域

  3. 更新pWin子窗口的尺寸和坐标,坐标平移,截取超过pWin窗口边界的区域

  4. 根据pWin的新窗口,计算屏幕可能变化的区域,并计算窗口及子窗口的可见区域,重绘区域等。

  5. 将pWin窗口的原有效内容迁移至新窗口内

  6. 根据窗口exposed向client发送重绘请求

compCopyWindow

分两种情况更新新区域内容

  1. 窗口有compWindow,若窗口尺寸未变,则创建Damage,告知新区域内容有变化;若窗口尺寸变化时,pixmap为新区域的内容,需将旧pixmap的内容拷贝至pixmap中,更新窗口内容至新区域。

  2. 窗口无compWindow,则传递给后续的CopyWindow处理

damageCopyWindow

创建Damage,记录变化区域

glamor_copy_window

使用openGL拷贝数据

ResizeWindow

窗口缩放包含窗口尺寸变化,窗口位置变化等。当窗口尺寸发生变化时,需要计算窗口预留的内容以及子窗口的位置,这些有两个gravity参数标记:bitGravity和winGravity,这些均在miResizeWindow中实现

  • bitGravity:父窗口尺寸发生变化时,确定父窗口预留的窗口内容,以及在新窗口的放置位置

  • winGravity:父窗口尺寸发生变化时,子窗口的winGravity指定了如何计算子窗口的坐标

假设父窗口的尺寸变化为(dw, dh),坐标变化为(dx,dy),子窗口的原坐标为(origx,origy),子窗口的winGravity值如表,可计算其新坐标位置。具体实现在ResizeChildrenWinSize中。

winGravity

Deltas

子窗口位置

winGravity

Deltas

子窗口位置

UnmapGravity

N/A

UnmapWindow

NorthWestGravity

[0, 0]

[origx + dx, origy + dy]

North

[dw/2, 0]

[origx + dx + dw/2, origy + dy]

NorthEast

[dw, 0]

[origx + dx + dw, origy + dy]

West

[0, dh/2]

[origx + dx, origy + dy + dh/2]

Center

[dw/2, dh/2]

[origx + dx + dw/2, origy + dy + dh/2]

East

[dw, dh/2]

[origx + dx + dw, origy + dy + dh/2]

SouthWest

[0, dh]

[origx + dx, origy + dy + dh]

South

[dw/2, dh]

[origx + dx + dw/2, origy + dy + dh]

SouthEast

[dw, dh]

[origx + dx + dw, origy + dy + dh]

StaticGravity

N/A

[origx, origy]

xwl_resize_window

在非根模式下,额外处理顶级窗口与视口之间的关系,窗口内容在Wayland compositor视口中显示,可能只显示一部分内容。

compResizeWindow

回收compWindow中旧的pixmap资源。在compConfigNofity中,由于窗口尺寸的变化,重新分配了窗口的pixmap,原窗口的pixmap缓冲在pOldPixmap中。在miResizeWindow已经利用pOldPixmap将窗口原有内容移至新的pixmap中,此时可销毁旧的pixmap。

miResizeWindow

窗口缩放可能伴随窗口移动,窗口缩放涉及子窗口位置的变动,根据子窗口的winGravity来调整子窗口在窗口中的相对位置(子窗口的布局在新窗口内可能发生变化),子窗口不会缩放,但会窗口区域会截取在父窗口区域内;窗口缩放时涉及原窗口内容在新窗口中的放置位置,由窗口bitGravity来确定,窗口缩小,只能保留原窗口部分内容(保留哪些内容由bitGravity设置);窗口扩大,可保留原窗口所有内容,新窗口部分区域无内容(或者composite将新窗口内容初始化为该区域的父窗口内容)(初始放置在哪个位置由bitGravity设置)。实现思路为:

  1. 统计不同winGravity子窗口在原窗口中的可见区域

  2. 标记在父窗口树中被pWin覆盖的窗口和子窗口,包括pWin本身以及子窗口

  3. 更新pWin窗口尺寸和坐标,变为新窗口区域

  4. 更新pWin子窗口的尺寸和坐标,子窗口的坐标变化受其winGravity影响(而非平移),尺寸截取在pWin新窗口内。子窗口的子窗口不受winGravity影响,与其父窗口保存相对位置

  5. 根据pWin的新窗口,计算屏幕可能变化的区域,并计算窗口及子窗口的可见区域,重绘区域等。

  6. 根据pWin的bitGravity计算窗口预留内容在新窗口的起始位置(nx, ny)

  7. 计算原窗口已绘制区域在新窗口有效的区域。新窗口的有效可见区域与原窗口已绘制区域在新窗口的重叠区。并将该区域归至相同gravity的子窗口区域中

  8. 将子窗口原内容迁移至新窗口内

    1. 确定子窗口的有效区域:计算Gravity分组的原子窗口区域与其在新窗口的重叠区域,该区域为子窗口有效区域

    2. 将子窗口有效区域的内容移至新窗口中

    3. 计算子窗口其它可见区域,用于通知client重绘

  9. 根据窗口exposed向client发送重绘请求

附录

ConfigureWindow

miComputeClips

WhereDoIGoInTheStack

根据pWin,pSib,smode以及pWin的原始坐标,计算pWin重排位置,返回pWin窗口之下的第一个兄弟窗口

smode

pSib

NULL

smode

pSib

NULL

Aboe

pWin重排在pSib之上

pWin重排在栈的顶层

Below

pWin重排在pSib之下

pWin重排在栈的底层

TopIf

若pSib遮挡了pWin,则pWin重排在栈顶层

若有兄弟窗口遮挡pWin,则pWin重排在栈顶层

BottomIf

若pWin遮挡了pSib,则pWin重排在栈底层

若pWin遮挡任意兄弟窗口,则pWin重排在栈底层

Opposite

若pSib遮挡了pWin,则pWin重排在栈顶层

若pWin遮挡了pSib,则pWin重排在栈底层

若有兄弟窗口遮挡pWin,则pWin重排在栈顶层

若pWin遮挡任意兄弟窗口,则pWin重排在栈底层