转至元数据结尾
转至元数据起始

xwayland是X11协议服务端的一种实现,其运行在Wayland server上。rootless window是由wayland server提供X11窗口管理和合成器,统一Wayland窗口和X11窗口。

1. 框架

wayland compositor提供了Wayland display、window manager和compositor功能,同时提供了xwayland manager功能(XWM),提供X11窗口管理,作为xwayland窗口状态和WWM之间的桥梁。

xwayland在框架中既作为X11应用和XWM的显示服务器,也作为Wayland compositor的wayland客户端。使用Wayland compositor作为输入输出,不再需要DDX驱动。

image-20240218-061900.png

在xwayland与Wayland compositor有两个异步通信通道,一个是XWM与xwayland通过X11协议通信,另一个是xwayland与wayland compositor通过Wayland协议通信。

在xwayland中,每个window在Wayland中存在一个对应的wl_surface对象,该surface对象用作输入输入出。

输入事件引用surface对象,该对象向Wayland compositor提供X11 window中的内容;X11 window和wl_surface位于不同协议服务端中,需要建立关联关系。

xwayland在realize_window过程中,调用wl_compositor_create_surface创建wl_surface对象,通过发送"WL_SURFACE_ID" atom的X11 ClientMessage事件发送给XWM,XWM将X11 window和wl_surface关联在一起。

2. xwanland实现

相比于原生xserver,增加了窗口和缩放处理的回调函数

2.1 初始化回调函数

2.1.1 窗口相关

窗口相关操作需要与wayland compositor交互

callback

implement

description

CreateWindow

xwl_glamor_create_window

增加识别合成器隐形重定向的窗口,即视觉配置与祖先不同的窗口

ReparentWindow

xwl_glamor_reparent_window

同上

RealizeWindow

xwl_realize_window

创建damage,wl_surface,并使wl_surface关联X11 window

UnrealizeWindow

xwl_unrealize_window

回收xwl相关显示资源

DestroyWindow

xwl_destroy_window

回收xwl相关资源

ResizeWindow

xwl_resize_window

调整分辨率引起的视口(viewport)坐标调整

MoveWindow

xwl_move_window

同上

XYToWindow

xwl_xy_to_window

处理鼠标移动,跨X11和wayland窗口的场景,若是则返回X11根窗口

CloseScreen

xwl_close_screen

回收资源

ChangeWindowAttributes

xwl_change_window_attributes

跟踪窗口管理器的注册,记录窗口管理器client ID

SetWindowPixmap

xwl_window_set_window_pixmap

画布尺寸变化时,释放wl_surface的xwl_window_buffer资源

CursorWarpedTo

xwl_cursor_warped_to

CursorConfinedTo

xwl_cursor_confined_to

CreateScreenResources

xwl_glamor_create_screen_resources

// 检查光标精灵是否丢失了光标焦点,及精灵跟踪的窗口栈是否失效,window为获取光标焦点的窗口
static Bool sprite_check_lost_focus(SpritePtr sprite, WindowPtr window){
    DeviceIntPtr device, master;
    struct xwl_seat *xwl_seat;
    // 找到sprite对应的wayland输入设备
    for (device = inputInfo.devices; device; device = device->next) {
        if (device->deviceProc == xwl_pointer_proc &&
            device->spriteInfo->sprite == sprite)
            break;
    }
    if (!device)
        return FALSE;
 
    xwl_seat = device->public.devicePrivate;
    if (!xwl_seat)
        return FALSE;
 
    master = GetMaster(device, POINTER_OR_FLOAT);
    if (!master || !master->lastSlave)
        return FALSE;
 
    // xwl_seat需为与主光标设备关联的从属设备
    if (master->lastSlave != get_pointer_device(xwl_seat))
        return FALSE;
 
    // 光标在X11窗口中,但光标锁定窗口与焦点窗口不同,涉及窗口切换
    if (xwl_seat->focus_window != NULL &&
        xwl_seat->cursor_confinement_window != NULL &&
        xwl_seat->focus_window != xwl_seat->cursor_confinement_window)
        return TRUE;
 
    // 光标不在X11窗口系统中,最后一次离开时的光标焦点窗口是当前获取焦点窗口的自身或祖先窗口
    if (xwl_seat->focus_window == NULL &&
        xwl_seat->last_xwindow != NullWindow &&
        (IsParent(xwl_seat->last_xwindow, window) || xwl_seat->last_xwindow == window))
        return TRUE;
 
    return FALSE;
}
 
// 根据(x,y)坐标,找到对应的窗口,光标将聚焦在该窗口上
static WindowPtr xwl_xy_to_window(ScreenPtr screen, SpritePtr sprite, int x, int y){
    struct xwl_screen *xwl_screen = xwl_screen_get(screen);
    // 返回(x,y)坐标所在的窗口
   WindowPtr ret = miXYToWindow(screen, sprite, x, y);
 
    // 场景:光标从Wayland窗口系统进入X11窗口系统,但X11窗口系统跟踪了上次离开X11窗口系统时的光标信息
    // 采用返回X11系统根窗口的方式,使X11系统能发送一个LeaveNotify事件通知client进行光标处理
    if (sprite_check_lost_focus(sprite, ret)) {
        sprite->spriteTraceGood = 1;
        return sprite->spriteTrace[0];
    }
    return ret;
}

2.1.2 缩放相关

与CRTC相关操作,非本地硬件,其操作需通过Wayland compositor实现

callback

implement

description

rrGetInfo

xwl_randr_get_info

rrSetConfig

xwl_randr_set_config

rrScreenSetSize

xwl_randr_screen_set_size

rrCrtcSet

xwl_randr_crtc_set

rrCrtcGet

xwl_randr_crtc_get

rrCrtcSetGamma

xwl_randr_crtc_set_gamma

rrCrtcGetGamma

xwl_randr_crtc_get_gamma

rrOutputSetProperty

xwl_randr_output_set_property

rrOutputValidateMode

xwl_output_validate_mode

rrModeDestroy

xwl_randr_mode_destroy

rrRequestLease

xwl_randr_request_lease

rrGetLease

xwl_randr_get_lease

rrTerminateLease

xwl_randr_terminate_lease

2.1.3 显存相关

callback

implement

description

init_wl_registry

xwl_glamor_gbm_init_wl_registry

has_wl_interfaces

xwl_glamor_gbm_has_wl_interfaces

init_egl

xwl_glamor_gbm_init_egl

init_screen

xwl_glamor_gbm_init_screen

get_wl_buffer_for_pixmap

xwl_glamor_gbm_get_wl_buffer_for_pixmap

check_flip

get_main_device

xwl_gbm_get_main_device

create_pixmap_for_window

xwl_glamor_gbm_create_pixmap_for_window

2.2 创建根窗口

创建X11根窗口,主要是三大步,假设Wayland server已经启动

  1. CreateRootWindow:创建根窗口对象,初始化窗口尺寸,属性等,几个关键属性设置为:

    1. cursorIsNone = TRUE

    2. backingStore = NotUseful

    3. visibility = VisibilityNotViewable

    4. redirectDraw = RedirectDrawNone

    5. forcedBG = FALSE

    6. backingStoreSupport =WhenMapped

    7. clipList = winSize = borderSize = borderClip = {0,0,0,0} :窗口区域,剪切区域,边框区域的坐标和宽高均为0

  2. CreateRootCursor:创建光标对象,分配资源

  3. InitRootWindow:初始化和显示根窗口,主要由几个回调函数完成

    1. CreateWindow:实际未做任何事情

    2. PositionWindow:实际未做任何事情

    3. ChangeWindowAttributes:获取窗口管理器的client ID

    4. RealizeWindow:初始化根窗口的可绘制区间

    5. ClipNotify:根窗口未使用扩展功能,实际未做任何事情

    6. PostValidateTree:实际未做任何事情

    7. WindowExposures:根窗口给定的区间为空(clipList为空),实际未做任何事情

创建根窗口后,未通知wayland server,根窗口与Wayland未建立联系,wayland不管理X11根窗口,其画布为空,不会被渲染。

注:

  1. xserver的回调一般为链式回调,由X11 核心协议和扩展协议实现具体功能,大部分执行过程可能是逆向的,具体要根据回调函数的实现

  2. 创建根窗口的过程不会向外发送事件

  3. 代码示例都经简化,只关注根窗口的执行流程,回调函数用实际函数代替

  4. 非根模式下,Screen的宽高均为0. width=height=0,默认值,未初始化宽高

2.2.1 CreateWindow

xwl_glamor_create_window → compCreateWindow → fbCreateWindow

在该环境下,只有fbCreateWindow初始化了窗口的画布,其它函数不做任何处理。

Bool fbCreateWindow(WindowPtr pWin) {
    // 初始化窗口画布PixMapPtr,非根模式下由fbCreatePixmap,而根模式由screen->CreatePixmap链式回调创建
    dixSetPrivate(&pWin->devPrivates, fbGetWinPrivateKey(pWin), fbGetScreenPixmap(pWin->drawable.pScreen));
    return TRUE;
}
 
Bool compCreateWindow(WindowPtr pWin) {
    ScreenPtr pScreen = pWin->drawable.pScreen;
    Bool ret = fbCreateWindow (pWin);
    // 条件不满足,根窗口没有父窗口
    if (pWin->parent && ret) {
        CompSubwindowsPtr csw = GetCompSubwindows(pWin->parent);
        CompClientWindowPtr ccw;
        PixmapPtr parent_pixmap = (*pScreen->GetWindowPixmap)(pWin->parent);
        PixmapPtr window_pixmap = (*pScreen->GetWindowPixmap)(pWin);
 
        if (window_pixmap != parent_pixmap)
            (*pScreen->SetWindowPixmap) (pWin, parent_pixmap);
        if (csw)
            for (ccw = csw->clients; ccw; ccw = ccw->next)
                compRedirectWindow(clients[CLIENT_ID(ccw->id)],
                                   pWin, ccw->update);
        if (compImplicitRedirect(pWin, pWin->parent))
            compRedirectWindow(serverClient, pWin, CompositeRedirectAutomatic);
    }
    return ret;
}
 
static Bool xwl_glamor_create_window(WindowPtr window) {
    // 条件不满足,根窗口没有父窗口
    if (window->parent)
        xwl_avoid_implicit_redirect(window);
    return compCreateWindow (window);
}

注:

  • 在初始化Screen时,xwl_screen_init将非根模式的Screen的clip_root_mode设置为ROOT_CLIP_INPUT_ONLY,所有渲染操作都会重定向。

  • 在创建Screen资源时,xwl_glamor_create_screen_resources在非根模式采用fbCreatePixmap创建画布(且宽高均为0),而根模式由screen->CreatePixmap链式回调创建

2.2.2 PositionWindow

miDbePositionWindow → compPositionWindow → fbPositionWindow

非根模式的根窗口未使用双缓冲,miDbePositionWindow中双缓冲部分不需处理,其相当于直接调用compPositionWindow,而根窗口的宽高和画布未修改,PositionWindow未做任何事。

// 桩函数,啥都不做
Bool fbPositionWindow(WindowPtr pWin, int x, int y){ return TRUE; }
 
Bool
compPositionWindow(WindowPtr pWin, int x, int y)
{
    ScreenPtr pScreen = pWin->drawable.pScreen;
    Bool ret = TRUE;
    // 根窗口不满足该条件
    if (pWin->redirectDraw != RedirectDrawNone) {
        PixmapPtr pPixmap = (*pScreen->GetWindowPixmap) (pWin);
        int bw = wBorderWidth(pWin);
        int nx = pWin->drawable.x - bw;
        int ny = pWin->drawable.y - bw;
 
        if (pPixmap->screen_x != nx || pPixmap->screen_y != ny) {
            pPixmap->screen_x = nx;
            pPixmap->screen_y = ny;
            pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER;
        }
    }
 
    if (!fbPositionWindow(pWin, x, y))
        ret = FALSE;
    // 更新重叠窗口,此时根窗口此时无重叠窗口
    if (updateOverlayWindow(pScreen) != Success)
        ret = FALSE;
    return ret;
}

2.2.3 ChangeWindowAttributes

xwl_change_window_attributes → compChangeWindowAttributes → fbChangeWindowAttributes

xwl_change_window_attributes主要功能是找到窗口管理器的client ID。其它函数因mask等条件不满足,未做任何事情

// mask = (CWBorderPixel | CWCursor || CWBackingStore | CWBackPixel)
// 处理pixmap,而根窗口为pixel。
Bool fbChangeWindowAttributes(WindowPtr pWin, unsigned long mask) {
    if (mask & CWBackPixmap) {
        if (pWin->backgroundState == BackgroundPixmap)
            fbFixupWindowPixmap(&pWin->drawable, &pWin->background.pixmap);
    }
    if (mask & CWBorderPixmap) {
        if (pWin->borderIsPixel == FALSE)
            fbFixupWindowPixmap(&pWin->drawable, &pWin->border.pixmap);
    }
    return TRUE;
}
// 处理后端存储,而根窗口不支持后端存储
static Bool compChangeWindowAttributes(WindowPtr pWin, unsigned long mask){
    ScreenPtr pScreen = pWin->drawable.pScreen;
    Bool ret = fbChangeWindowAttributes(pWin, mask);
    // 根窗口的backingStoreSupport为WhenMapped,条件满足,但backingStore = NotUseful,compCheckBackingStore啥事也不做
    if (ret && (mask & CWBackingStore) && pScreen->backingStoreSupport != NotUseful)
        compCheckBackingStore(pWin);
    return ret;
}
 
Bool xwl_change_window_attributes(WindowPtr window, unsigned long mask)
{
    ScreenPtr screen = window->drawable.pScreen;
    struct xwl_screen *xwl_screen = xwl_screen_get(screen);
    OtherClients *others;
    Bool ret = compChangeWindowAttributes(window, mask);
    if (window != screen->root || !(mask & CWEventMask))
        return ret;
 
    // 查找窗口管理器客户端,窗口管理器将其mask设置为 SubstructureRedirectMask
    for (others = wOtherClients(window); others; others = others->next) {
        if (others->mask & (SubstructureRedirectMask | ResizeRedirectMask))
            xwl_screen->wm_client_id = CLIENT_ID(others->resource);
    }
    return ret;
}

2.2.4 RealizeWindow

xwl_realize_window → compRealizeWindow → fbRealizeWindow

初始化根窗口的可绘制区间。其它因条件不满足,未做任何事情

Bool fbRealizeWindow(WindowPtr pWindow) { return TRUE; }
 
Bool compRealizeWindow(WindowPtr pWin) {
    compCheckRedirect(pWin);
   fbRealizeWindow(pWin);
    return TRUE;
}
 
Bool xwl_realize_window(WindowPtr window){
    ScreenPtr screen = window->drawable.pScreen;
    CompScreenPtr comp_screen = GetCompScreen(screen);
    struct xwl_screen *xwl_screen = xwl_screen_get(screen);
    ret = compRealizeWindow(window);
    if (xwl_screen->rootless) {
        /* We do not want the COW to be mapped when rootless in Xwayland */
        if (window == comp_screen->pOverlayWin) {
            window->mapped = FALSE;
            return TRUE;
        }
        // 初始化根窗口空间,其它空间清空
        if (!window->parent) {
            BoxRec box = { 0, 0, xwl_screen->width, xwl_screen->height };
            RegionReset(&window->winSize, &box);
            RegionNull(&window->clipList);
            RegionNull(&window->borderClip);
        }
    }
    // 条件不满足,根窗口的父窗口为空,只有顶层窗口满足该条件
    if (xwl_screen->rootless ? (window->drawable.class == InputOutput && window->parent == window->drawable.pScreen->root) : !window->parent) {
        if (!register_damage(window)) return FALSE;
    }
    // 该函数对根窗口不做任何处理,其redirectDraw = RedirectDrawNone
    return ensure_surface_for_window(window);
}

2.2.5 ClipNotify

present_clip_notify → compClipNotify → xf86XVClipNotify

根窗口未使用扩展功能,这3个函数什么都不做。

2.2.6 WindowExposures

xf86XVWindowExposures → miWindowExposures

根窗口给定的区间为空,这两个函数什么都不做。