aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLloyd Pique <lpique@google.com>2023-08-17 00:07:24 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-08-17 00:07:24 +0000
commit28e86b61c69b3b42385b0c535c2044e84657cffb (patch)
tree4ba1a54fb9b07b1033772134fa638425ce4c6bfa
parent2ceeddf7b84efda345ddcce713a808e0d5d94748 (diff)
parentb67f5b97f6c383dfc08431a505f3fa745eefc060 (diff)
downloadwayland-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>
-rw-r--r--patches/0001-client-Support-client-protocol-loggers.diff326
-rw-r--r--patches/0001-connection-Simplify-wl_closure_print.diff247
-rw-r--r--patches/0002-client-Add-message-observer-interface.diff834
-rw-r--r--patches/0002-tests-Add-demo-real-world-protocol-logging.diff169
-rw-r--r--patches/0003-client-server-Safe-casts-from-wl_object.diff136
-rw-r--r--patches/0003-protocol-logger-test-Demonstrate-logging.diff174
-rw-r--r--patches/0004-client-Safe-cast-a-wl_object-to-wl_proxy.diff87
-rw-r--r--patches/0005-server-Safe-cast-a-wl_object-to-wl_resource.diff67
-rw-r--r--patches/0006-client-Log-unknown-messages-through-the-observer-API.diff387
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, &registry_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);
++}