wayland入门学习

The Wayland Protocol(自译中文版)这本书能快速深入理解Wayland的概念、设计和实现,并为您提供构建自行构建Wayland客户端和服务端所需的工具。通过简单的例子,快速去理解wayland的设计和原理。

Wayland基于domain socket实现了一套display server与client间通信的库,并且以XML形式定义了一套可扩展通信协议。这个协议分为Wayland核心协议和扩展协议。

Wayland协议中最基础的是提供了一种面向对象的跨进程过程调用的功能,在作用上类似于Android中的Binder。与Binder不同的是,在Wayland中Client和Server底层通过domain socket进行连接。和Binder一样,domain socket支持在进程间传递fd,这为传递graphic buffer和shared memory等提供了基础。

下面是从第四节开始的wayland和server的例子。

创建display server和client

例子链接

client代码,连接到 Wayland 服务端:

#include 
#include 

/* 
	build: cc -o client client.c -lwayland-client
*/

int
main(int argc, char *argv[])
{
    struct wl_display *display = wl_display_connect(NULL);
    if (!display) {
        fprintf(stderr, "Failed to connect to Wayland display.\n");
        return 1;
    }
    fprintf(stderr, "Connection established!\n");

    wl_display_disconnect(display);
    return 0;
}

wl_display_connect 是客户端建立 Wayland 连接最常见的方式,其声明如下:

struct wl_display *wl_display_connect(const char *name);

参数 name 是 Wayland 显示服务的名称,通常是 “wayland-0”,这与 $XDG_RUNTIME_DIR 中的 Unix 套接字的名称相对应。

$ echo $XDG_RUNTIME_DIR
/run/user/1000

可以看到/run/user/1000目录下有这几个文件:

$ ls /run/user/1000/wayland-*
/run/user/1000/wayland-0  /run/user/1000/wayland-0.lock  /run/user/1000/wayland-1  /run/user/1000/wayland-1.lock

server代码,创建一个 wl_display 来管理连接状态:

#include 
#include 

int
main(int argc, char *argv[])
{
    struct wl_display *display = wl_display_create();
    if (!display) {
        fprintf(stderr, "Unable to create Wayland display.\n");
        return 1;
    }

    const char *socket = wl_display_add_socket_auto(display);
    if (!socket) {
        fprintf(stderr, "Unable to add socket to Wayland display.\n");
        return 1;
    }

    fprintf(stderr, "Running Wayland display on %s\n", socket);
    wl_display_run(display);

    wl_display_destroy(display);
    return 0;
}

ubuntu编译:

cc -o server server.c -lwayland-server
cc -o client client.c -lwayland-client

启动server,然后执行client,就可以看到连接server成功!

$ ./server &
$ ./client 
Connection established!

绘制

在ubuntu上,前面wayland server这个简单server还不能绘制,不能输出interface等。最快的是安装Weston(Weston是Wayland compositor的参考实现),启动weston后,往下学习就变得很直观了。

在安装weston后,直接启动weston或者在虚拟terminal(tty(ctrl+alt+[f2-f7])环境下执行globals就能输出interface的打印和绘制窗口了。globals也是来自这个官方文档,代码如下:

#include 
#include 
#include 


/* 
cc -o globals  globals.c -lwayland-client 
 */

static void
registry_handle_global(void *data, struct wl_registry *registry,
        uint32_t name, const char *interface, uint32_t version)
{
    printf("interface: '%s', version: %d, name: %d\n",
            interface, version, name);
}

static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
        uint32_t name)
{
    // This space deliberately left blank
}

static const struct wl_registry_listener
registry_listener = {
    .global = registry_handle_global,
    .global_remove = registry_handle_global_remove,
};

int
main(int argc, char *argv[])
{
    struct wl_display *display = wl_display_connect(NULL);
    if (!display) {
        fprintf(stderr, "Failed to connect to Wayland display.\n");
        return 1;
    }
    fprintf(stderr, "Connection established!\n");

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);
    wl_display_roundtrip(display);
    return 0;
}

在ubuntu上用前面的server作为wayland server,globals程序是没有输出的,因为我们的server代码还很简单,没有注册这些interface,而使用weston server就能得到了完整的输出了。

使用weston

Wayland是一套display server(Wayland compositor)与client间的通信协议,而Weston是Wayland compositor的参考实现。

安装运行weston

sudo apt install weston
weston&

或者ctl+alt+f4切换到tty4,也可以用sudo chvt 4来切换,数字4代表tty4,然后输入:

weston-launch

执行weston-launch后,切换到tty4,可以看到weston完整的桌面,点击左上角的按钮后会出现新终端界面。这时候执行前面编译的globals程序,就会输出interface的打印。

interface: 'wl_compositor', version: 4, name: 1
interface: 'wl_subcompositor', version: 1, name: 2
interface: 'wp_viewporter', version: 1, name: 3
interface: 'zxdg_output_manager_v1', version: 2, name: 4
interface: 'wp_presentation', version: 1, name: 5
interface: 'zwp_relative_pointer_manager_v1', version: 1, name: 6
interface: 'zwp_pointer_constraints_v1', version: 1, name: 7
interface: 'zwp_input_timestamps_manager_v1', version: 1, name: 8
interface: 'wl_data_device_manager', version: 3, name: 9
interface: 'wl_shm', version: 1, name: 10
interface: 'wl_drm', version: 2, name: 11
interface: 'wl_seat', version: 7, name: 12
interface: 'zwp_linux_dmabuf_v1', version: 3, name: 13
interface: 'weston_direct_display_v1', version: 1, name: 14
interface: 'zwp_linux_explicit_synchronization_v1', version: 2, name: 15
interface: 'weston_content_protection', version: 1, name: 16
interface: 'wl_output', version: 3, name: 17
interface: 'wl_output', version: 3, name: 18
interface: 'zwp_input_panel_v1', version: 1, name: 19
interface: 'zwp_input_method_v1', version: 1, name: 20
interface: 'zwp_text_input_manager_v1', version: 1, name: 21
interface: 'xdg_wm_base', version: 1, name: 22
interface: 'zxdg_shell_v6', version: 1, name: 23
interface: 'wl_shell', version: 1, name: 24
interface: 'weston_desktop_shell', version: 1, name: 25
interface: 'weston_screenshooter', version: 1, name: 26

棋盘格例子

显示640x480 像素的棋盘格的例子

启动weston服务,然后执行draw-grid就可以看到下面的效果图:

$ cc draw-grid draw-grid.c xdg-shell-protocol.c -lwayland-client -lrt
$ weston &
$ ./draw-grid

wayland入门学习_第1张图片

这个是启动了weston作为display server,然后编译棋盘格代码,编译命令在下面代码的注释里面。

draw-grid的代码:

/*
cc -o  draw-grid  draw-grid.c xdg-shell-protocol.c -lwayland-client -lrt
*/

#define _POSIX_C_SOURCE 200112L
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "xdg-shell-client-protocol.h"

/* Shared memory support code */
static void
randname(char *buf)
{
    // 返回一个随机的名字
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    long r = ts.tv_nsec;
    for (int i = 0; i < 6; ++i) {
        buf[i] = 'A'+(r&15)+(r&16)*2;
        r >>= 5;
    }
}

static int
create_shm_file(void)
{
    int retries = 100;
    do {
        char name[] = "/wl_shm-XXXXXX";
        randname(name + sizeof(name) - 7);
        --retries;
        int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
        if (fd >= 0) {
            shm_unlink(name);
            return fd;
        }
    } while (retries > 0 && errno == EEXIST);
    return -1;
}

static int
allocate_shm_file(size_t size)
{
    int fd = create_shm_file();
    if (fd < 0)
        return -1;
    int ret;
    do {
        /* 
        * 关于 ftruncate 的内容可以参考
        * https://www.man7.org/linux/man-pages/man3/ftruncate.3p.html
        */
        ret = ftruncate(fd, size);
    } while (ret < 0 && errno == EINTR);
    if (ret < 0) {
        close(fd);
        return -1;
    }
    return fd;
}

/* Wayland code */
struct client_state {
    /* Globals */
    struct wl_display *wl_display;
    struct wl_registry *wl_registry;
    struct wl_shm *wl_shm;
    struct wl_compositor *wl_compositor;
    struct xdg_wm_base *xdg_wm_base;
    /* Objects */
    struct wl_surface *wl_surface;
    struct xdg_surface *xdg_surface;
    struct xdg_toplevel *xdg_toplevel;
};

static void
wl_buffer_release(void *data, struct wl_buffer *wl_buffer)
{
    /* Sent by the compositor when it's no longer using this buffer */
    wl_buffer_destroy(wl_buffer);
}

static const struct wl_buffer_listener wl_buffer_listener = {
    .release = wl_buffer_release,
};

static struct wl_buffer *
draw_frame(struct client_state *state)
{
    const int width = 640, height = 480;
    int stride = width * 4;
    int size = stride * height;

    int fd = allocate_shm_file(size);
    if (fd == -1) {
        return NULL;
    }

    uint32_t *data = mmap(NULL, size,
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        close(fd);
        return NULL;
    }

    // 创建一个共享内存池
    struct wl_shm_pool *pool = wl_shm_create_pool(state->wl_shm, fd, size);

    // 创建一个缓冲区 
    struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
            width, height, stride, WL_SHM_FORMAT_XRGB8888);
    wl_shm_pool_destroy(pool);
    close(fd);

    /* Draw checkerboxed background */
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            if ((x + y / 8 * 8) % 16 < 8)
                data[y * width + x] = 0xFF666666;
            else
                data[y * width + x] = 0xFFEEEEEE;
        }
    }

    munmap(data, size);

    // 添加监听器用于检测释放缓冲区的事件
    wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL);
    return buffer;
}

static void
xdg_surface_configure(void *data,
        struct xdg_surface *xdg_surface, uint32_t serial)
{
    struct client_state *state = data;

    // 返回一个 ack_configure 以示确认
    xdg_surface_ack_configure(xdg_surface, serial);

    // 向缓冲区中绘制内容
    struct wl_buffer *buffer = draw_frame(state);

    // 将缓冲区内容附加到表面
    wl_surface_attach(state->wl_surface, buffer, 0, 0);

    // 提交表面
    wl_surface_commit(state->wl_surface);
}

static const struct xdg_surface_listener xdg_surface_listener = {
    .configure = xdg_surface_configure,
};

static void
xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
{
    xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
    .ping = xdg_wm_base_ping,
};

static void
registry_global(void *data, struct wl_registry *wl_registry,
        uint32_t name, const char *interface, uint32_t version)
{
    struct client_state *state = data;

    // 绑定到全局
    if (strcmp(interface, wl_shm_interface.name) == 0) {
        state->wl_shm = wl_registry_bind(
                wl_registry, name, &wl_shm_interface, 1);
    } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
        state->wl_compositor = wl_registry_bind(
                wl_registry, name, &wl_compositor_interface, 4);
    } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
        state->xdg_wm_base = wl_registry_bind(
                wl_registry, name, &xdg_wm_base_interface, 1);
        xdg_wm_base_add_listener(state->xdg_wm_base,
                &xdg_wm_base_listener, state);
    }
}

static void
registry_global_remove(void *data,
        struct wl_registry *wl_registry, uint32_t name)
{
    /* This space deliberately left blank */
}

static const struct wl_registry_listener wl_registry_listener = {
    .global = registry_global,
    .global_remove = registry_global_remove,
};

int
main(int argc, char *argv[])
{
    // 初始化状态
    struct client_state state = { 0 };
    
    // 获取默认显示器
    state.wl_display = wl_display_connect(NULL);
    
    // 注册到默认显示器
    state.wl_registry = wl_display_get_registry(state.wl_display);
    
    // 添加事件监听
    wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state);

    // 以 wl_display 作为代理连接到混成器
    wl_display_roundtrip(state.wl_display);

    // 从混成器创建一个表面
    state.wl_surface = wl_compositor_create_surface(state.wl_compositor);

    // 从 wl_surface 创建一个 xdg_surface
    state.xdg_surface = xdg_wm_base_get_xdg_surface(
            state.xdg_wm_base, state.wl_surface);

    // 添加事件监听
    xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);

    // 获得顶层窗口
    state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);

    // 设置标题
    xdg_toplevel_set_title(state.xdg_toplevel, "Example client");

    // 提交表面
    wl_surface_commit(state.wl_surface);

    while (wl_display_dispatch(state.wl_display)) {
        /* This space deliberately left blank */
    }

    return 0;
}

生成xdg-shell-protocol代码

这个例子中需要先生成xdg-shell-protocol.c和xdg-shell-client-protocol.h,这就用到了wayland-scanner,wayland-scanner,它可以用于从诸如 wayland.xml 之类的文件生成 C 头文件和上下文代码。

xdb-shell协议以xml格式提供,我们在使用时要把它生成为代码:

# 获取wayland-protoclas路径
$ pkg-config wayland-protocols --variable=pkgdatadir
/usr/share/wayland-protocols

# 获取wayland-scanner路径
$ pkg-config --variable=wayland_scanner wayland-scanner
/usr/bin/wayland-scanner

# 获取wayland-client的lib flags
$ pkg-config wayland-client --cflags --libs
-lwayland-client

# xdg-shell的路径
$ ls /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml
# 生成代码

$ wayland-scanner private-code   < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml   > xdg-shell-protocol.c
$ wayland-scanner client-header   < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml   > xdg-shell-client-protocol.h

#或者
$ wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h
$ wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml  xdg-shell-protocol.c

关于tty

TTY来源于Teletypewriter,翻译过来就是电传打字机。Teletypewriter和普通打字机的区别在于,Teletypewriter连接到通信设备以发送所打印的文字信息,电传打字机使人类能够通过电线更快地通信,这就是“TTY”这个词最初产生的时间。

随着技术的进一步发展,实体的电传打字机进行了“虚拟化”。因此不再需要物理、机械的TTY,而是虚拟的电子TTY。早期的计算机甚至没有视频屏幕,东西印在纸上,而不是显示在屏幕上。因此,你会看到“打印”一词的使用,而不是“显示”。随着技术的进步,显示器才被添加到终端中,可以称它们为“物理”终端。然后,这些演变成软件模拟终端,并具有增强的能力和功能。这就是所谓的“终端模拟器”。

Linux中的TTY是一个抽象设备。有时它指的是物理输入设备,如串行端口,有时它指的是允许用户与系统交互的虚拟TTY。

可以在大多数发行版上使用以下键盘快捷键来获取TTY屏幕:

CTRL + ALT + F1 – 锁定屏幕
CTRL + ALT + F2 – 桌面环境
CTRL + ALT + F3 – TTY3
CTRL + ALT + F4 – TTY4
CTRL + ALT + F5 – TT5
CTRL + ALT + F6 – TTY6

参考链接

The Wayland Protocol(自译中文版)

第一个黑框框

Wayland入门1:运行测试程序

你可能感兴趣的:(wayland,docker,ubuntu,容器)