目录

一、连接服务端

wayland是一个CS的协议架构。client第一步要做的就是连接server,也就是compositor。使用的方法是wl_display_connect。参数为NULL时,表示使用默认的名字“wayland-0”。

函数调用成功的话,会返回一个wl_display类型的变量——display,可以简单理解为client和server之间的连接标识。

连接server

1// Connect to the Wayland compositor 2struct wl_display *display = wl_display_connect(NULL); 3if (display == NULL) { 4    fprintf(stderr, "failed to create display\n"); 5    return EXIT_FAILURE; 6}  

二、查询所有已注册的协议对象(接口)

为什么要查询所有已注册协议对象(接口)?先说结论,这是因为wayland的扩展协议是以模块化形式编码及动态注册到服务端的。所以client也需要动态查看服务端目前支持的协议,从而决定自己使用什么协议完成编程。

这里动态注册是指compositor启动时,实时判断当前的资源之后,再决定是否注册该协议到服务端。另外对于同一类资源,如缓存,还有多种协议实现,client可以动态选择不同资源接口实现目的。所以需要查询接口是否存在,以及确认它的版本。

wl_display_get_registry获取一个绑定到输入参数display上的registry对象返回。这个函数实际上会请求服务端发送所有已注册的协议对象给客户端。所以下一步要将服务端的response进行处理。

wl_registry_add_listener的作用是将一个处理registry事件的函数绑定到registry事件上。实际上wl_registry_add_listener内部是调用wl_proxy_add_listener( cast(wl_proxy*)wl_registry,  cast(Callback*)listener, data)来完成的。

这里的callback就是用来处理服务端返回的所有interface 的registry事件的。

wl_display_roundtrip() 阻塞当前执行流,等待服务端处理完所有异步请求,这里就是请求是指wl_display_get_registry发送的获取所有registry对象的请求。

1// Obtain the wl_registry and fetch the list of globals 2    struct wl_registry *registry = wl_display_get_registry(display); 3    wl_registry_add_listener(registry, &registry_listener, NULL); 4    if (wl_display_roundtrip(display) == -1) { 5        return EXIT_FAILURE; 6    } 7  8    // Check that all globals we require are available 9    if (shm == NULL || compositor == NULL || xdg_wm_base == NULL) { 10        fprintf(stderr, "no wl_shm, wl_compositor or xdg_wm_base support\n"); 11        return EXIT_FAILURE; 12    } 13    

上述代码中的registry_listener是一个wl_registry_listener 类型的结构体。该结构体包含两个函数成员做回调函数,用于处理接口的注册和移除事件。有点意思的是name并不是一个字符串,可以理解为服务端返回的可以用于wl_registry_bind做绑定的一个可表唯一的id值。

可以看到在registry_handle_global方法中,程序先判断接口的名字是否是程序需要的,如果是,则通过wl_registry_bind绑定该接口——实际上是向服务器申请获取这个接口类型的一个id引用,后续就可以用该id调用相关的方法。

registry_listener

1static void 2registry_handle_global(void *data, struct wl_registry *registry, 3        uint32_t name, const char *interface, uint32_t version) 4{ 5    printf("interface: '%s', version: %d, name: %d\n", 6            interface, version, name); 7    if (strcmp(interface, wl_shm_interface.name) == 0) { 8        shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 9    } else if (strcmp(interface, wl_seat_interface.name) == 0) { 10        struct wl_seat *seat = 11            wl_registry_bind(registry, name, &wl_seat_interface, 1); 12        wl_seat_add_listener(seat, &seat_listener, NULL); 13    } else if (strcmp(interface, wl_compositor_interface.name) == 0) { 14        compositor = wl_registry_bind(registry, name, 15            &wl_compositor_interface, 1); 16    } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { 17        xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); 18        xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); 19    } 20} 21  22static void 23registry_handle_global_remove(void *data, struct wl_registry *registry, 24        uint32_t name) 25{ 26    // This space deliberately left blank 27} 28  29static const struct wl_registry_listener 30registry_listener = { 31    .global = registry_handle_global, 32    .global_remove = registry_handle_global_remove, 33};

2.1 这些对象是什么,谁注册了这些对象,这些对象和什么有关

这些对象如下所示。大部分对象由server注册,还有些则由egl实现注册。name实际上表示的是这个接口在服务端的一个id序号,用于绑定接口。

对象列表

1interface: 'wl_compositor', version: 5, name: 1 2interface: 'wl_drm', version: 2, name: 2 3interface: 'wl_shm', version: 1, name: 3 4interface: 'wl_output', version: 4, name: 4 5interface: 'zxdg_output_manager_v1', version: 3, name: 5 6interface: 'wl_data_device_manager', version: 3, name: 6 7interface: 'zwp_primary_selection_device_manager_v1', version: 1, name: 7 8interface: 'wl_subcompositor', version: 1, name: 8

2.2 由wayland server注册的对象

它们由wayland server注册,如下代码所示,具体的注册方法是wl_global_create。

registry dma buf

1gboolean 2meta_wayland_dma_buf_init (MetaWaylandCompositor *compositor) 3{ 4  MetaBackend *backend = meta_get_backend (); 5  MetaEgl *egl = meta_backend_get_egl (backend); 6  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); 7  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); 8  EGLDisplay egl_display = cogl_egl_context_get_egl_display (cogl_context); 9  10  g_assert (backend && egl && clutter_backend && cogl_context && egl_display); 11  12  if (!meta_egl_has_extensions (egl, egl_display, NULL, 13                                "EGL_EXT_image_dma_buf_import_modifiers", 14                                NULL)) 15    return FALSE; 16  17  if (!wl_global_create (compositor->wayland_display, 18                         &zwp_linux_dmabuf_v1_interface, 19                         META_ZWP_LINUX_DMABUF_V1_VERSION, 20                         compositor, 21                         dma_buf_bind)) 22    return FALSE; 23  24  return TRUE; 25}

上面的代码截取自mutter。可以看到这段代码查询了系统当前的egl是否存在EGL_EXT_image_dma_buf_import_modifiers扩展,如果不存在,就不注册zwp_linux_dmabuf_v1_interface到系统中。所以说这些对象和系统当前的egl实现(开源egl实现是mesa)有关。

2.2 由egl实现——mesa注册的对象

另外还有些协议对象是由mesa实现的,如mesa主目录下的 src/egl/wayland/wayland-drm/wayland-drm.c有如下代码:

drm implement by mesa

1struct wl_drm * 2wayland_drm_init(struct wl_display *display, char *device_name, 3                 const struct wayland_drm_callbacks *callbacks, void *user_data, 4                 uint32_t flags) 5{ 6   struct wl_drm *drm; 7  8   drm = malloc(sizeof *drm); 9   if (!drm) 10      return NULL; 11  12   drm->display = display; 13   drm->device_name = strdup(device_name); 14   drm->callbacks = *callbacks; 15   drm->user_data = user_data; 16   drm->flags = flags; 17  18   drm->buffer_interface.destroy = buffer_destroy; 19  20   drm->wl_drm_global = 21      wl_global_create(display, &wl_drm_interface, 2, drm, bind_drm); 22  23   return drm; 24}

调用wl_global_create注册了wl_drm_interface.版本号是2.

三、创建surface

surface可以为渲染表面的逻辑概念,它有一些属性,比如宽和高,位置和像素类型等,需要绑定buffer才能存储渲染数据,buffer可以自由绑定,这样灵活性强。一个表面绑定不同的buffer,就可以实现切换不同的渲染数据。

wl_compositor_create_surface创建一块绑定到compositor的surface。这是wayland最底层的surface,此时surface和应用窗口还没关联上。

XDG (cross-desktop group) shell 是 Wayland 的一个标准扩展协议,描述了应用窗口的语义。换句话说它能将wl_surfaces转换成桌面环境下的窗口。它定义了两个 wl_surface 角色:"toplevel" 用于你的顶层应用窗口;"popup" 则用于诸如上下文菜单、下拉菜单、工具提示等等——它们是顶层窗口的子集

xdg_wm_base_get_xdg_surface创建一块绑定到surface的xdg_surface(应用窗口surface)。

1// Create a wl_surface, a xdg_surface and a xdg_toplevel 2    surface = wl_compositor_create_surface(compositor); 3    struct xdg_surface *xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); 4    xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); 5  6    xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); 7    xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); 8  9    // Perform the initial commit and wait for the first configure event 10    wl_surface_commit(surface); 11    while (wl_display_dispatch(display) != -1 && !configured) { 12        // This space intentionally left blank 13    }

四、创建缓存并绑定到surface

使用wl_shm_create_pool创建一个pool。pool是一个管理buffer的池子,它的作用是减少buffer反复申请和注销给系统内存管理带来碎片化问题。buffer释放后,这段内存还给pool,下一次重新申请buffer时,继续从pool中划分。

所以可以看到wl_shm_pool_create_buffer依赖pool作为参数去创建buffer。

wl_shm_pool_destroy销毁pool,但不会立即销毁pool中的buffer。buffer全部释放后,会自动解除内存映射。

wl_surface_attach将buffer绑定到surface

create buffer

1create_shm_file(void) 2{ 3    int retries = 100; 4    do { 5        char name[] = "/wl_shm-XXXXXX"; 6        randname(name + sizeof(name) - 7); 7        --retries; 8        int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); 9        if (fd >= 0) { 10            shm_unlink(name); 11            return fd; 12        } 13    } while (retries > 0 && errno == EEXIST); 14    return -1; 15} 16  17static struct wl_buffer *create_buffer(void) { 18    int stride = width * 4; 19    int size = stride * height; 20  21    // Allocate a shared memory file with the right size 22    int fd = create_shm_file(size); 23    if (fd < 0) { 24        fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size); 25        return NULL; 26    } 27  28    // Map the shared memory file 29    shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 30    if (shm_data == MAP_FAILED) { 31        fprintf(stderr, "mmap failed: %m\n"); 32        close(fd); 33        return NULL; 34    } 35  36    // Create a wl_buffer from our shared memory file descriptor 37    struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 38    struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,stride, WL_SHM_FORMAT_ARGB8888); 39    wl_shm_pool_destroy(pool); 40  41    // Now that we've mapped the file and created the wl_buffer, we no longer 42    // need to keep file descriptor opened 43    close(fd); 44  45    // Copy pixels into our shared memory file (MagickImage is from cat.h) 46    memcpy(shm_data, MagickImage, size); 47  48    return buffer; 49} 50  51// Create a wl_buffer, attach it to the surface and commit the surface 52    struct wl_buffer *buffer = create_buffer(); 53    if (buffer == NULL) { 54        return EXIT_FAILURE; 55    } 56  57    wl_surface_attach(surface, buffer, 0, 0); 58    

五 提交surface并显示

wl_surface_commit提交surface到compositor用于显示,这意味着请求compositor读取surface的内容,compositor读取完内容后,会向这个buffer发送release事件。在compositor返回这个buffer的release信号之前,修改buffer会导致未定义的问题。

当一个surface绑定空buffer或未绑定buffer,调用wl_surface_commit时的含义是清空这个surface当前的内容。

commit

1wl_surface_commit(surface); 2  3// Continue dispatching events until the user closes the toplevel 4while (wl_display_dispatch(display) != -1 && running) { 5    // This space intentionally left blank 6} 7  8xdg_toplevel_destroy(xdg_toplevel); 9xdg_surface_destroy(xdg_surface); 10wl_surface_destroy(surface); 11wl_buffer_destroy(buffer); 12  13return EXIT_SUCCESS;


完整的代码:https://github.com/warlice/hello-wayland/blob/master/main.c