Wayland入门教程08.09:第一个窗口

系列索引地址:Wayland入门教程索引

上一篇:Wayland入门教程08.08:shell stable协议

本文介绍如何显示一个窗口。

Wayland窗口绘制有两种方法:1) 共享内存方式(SHM)、2)EGL。

共享内存

在添加注册函数中添加一个shm部分的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void
global_registry_handler(void *data, struct wl_registry *registry,
uint32_t id, const char *interface, uint32_t version)
{
printf("Got a registry event for %s id %d\n", interface, id);
if (strcmp(interface, "wl_compositor") == 0)
{
compositor = wl_registry_bind(registry, id, &wl_compositor_interface, version);
}
else if (strcmp(interface, xdg_wm_base_interface.name) == 0)
{
xdg_shell = wl_registry_bind(registry, id, &xdg_wm_base_interface, version);
}
else if (strcmp(interface, "wl_shm") == 0)
{
shm = wl_registry_bind(registry, id, &wl_shm_interface, version);
wl_shm_add_listener(shm, &shm_listener, NULL);
}
}

可以看到共享内存中有回调函数shm_listener,我们要实现它:

1
2
3
struct wl_shm_listener shm_listener={
.format=shm_format
};

或者和之前一样

1
2
3
struct wl_shm_listener shm_listener={
shm_format
};

这样也可以,具体声明在wayland-client-protocol.h

此函数的作用是

1
2
3
4
5
6
7
8
9
10
11
12
struct wl_shm_listener {
/**
* pixel format description
*
* Informs the client about a valid pixel format that can be used
* for buffers. Known formats include argb8888 and xrgb8888.
* @param format buffer pixel format
*/
void (*format)(void *data,
struct wl_shm *wl_shm,
uint32_t format);
};

就是说,此函数返回的参数可以知道buffer支持的像素类型。

那么我们就输出支持的所有像素类型名称。

1
2
3
4
5
static void 
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
fprintf(stderr,"Format %d\n",format);
}

编译运行执行输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hyper@ubuntu:~/Nutstore Files/Nutstore/Wayland_Freshman/9.window$ ./window
connected to display
Format 0
Format 1
Format 909199186
Format 808669761
Format 808669784
Format 808665665
Format 808665688
Format 1211388481
Format 1211388504
Format 1211384385
Format 1211384408
Found compositor
Created surface
Created shell surface
disconnected from display

输出一大堆数字,使用的时候我们要将其强制转换为enum wl_shm_format类型。

此类型可以在wayland-client-protocol.h中找到详细信息。

名称数值16进制数值
WL_SHM_FORMAT_ARGB888800
WL_SHM_FORMAT_XRGB888811
WL_SHM_FORMAT_RGB5659091991860x36314752
WL_SHM_FORMAT_ARGB21010108086697610x30335241
WL_SHM_FORMAT_XRGB21010108086697840x30335258
WL_SHM_FORMAT_ARGB16161616F12113884810x48345241

从几个典型值可以看出,主要支持16位、32位、64位像素值。

创建窗口

Wayland窗口:wl_surface

Wayland窗口绘制完全由程序控制,包括标题栏绘制,边框绘制,窗口移动,改变大小等。 其中与窗口绘制有关的函数有:

  • wl_surface_attach() 将缓存绑定到窗口上,窗口大小会根据缓存重新计算。
  • wl_surface_damage() 标记窗口失效的区域
  • wl_surface_commit() 缓存提交请求,合成器会锁定提交的缓存,直到下一次wl_surface_attach或合成器主动释放。
  • wl_surface_frame() 申请帧绘制回调,每当绘制完一帧就发送wl_callback::done消息。

创建函数和代码

1
2
3
4
5
6
7
static void 
create_window(){
buffer = create_buffer();

wl_surface_attach(surface,buffer,0,0);
wl_surface_commit(surface);
}

然后创建缓存

Walyand缓存:wl_buffer

Wayland窗口显示的内容由wl_buffer负责。Wayland与X Server不同,Wayland只支持客户端直接绘制,合成器不提供对wl_buffer的绘制操作。与wl_buffer有关的函数有:

  • wl_shm_create_pool() 创建一个缓存池,缓存池可以mmap到程序的内存空间中。
  • wl_shm_pool_create() 创建一个wl_buffer。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static struct wl_buffer *
create_buffer() {
struct wl_shm_pool *pool;
int stride = WIDTH * 4; // 4 bytes per pixel
int size = stride * HEIGHT;
int fd;
struct wl_buffer *buff;

fd = os_create_anonymous_file(size);
if (fd < 0) {
fprintf(stderr, "creating a buffer file for %d B failed: %m\n",size);
exit(1);
}

shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm_data == MAP_FAILED) {
fprintf(stderr, "mmap failed: %m\n");
close(fd);
exit(1);
}

pool = wl_shm_create_pool(shm, fd, size);
buff = wl_shm_pool_create_buffer(pool, 0,
WIDTH, HEIGHT,
stride,
WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy(pool);
return buff;
}

根据需要创建一个给定大小的新的、唯一的、匿名的文件,并为其返回文件描述符。文件描述符被设置为cloexec。该文件立即适合于mmap()的给定大小的偏移量为零。

该文件不应该像磁盘那样有永久备份存储,但如果XDG_RUNTIME_DIR在操作系统中没有正确实现,可能有。

该文件名将从文件系统中删除。

该文件适用于通过使用*SCM_RIGHTS方法通过Unix套接字传输文件描述符来实现进程之间的缓冲区共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
static int
set_cloexec_or_close(int fd)
{
long flags;

if (fd == -1)
return -1;

flags = fcntl(fd, F_GETFD);
if (flags == -1)
goto err;

if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
goto err;

return fd;

err:
close(fd);
return -1;
}

static int
create_tmpfile_cloexec(char *tmpname)
{
int fd;

#ifdef HAVE_MKOSTEMP
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
#else
fd = mkstemp(tmpname);
if (fd >= 0) {
fd = set_cloexec_or_close(fd);
unlink(tmpname);
}
#endif
return fd;
}

int os_create_anonymous_file(off_t size)
{
static const char template[] = "/weston-shared-XXXXXX";
const char *path;
char *name;
int fd;

path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}

name = malloc(strlen(path) + sizeof(template));
if (!name)
return -1;

strcpy(name, path);
strcat(name, template);
printf("%s\n", name);

fd = create_tmpfile_cloexec(name);

free(name);

if (fd < 0)
return -1;

if (ftruncate(fd, size) < 0)
{
close(fd);
return -1;
}

return fd;
}

显示窗口

调用wl_display_dispath()函数的线程会自动成为主线程,并且拥有主消息队列。

Wayland允许创建多个消息队列,使用wl_display_create_queue()创建,新建的消息队列可以绑定到一个wl_proxy对象上。

与Win32消息循环不同,Wayland消息循环只需要调用一个函数:

1
2
3
int ret = 0;
while(ret!=-1)
ret = wl_display_dispatch(dpy);

wl_proxy消息队列的消息循环使用:

1
2
3
int ret = 0;
while(ret!=-1)
ret = wl_display_dispatch_queue(dpy, queue);

消息循环代码为

1
2
3
while(wl_display_dispatch(display)!=-1){
;
}

效果为:

display

  • 因为是死循环,那么这个窗口将一直存在直到手动杀死。
  • 除了显示窗口没有其他操作,如果点击窗口就会出现程序未响应的情况。
  • 这是一个最基本的窗口,没有状态栏、没有最大最小按钮、没有拖拽移动功能

窗口默认为黑色的,因为shm_data值没有修改过,默认为0。我们将其修改一下。

在create_buffer函数中,我们将shm_data和fd对应的文件进行了映射,那么修改shm_data的数据就会相应的修改fd文件的数据。反之亦然。

以像素的方式修改每个值

1
2
3
4
5
6
7
8
9
10
static void
paint_pixels() {
int n;
uint32_t *pixel = shm_data;

fprintf(stderr, "Painting pixels\n");
for (n =0; n < WIDTH*HEIGHT; n++) {
*pixel++ = 0xff0000;//红色
}
}

效果为:

red_bg

EGL版

有了上面的基础,EGL版本就简单一些

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
display = wl_display_connect(0);
if(!display){
printf("Cannot connect to wayland compositor.\n");
return -1;
}

registry = wl_display_get_registry(display);
wl_registry_add_listener(registry,&registry_listener,NULL);

wl_display_dispatch(display);
wl_display_roundtrip(display);

if (compositor == NULL || shell == NULL)
{
fprintf(stderr, "Can't find compositor or shell\n");
return -1;
}else{
fprintf(stderr, "Found compositor and shell\n");
}

surface = wl_compositor_create_surface(compositor);
if (surface == NULL)
{
fprintf(stderr, "Can't create surface\n");
return -1;
}else{
fprintf(stderr, "Created surface\n");
}

struct xdg_surface *shell_surface = xdg_wm_base_get_xdg_surface(xdg_shell, surface);
if (shell_surface == NULL)
{
fprintf(stderr, "Can't create shell surface\n");
exit(1);
}
else
{
fprintf(stderr, "Created shell surface\n");
}
// 窗口处理
struct xdg_toplevel *toplevel = xdg_surface_get_toplevel(shell_surface);
xdg_surface_add_listener(shell_surface, &surface_listener, NULL);

连接到wayland服务器,创建窗口,还要添加注册捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void
global_registry_handler(void *data, struct wl_registry *registry,
uint32_t id, const char *interface, uint32_t version)
{
printf("Got a registry event for %s id %d\n", interface, id);
if (strcmp(interface, "wl_compositor") == 0)
{
compositor = wl_registry_bind(registry, id, &wl_compositor_interface, version);
}
else if (strcmp(interface, xdg_wm_base_interface.name) == 0)
{
xdg_shell = wl_registry_bind(registry, id, &xdg_wm_base_interface, version);
}
}

static void registry_remover(void *data, struct wl_registry *registry, uint32_t id){}

static const struct wl_registry_listener registry_listener= {
registry_handler,
registry_remover
};

窗口显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// init egl
EGLint major, minor, config_count, tmp_n;
EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};

egl_display = eglGetDisplay((EGLNativeDisplayType)display);
eglInitialize(egl_display, &major, &minor);
printf("EGL major: %d, minor %d\n", major, minor);
eglGetConfigs(egl_display, 0, 0, &config_count);
printf("EGL has %d configs\n", config_count);
eglChooseConfig(egl_display, config_attribs, &(egl_config), 1, &tmp_n);
egl_context = eglCreateContext(
egl_display,
egl_config,
EGL_NO_CONTEXT,
context_attribs);

// init window
egl_window = wl_egl_window_create(surface, WIDTH, HEIGHT);
egl_surface = eglCreateWindowSurface(
egl_display,
egl_config,
egl_window,
0);
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);

eglSwapBuffers(egl_display, egl_surface);

获取窗口、初始化、获取配置、选择配置、创建上下文、创建EGL窗口、交换显示缓冲区

具体的到EGL开发系列中再说。

窗口绘制完成后,循环显示窗口

1
2
3
4
5
while (wl_display_dispatch(display) != -1) {
}

// disconnect
wl_display_disconnect(display);

编译运行显示窗口

default

默认为黑色背景,我们调整一下。

可以看到代码中有swapbuffer字样,在标准的OpenGL开发中一般是调用绘图函数,然后使用双缓冲技术交换显示数据。绘制的背景颜色代码应该在此代码的上方。

1
2
glClearColor(0.5f, 0.5f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

将背景设置为棕色

效果为:

colored

可以参考OpenGLut入门系列教程索引

总结

这是共享内存的流程图

flow

我们来看一下EGL调用流程图

flow

可以看到,EGL方式相对简单,代码量少,函数调用少。

这个操作流程是以后的基础。

有了窗口,我们就可以在窗口上进行更复杂的操作。

完整代码在Wayland_Freshman中的08.09.window下。

下一篇:Wayland入门教程08.10:输入设备管理器


Wayland入门教程08.09:第一个窗口
https://blog.jackeylea.com/wayland/the-first-wayland-window/
作者
JackeyLea
发布于
2021年9月30日
许可协议