/
Configure Window

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重排在栈底层

Add label

Related content

1.3.5 Configure Window
1.3.5 Configure Window
More like this
1.3.4 桌面窗口事件交互流程
1.3.4 桌面窗口事件交互流程
More like this
桌面窗口事件交互流程
桌面窗口事件交互流程
More like this
1.3.1 Composite extension
1.3.1 Composite extension
More like this
Composite extension
Composite extension
More like this
a. display & window之显示
a. display & window之显示
More like this