系列索引地址:Wayland开发入门教程索引
上一篇:Wayland开发入门教程08:获取全局对象
前面已经可以成功的连接服务器了。本文介绍如何显示一个窗口。
Wayland窗口绘制有两种方法:1) 共享内存方式(SHM)、2)EGL。
要想使用EGL,你得会EGL,还得先会OpenGL,这两个哪一个工程量都不小。本文使用方式1绘制窗口(第二种方法等OpenGL开发系列和EGL开发系列更新完再说)。
窗口先将上一篇的代码拿过来,当然不需要输出所有的registry信息。
所以将
1 printf ("Got a registry event for %s id %d\n" , interface, id);
删除。
改动之后最好编译一下,以确保每一步都正确。
在添加注册函数中添加一个shm部分的处理
1 2 3 4 5 6 7 8 9 10 11 12 static void global_registry_handler (void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { if (strcmp (interface, "wl_compositor" ) == 0 ) { BIND_WL_REG (registry, compositor, id, &wl_compositor_interface, 1 ); } else if (strcmp (interface, "wl_shell" ) == 0 ) { BIND_WL_REG (registry, shell, id, &wl_shell_interface, 1 ); } else if (strcmp (interface, "wl_shm" )==0 ){ BIND_WL_REG (registry, shm, id, &wl_shm_interface, 1 ); 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 6 7 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_ARGB8888 0 0 WL_SHM_FORMAT_XRGB8888 1 1 WL_SHM_FORMAT_RGB565 909199186 0x36314752 WL_SHM_FORMAT_ARGB2101010 808669761 0x30335241 WL_SHM_FORMAT_XRGB2101010 808669784 0x30335258 WL_SHM_FORMAT_ARGB16161616F 1211388481 0x48345241
从几个典型值可以看出,主要支持16位、32位、64位像素值。
创建窗口 Wayland窗口:wl_surfaceWayland窗口绘制完全由程序控制,包括标题栏绘制,边框绘制,窗口移动,改变大小等。 其中与窗口绘制有关的函数有:
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 8 static void create_window () { buffer = create_buffer (); wl_surface_attach (surface,buffer,0 ,0 ); wl_surface_commit (surface); }
然后创建缓存
Walyand缓存:wl_bufferWayland窗口显示的内容由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 30 static struct wl_buffer *create_buffer () { struct wl_shm_pool *pool; int stride = WIDTH * 4 ; 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; }
显示窗口1 2 3 while (wl_display_dispatch (display)!=-1 ){ ; }
效果为:
因为是死循环,那么这个窗口将一直存在直到手动杀死。 除了显示窗口没有其他操作,如果点击窗口就会出现程序未响应的情况。 这是一个最基本的窗口,没有状态栏、没有最大最小按钮、没有拖拽移动功能 窗口默认为黑色的,因为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 ; } }
效果为:
消息队列 主消息队列调用wl_display_dispath()函数的线程会自动成为主线程,并且拥有主消息队列。
wl_proxy消息队列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);
Wayland消息处理Wayland没有窗口函数,而是使用wl_proxy listener处理具体的消息。wl_proxy代表服务器端的对象,服务器发送的消息会放入消息队列,如果使用wl_proxy_set_queue()设置过其他消息队列,则消息会放入指定的消息队列,wl_display_dispatch负责读取消息并调用proxy的listener。proxy的listener是由 wl_proxy_add_listener()指定的。
wl_proxy listenerwl_pointer_add_listener() 鼠标消息处理 wl_keyboard_add_listener() 键盘消息处理 wl_callback_add_listener() wl_callback由wl_surface_frame() 创建,每当服务器显示下一帧使会给wl_callback发送一条消息。 总结我们来看一下调用流程图
这个操作流程是以后的基础。
有了窗口,我们就可以在窗口上进行更复杂的操作。
完整代码在Wayland_Freshman 中的09.window
下。
下一篇:Wayland开发入门教程10:第一个EGL窗口