Prepare to update to 1.22.0

Updated the patches to match the upstream pending merge request:

https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/219

Test: None
Bug: 295053492

Change-Id: I91b450f1e1930b0214ba8a628be1be659e030b88
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..32e8e33
--- /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 2edf621..09e5995 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..db018e5
--- /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 09e5995..4bcca58 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);
++}