| /* |
| * Copyright © 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial |
| * portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <stdlib.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <sys/un.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "wayland-client.h" |
| #include "wayland-server.h" |
| #include "test-runner.h" |
| |
| #define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0]) |
| |
| /* Ensure the connection doesn't fail due to lack of XDG_RUNTIME_DIR. */ |
| static const char * |
| require_xdg_runtime_dir(void) |
| { |
| char *val = getenv("XDG_RUNTIME_DIR"); |
| assert(val && val[0] == '/' && "set $XDG_RUNTIME_DIR to run this test"); |
| |
| return val; |
| } |
| |
| struct expected_compositor_message { |
| enum wl_protocol_logger_type type; |
| const char *class; |
| int opcode; |
| const char *message_name; |
| int args_count; |
| }; |
| |
| struct compositor { |
| struct wl_display *display; |
| struct wl_event_loop *loop; |
| struct wl_protocol_logger *logger; |
| |
| struct expected_compositor_message *expected_msg; |
| int expected_msg_count; |
| int actual_msg_count; |
| struct wl_client *client; |
| }; |
| |
| struct expected_client_message { |
| enum wl_client_message_type type; |
| enum wl_client_message_discarded_reason discarded_reason; |
| const char *class; |
| int opcode; |
| const char *message_name; |
| int args_count; |
| }; |
| |
| struct client { |
| struct wl_display *display; |
| struct wl_callback *cb; |
| struct wl_client_observer *sequence_observer; |
| struct wl_client_observer *stderr_logger; |
| |
| struct expected_client_message *expected_msg; |
| int expected_msg_count; |
| int actual_msg_count; |
| }; |
| |
| #define ASSERT_LT(arg1, arg2, ...) \ |
| if (arg1 >= arg2) \ |
| fprintf(stderr, __VA_ARGS__); \ |
| assert(arg1 < arg2) |
| |
| #define ASSERT_EQ(arg1, arg2, ...) \ |
| if (arg1 != arg2) \ |
| fprintf(stderr, __VA_ARGS__); \ |
| assert(arg1 == arg2) |
| |
| #define ASSERT_STR_EQ(arg1, arg2, ...) \ |
| if (strcmp(arg1, arg2) != 0) \ |
| fprintf(stderr, __VA_ARGS__); \ |
| assert(strcmp(arg1, arg2) == 0) |
| |
| static void |
| compositor_sequence_observer_func( |
| void *user_data, enum wl_protocol_logger_type actual_type, |
| const struct wl_protocol_logger_message *actual_msg) |
| { |
| struct compositor *c = user_data; |
| struct expected_compositor_message *expected_msg; |
| int actual_msg_count = c->actual_msg_count++; |
| char details_msg[256]; |
| |
| c->client = wl_resource_get_client(actual_msg->resource); |
| |
| if (!c->expected_msg) |
| return; |
| |
| ASSERT_LT(actual_msg_count, c->expected_msg_count, |
| "actual count %d exceeds expected count %d\n", |
| actual_msg_count, c->expected_msg_count); |
| |
| expected_msg = &c->expected_msg[actual_msg_count]; |
| |
| snprintf(details_msg, sizeof details_msg, |
| "compositor msg %d of %d actual [%d, '%s', %d, '%s', %d] vs " |
| "expected [%d, '%s', %d, '%s', %d]\n", |
| c->actual_msg_count, c->expected_msg_count, actual_type, |
| wl_resource_get_class(actual_msg->resource), |
| actual_msg->message_opcode, actual_msg->message->name, |
| actual_msg->arguments_count, expected_msg->type, |
| expected_msg->class, expected_msg->opcode, |
| expected_msg->message_name, expected_msg->args_count); |
| |
| ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s", |
| details_msg); |
| ASSERT_STR_EQ(expected_msg->class, |
| wl_resource_get_class(actual_msg->resource), |
| "class mismatch: %s", details_msg); |
| ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode, |
| "opcode mismatch: %s", details_msg); |
| ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name, |
| "message name mismatch: %s", details_msg); |
| ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count, |
| "arg count mismatch: %s", details_msg); |
| } |
| |
| static void |
| client_sequence_observer_func( |
| void *user_data, enum wl_client_message_type actual_type, |
| const struct wl_client_observed_message *actual_msg) |
| { |
| struct client *c = user_data; |
| struct expected_client_message *expected_msg; |
| int actual_msg_count = c->actual_msg_count++; |
| char details_msg[256]; |
| |
| if (!c->expected_msg) |
| return; |
| |
| ASSERT_LT(actual_msg_count, c->expected_msg_count, |
| "actual count %d exceeds expected count %d\n", |
| actual_msg_count, c->expected_msg_count); |
| expected_msg = &c->expected_msg[actual_msg_count]; |
| |
| snprintf(details_msg, sizeof details_msg, |
| "client msg %d of %d actual [%d, %d, '%s', %d, '%s', %d] vs " |
| "expected [%d, %d, '%s', %d, '%s', %d]\n", |
| c->actual_msg_count, c->expected_msg_count, actual_type, |
| actual_msg->discarded_reason, |
| wl_proxy_get_class(actual_msg->proxy), |
| actual_msg->message_opcode, actual_msg->message->name, |
| actual_msg->arguments_count, expected_msg->type, |
| expected_msg->discarded_reason, expected_msg->class, |
| expected_msg->opcode, expected_msg->message_name, |
| expected_msg->args_count); |
| |
| ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s", |
| details_msg); |
| ASSERT_EQ(expected_msg->discarded_reason, actual_msg->discarded_reason, |
| "discarded reason mismatch: %s", details_msg); |
| ASSERT_STR_EQ(expected_msg->class, |
| wl_proxy_get_class(actual_msg->proxy), |
| "class mismatch: %s", details_msg); |
| ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode, |
| "opcode mismatch: %s", details_msg); |
| ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name, |
| "message name mismatch: %s", details_msg); |
| ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count, |
| "arg count mismatch: %s", details_msg); |
| } |
| |
| // A slightly simplified version of get_next_argument() from src/connection.c |
| static const char * |
| get_next_argument_type(const char *signature, char *type) |
| { |
| for (; *signature; ++signature) { |
| assert(strchr("iufsonah?", *signature) != NULL); |
| switch (*signature) { |
| case 'i': |
| case 'u': |
| case 'f': |
| case 's': |
| case 'o': |
| case 'n': |
| case 'a': |
| case 'h': |
| *type = *signature; |
| return signature + 1; |
| case '?': |
| break; |
| } |
| } |
| *type = 0; |
| return signature; |
| } |
| |
| // This duplicates what the internal wl_closure_print function does, and can be |
| // used as a starting point for a client or server that wants to log messages. |
| static void |
| client_log_to_stderr_demo(void *user_data, enum wl_client_message_type type, |
| const struct wl_client_observed_message *message) |
| { |
| int i; |
| char arg_type; |
| const char *signature = message->message->signature; |
| const union wl_argument *args = message->arguments; |
| struct wl_proxy *arg_proxy; |
| const char *arg_class; |
| struct timespec tp; |
| unsigned long long time; |
| FILE *f; |
| char *buffer; |
| size_t buffer_length; |
| |
| f = open_memstream(&buffer, &buffer_length); |
| if (f == NULL) |
| return; |
| |
| clock_gettime(CLOCK_REALTIME, &tp); |
| time = (tp.tv_sec * 1000000LL) + (tp.tv_nsec / 1000); |
| |
| // Note: server logger will be given message->resource, and should |
| // use wl_resource_get_class and wl_resolurce_get_id. |
| fprintf(f, "[%7llu.%03llu] %s%s%s%s%s@%u.%s(", time / 1000, time % 1000, |
| (message->discarded_reason_str ? "discarded[" : ""), |
| (message->discarded_reason_str ? message->discarded_reason_str |
| : ""), |
| (message->discarded_reason_str ? "] " : ""), |
| (type == WL_CLIENT_MESSAGE_REQUEST) ? " -> " : "", |
| wl_proxy_get_class(message->proxy), |
| wl_proxy_get_id(message->proxy), message->message->name); |
| |
| for (i = 0; i < message->arguments_count; i++) { |
| signature = get_next_argument_type(signature, &arg_type); |
| if (i > 0) |
| fprintf(f, ", "); |
| |
| switch (arg_type) { |
| case 'u': |
| fprintf(f, "%u", args[i].u); |
| break; |
| case 'i': |
| fprintf(f, "%d", args[i].i); |
| break; |
| case 'f': |
| fprintf(f, "%f", wl_fixed_to_double(args[i].f)); |
| break; |
| case 's': |
| if (args[i].s) |
| fprintf(f, "\"%s\"", args[i].s); |
| else |
| fprintf(f, "nil"); |
| break; |
| case 'o': |
| if (args[i].o) { |
| // Note: server logger should instead use |
| // wl_resource_from_object, and then |
| // wl_resource_get_class and |
| // wl_resource_get_id. |
| arg_proxy = wl_proxy_from_object(args[i].o); |
| arg_class = wl_proxy_get_class(arg_proxy); |
| |
| fprintf(f, "%s@%u", |
| arg_class ? arg_class : "[unknown]", |
| wl_proxy_get_id(arg_proxy)); |
| } else { |
| fprintf(f, "nil"); |
| } |
| break; |
| case 'n': |
| fprintf(f, "new id %s@", |
| (message->message->types[i]) |
| ? message->message->types[i]->name |
| : "[unknown]"); |
| if (args[i].n != 0) |
| fprintf(f, "%u", args[i].n); |
| else |
| fprintf(f, "nil"); |
| break; |
| case 'a': |
| fprintf(f, "array"); |
| break; |
| case 'h': |
| fprintf(f, "fd %d", args[i].h); |
| break; |
| } |
| } |
| |
| fprintf(f, ")\n"); |
| |
| if (fclose(f) == 0) { |
| fprintf(stderr, "%s", buffer); |
| free(buffer); |
| } |
| } |
| |
| static void |
| callback_done(void *data, struct wl_callback *cb, uint32_t time) |
| { |
| wl_callback_destroy(cb); |
| } |
| |
| static const struct wl_callback_listener callback_listener = { |
| callback_done, |
| }; |
| |
| static void |
| logger_setup(struct compositor *compositor, struct client *client) |
| { |
| const char *socket; |
| |
| require_xdg_runtime_dir(); |
| |
| compositor->display = wl_display_create(); |
| compositor->loop = wl_display_get_event_loop(compositor->display); |
| socket = wl_display_add_socket_auto(compositor->display); |
| |
| compositor->logger = wl_display_add_protocol_logger( |
| compositor->display, compositor_sequence_observer_func, |
| compositor); |
| |
| client->display = wl_display_connect(socket); |
| client->sequence_observer = wl_display_create_client_observer( |
| client->display, client_sequence_observer_func, client); |
| client->stderr_logger = wl_display_create_client_observer( |
| client->display, client_log_to_stderr_demo, client); |
| } |
| |
| static void |
| logger_teardown(struct compositor *compositor, struct client *client) |
| { |
| wl_client_observer_destroy(client->sequence_observer); |
| wl_client_observer_destroy(client->stderr_logger); |
| wl_display_disconnect(client->display); |
| |
| wl_client_destroy(compositor->client); |
| wl_protocol_logger_destroy(compositor->logger); |
| wl_display_destroy(compositor->display); |
| } |
| |
| TEST(logger) |
| { |
| test_set_timeout(1); |
| |
| struct expected_compositor_message compositor_messages[] = { |
| { |
| .type = WL_PROTOCOL_LOGGER_REQUEST, |
| .class = "wl_display", |
| .opcode = 0, |
| .message_name = "sync", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_PROTOCOL_LOGGER_EVENT, |
| .class = "wl_callback", |
| .opcode = 0, |
| .message_name = "done", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_PROTOCOL_LOGGER_EVENT, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "delete_id", |
| .args_count = 1, |
| }, |
| }; |
| struct expected_client_message client_messages[] = { |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 0, |
| .message_name = "sync", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "delete_id", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_callback", |
| .opcode = 0, |
| .message_name = "done", |
| .args_count = 1, |
| }, |
| }; |
| struct compositor compositor = { 0 }; |
| struct client client = { 0 }; |
| |
| logger_setup(&compositor, &client); |
| |
| compositor.expected_msg = &compositor_messages[0]; |
| compositor.expected_msg_count = ARRAY_LENGTH(compositor_messages); |
| |
| client.expected_msg = &client_messages[0]; |
| client.expected_msg_count = ARRAY_LENGTH(client_messages); |
| |
| client.cb = wl_display_sync(client.display); |
| wl_callback_add_listener(client.cb, &callback_listener, NULL); |
| wl_display_flush(client.display); |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| while (client.actual_msg_count < client.expected_msg_count) { |
| wl_display_dispatch(client.display); |
| } |
| |
| logger_teardown(&compositor, &client); |
| } |
| |
| TEST(client_discards_if_dead_on_dispatch) |
| { |
| test_set_timeout(1); |
| |
| struct expected_client_message client_messages[] = { |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 0, |
| .message_name = "sync", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "delete_id", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = |
| WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, |
| .class = "wl_callback", |
| .opcode = 0, |
| .message_name = "done", |
| .args_count = 1, |
| }, |
| }; |
| struct compositor compositor = { 0 }; |
| struct client client = { 0 }; |
| |
| logger_setup(&compositor, &client); |
| |
| compositor.expected_msg_count = 3; |
| |
| client.expected_msg = &client_messages[0]; |
| client.expected_msg_count = ARRAY_LENGTH(client_messages); |
| |
| client.cb = wl_display_sync(client.display); |
| wl_callback_add_listener(client.cb, &callback_listener, NULL); |
| wl_display_flush(client.display); |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| wl_display_prepare_read(client.display); |
| wl_display_read_events(client.display); |
| |
| // To get a WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, we |
| // destroy the callback after reading client events, but before |
| // dispatching them. |
| wl_callback_destroy(client.cb); |
| |
| while (client.actual_msg_count < client.expected_msg_count) { |
| wl_display_dispatch(client.display); |
| } |
| |
| logger_teardown(&compositor, &client); |
| } |
| |
| TEST(client_discards_if_no_listener_on_dispatch) |
| { |
| test_set_timeout(1); |
| |
| struct expected_client_message client_messages[] = { |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 0, |
| .message_name = "sync", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "delete_id", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = |
| WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, |
| .class = "wl_callback", |
| .opcode = 0, |
| .message_name = "done", |
| .args_count = 1, |
| }, |
| }; |
| struct compositor compositor = { 0 }; |
| struct client client = { 0 }; |
| |
| logger_setup(&compositor, &client); |
| |
| compositor.expected_msg_count = 3; |
| |
| client.expected_msg = &client_messages[0]; |
| client.expected_msg_count = ARRAY_LENGTH(client_messages); |
| |
| client.cb = wl_display_sync(client.display); |
| wl_display_flush(client.display); |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| while (client.actual_msg_count < client.expected_msg_count) { |
| wl_display_dispatch(client.display); |
| } |
| |
| wl_callback_destroy(client.cb); |
| |
| logger_teardown(&compositor, &client); |
| } |
| |
| TEST(client_discards_if_invalid_id_on_demarshal) |
| { |
| test_set_timeout(1); |
| |
| struct expected_client_message client_messages[] = { |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 0, |
| .message_name = "sync", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = |
| WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL, |
| .class = "[unknown]", |
| .opcode = 0, |
| .message_name = "[event 0, 0 fds, 12 bytes]", |
| .args_count = 0, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "delete_id", |
| .args_count = 1, |
| }, |
| }; |
| struct compositor compositor = { 0 }; |
| struct client client = { 0 }; |
| |
| logger_setup(&compositor, &client); |
| |
| compositor.expected_msg_count = 3; |
| |
| client.expected_msg = &client_messages[0]; |
| client.expected_msg_count = ARRAY_LENGTH(client_messages); |
| |
| client.cb = wl_display_sync(client.display); |
| wl_display_flush(client.display); |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| // To get a WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL, we |
| // destroy the callback before reading and dispatching client events. |
| wl_callback_destroy(client.cb); |
| |
| while (client.actual_msg_count < client.expected_msg_count) { |
| wl_display_dispatch(client.display); |
| } |
| |
| logger_teardown(&compositor, &client); |
| } |
| |
| static const struct wl_keyboard_interface keyboard_interface = { 0 }; |
| |
| static void |
| seat_get_pointer(struct wl_client *client, struct wl_resource *resource, |
| uint32_t id) |
| { |
| assert(false && "Not expected to be called by client."); |
| } |
| |
| static void |
| seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, |
| uint32_t id) |
| { |
| struct wl_resource *keyboard_res; |
| |
| keyboard_res = |
| wl_resource_create(client, &wl_keyboard_interface, |
| wl_resource_get_version(resource), id); |
| wl_resource_set_implementation(keyboard_res, &keyboard_interface, NULL, |
| NULL); |
| |
| wl_keyboard_send_key(keyboard_res, 0, 0, 0, 0); |
| } |
| |
| static void |
| seat_get_touch(struct wl_client *client, struct wl_resource *resource, |
| uint32_t id) |
| { |
| assert(false && "Not expected to be called by client."); |
| } |
| |
| static void |
| seat_release(struct wl_client *client, struct wl_resource *resource) |
| { |
| wl_resource_destroy(resource); |
| } |
| |
| static const struct wl_seat_interface seat_interface = { |
| &seat_get_pointer, |
| &seat_get_keyboard, |
| &seat_get_touch, |
| &seat_release, |
| }; |
| |
| static void |
| bind_seat(struct wl_client *client, void *data, uint32_t vers, uint32_t id) |
| { |
| struct wl_resource *seat_res; |
| |
| seat_res = wl_resource_create(client, &wl_seat_interface, vers, id); |
| wl_resource_set_implementation(seat_res, &seat_interface, NULL, NULL); |
| } |
| |
| static void |
| registry_seat_listener_handle_global(void *data, struct wl_registry *registry, |
| uint32_t id, const char *intf, |
| uint32_t ver) |
| { |
| uint32_t *seat_id_ptr = data; |
| |
| if (strcmp(intf, wl_seat_interface.name) == 0) { |
| *seat_id_ptr = id; |
| } |
| } |
| |
| static const struct wl_registry_listener registry_seat_listener = { |
| registry_seat_listener_handle_global, NULL |
| }; |
| |
| TEST(client_discards_if_zombie_on_demarshal) |
| { |
| test_set_timeout(1); |
| |
| struct expected_client_message client_messages[] = { |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_display", |
| .opcode = 1, |
| .message_name = "get_registry", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_registry", |
| .opcode = 0, |
| .message_name = "global", |
| .args_count = 3, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_registry", |
| .opcode = 0, |
| .message_name = "bind", |
| .args_count = 4, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_seat", |
| .opcode = 1, |
| .message_name = "get_keyboard", |
| .args_count = 1, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_keyboard", |
| .opcode = 0, |
| .message_name = "release", |
| .args_count = 0, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_REQUEST, |
| .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, |
| .class = "wl_seat", |
| .opcode = 3, |
| .message_name = "release", |
| .args_count = 0, |
| }, |
| { |
| .type = WL_CLIENT_MESSAGE_EVENT, |
| .discarded_reason = |
| WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL, |
| .class = "[zombie]", |
| .opcode = 3, |
| .message_name = "[event 3, 0 fds, 24 bytes]", |
| .args_count = 0, |
| }, |
| }; |
| |
| struct compositor compositor = { 0 }; |
| struct client client = { 0 }; |
| struct wl_global *g_keyboard; |
| struct wl_registry *registry; |
| struct wl_seat *seat; |
| struct wl_keyboard *keyboard; |
| int32_t seat_id; |
| |
| logger_setup(&compositor, &client); |
| |
| client.expected_msg = &client_messages[0]; |
| client.expected_msg_count = ARRAY_LENGTH(client_messages); |
| |
| g_keyboard = wl_global_create(compositor.display, &wl_seat_interface, |
| 5, &compositor.display, bind_seat); |
| |
| registry = wl_display_get_registry(client.display); |
| wl_registry_add_listener(registry, ®istry_seat_listener, &seat_id); |
| wl_display_flush(client.display); |
| |
| compositor.actual_msg_count = 0; |
| compositor.expected_msg_count = 2; |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| wl_display_dispatch(client.display); |
| |
| seat = wl_registry_bind(registry, seat_id, &wl_seat_interface, 5); |
| keyboard = wl_seat_get_keyboard(seat); |
| wl_display_flush(client.display); |
| |
| compositor.actual_msg_count = 0; |
| compositor.expected_msg_count = 3; |
| |
| while (compositor.actual_msg_count < compositor.expected_msg_count) { |
| wl_event_loop_dispatch(compositor.loop, -1); |
| wl_display_flush_clients(compositor.display); |
| } |
| |
| wl_keyboard_release(keyboard); |
| wl_seat_release(seat); |
| |
| wl_display_dispatch(client.display); |
| |
| wl_registry_destroy(registry); |
| |
| wl_global_destroy(g_keyboard); |
| |
| logger_teardown(&compositor, &client); |
| } |