Thursday, December 12, 2013

Analyzing the simplest Wayland Clinet test

Wayland is a replacement of X System.
This document is based on current Wayland 1.3 documentation & its source code.



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 context. Similar concept of XDisplay in X System.

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, &registry_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.

wl_compositor
        -> wl_surface       //<- surface

wl_shell
        -> wl_shell_surface

wl_shm
        -> wl_shm_pool
                -> wl_buffer    //<- graphics buffer for surface

wl_seat
        -> 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 Edition

Here 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.







  • If wl_OBJ_FUNC() exists in the client, there is OBJ_FUNC() in the server.
  • if wl_OBJ_listener and its callback FUNC() exists, there is wl_OBJ_send_FUNC() in the server.


  • Clients' wl_OBJ_FUNC()s and a server's wl_OJB_send_FUNC()s just post the event/request on it's queue, not do IPC directly. IPC happens when calling wl_display_dispatch() in the clients and calling wl_connection_flush()/read() in the server.

    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,
               &registry_interface,
               display, unbind_resource);
    
     wl_list_insert(&display->registry_resource_list,
             &registry_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

    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




    3 comments:

    Anonymous said...

    Thank you!

    Anonymous said...

    Nice article

    Broadvell said...

    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