diff options
author | Lloyd Pique <lpique@google.com> | 2023-08-17 00:07:24 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-08-17 00:07:24 +0000 |
commit | 28e86b61c69b3b42385b0c535c2044e84657cffb (patch) | |
tree | 4ba1a54fb9b07b1033772134fa638425ce4c6bfa | |
parent | 2ceeddf7b84efda345ddcce713a808e0d5d94748 (diff) | |
parent | b67f5b97f6c383dfc08431a505f3fa745eefc060 (diff) | |
download | wayland-28e86b61c69b3b42385b0c535c2044e84657cffb.tar.gz |
Prepare to update to 1.22.0 am: b67f5b97f6
Original change: https://android-review.googlesource.com/c/platform/external/wayland/+/2699956
Change-Id: If5f9d74807250921fb932c382b442f3f0ebbf474
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
9 files changed, 1796 insertions, 631 deletions
diff --git a/patches/0001-client-Support-client-protocol-loggers.diff b/patches/0001-client-Support-client-protocol-loggers.diff deleted file mode 100644 index 662f38b..0000000 --- a/patches/0001-client-Support-client-protocol-loggers.diff +++ /dev/null @@ -1,326 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lloyd Pique <lpique@google.com> -Date: Fri, 29 Jan 2021 14:26:03 -0800 -Subject: [PATCH 1/3] client: Support client protocol loggers - -This is very much based on commit 450f06e2 which added server protocol -loggers. - -Adds a new pair of public API functions: - -* wl_display_add_protocol_logger_client allows the client to register a - function to be called to log each message that is sent or received. - -* wl_protocol_logger_client_destroy allows the client to unregister the - function. - -As with the server protocol loggers, this is akin to setting -WAYLAND_DEBUG=1, but allows the client code to choose how the messages -are logged, and it can also enable and disable logging at run time. - -The logging logic for the client was also changed to log all events, not -just the ones that have listeners or dispatchers. - -Signed-off-by: Lloyd Pique <lpique@google.com> - -diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h -index 0cd96e0..547ae04 100644 ---- a/src/wayland-client-core.h -+++ b/src/wayland-client-core.h -@@ -267,6 +267,32 @@ wl_display_read_events(struct wl_display *display); - void - wl_log_set_handler_client(wl_log_func_t handler); - -+enum wl_protocol_logger_client_type { -+ WL_PROTOCOL_LOGGER_CLIENT_REQUEST, -+ WL_PROTOCOL_LOGGER_CLIENT_EVENT, -+}; -+ -+struct wl_protocol_logger_client_message { -+ struct wl_proxy *proxy; -+ int message_opcode; -+ const struct wl_message *message; -+ int arguments_count; -+ const union wl_argument *arguments; -+}; -+ -+typedef void (*wl_protocol_logger_client_func_t)( -+ void *user_data, -+ enum wl_protocol_logger_client_type direction, -+ const struct wl_protocol_logger_client_message *message); -+ -+struct wl_protocol_logger_client * -+wl_display_add_protocol_logger_client(struct wl_display *display, -+ wl_protocol_logger_client_func_t, -+ void *user_data); -+ -+void -+wl_protocol_logger_client_destroy(struct wl_protocol_logger_client *logger); -+ - #ifdef __cplusplus - } - #endif -diff --git a/src/wayland-client.c b/src/wayland-client.c -index 21d4606..7f5a651 100644 ---- a/src/wayland-client.c -+++ b/src/wayland-client.c -@@ -107,12 +107,47 @@ struct wl_display { - int reader_count; - uint32_t read_serial; - pthread_cond_t reader_cond; -+ -+ struct wl_list protocol_loggers; - }; - - /** \endcond */ - -+struct wl_protocol_logger_client { -+ struct wl_list link; -+ wl_protocol_logger_client_func_t func; -+ void *user_data; -+}; -+ - static int debug_client = 0; - -+static void -+log_closure(struct wl_closure *closure, struct wl_proxy* proxy, int send) -+{ -+ struct wl_display *display = proxy->display; -+ struct wl_protocol_logger_client *protocol_logger; -+ struct wl_protocol_logger_client_message message; -+ -+ if (debug_client) -+ wl_closure_print(closure, &proxy->object, send); -+ -+ if (!wl_list_empty(&display->protocol_loggers)) { -+ message.proxy = proxy; -+ message.message_opcode = closure->opcode; -+ message.message = closure->message; -+ message.arguments_count = closure->count; -+ message.arguments = closure->args; -+ wl_list_for_each(protocol_logger, &display->protocol_loggers, -+ link) { -+ protocol_logger->func( -+ protocol_logger->user_data, -+ send ? WL_PROTOCOL_LOGGER_CLIENT_REQUEST : -+ WL_PROTOCOL_LOGGER_CLIENT_EVENT, -+ &message); -+ } -+ } -+} -+ - /** - * This helper function wakes up all threads that are - * waiting for display->reader_cond (i. e. when reading is done, -@@ -751,8 +786,7 @@ wl_proxy_marshal_array_constructor_versioned(struct wl_proxy *proxy, - goto err_unlock; - } - -- if (debug_client) -- wl_closure_print(closure, &proxy->object, true); -+ log_closure(closure, proxy, true); - - if (wl_closure_send(closure, proxy->display->connection)) { - wl_log("Error sending request: %s\n", strerror(errno)); -@@ -1056,6 +1090,7 @@ wl_display_connect_to_fd(int fd) - pthread_mutex_init(&display->mutex, NULL); - pthread_cond_init(&display->reader_cond, NULL); - display->reader_count = 0; -+ wl_list_init(&display->protocol_loggers); - - wl_map_insert_new(&display->objects, 0, NULL); - -@@ -1177,6 +1212,7 @@ wl_display_disconnect(struct wl_display *display) - wl_map_release(&display->objects); - wl_event_queue_release(&display->default_queue); - wl_event_queue_release(&display->display_queue); -+ wl_list_remove(&display->protocol_loggers); - pthread_mutex_destroy(&display->mutex); - pthread_cond_destroy(&display->reader_cond); - close(display->fd); -@@ -1439,16 +1475,12 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) - - pthread_mutex_unlock(&display->mutex); - -- if (proxy->dispatcher) { -- if (debug_client) -- wl_closure_print(closure, &proxy->object, false); -+ log_closure(closure, proxy, false); - -+ if (proxy->dispatcher) { - wl_closure_dispatch(closure, proxy->dispatcher, - &proxy->object, opcode); - } else if (proxy->object.implementation) { -- if (debug_client) -- wl_closure_print(closure, &proxy->object, false); -- - wl_closure_invoke(closure, WL_CLOSURE_INVOKE_CLIENT, - &proxy->object, opcode, proxy->user_data); - } -@@ -2280,3 +2312,60 @@ wl_log_set_handler_client(wl_log_func_t handler) - { - wl_log_handler = handler; - } -+ -+/** Adds a new protocol client logger. -+ * -+ * When a new protocol message arrives or is sent from the client -+ * all the protocol logger functions will be called, carrying the -+ * \a user_data pointer, the type of the message (request or -+ * event) and the actual message. -+ * The lifetime of the messages passed to the logger function ends -+ * when they return so the messages cannot be stored and accessed -+ * later. -+ * -+ * \a errno is set on error. -+ * -+ * \param display The display object -+ * \param func The function to call to log a new protocol message -+ * \param user_data The user data pointer to pass to \a func -+ * -+ * \return The protocol logger object on success, NULL on failure. -+ * -+ * \sa wl_protocol_logger_client_destroy -+ * -+ * \memberof wl_display -+ */ -+WL_EXPORT struct wl_protocol_logger_client * -+wl_display_add_protocol_logger_client(struct wl_display *display, -+ wl_protocol_logger_client_func_t func, -+ void *user_data) -+{ -+ struct wl_protocol_logger_client *logger; -+ -+ logger = malloc(sizeof *logger); -+ if (!logger) -+ return NULL; -+ -+ logger->func = func; -+ logger->user_data = user_data; -+ wl_list_insert(&display->protocol_loggers, &logger->link); -+ -+ return logger; -+} -+ -+/** Destroys a protocol client logger. -+ * -+ * This function destroys a protocol client logger and removes it from the -+ * display it was added to with \a wl_display_add_protocol_logger_client. -+ * The \a logger object becomes invalid after calling this function. -+ * -+ * \sa wl_display_add_protocol_logger_client -+ * -+ * \memberof wl_protocol_logger_client -+ */ -+WL_EXPORT void -+wl_protocol_logger_client_destroy(struct wl_protocol_logger_client *logger) -+{ -+ wl_list_remove(&logger->link); -+ free(logger); -+} -diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c -index 80c74aa..e409368 100644 ---- a/tests/protocol-logger-test.c -+++ b/tests/protocol-logger-test.c -@@ -52,6 +52,12 @@ struct compositor { - struct wl_client *client; - }; - -+struct client { -+ struct wl_display *display; -+ struct wl_callback *cb; -+ int message; -+}; -+ - struct message { - enum wl_protocol_logger_type type; - const char *class; -@@ -82,6 +88,36 @@ struct message { - }, - }; - -+struct client_message { -+ enum wl_protocol_logger_client_type type; -+ const char *class; -+ int opcode; -+ const char *message_name; -+ int args_count; -+} client_messages[] = { -+ { -+ .type = WL_PROTOCOL_LOGGER_CLIENT_REQUEST, -+ .class = "wl_display", -+ .opcode = 0, -+ .message_name = "sync", -+ .args_count = 1, -+ }, -+ { -+ .type = WL_PROTOCOL_LOGGER_CLIENT_EVENT, -+ .class = "wl_display", -+ .opcode = 1, -+ .message_name = "delete_id", -+ .args_count = 1, -+ }, -+ { -+ .type = WL_PROTOCOL_LOGGER_CLIENT_EVENT, -+ .class = "wl_callback", -+ .opcode = 0, -+ .message_name = "done", -+ .args_count = 1, -+ }, -+}; -+ - static void - logger_func(void *user_data, enum wl_protocol_logger_type type, - const struct wl_protocol_logger_message *message) -@@ -98,6 +134,20 @@ logger_func(void *user_data, enum wl_protocol_logger_type type, - c->client = wl_resource_get_client(message->resource); - } - -+static void -+client_logger_func(void *user_data, enum wl_protocol_logger_client_type type, -+ const struct wl_protocol_logger_client_message *message) -+{ -+ struct client *c = user_data; -+ struct client_message *msg = &client_messages[c->message++]; -+ -+ assert(msg->type == type); -+ assert(strcmp(msg->class, wl_proxy_get_class(message->proxy)) == 0); -+ assert(msg->opcode == message->message_opcode); -+ assert(strcmp(msg->message_name, message->message->name) == 0); -+ assert(msg->args_count == message->arguments_count); -+} -+ - static void - callback_done(void *data, struct wl_callback *cb, uint32_t time) - { -@@ -114,11 +164,9 @@ TEST(logger) - - const char *socket; - struct compositor compositor = { 0 }; -- struct { -- struct wl_display *display; -- struct wl_callback *cb; -- } client; -+ struct client client = { 0 }; - struct wl_protocol_logger *logger; -+ struct wl_protocol_logger_client *logger_client; - - require_xdg_runtime_dir(); - -@@ -130,6 +178,8 @@ TEST(logger) - logger_func, &compositor); - - client.display = wl_display_connect(socket); -+ logger_client = wl_display_add_protocol_logger_client( -+ client.display, client_logger_func, &client); - client.cb = wl_display_sync(client.display); - wl_callback_add_listener(client.cb, &callback_listener, NULL); - wl_display_flush(client.display); -@@ -142,6 +192,7 @@ TEST(logger) - wl_display_dispatch(client.display); - wl_display_disconnect(client.display); - -+ wl_protocol_logger_client_destroy(logger_client); - wl_client_destroy(compositor.client); - wl_protocol_logger_destroy(logger); - wl_display_destroy(compositor.display); diff --git a/patches/0001-connection-Simplify-wl_closure_print.diff b/patches/0001-connection-Simplify-wl_closure_print.diff new file mode 100644 index 0000000..7223b1c --- /dev/null +++ b/patches/0001-connection-Simplify-wl_closure_print.diff @@ -0,0 +1,247 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Thu, 10 Mar 2022 14:56:02 -0800 +Subject: [PATCH 1/6] connection: Simplify wl_closure_print + +Client message observers 1/6 + +Cleans up wl_closure_print(), and adds a client-private closure_log() +intermediate function. + +This patch simplifies wl_closure_print() slightly by moving some client-only +details to a new closure_log() intermediate function. This new function will +also handle delivering messages to the new listener callback in a subsequent +patch. + +closure_log() internally handles the check for logging being enabled, +simplifying its callers, and returns early if logging is not enabled. This check +becomes a bit more complex when there can be listeners. + +closure_log() also handles the work same transformation performed by +id_from_object(), by making a copy of the args, and applying the transform to +the copy before passing the arguments to wl_closure_print(). Doing it this way +means the same arguments can also be passed to the eventual listeners. + +The boolean "discarded" argument for wl_closure_print() has been replaced by a +"discarded_reason" string argument, allowing an arbitrary reason string to be +passed in. For now only "discarded[]" is printed as an empty reason string is +passed if the message was discarded, but that will also change. + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/COPYING b/COPYING +index eb25a4e..843b844 100644 +--- a/COPYING ++++ b/COPYING +@@ -2,6 +2,7 @@ Copyright © 2008-2012 Kristian Høgsberg + Copyright © 2010-2012 Intel Corporation + Copyright © 2011 Benjamin Franzke + Copyright © 2012 Collabora, Ltd. ++Copyright 2022 Google LLC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), +diff --git a/src/connection.c b/src/connection.c +index ceaeac1..110b614 100644 +--- a/src/connection.c ++++ b/src/connection.c +@@ -1264,7 +1264,7 @@ wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection) + + void + wl_closure_print(struct wl_closure *closure, struct wl_object *target, +- int send, int discarded, uint32_t (*n_parse)(union wl_argument *arg)) ++ bool send, const char *discarded_reason) + { + int i; + struct argument_details arg; +@@ -1283,9 +1283,11 @@ wl_closure_print(struct wl_closure *closure, struct wl_object *target, + clock_gettime(CLOCK_REALTIME, &tp); + time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000); + +- fprintf(f, "[%7u.%03u] %s%s%s@%u.%s(", ++ fprintf(f, "[%7u.%03u] %s%s%s%s%s@%u.%s(", + time / 1000, time % 1000, +- discarded ? "discarded " : "", ++ (discarded_reason != NULL) ? "discarded[" : "", ++ (discarded_reason != NULL) ? discarded_reason : "", ++ (discarded_reason != NULL) ? "] " : "", + send ? " -> " : "", + target->interface->name, target->id, + closure->message->name); +@@ -1330,10 +1332,7 @@ wl_closure_print(struct wl_closure *closure, struct wl_object *target, + fprintf(f, "nil"); + break; + case 'n': +- if (n_parse) +- nval = n_parse(&closure->args[i]); +- else +- nval = closure->args[i].n; ++ nval = closure->args[i].n; + + fprintf(f, "new id %s@", + (closure->message->types[i]) ? +diff --git a/src/wayland-client.c b/src/wayland-client.c +index 105f9be..ae47307 100644 +--- a/src/wayland-client.c ++++ b/src/wayland-client.c +@@ -115,6 +115,73 @@ struct wl_display { + + static int debug_client = 0; + ++/** ++ * This helper function adjusts the closure arguments before they are logged. ++ * On the client, after the call to create_proxies(), NEW_ID arguments will ++ * point to a wl_proxy accessible via arg.o instead of being an int32 ++ * accessible by arg.n, which is what wl_closure_print() attempts to print. ++ * This helper transforms the argument back into an id, so wl_closure_print() ++ * doesn't need to handle that as a special case. ++ * ++ * \param closure closure to adjust ++ * \param send if this is closure is for a request ++ * ++ */ ++static void ++adjust_closure_args_for_logging(struct wl_closure *closure, bool send) ++{ ++ int i; ++ struct argument_details arg; ++ const struct wl_proxy *proxy; ++ const char *signature = closure->message->signature; ++ ++ // No adjustment needed for a send. ++ if (send) ++ return; ++ ++ for (i = 0; i < closure->count; i++) { ++ signature = get_next_argument(signature, &arg); ++ ++ switch (arg.type) { ++ case 'n': ++ proxy = (struct wl_proxy *)closure->args[i].o; ++ closure->args[i].n = proxy ? proxy->object.id : 0; ++ break; ++ } ++ } ++} ++ ++/** ++ * This function helps log closures from the client, assuming logging is ++ * enabled. ++ * ++ * \param closure closure for the message ++ * \param proxy proxy for the message ++ * \param send true if this is closure is for a request ++ * \param discarded true if this is message is being discarded ++ * ++ */ ++static void ++closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, ++ bool discarded) ++{ ++ struct wl_closure adjusted_closure = { 0 }; ++ ++ if (!debug_client) ++ return; ++ ++ // Note: The real closure has extra data (referenced by its args ++ // immediately following the structure in memory, but we don't ++ // need to duplicate that. ++ memcpy(&adjusted_closure, closure, sizeof(struct wl_closure)); ++ ++ // Adjust the closure arguments. ++ adjust_closure_args_for_logging(&adjusted_closure, send); ++ ++ wl_closure_print(&adjusted_closure, &proxy->object, send, ++ discarded ? "" : NULL); ++} ++ + /** + * This helper function wakes up all threads that are + * waiting for display->reader_cond (i. e. when reading is done, +@@ -885,8 +952,7 @@ wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode, + goto err_unlock; + } + +- if (debug_client) +- wl_closure_print(closure, &proxy->object, true, false, NULL); ++ closure_log(closure, proxy, true, false); + + if (wl_closure_send(closure, proxy->display->connection)) { + wl_log("Error sending request: %s\n", strerror(errno)); +@@ -1579,19 +1645,6 @@ queue_event(struct wl_display *display, int len) + return size; + } + +-static uint32_t +-id_from_object(union wl_argument *arg) +-{ +- struct wl_proxy *proxy; +- +- if (arg->o) { +- proxy = (struct wl_proxy *)arg->o; +- return proxy->object.id; +- } +- +- return 0; +-} +- + static void + dispatch_event(struct wl_display *display, struct wl_event_queue *queue) + { +@@ -1610,8 +1663,7 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) + proxy = closure->proxy; + proxy_destroyed = !!(proxy->flags & WL_PROXY_FLAG_DESTROYED); + if (proxy_destroyed) { +- if (debug_client) +- wl_closure_print(closure, &proxy->object, false, true, id_from_object); ++ closure_log(closure, proxy, false, true); + destroy_queued_closure(closure); + return; + } +@@ -1619,15 +1671,11 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) + pthread_mutex_unlock(&display->mutex); + + if (proxy->dispatcher) { +- if (debug_client) +- wl_closure_print(closure, &proxy->object, false, false, id_from_object); +- ++ closure_log(closure, proxy, false, false); + wl_closure_dispatch(closure, proxy->dispatcher, + &proxy->object, opcode); + } else if (proxy->object.implementation) { +- if (debug_client) +- wl_closure_print(closure, &proxy->object, false, false, id_from_object); +- ++ closure_log(closure, proxy, false, false); + wl_closure_invoke(closure, WL_CLOSURE_INVOKE_CLIENT, + &proxy->object, opcode, proxy->user_data); + } +diff --git a/src/wayland-private.h b/src/wayland-private.h +index 9274f1b..66fc78f 100644 +--- a/src/wayland-private.h ++++ b/src/wayland-private.h +@@ -211,9 +211,8 @@ int + wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection); + + void +-wl_closure_print(struct wl_closure *closure, +- struct wl_object *target, int send, int discarded, +- uint32_t (*n_parse)(union wl_argument *arg)); ++wl_closure_print(struct wl_closure *closure, struct wl_object *target, ++ bool send, const char *discarded_reason); + + void + wl_closure_destroy(struct wl_closure *closure); +diff --git a/src/wayland-server.c b/src/wayland-server.c +index d51acc6..be98f7d 100644 +--- a/src/wayland-server.c ++++ b/src/wayland-server.c +@@ -157,7 +157,7 @@ log_closure(struct wl_resource *resource, + struct wl_protocol_logger_message message; + + if (debug_server) +- wl_closure_print(closure, object, send, false, NULL); ++ wl_closure_print(closure, object, send, NULL); + + if (!wl_list_empty(&display->protocol_loggers)) { + message.resource = resource; diff --git a/patches/0002-client-Add-message-observer-interface.diff b/patches/0002-client-Add-message-observer-interface.diff new file mode 100644 index 0000000..ce3bec1 --- /dev/null +++ b/patches/0002-client-Add-message-observer-interface.diff @@ -0,0 +1,834 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Thu, 10 Mar 2022 17:44:32 -0800 +Subject: [PATCH 2/6] client: Add message observer interface + +Client message observers 2/6 + +Introduce a client message observer interface, strongly resembling the server +protocol logger interface added in commit 450f06e2. + +This means a new pair of public API functions: + +* wl_display_create_client_observer(): allows a client to register an observer + function, which is called for messages that are received or sent. + +* wl_client_observer_destroy() which destroys the observer created by the prior + function. + +With these changes, a client can set and clear an observer at run-time, and can +use it to log client messages to a location other than stderr. + +The existing protocol-logger-test has also been revised and extended to demonstrate +using the new API for test use, to validate the sequence of messages sent and +received by the client, on top of the existing checks to do the same for the +server messages. + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h +index ce91a6f..2aa72a4 100644 +--- a/src/wayland-client-core.h ++++ b/src/wayland-client-core.h +@@ -285,6 +285,104 @@ wl_display_read_events(struct wl_display *display); + void + wl_log_set_handler_client(wl_log_func_t handler); + ++/** ++ * The message type. ++ */ ++enum wl_client_message_type { ++ /** The message is a request */ ++ WL_CLIENT_MESSAGE_REQUEST, ++ ++ /** The message is an event */ ++ WL_CLIENT_MESSAGE_EVENT, ++}; ++ ++/** ++ * The message discard reason codes. ++ */ ++enum wl_client_message_discarded_reason { ++ /** The message was handled normally, and not discarded. */ ++ WL_CLIENT_MESSAGE_NOT_DISCARDED = 0, ++ ++ /** The target was not alive at dispatch time */ ++ WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, ++ ++ /** The target had no listener or dispatcher */ ++ WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, ++}; ++ ++/** ++ * The structure used to communicate details about an observed message to the ++ * registered observers. ++ */ ++struct wl_client_observed_message { ++ /** The target for the message */ ++ struct wl_proxy *proxy; ++ ++ /** The message opcode */ ++ int message_opcode; ++ ++ /** The protocol message structure */ ++ const struct wl_message *message; ++ ++ /** The count of arguments to the message */ ++ int arguments_count; ++ ++ /** The argument array for the messagge */ ++ const union wl_argument *arguments; ++ ++ /** The discard reason code */ ++ enum wl_client_message_discarded_reason discarded_reason; ++ ++ /** ++ * The discard reason string, or NULL if the event was not discarded. ++ * ++ * This string is only for convenience for a observer that does ++ * logging. The string values should not be considered stable, and ++ * are not localized. ++ */ ++ const char *discarded_reason_str; ++}; ++ ++/** ++ * The signature for a client message observer function, as registered with ++ * wl_display_add_client_observer(). ++ * ++ * \param user_data \c user_data pointer given when the observer was ++ * registered with \c wl_display_create_client_observer ++ * \param type type of message ++ * \param message details for the message ++ */ ++typedef void (*wl_client_message_observer_func_t)( ++ void *user_data, enum wl_client_message_type type, ++ const struct wl_client_observed_message *message); ++ ++/** \class wl_client_observer ++ * ++ * \brief Represents a client message observer ++ * ++ * A client observer allows the client to observe all request and event ++ * message traffic to and from the client. For events, the observer is ++ * also given a discard reason if the event wasn't handled. ++ * ++ * The typical use for the observer is to allow the client implementation to ++ * do its own debug logging, as the default when setting WAYLAND_DEBUG is to ++ * log to stderr. ++ * ++ * With this runtime call, the client can also enable and disable the observer ++ * at any time. ++ * ++ * The protocol-logger-test.c file has an example of a logger implementation. ++ */ ++struct wl_client_observer; ++ ++struct wl_client_observer * ++wl_display_create_client_observer(struct wl_display *display, ++ wl_client_message_observer_func_t observer, ++ void *user_data); ++ ++void ++wl_client_observer_destroy(struct wl_client_observer *observer); ++ + #ifdef __cplusplus + } + #endif +diff --git a/src/wayland-client.c b/src/wayland-client.c +index ae47307..04b4f60 100644 +--- a/src/wayland-client.c ++++ b/src/wayland-client.c +@@ -109,10 +109,19 @@ struct wl_display { + int reader_count; + uint32_t read_serial; + pthread_cond_t reader_cond; ++ ++ struct wl_list observers; + }; + + /** \endcond */ + ++struct wl_client_observer { ++ struct wl_list link; ++ struct wl_display *display; ++ wl_client_message_observer_func_t func; ++ void *user_data; ++}; ++ + static int debug_client = 0; + + /** +@@ -151,6 +160,28 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send) + } + } + ++/** ++ * Maps the \c discard_reason to a string suitable for logging. ++ * ++ * \param discarded_reason reason for discard ++ * \return A string describing the reason, or NULL. ++ * ++ */ ++static const char * ++get_discarded_reason_str( ++ enum wl_client_message_discarded_reason discarded_reason) ++{ ++ switch (discarded_reason) { ++ case WL_CLIENT_MESSAGE_NOT_DISCARDED: ++ return NULL; ++ case WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH: ++ return "dead proxy on dispatch"; ++ case WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH: ++ return "no listener on dispatch"; ++ } ++ return NULL; ++} ++ + /** + * This function helps log closures from the client, assuming logging is + * enabled. +@@ -158,16 +189,18 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send) + * \param closure closure for the message + * \param proxy proxy for the message + * \param send true if this is closure is for a request +- * \param discarded true if this is message is being discarded +- * ++ * \param discarded_reason reason if the message is being discarded, or ++ * WL_CLIENT_MESSAGE_NOT_DISCARDED + */ + static void + closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, +- bool discarded) ++ enum wl_client_message_discarded_reason discarded_reason) + { ++ struct wl_display *display = proxy->display; ++ const char *discarded_reason_str; + struct wl_closure adjusted_closure = { 0 }; + +- if (!debug_client) ++ if (!debug_client && wl_list_empty(&display->observers)) + return; + + // Note: The real closure has extra data (referenced by its args +@@ -178,8 +211,30 @@ closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, + // Adjust the closure arguments. + adjust_closure_args_for_logging(&adjusted_closure, send); + +- wl_closure_print(&adjusted_closure, &proxy->object, send, +- discarded ? "" : NULL); ++ discarded_reason_str = get_discarded_reason_str(discarded_reason); ++ ++ if (debug_client) ++ wl_closure_print(&adjusted_closure, &proxy->object, send, ++ discarded_reason_str); ++ ++ if (!wl_list_empty(&display->observers)) { ++ enum wl_client_message_type type = ++ send ? WL_CLIENT_MESSAGE_REQUEST ++ : WL_CLIENT_MESSAGE_EVENT; ++ struct wl_client_observer *observer; ++ struct wl_client_observed_message message; ++ ++ message.proxy = proxy; ++ message.message_opcode = adjusted_closure.opcode; ++ message.message = adjusted_closure.message; ++ message.arguments_count = adjusted_closure.count; ++ message.arguments = adjusted_closure.args; ++ message.discarded_reason = discarded_reason; ++ message.discarded_reason_str = discarded_reason_str; ++ wl_list_for_each(observer, &display->observers, link) { ++ observer->func(observer->user_data, type, &message); ++ } ++ } + } + + /** +@@ -952,7 +1007,7 @@ wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode, + goto err_unlock; + } + +- closure_log(closure, proxy, true, false); ++ closure_log(closure, proxy, true, WL_CLIENT_MESSAGE_NOT_DISCARDED); + + if (wl_closure_send(closure, proxy->display->connection)) { + wl_log("Error sending request: %s\n", strerror(errno)); +@@ -1259,6 +1314,7 @@ wl_display_connect_to_fd(int fd) + pthread_mutex_init(&display->mutex, NULL); + pthread_cond_init(&display->reader_cond, NULL); + display->reader_count = 0; ++ wl_list_init(&display->observers); + + if (wl_map_insert_at(&display->objects, 0, 0, NULL) == -1) + goto err_connection; +@@ -1388,6 +1444,7 @@ wl_display_disconnect(struct wl_display *display) + wl_map_release(&display->objects); + wl_event_queue_release(&display->default_queue); + wl_event_queue_release(&display->display_queue); ++ wl_list_remove(&display->observers); + pthread_mutex_destroy(&display->mutex); + pthread_cond_destroy(&display->reader_cond); + close(display->fd); +@@ -1663,25 +1720,29 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) + proxy = closure->proxy; + proxy_destroyed = !!(proxy->flags & WL_PROXY_FLAG_DESTROYED); + if (proxy_destroyed) { +- closure_log(closure, proxy, false, true); +- destroy_queued_closure(closure); +- return; +- } +- +- pthread_mutex_unlock(&display->mutex); ++ closure_log(closure, proxy, false, ++ WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH); ++ } else if (proxy->dispatcher) { ++ closure_log(closure, proxy, false, ++ WL_CLIENT_MESSAGE_NOT_DISCARDED); + +- if (proxy->dispatcher) { +- closure_log(closure, proxy, false, false); ++ pthread_mutex_unlock(&display->mutex); + wl_closure_dispatch(closure, proxy->dispatcher, + &proxy->object, opcode); ++ pthread_mutex_lock(&display->mutex); + } else if (proxy->object.implementation) { +- closure_log(closure, proxy, false, false); ++ closure_log(closure, proxy, false, ++ WL_CLIENT_MESSAGE_NOT_DISCARDED); ++ ++ pthread_mutex_unlock(&display->mutex); + wl_closure_invoke(closure, WL_CLOSURE_INVOKE_CLIENT, + &proxy->object, opcode, proxy->user_data); ++ pthread_mutex_lock(&display->mutex); ++ } else { ++ closure_log(closure, proxy, false, ++ WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH); + } + +- pthread_mutex_lock(&display->mutex); +- + destroy_queued_closure(closure); + } + +@@ -2538,3 +2599,64 @@ wl_log_set_handler_client(wl_log_func_t handler) + { + wl_log_handler = handler; + } ++ ++/** Creates an client message observer. ++ * ++ * Note that the observer can potentially start receiving traffic immediately ++ * after being created, and even before this call returns. ++ * ++ * \param display client display to register with ++ * \param func function to call when client messages are observed ++ * \param user_data \c user_data pointer to pass to the observer ++ * ++ * \return The created observer, or NULL. ++ * ++ * \sa wl_client_observer_destroy ++ * ++ * \memberof wl_display ++ */ ++ ++WL_EXPORT struct wl_client_observer * ++wl_display_create_client_observer(struct wl_display *display, ++ wl_client_message_observer_func_t func, ++ void *user_data) ++{ ++ struct wl_client_observer *observer; ++ ++ observer = malloc(sizeof *observer); ++ if (!observer) ++ return NULL; ++ ++ observer->display = display; ++ observer->func = func; ++ observer->user_data = user_data; ++ ++ pthread_mutex_lock(&display->mutex); ++ ++ wl_list_insert(&display->observers, &observer->link); ++ ++ pthread_mutex_unlock(&display->mutex); ++ ++ return observer; ++} ++ ++/** Destroys a client message obsever. ++ * ++ * This function destroys a client message observer, and removes it from the ++ * display it was added to with \c wl_display_create_client_observer. ++ * ++ * \param observer observer to destroy. ++ * ++ * \memberof wl_client_observer ++ */ ++WL_EXPORT void ++wl_client_observer_destroy(struct wl_client_observer *observer) ++{ ++ pthread_mutex_lock(&observer->display->mutex); ++ ++ wl_list_remove(&observer->link); ++ ++ pthread_mutex_unlock(&observer->display->mutex); ++ ++ free(observer); ++} +diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c +index a0ebd22..3b9dc3e 100644 +--- a/tests/protocol-logger-test.c ++++ b/tests/protocol-logger-test.c +@@ -29,12 +29,15 @@ + #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) +@@ -45,57 +48,146 @@ require_xdg_runtime_dir(void) + 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; +- int message; ++ struct wl_protocol_logger *logger; ++ ++ struct expected_compositor_message *expected_msg; ++ int expected_msg_count; ++ int actual_msg_count; + struct wl_client *client; + }; + +-struct message { +- enum wl_protocol_logger_type type; ++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; +-} 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 client { ++ struct wl_display *display; ++ struct wl_callback *cb; ++ struct wl_client_observer *sequence_observer; ++ ++ 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 +-logger_func(void *user_data, enum wl_protocol_logger_type type, +- const struct wl_protocol_logger_message *message) ++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 message *msg = &messages[c->message++]; ++ 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(msg->type == type); +- assert(strcmp(msg->class, wl_resource_get_class(message->resource)) == 0); +- assert(msg->opcode == message->message_opcode); +- assert(strcmp(msg->message_name, message->message->name) == 0); +- assert(msg->args_count == message->arguments_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]; + +- c->client = wl_resource_get_client(message->resource); ++ 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); + } + + static void +@@ -108,41 +200,236 @@ 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); ++} ++ ++static void ++logger_teardown(struct compositor *compositor, struct client *client) ++{ ++ wl_client_observer_destroy(client->sequence_observer); ++ 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); + +- const char *socket; ++ 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 { +- struct wl_display *display; +- struct wl_callback *cb; +- } client; +- struct wl_protocol_logger *logger; ++ struct client client = { 0 }; + +- require_xdg_runtime_dir(); ++ logger_setup(&compositor, &client); + +- compositor.display = wl_display_create(); +- compositor.loop = wl_display_get_event_loop(compositor.display); +- socket = wl_display_add_socket_auto(compositor.display); ++ compositor.expected_msg = &compositor_messages[0]; ++ compositor.expected_msg_count = ARRAY_LENGTH(compositor_messages); + +- logger = wl_display_add_protocol_logger(compositor.display, +- logger_func, &compositor); ++ client.expected_msg = &client_messages[0]; ++ client.expected_msg_count = ARRAY_LENGTH(client_messages); + +- client.display = wl_display_connect(socket); + client.cb = wl_display_sync(client.display); + wl_callback_add_listener(client.cb, &callback_listener, NULL); + wl_display_flush(client.display); + +- while (compositor.message < 3) { ++ 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); +- wl_display_disconnect(client.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); + +- wl_client_destroy(compositor.client); +- wl_protocol_logger_destroy(logger); +- wl_display_destroy(compositor.display); ++ logger_teardown(&compositor, &client); + } diff --git a/patches/0002-tests-Add-demo-real-world-protocol-logging.diff b/patches/0002-tests-Add-demo-real-world-protocol-logging.diff deleted file mode 100644 index b262ec0..0000000 --- a/patches/0002-tests-Add-demo-real-world-protocol-logging.diff +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lloyd Pique <lpique@google.com> -Date: Fri, 29 Jan 2021 17:24:56 -0800 -Subject: [PATCH 2/3] tests: Add demo real-world protocol logging - -Adds a real-world sample function for protocol message loging, -duplicating the same output produced by the internal wl_closure_print. - -Signed-off-by: Lloyd Pique <lpique@google.com> - -diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c -index e409368..d0bca41 100644 ---- a/tests/protocol-logger-test.c -+++ b/tests/protocol-logger-test.c -@@ -29,10 +29,12 @@ - #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 "wayland-util.h" - #include "test-runner.h" - - /* Ensure the connection doesn't fail due to lack of XDG_RUNTIME_DIR. */ -@@ -148,6 +150,116 @@ client_logger_func(void *user_data, enum wl_protocol_logger_client_type type, - assert(msg->args_count == message->arguments_count); - } - -+// 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_protocol_logger_client_type type, -+ const struct wl_protocol_logger_client_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 int time; -+ -+ clock_gettime(CLOCK_REALTIME, &tp); -+ time = (tp.tv_sec * 1000000L) + (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(stderr, "[%10.3f] %s%s@%u.%s(", -+ time / 1000.0, -+ (type == WL_PROTOCOL_LOGGER_CLIENT_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(stderr, ", "); -+ -+ switch (arg_type) { -+ case 'u': -+ fprintf(stderr, "%u", args[i].u); -+ break; -+ case 'i': -+ fprintf(stderr, "%d", args[i].i); -+ break; -+ case 'f': -+ fprintf(stderr, "%f", wl_fixed_to_double(args[i].f)); -+ break; -+ case 's': -+ if (args[i].s) -+ fprintf(stderr, "\"%s\"", args[i].s); -+ else -+ fprintf(stderr, "nil"); -+ break; -+ case 'o': -+ if (args[i].o) { -+ // Note: server logger should instead cast to -+ // wl_resource, and use wl_resource_get_class -+ // and wl_resource_get_id. -+ arg_proxy = (struct wl_proxy *)(args[i].o); -+ arg_class = wl_proxy_get_class(arg_proxy); -+ -+ fprintf(stderr, "%s@%u", -+ arg_class ? arg_class : "[unknown]", -+ wl_proxy_get_id(arg_proxy)); -+ } else { -+ fprintf(stderr, "nil"); -+ } -+ break; -+ case 'n': -+ fprintf(stderr, "new id %s@", -+ (message->message->types[i]) ? -+ message->message->types[i]->name : -+ "[unknown]"); -+ if (args[i].n != 0) -+ fprintf(stderr, "%u", args[i].n); -+ else -+ fprintf(stderr, "nil"); -+ break; -+ case 'a': -+ fprintf(stderr, "array"); -+ break; -+ case 'h': -+ fprintf(stderr, "fd %d", args[i].h); -+ break; -+ } -+ } -+ -+ fprintf(stderr, ")\n"); -+} -+ - static void - callback_done(void *data, struct wl_callback *cb, uint32_t time) - { -@@ -167,6 +279,7 @@ TEST(logger) - struct client client = { 0 }; - struct wl_protocol_logger *logger; - struct wl_protocol_logger_client *logger_client; -+ struct wl_protocol_logger_client *logger_client_demo; - - require_xdg_runtime_dir(); - -@@ -180,6 +293,8 @@ TEST(logger) - client.display = wl_display_connect(socket); - logger_client = wl_display_add_protocol_logger_client( - client.display, client_logger_func, &client); -+ logger_client_demo = wl_display_add_protocol_logger_client( -+ client.display, client_log_to_stderr_demo, &client); - client.cb = wl_display_sync(client.display); - wl_callback_add_listener(client.cb, &callback_listener, NULL); - wl_display_flush(client.display); -@@ -193,6 +308,7 @@ TEST(logger) - wl_display_disconnect(client.display); - - wl_protocol_logger_client_destroy(logger_client); -+ wl_protocol_logger_client_destroy(logger_client_demo); - wl_client_destroy(compositor.client); - wl_protocol_logger_destroy(logger); - wl_display_destroy(compositor.display); diff --git a/patches/0003-client-server-Safe-casts-from-wl_object.diff b/patches/0003-client-server-Safe-casts-from-wl_object.diff deleted file mode 100644 index 144daae..0000000 --- a/patches/0003-client-server-Safe-casts-from-wl_object.diff +++ /dev/null @@ -1,136 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lloyd Pique <lpique@google.com> -Date: Fri, 29 Jan 2021 15:01:46 -0800 -Subject: [PATCH 3/3] client+server: Safe casts from wl_object * - -This allows client or server code to safely convert an opaque pointer to a -wl_object, which has no accessors, into an opaque pointer to either wl_proxy -(client) or wl_resource (server), which does have some useful accessors. - -This is helpful in implementing callbacks that are given a raw "union -wl_argument *" array, and that want to inspect the ".o" field beyond getting -the raw pointer value. - -Right now the callback would have to assume that the "wl_resource *" or -"wl_proxy *" could be constructed by a simple cast from the "wl_object *" value, -as the wl_object is the first thing in both structures. - -With these two conversion functions, clients and servers could be cleaned up to -no longer make that assumption, and maybe some day the core code could do -make a change that would break that assumption. - -Signed-off-by: Lloyd Pique <lpique@google.com> - -diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h -index 547ae04..94c25e3 100644 ---- a/src/wayland-client-core.h -+++ b/src/wayland-client-core.h -@@ -204,6 +204,9 @@ wl_proxy_get_class(struct wl_proxy *proxy); - void - wl_proxy_set_queue(struct wl_proxy *proxy, struct wl_event_queue *queue); - -+struct wl_proxy * -+wl_proxy_from_object(struct wl_object *object); -+ - struct wl_display * - wl_display_connect(const char *name); - -diff --git a/src/wayland-client.c b/src/wayland-client.c -index 7f5a651..74d4861 100644 ---- a/src/wayland-client.c -+++ b/src/wayland-client.c -@@ -2307,6 +2307,28 @@ wl_proxy_wrapper_destroy(void *proxy_wrapper) - free(wrapper); - } - -+/** Safely converts an object into its corresponding proxy -+ * -+ * \param object The object to convert -+ * \return A corresponding proxy, or NULL on failure -+ * -+ * Safely converts an object into its corresponding proxy. -+ * -+ * This is useful for implementing functions that are given a \c wl_argument -+ * array, and that need to do further introspection on the ".o" field, as it -+ * is otherwise an opaque type. -+ * -+ * \memberof wl_proxy -+ */ -+WL_EXPORT struct wl_proxy * -+wl_proxy_from_object(struct wl_object *object) -+{ -+ struct wl_proxy *proxy; -+ if (object == NULL) -+ return NULL; -+ return wl_container_of(object, proxy, object); -+} -+ - WL_EXPORT void - wl_log_set_handler_client(wl_log_func_t handler) - { -diff --git a/src/wayland-server-core.h b/src/wayland-server-core.h -index 64d7169..e5f4e43 100644 ---- a/src/wayland-server-core.h -+++ b/src/wayland-server-core.h -@@ -587,6 +587,9 @@ struct wl_listener * - wl_resource_get_destroy_listener(struct wl_resource *resource, - wl_notify_func_t notify); - -+struct wl_resource * -+wl_resource_from_object(struct wl_object *object); -+ - #define wl_resource_for_each(resource, list) \ - for (resource = 0, resource = wl_resource_from_link((list)->next); \ - wl_resource_get_link(resource) != (list); \ -diff --git a/src/wayland-server.c b/src/wayland-server.c -index d83bdec..ca0d98d 100644 ---- a/src/wayland-server.c -+++ b/src/wayland-server.c -@@ -858,6 +858,28 @@ wl_resource_get_class(struct wl_resource *resource) - return resource->object.interface->name; - } - -+/** Safely converts an object into its corresponding resource -+ * -+ * \param object The object to convert -+ * \return A corresponding resource, or NULL on failure -+ * -+ * Safely converts an object into its corresponding resource. -+ * -+ * This is useful for implementing functions that are given a \c wl_argument -+ * array, and that need to do further introspection on the ".o" field, as it -+ * is otherwise an opaque type. -+ * -+ * \memberof wl_resource -+ */ -+WL_EXPORT struct wl_resource * -+wl_resource_from_object(struct wl_object *object) -+{ -+ struct wl_resource *resource; -+ if (object == NULL) -+ return NULL; -+ return wl_container_of(object, resource, object); -+} -+ - WL_EXPORT void - wl_client_add_destroy_listener(struct wl_client *client, - struct wl_listener *listener) -diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c -index d0bca41..b66e761 100644 ---- a/tests/protocol-logger-test.c -+++ b/tests/protocol-logger-test.c -@@ -225,10 +225,10 @@ client_log_to_stderr_demo(void *user_data, - break; - case 'o': - if (args[i].o) { -- // Note: server logger should instead cast to -- // wl_resource, and use wl_resource_get_class -- // and wl_resource_get_id. -- arg_proxy = (struct wl_proxy *)(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(stderr, "%s@%u", diff --git a/patches/0003-protocol-logger-test-Demonstrate-logging.diff b/patches/0003-protocol-logger-test-Demonstrate-logging.diff new file mode 100644 index 0000000..54ad6f1 --- /dev/null +++ b/patches/0003-protocol-logger-test-Demonstrate-logging.diff @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Fri, 11 Mar 2022 17:57:37 -0800 +Subject: [PATCH 3/6] protocol-logger-test: Demonstrate logging + +Client message observers 3/6 + +Adds code demonstrating how to replicate the output produced by the internal +wl_closure_print() using the client message observer interface. + +If you run protocol-logger-test with "WAYLAND_DEBUG=client", you can see the +client messages logged to stderr twice, with the same strings. + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c +index 3b9dc3e..082f055 100644 +--- a/tests/protocol-logger-test.c ++++ b/tests/protocol-logger-test.c +@@ -80,6 +80,7 @@ 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; +@@ -190,6 +191,130 @@ client_sequence_observer_func( + "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 cast to ++ // wl_resource, and use wl_resource_get_class ++ // and wl_resource_get_id. ++ arg_proxy = (struct wl_proxy *)(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) + { +@@ -218,12 +343,15 @@ logger_setup(struct compositor *compositor, struct client *client) + 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); diff --git a/patches/0004-client-Safe-cast-a-wl_object-to-wl_proxy.diff b/patches/0004-client-Safe-cast-a-wl_object-to-wl_proxy.diff new file mode 100644 index 0000000..d6a4c66 --- /dev/null +++ b/patches/0004-client-Safe-cast-a-wl_object-to-wl_proxy.diff @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Fri, 11 Mar 2022 18:17:20 -0800 +Subject: [PATCH 4/6] client: Safe cast a "wl_object *" to "wl_proxy *" + +Client message observers 4/6 + +When given an array of wl_arguments for a wl_closure, the ".o" field is an +opaque wl_object pointer, which the client code cannot really do anything with, +without a potentially unsafe cast that assumes details about the internal +implementation. + +By adding a wl_proxy_from_object() function to the client interface, the client +can safely get the wl_proxy pointer. + +This can be used by client message observers in particular to get the proxy id +and class name, for logging those details. + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h +index 2aa72a4..a57cbe0 100644 +--- a/src/wayland-client-core.h ++++ b/src/wayland-client-core.h +@@ -222,6 +222,9 @@ wl_proxy_get_class(struct wl_proxy *proxy); + void + wl_proxy_set_queue(struct wl_proxy *proxy, struct wl_event_queue *queue); + ++struct wl_proxy * ++wl_proxy_from_object(struct wl_object *object); ++ + struct wl_display * + wl_display_connect(const char *name); + +diff --git a/src/wayland-client.c b/src/wayland-client.c +index 04b4f60..ab68bdb 100644 +--- a/src/wayland-client.c ++++ b/src/wayland-client.c +@@ -2594,6 +2594,28 @@ wl_proxy_wrapper_destroy(void *proxy_wrapper) + free(wrapper); + } + ++/** Safely converts an object into its corresponding proxy ++ * ++ * \param object object to get the proxy for ++ * \return A corresponding proxy, or NULL on failure. ++ * ++ * Safely converts an object into its corresponding proxy. ++ * ++ * This is useful for implementing functions that are given a \c wl_argument ++ * array, and that need to do further introspection on the ".o" field, as it ++ * is otherwise an opaque type. ++ * ++ * \memberof wl_proxy ++ */ ++WL_EXPORT struct wl_proxy * ++wl_proxy_from_object(struct wl_object *object) ++{ ++ struct wl_proxy *proxy; ++ if (object == NULL) ++ return NULL; ++ return wl_container_of(object, proxy, object); ++} ++ + WL_EXPORT void + wl_log_set_handler_client(wl_log_func_t handler) + { +diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c +index 082f055..9420b5e 100644 +--- a/tests/protocol-logger-test.c ++++ b/tests/protocol-logger-test.c +@@ -275,10 +275,11 @@ client_log_to_stderr_demo(void *user_data, enum wl_client_message_type type, + break; + case 'o': + if (args[i].o) { +- // Note: server logger should instead cast to +- // wl_resource, and use wl_resource_get_class +- // and wl_resource_get_id. +- arg_proxy = (struct wl_proxy *)(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", diff --git a/patches/0005-server-Safe-cast-a-wl_object-to-wl_resource.diff b/patches/0005-server-Safe-cast-a-wl_object-to-wl_resource.diff new file mode 100644 index 0000000..9e5f886 --- /dev/null +++ b/patches/0005-server-Safe-cast-a-wl_object-to-wl_resource.diff @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Fri, 11 Mar 2022 19:10:07 -0800 +Subject: [PATCH 5/6] server: Safe cast a "wl_object *" to "wl_resource *" + +Client message observers 5/6 + +When given an array of wl_arguments for a wl_closure, the .o field is an +opaque wl_object pointer, which the server implementation cannot really do +anything with, without a potentially unsafe cast that assumes details about the +internal implementation. + +By adding a wl_resource_from_object() function to the client interface, the client +can safely get the wl_resource pointer. + +This can be used by server protocol loggers in particular to get the resource id +and class name, for logging those details + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/src/wayland-server-core.h b/src/wayland-server-core.h +index df95821..63c6a62 100644 +--- a/src/wayland-server-core.h ++++ b/src/wayland-server-core.h +@@ -608,6 +608,9 @@ struct wl_listener * + wl_resource_get_destroy_listener(struct wl_resource *resource, + wl_notify_func_t notify); + ++struct wl_resource * ++wl_resource_from_object(struct wl_object *object); ++ + #define wl_resource_for_each(resource, list) \ + for (resource = 0, resource = wl_resource_from_link((list)->next); \ + wl_resource_get_link(resource) != (list); \ +diff --git a/src/wayland-server.c b/src/wayland-server.c +index be98f7d..468322d 100644 +--- a/src/wayland-server.c ++++ b/src/wayland-server.c +@@ -866,6 +866,28 @@ wl_resource_get_class(struct wl_resource *resource) + return resource->object.interface->name; + } + ++/** Safely converts an object into its corresponding resource ++ * ++ * \param object object to get the resource for ++ * \return A corresponding resource, or NULL on failure ++ * ++ * Safely converts an object into its corresponding resource. ++ * ++ * This is useful for implementing functions that are given a \c wl_argument ++ * array, and that need to do further introspection on the ".o" field, as it ++ * is otherwise an opaque type. ++ * ++ * \memberof wl_resource ++ */ ++WL_EXPORT struct wl_resource * ++wl_resource_from_object(struct wl_object *object) ++{ ++ struct wl_resource *resource; ++ if (object == NULL) ++ return NULL; ++ return wl_container_of(object, resource, object); ++} ++ + /** + * Add a listener to be called at the beginning of wl_client destruction + * diff --git a/patches/0006-client-Log-unknown-messages-through-the-observer-API.diff b/patches/0006-client-Log-unknown-messages-through-the-observer-API.diff new file mode 100644 index 0000000..972dd87 --- /dev/null +++ b/patches/0006-client-Log-unknown-messages-through-the-observer-API.diff @@ -0,0 +1,387 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lloyd Pique <lpique@google.com> +Date: Fri, 11 Mar 2022 20:04:55 -0800 +Subject: [PATCH 6/6] client: Log unknown messages through the observer API + +Client message observers 6/6 + +When the client code receives an event message for an unknown (or zombie) +object, the code was logging a message only to stderr, and only if debug_client +was set. + +Introduce a helper function to create some temporary wl_closure and related +structures so that the unknown message can be sent out using the new client +observer API. This allows the client implementation to potentially log it +somewhere more useful than to just stderr, and it can register an observer at +any time too. + +Note that the message that is logged is now structured slightly differently, +though it contains the same content. + +Signed-off-by: Lloyd Pique <lpique@google.com> + +diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h +index a57cbe0..af7c184 100644 +--- a/src/wayland-client-core.h ++++ b/src/wayland-client-core.h +@@ -311,6 +311,9 @@ enum wl_client_message_discarded_reason { + + /** The target had no listener or dispatcher */ + WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, ++ ++ /** The target was not valid when the event was demarshalled */ ++ WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL, + }; + + /** +diff --git a/src/wayland-client.c b/src/wayland-client.c +index ab68bdb..d54e715 100644 +--- a/src/wayland-client.c ++++ b/src/wayland-client.c +@@ -178,6 +178,8 @@ get_discarded_reason_str( + return "dead proxy on dispatch"; + case WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH: + return "no listener on dispatch"; ++ case WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL: ++ return "unknown id on demarshal"; + } + return NULL; + } +@@ -237,6 +239,53 @@ closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, + } + } + ++/** ++ * This function helps log unknown messages on the client, when logging is ++ * enabled. ++ * ++ * \param display current display ++ * \param zombie true if there was a zombie for the message target ++ * \param id id of the proxy this message was meant for ++ * \param opcode opcode from the message ++ * \param num_fds number of fd arguments for this message ++ * \param num_bytes byte size of this message ++ */ ++static void ++log_unknown_message(struct wl_display *display, bool zombie, uint32_t id, ++ int opcode, int num_fds, int num_bytes) ++{ ++ char event_detail[100]; ++ struct wl_interface unknown_interface = { 0 }; ++ struct wl_proxy unknown_proxy = { 0 }; ++ struct wl_message unknown_message = { 0 }; ++ struct wl_closure unknown_closure = { 0 }; ++ ++ if (!debug_client && wl_list_empty(&display->observers)) ++ return; ++ ++ snprintf(event_detail, sizeof event_detail, ++ "[event %d, %d fds, %d bytes]", opcode, num_fds, num_bytes); ++ ++ unknown_interface.name = zombie ? "[zombie]" : "[unknown]"; ++ ++ unknown_proxy.object.interface = &unknown_interface; ++ unknown_proxy.object.id = id; ++ unknown_proxy.display = display; ++ unknown_proxy.refcount = -1; ++ unknown_proxy.flags = WL_PROXY_FLAG_WRAPPER; ++ ++ unknown_message.name = event_detail; ++ unknown_message.signature = ""; ++ unknown_message.types = NULL; ++ ++ unknown_closure.message = &unknown_message; ++ unknown_closure.opcode = opcode; ++ unknown_closure.proxy = &unknown_proxy; ++ ++ closure_log(&unknown_closure, &unknown_proxy, false, ++ WL_CLIENT_MESSAGE_DISCARD_UNKNOWN_ID_ON_DEMARSHAL); ++} ++ + /** + * This helper function wakes up all threads that are + * waiting for display->reader_cond (i. e. when reading is done, +@@ -1626,8 +1675,6 @@ queue_event(struct wl_display *display, int len) + struct wl_closure *closure; + const struct wl_message *message; + struct wl_event_queue *queue; +- struct timespec tp; +- unsigned int time; + int num_zombie_fds; + + wl_connection_copy(display->connection, p, sizeof p); +@@ -1645,17 +1692,9 @@ queue_event(struct wl_display *display, int len) + num_zombie_fds = (zombie && opcode < zombie->event_count) ? + zombie->fd_count[opcode] : 0; + +- if (debug_client) { +- clock_gettime(CLOCK_REALTIME, &tp); +- time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000); ++ log_unknown_message(display, !!zombie, id, opcode, ++ num_zombie_fds, size); + +- fprintf(stderr, "[%7u.%03u] discarded [%s]@%d.[event %d]" +- "(%d fd, %d byte)\n", +- time / 1000, time % 1000, +- zombie ? "zombie" : "unknown", +- id, opcode, +- num_zombie_fds, size); +- } + if (num_zombie_fds > 0) + wl_connection_close_fds_in(display->connection, + num_zombie_fds); +diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c +index 9420b5e..94e437d 100644 +--- a/tests/protocol-logger-test.c ++++ b/tests/protocol-logger-test.c +@@ -562,3 +562,250 @@ TEST(client_discards_if_no_listener_on_dispatch) + + 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); ++} |