1.3.5 Configure Window
- 1 ReflectStackChange
- 2 ConfigNotify
- 3 MoveWindow
- 3.1 xwl_move_window
- 3.2 compMoveWindow
- 3.3 miMoveWindow
- 3.3.1 compCopyWindow
- 3.3.2 damageCopyWindow
- 3.3.3 glamor_copy_window
- 4 ResizeWindow
- 4.1 xwl_resize_window
- 4.2 compResizeWindow
- 4.3 miResizeWindow
- 5 附录
client通过发送ConfigureWindow请求配置窗口,配置窗口的内容包括窗口位置、窗口尺寸、窗口栈的调整、窗口边框大小等。X11 Server通过ProcConfigureWindow请求,调用ConfigureWindow实现。
根据掩码获取窗口的新坐标,宽高尺寸(CWX | CWY | CWWidth | CWHeight)等。涉及窗口移动和缩放
根据掩码获取窗口边框大小,窗口栈调整位置等
CWBorderWidth:调整窗口边框
CWStackMode:调整窗口在栈中的位置,包括:TopIf、BottomIf、Opposite、Above、Below等;其中CWSibling可指定相对哪个兄弟窗口的栈位置变化
重定向ConfigureWindow请求至管理客户端,如:窗口管理器等,发送ConfigureRequest事件
重定向窗口缩放至管理客户端,发送ResizeRequest事件
通过回调ConfigNotify函数告知服务端的X11扩展协议窗口配置变化,如:composite等
通过发送ConfigureNotify事件,通知感兴趣的client窗口配置变化
回调ChangeBorderWidth处理边框
回调MoveWindow处理窗口移动
回调ResizeWindow处理窗口缩放
调用ReflectStackChange处理窗口在窗口栈中的位置变化
调用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区域。
计算可能变化的可见区域(totalClip),为减少重绘开销。有两种方式:
父窗口的borderClip区域减去pChild之上的子窗口区域(boarderSize),这些子窗口区域的显示内容不会改变
所有与移动窗口有重叠的子窗口的可见区域之和,为父窗口的最大可见区域,这些区域的内容会发生变化
过滤优化:在可见区域(totalClip)内,计算子窗口间是否重叠,若无,则不需重绘
子窗口的clipList,boardList,exposed区域,按照深度优先递归计算窗口的可见区域
窗口当可见性发生变化时(Unobscured, PartiallyObscured, FullyObscured),可能发送VisibilityNotify事件
当可见区域发生变化时,回调ClipNotify函数(present_clip_notify/compClipNotify/xf86XVClipNotify)
计算父窗口的可见区域,需重绘区域(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)事件,采用深度优先遍历窗口树
重绘父窗口图形
Server调用PaintWindow绘制边框
调用WindowExposures
Server调用PaintWindow绘制窗口背景
发送重绘事件给客户端
重绘子窗口图形,流程与上类似
WindowsRestructured
处理光标和键盘焦点,并重构光标窗口:
计算获得当前焦点坐标所在窗口(最低级窗口)
若当前焦点窗口与原窗口不同,则将焦点离开原窗口,进入新窗口
如果是光标设备,则重绘光标窗口,根据光标所在窗口特性,可绘制不同的光标图形,一般由miPointerDisplayCursor函数实现,动画光标由AnimCurDisplayCursor实现
ConfigNotify
ConfigNotify回调通知X11扩展窗口配置发送变化,涉及一个回调函数链,其调用顺序为:
present_config_notify:发送PresentConfigureNotify扩展事件给客户端
compConfigNotify:针对拥有compWindow的窗口进行处理,若窗口尺寸有变化(ResizeWindow),则分配新的pixmap
DRI2ConfigNotify:失效原有的显存内存
present_config_notify
发送PresentConfigureNotify扩展事件给客户端。
compConfigNotify
针对拥有compWindow的窗口进行处理,若窗口尺寸有变化,则分配新的pixmap,并将父窗口pixmap中新区域的窗口内容拷贝至新的pixmap中,若窗口的色彩深度与父窗口不同,则内容需要进行合成,旧的pixmap缓冲在cw->pOldPixmap中
MoveWindow
移动窗口涉及窗口原位置遮挡的兄弟窗口需要显示内容,窗口移动后须在新位置显示窗口内容。其实现涉及一个回调函数链,包括:xwayland、composite、machine independent的回调函数。其调用顺序为:
xwl_move_window:更新顶层窗口的视口(viewport)
compMoveWindow:处理compWindow
miMoveWindow:移动窗口,重新计算子窗口的位置和尺寸,显示窗口内容等
xwl_move_window
在非根模式下,额外处理顶级窗口与视口之间的关系,窗口内容在Wayland compositor视口中显示,可能只显示一部分内容。
compMoveWindow
回收compWindow中旧的pixmap资源
miMoveWindow
实现窗口移动功能,窗口移动涉及原被遮挡窗口内容显示,窗口新位置内容显示,窗口栈的更新等,同时其子窗口也随之移动。
标记在父窗口树中被pWin覆盖的窗口和子窗口,包括pWin本身以及子窗口
更新pWin窗口尺寸和坐标,坐标平移,截取超过父窗口边界的区域
更新pWin子窗口的尺寸和坐标,坐标平移,截取超过pWin窗口边界的区域
根据pWin的新窗口,计算屏幕可能变化的区域,并计算窗口及子窗口的可见区域,重绘区域等。
将pWin窗口的原有效内容迁移至新窗口内
根据窗口exposed向client发送重绘请求
compCopyWindow
分两种情况更新新区域内容
窗口有compWindow,若窗口尺寸未变,则创建Damage,告知新区域内容有变化;若窗口尺寸变化时,pixmap为新区域的内容,需将旧pixmap的内容拷贝至pixmap中,更新窗口内容至新区域。
窗口无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 | 子窗口位置 |
---|---|---|
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设置)。实现思路为:
统计不同winGravity子窗口在原窗口中的可见区域
标记在父窗口树中被pWin覆盖的窗口和子窗口,包括pWin本身以及子窗口
更新pWin窗口尺寸和坐标,变为新窗口区域
更新pWin子窗口的尺寸和坐标,子窗口的坐标变化受其winGravity影响(而非平移),尺寸截取在pWin新窗口内。子窗口的子窗口不受winGravity影响,与其父窗口保存相对位置
根据pWin的新窗口,计算屏幕可能变化的区域,并计算窗口及子窗口的可见区域,重绘区域等。
根据pWin的bitGravity计算窗口预留内容在新窗口的起始位置(nx, ny)
计算原窗口已绘制区域在新窗口有效的区域。新窗口的有效可见区域与原窗口已绘制区域在新窗口的重叠区。并将该区域归至相同gravity的子窗口区域中
将子窗口原内容迁移至新窗口内
确定子窗口的有效区域:计算Gravity分组的原子窗口区域与其在新窗口的重叠区域,该区域为子窗口有效区域
将子窗口有效区域的内容移至新窗口中
计算子窗口其它可见区域,用于通知client重绘
根据窗口exposed向client发送重绘请求
附录
ConfigureWindow
miComputeClips
WhereDoIGoInTheStack
根据pWin,pSib,smode以及pWin的原始坐标,计算pWin重排位置,返回pWin窗口之下的第一个兄弟窗口
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重排在栈底层 |