This document is based on current Wayland 1.3 documentation & its source code.
- http://fossies.org/dox/wayland-1.3.0/
- http://wayland.freedesktop.org/docs/html/appe-Wayland-Protocol.html
The simplest testcase is weston-simple-touch. This source code
is http://cgit.freedesktop.org/wayland/weston/tree/clients/simple-touch.c
Analysis on Client code only
Main function is very simple. Setup by calling touch_create(). and looping by wl_display_dispatch().int main(int argc, char **argv) { struct touch *touch; int ret = 0; touch = touch_create(600, 500); while (ret != -1) ret = wl_display_dispatch(touch->display); return 0; }
Initialization & Structures
At first we set up wl_display & wl_registry.
display
display
- display context. Similar concept of XDisplay in X System.
registry
registry
- generated from display. global registry object : bind an object to the display
static struct touch * touch_create(int width, int height) { struct touch *touch; touch = malloc(sizeof *touch); if (touch == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } touch->display = wl_display_connect(NULL); assert(touch->display); touch->has_argb = 0; touch->registry = wl_display_get_registry(touch->display); wl_registry_add_listener(touch->registry, ®istry_listener, touch); wl_display_dispatch(touch->display); wl_display_roundtrip(touch->display); if (!touch->has_argb) { fprintf(stderr, "WL_SHM_FORMAT_ARGB32 not available\n"); exit(1); } ...
Objects which can be bound to registry are:
- wl_compositor
- wl_subcompositor
- screenshooter
- text_cursor_position
- wl_text_input_manager
- wl_shm
- wl_output
- wl_drm
- wl_seat
- wl_input_method
- wl_shell
- desktop_shell
- screensaver
- wl_input_panel
- workspace_manager
- ...
Some ones have wl_ prefix, others don't. But I do not know why exactly yet. I suppose wl_ mean wayland spec, and no prefix means supplemental add on.
And we can extract more specific objects from registry-bound objects.
In this example, we use the following objects only.
And we can extract more specific objects from registry-bound objects.
In this example, we use the following objects only.
wl_compositor
-> wl_surface //<- surface
wl_shell
-> wl_shell_surface
wl_shm
-> wl_shm_pool
-> wl_buffer //<- graphics buffer for surface
-> wl_shm_pool
-> wl_buffer //<- graphics buffer for surface
wl_seat
-> wl_touch //<- touch
-> wl_touch //<- touch
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct touch *touch = data; if (strcmp(interface, "wl_compositor") == 0) { touch->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_shell") == 0) { touch->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { touch->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); wl_shm_add_listener(touch->shm, &shm_listener, touch); } else if (strcmp(interface, "wl_seat") == 0) { touch->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(touch->seat, &seat_listener, touch); } }
Touch object
To get wl_touch, add touch listener on wl_seat(http://fossies.org/dox/wayland-1.3.0/structwl__seat__interface.html). wl_seat is group of input devices.static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct touch *touch = data; if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !touch->wl_touch) { touch->wl_touch = wl_seat_get_touch(seat); wl_touch_set_user_data(touch->wl_touch, touch); wl_touch_add_listener(touch->wl_touch, &touch_listener, touch); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && touch->wl_touch) { wl_touch_destroy(touch->wl_touch); touch->wl_touch = NULL; } } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, };
After getting wl_touch in wl_seat_listener, finally we can control touch event using wl_touch, wl_touch_listener. It is so a long way.
touch_down()/up()/move() seem clear. But I don't understand touch_handle_frame()/cancel() yet.
touch_paint() is just a drawing function that contains surface update routine such as wl_surface_attach(), wl_surface_damage(), wl_surface_commit(). touch->data is user memory address, which is explained in the next section.
static void touch_paint(struct touch *touch, int32_t x, int32_t y, int32_t id) { uint32_t *p, c; static const uint32_t colors[] = { 0xffff0000, 0xffffff00, 0xff0000ff, 0xffff00ff, 0xff00ff00, 0xff00ffff, }; if (id < (int32_t) ARRAY_LENGTH(colors)) c = colors[id]; else c = 0xffffffff; if (x < 2 || x >= touch->width - 2 || y < 2 || y >= touch->height - 2) return; p = (uint32_t *) touch->data + (x - 2) + (y - 2) * touch->width; p[2] = c; p += touch->width; p[1] = c; p[2] = c; p[3] = c; p += touch->width; p[0] = c; p[1] = c; p[2] = c; p[3] = c; p[4] = c; p += touch->width; p[1] = c; p[2] = c; p[3] = c; p += touch->width; p[2] = c; wl_surface_attach(touch->surface, touch->buffer, 0, 0); wl_surface_damage(touch->surface, x - 2, y - 2, 5, 5); /* todo: We could queue up more damage before committing, if there * are more input events to handle. */ wl_surface_commit(touch->surface); } static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); touch_paint(touch, x, y, id); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); touch_paint(touch, x, y, id); } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, };
Surface & Graphics buffer object
The following code is the rest of the touch_create() function, which set up the wl_surface, wl_shell_surface and attach a graphics buffer on wl_surface.At first, we create wl_surface, and wl_buffer, which is pointing real memory array. And then just link this two objects by wl_surface_attach(). Finally we create damage and commit all state.
static struct touch * touch->width = width; touch->height = height; touch->surface = wl_compositor_create_surface(touch->compositor); touch->shell_surface = wl_shell_get_shell_surface(touch->shell, touch->surface); create_shm_buffer(touch); if (touch->shell_surface) { wl_shell_surface_add_listener(touch->shell_surface, &shell_surface_listener, touch); wl_shell_surface_set_toplevel(touch->shell_surface); } wl_surface_set_user_data(touch->surface, touch); wl_shell_surface_set_title(touch->shell_surface, "simple-touch"); memset(touch->data, 64, width * height * 4); wl_surface_attach(touch->surface, touch->buffer, 0, 0); wl_surface_damage(touch->surface, 0, 0, width, height); wl_surface_commit(touch->surface); return touch; }At first we create a file with input size, and then extract user address by mmap. This user address is for direct-pixel manipulating in touch_paint(). And we need to wl_buffer which is interpreted by Wayland system. wl_shm_pool means a memory chunk. wl_buffer represents more specific graphics buffer with width, height, stride, pixel format. I was confused with close(fd) right after creating wl_buffer ( call wl_shm_pool_create_buffer() ), but APUE 3rd edition said
... Closing the file descriptor used when we mapped the region does not unmap the region. ...And other web page also said (http://pubs.opengroup.org/onlinepubs/009695399/functions/mmap.html)
The mmap() function shall add an extra reference to the file associated with the file descriptor fildes which is not removed by a subsequent close() on that file descriptor. This reference shall be removed when there are no more mappings to the file.
shm_format() callback function in wl_shm_listener is just for checking whether the format of ARGB8888 is available or not on compositor.
static void create_shm_buffer(struct touch *touch) { struct wl_shm_pool *pool; int fd, size, stride; stride = touch->width * 4; size = stride * touch->height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size); exit(1); } touch->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (touch->data == MAP_FAILED) { fprintf(stderr, "mmap failed: %m\n"); close(fd); exit(1); } pool = wl_shm_create_pool(touch->shm, fd, size); touch->buffer = wl_shm_pool_create_buffer(pool, 0, touch->width, touch->height, stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); close(fd); } static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct touch *touch = data; if (format == WL_SHM_FORMAT_ARGB8888) touch->has_argb = 1; } struct wl_shm_listener shm_listener = { shm_format };
Interaction with the server (weston)
One of the important information shared between client & server is fd (file descriptor). Wayland uses domain-socket IPC to transfer it. I do not dive deep inside. Reference is source code of "Sending a file descriptor over a UNIX domain socket" in APUE 3rd EditionHere I just check the interaction point: which functions or call-stacks perform that IPC.
In the client, wl_display_dispatch() and other "dispatch" functions such as wl_display_roundtrip().
<send>
sendmsg ()
wl_connection_flush () at connection.c:286
wl_display_dispatch_queue () at wayland-client.c:1201
wl_display_dispatch () at wayland-client.c:1290
<receive>
recvmsg ()
wl_os_recvmsg_cloexec () at wayland-os.c:123
wl_connection_read () at connection.c:327
read_events () at wayland-client.c:964
wl_display_dispatch_queue () at wayland-client.c:1220
wl_display_dispatch () at wayland-client.c:1290
In the server(weston compositor)
<send>
sendmsg ()
wl_connection_flush () at connection.c:286
wl_display_flush_clients () at wayland-server.c:880
wl_display_run () at wayland-server.c:868
main () at compositor.c:3377
<receive>
recvmsg ()
wl_os_recvmsg_cloexec () at wayland-os.c:123
wl_connection_read () at connection.c:327
wl_client_connection_data () at wayland-server.c:261
wl_event_source_fd_dispatch () at event-loop.c:86
wl_event_loop_dispatch () at event-loop.c:421
wl_display_run () at wayland-server.c:869
main () at compositor.c:3377
Now go to the source code. There is a pattern in Wayland code.
Touch object
If the client calls wl_display_registry(), server will receive the message and run display_registry()wayland/src/wayland-server.c
display_get_registry(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct wl_display *display = resource->data; struct wl_resource *registry_resource; struct wl_global *global; registry_resource = wl_resource_create(client, &wl_registry_interface, 1, id); if (registry_resource == NULL) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(registry_resource, ®istry_interface, display, unbind_resource); wl_list_insert(&display->registry_resource_list, ®istry_resource->link); wl_list_for_each(global, &display->global_list, link) wl_resource_post_event(registry_resource, WL_REGISTRY_GLOBAL, global->name, global->interface->name, global->version); }If call wl_registry_bind(registry, name/*seat_name*/, &wl_seat_interface, 1), registry_bind() -> bind_seat() is called in the server. And this function set up seat_get_touch() as get_touch() callback. In line 1481, wl_seat_send_capabilities() wake-up client wl_seat_listener's callback capabilities( seat_handle_capabilities() in this example).
wayland/src/wayland-server.c
static const struct wl_seat_interface seat_interface = { seat_get_pointer, seat_get_keyboard, seat_get_touch, }; static void bind_seat(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct weston_seat *seat = data; struct wl_resource *resource; enum wl_seat_capability caps = 0; resource = wl_resource_create(client, &wl_seat_interface, MIN(version, 3), id); wl_list_insert(&seat->base_resource_list, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &seat_interface, data, unbind_resource); if (seat->pointer) caps |= WL_SEAT_CAPABILITY_POINTER; if (seat->keyboard) caps |= WL_SEAT_CAPABILITY_KEYBOARD; if (seat->touch) caps |= WL_SEAT_CAPABILITY_TOUCH; wl_seat_send_capabilities(resource, caps); if (version >= 2) wl_seat_send_name(resource, seat->seat_name); }And then in seat_handle_capabilities(), we call wl_seat_get_touch(seat), seat_get_touch() is called.
wayland/src/wayland-server.c
static void seat_get_touch(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct weston_seat *seat = wl_resource_get_user_data(resource); struct wl_resource *cr; if (!seat->touch) return; cr = wl_resource_create(client, &wl_touch_interface, wl_resource_get_version(resource), id); if (cr == NULL) { wl_client_post_no_memory(client); return; } if (seat->touch->focus && wl_resource_get_client(seat->touch->focus->resource) == client) { wl_list_insert(&seat->touch->resource_list, wl_resource_get_link(cr)); } else { wl_list_insert(&seat->touch->focus_resource_list, wl_resource_get_link(cr)); } wl_resource_set_implementation(cr, &touch_interface, seat, unbind_resource); }Which actions do wake up wl_touch_listener's callback? In the case of touch down event, wl_touch_send_down() by default_grab_touch_down() wake up wl_touch_listener's down() callback.
wayland/src/wayland-server-protocol.h
static inline void wl_touch_send_down(struct wl_resource *resource_, uint32_t serial, uint32_t time, struct wl_resource *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { wl_resource_post_event(resource_, WL_TOUCH_DOWN, serial, time, surface, id, x, y); }
The server's related callstack is as follows, dafault_grab_touch_down() is registered as input device handler's callback
default_grab_touch_down () at input.c:172
notify_touch () at input.c:1069
evdev_flush_motion () at evdev.c:265
evdev_process_events () at evdev.c:365
evdev_device_data () at evdev.c:396
wl_event_source_fd_dispatch () at event-loop.c:86
wl_event_loop_dispatch () at event-loop.c:421
weston_compositor_read_input () at compositor.c:1349
wl_event_source_fd_dispatch () at event-loop.c:86
wl_event_loop_dispatch () at event-loop.c:421
wl_display_run () at wayland-server.c:869
main () at compositor.c:3377
Surface and graphics buffer object
This sequence is same as the case of touch object
Here I check shm_create_pool(). This function extract graphics buffer from given fd. I don't know why Wayland distinguish wl_shm_pool & wl_shm_buffer yet.
wayland/src/wayland-server.c
Compositor-related functions such as wl_surface_attach(...) are a bit different. The counter part of the client functions are located in weston/src/compositor.c, not in wayland/src/wayland-server.c. I think that compositor related things such like surface handling are not fixed as wayland protocol, depend on compositor implementation.
Additionally I will check wl_surface_frame(). This is used in weston/clients/simple-egl.c and simple-shm.c. The return-type of wl_surface_frame() is struct wl_callback, not wl_frame or wl_surface_frame something. wl_callback is callback object. Its listener has only one callback function, done(). It seems a generic type of objects receiving event from the server for expanding features.
wl_surface_frame() -> surface_frame()
wl_callback_listener's done() callback <- wl_callback_send_done(cb->resource, msc)
The server's related callstack is as fallows. After page flip is done, weston broadcast frame done event to all clients.
wl_registry_bind(registry, name, &wl_shm_interface, 1); -> bind_shm();
shm_format()(wl_shm_listener's format() callback) <- wl_shm_send_format(resource, WL_SHM_FORMAT_ARGB8888);
wl_shm_create_pool(...); -> shm_create_pool(...);
wl_shm_pool_create_buffer(...); -> shm_pool_create_buffer(...);
Here I check shm_create_pool(). This function extract graphics buffer from given fd. I don't know why Wayland distinguish wl_shm_pool & wl_shm_buffer yet.
wayland/src/wayland-server.c
static void shm_create_pool(struct wl_client *client, struct wl_resource *resource, uint32_t id, int fd, int32_t size) { struct wl_shm_pool *pool; pool = malloc(sizeof *pool); if (pool == NULL) { wl_client_post_no_memory(client); goto err_close; } if (size <= 0) { wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE, "invalid size (%d)", size); goto err_free; } pool->refcount = 1; pool->size = size; pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (pool->data == MAP_FAILED) { wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD, "failed mmap fd %d", fd); goto err_free; } close(fd); pool->resource = wl_resource_create(client, &wl_shm_pool_interface, 1, id); if (!pool->resource) { wl_client_post_no_memory(client); munmap(pool->data, pool->size); free(pool); return; } wl_resource_set_implementation(pool->resource, &shm_pool_interface, pool, destroy_pool); return; err_close: close(fd); err_free: free(pool); }
Compositor-related functions such as wl_surface_attach(...) are a bit different. The counter part of the client functions are located in weston/src/compositor.c, not in wayland/src/wayland-server.c. I think that compositor related things such like surface handling are not fixed as wayland protocol, depend on compositor implementation.
wl_registry_bind(registry, name, &wl_compositor_interface, 1) -> compositor_bind(...)
wl_compositor_create_surface(...) -> compositor_create_surface(...)
wl_surface_attach(...) -> surface_attach(...)
wl_surface_damage(...) -> surface_damage(...)
Additionally I will check wl_surface_frame(). This is used in weston/clients/simple-egl.c and simple-shm.c. The return-type of wl_surface_frame() is struct wl_callback, not wl_frame or wl_surface_frame something. wl_callback is callback object. Its listener has only one callback function, done(). It seems a generic type of objects receiving event from the server for expanding features.
wl_surface_frame() -> surface_frame()
wl_callback_listener's done() callback <- wl_callback_send_done(cb->resource, msc)
window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window);
The server's related callstack is as fallows. After page flip is done, weston broadcast frame done event to all clients.
wl_resource_post_event () at wayland-server.c:148
wl_callback_send_done () at wayland-server-protocol.h:202
weston_output_repaint () at compositor.c:1334
weston_output_finish_frame () at compositor.c:1364
page_flip_handler () at compositor-drm.c:719
drmHandleEvent ()
Another example using wl_callback is wl_display_sync()
wl_resource_post_event () at wayland-server.c:148
wl_callback_send_done () at wayland-server-protocol.h:202
display_sync () at wayland-server.c:650
2 comments:
Nice article
hi, I am also learning about wayland.
Could you please tell me how can you print out functions when wayland is running like:
recvmsg ()
wl_os_recvmsg_cloexec () at wayland-os.c:123
wl_connection_read () at connection.c:327
read_events () at wayland-client.c:964
wl_display_dispatch_queue () at wayland-client.c:1220
wl_display_dispatch () at wayland-client.c:1290
Thank you
Post a Comment