From 6ab970464b10fed151270990bd3831d9d159d84c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 16 Mar 2026 17:16:35 +0100 Subject: [PATCH] protocol: add wl_display_upgrade global This global allows the IPC library to upgrade the version of the wl_display global. To ensure maximum flexibility, there are strict requirements on the connection state at the time of the upgrade. In particular, after the compositor has destroyed the wl_display_upgrade object, the wl_display is the only object in the connection. Since there are many consumers of wl_display and wl_registry, and the version of these objects is fixed after the upgrade, care must be taken not to break any of these consumers. Since no consumer checks the version of these objects and there is no way to get a wl_registry with a lower version, incrementing the minor version cannot change the semantics of existing messages and no new events can be added to wl_registry. As an exception, new events can be added to wl_display since those events are handled internally by the IPC library which is also in charge of upgrading the version. An additional change that could be made in version 2 of wl_display is adding a wl_display.delete_server_id request, implementing symmetric delete_id. A global that wants to make full use of destructor events could then specify that the global must only be exposed on a wl_registry whose version (and therefore the version of the wl_display) is at least 2. Signed-off-by: Julian Orth --- protocol/wayland.xml | 72 +++++++++++++++++++++++++++++++++++- src/wayland-client-core.h | 3 ++ src/wayland-client.c | 78 +++++++++++++++++++++++++++++++++++++++ src/wayland-server-core.h | 3 ++ src/wayland-server.c | 66 ++++++++++++++++++++++++++++++++- 5 files changed, 218 insertions(+), 4 deletions(-) diff --git a/protocol/wayland.xml b/protocol/wayland.xml index 513b8fd8..78587f11 100644 --- a/protocol/wayland.xml +++ b/protocol/wayland.xml @@ -28,10 +28,14 @@ SOFTWARE. - + The core global object. This is a special singleton object. It is used for internal Wayland protocol features. + + This interface guarantees that the semantics of requests of this interface + are unaffected by the minor version of this interface. The semantics of + events may change and new events may be added. @@ -110,7 +114,7 @@ - + The singleton global registry object. The server has a number of global objects that are available to all clients. These objects @@ -132,6 +136,10 @@ request. This creates a client-side handle that lets the object emit events to the client and lets the client invoke requests on the object. + + This interface guarantees that the semantics of the messages of this + interface are unaffected by the minor version of this interface; and that + no new events will be added to any minor version of this interface. @@ -171,6 +179,14 @@ + + + + + + Objects created from this registry are not affected by this request. + + @@ -3362,4 +3378,56 @@ + + + This global allows the IPC library to upgrade the version of the + wl_display object. + + This global must not be bound more than once over the lifetime of the + connection, otherwise the already_bound error is emitted. + + This global should only be bound by the IPC library because the IPC + library must be aware of the version of the wl_display object. + + A compositor that advertises this global must also advertise the wl_fixes + global. + + + + + These errors can be emitted in response to wl_display_upgrade requests. + + + + + + + + + This event informs the client of the maximum wl_display version + supported by the compositor. It is sent immediately after this global + has been bound. The version is guaranteed to be at least 1. + + + + + + + Negotiates an upgrade of the version of the wl_display object. + + At the time of the request, no objects other than the wl_display and + this object must exist, otherwise the has_objects error is emitted. In + particular, the client must use wl_fixes to destroy the registry from + which this object was created. + + The version must be at least 1 and must not be larger than the version + sent in the version event, otherwise the out_of_bounds error is emitted. + + + + + diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h index e0523e49..24911d6c 100644 --- a/src/wayland-client-core.h +++ b/src/wayland-client-core.h @@ -245,6 +245,9 @@ wl_display_connect_to_fd(int fd); void wl_display_disconnect(struct wl_display *display); +void +wl_display_upgrade(struct wl_display *display); + int wl_display_get_fd(struct wl_display *display); diff --git a/src/wayland-client.c b/src/wayland-client.c index 50b3b4b2..19b4bb7f 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -1405,6 +1405,84 @@ wl_display_disconnect(struct wl_display *display) free(display); } +struct upgrade_listener { + struct wl_fixes *wl_fixes; + struct wl_display_upgrade *wl_display_upgrade; + uint32_t version; +}; + +static void upgrade_max_version(void *data, + struct wl_display_upgrade *wl_display_upgrade, + uint32_t version) +{ + struct upgrade_listener *listener = data; + listener->version = version; +} + +static struct wl_display_upgrade_listener upgrade_upgrade_listener = { + .max_version = upgrade_max_version, +}; + +static void upgrade_global(void *data, struct wl_registry *wl_registry, + uint32_t name, const char *interface, + uint32_t version) +{ + struct upgrade_listener *listener = data; + if (!listener->wl_fixes && strcmp(interface, wl_fixes_interface.name) == 0) + listener->wl_fixes = wl_registry_bind(wl_registry, name, &wl_fixes_interface, 1); + else if (!listener->wl_display_upgrade && strcmp(interface, wl_display_upgrade_interface.name) == 0) { + listener->wl_display_upgrade = wl_registry_bind(wl_registry, name, &wl_display_upgrade_interface, 1); + wl_display_upgrade_add_listener(listener->wl_display_upgrade, &upgrade_upgrade_listener, data); + } +} + +static void upgrade_global_remove(void *data, struct wl_registry *wl_registry, + uint32_t name) +{ + // nothing +} + +static struct wl_registry_listener upgrade_registry_listener = { + .global = upgrade_global, + .global_remove = upgrade_global_remove, +}; + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +/** Upgrade the wl_display version. + * + * \param display The display context object + * + * Tries to upgrade the version of the wl_display. After this function returns, + * wl_display_get_version can be used to retrieve the new version of the + * wl_display. + * + * This function should be called immediately after creating the display. It + * must not be called any later than that or multiple times, otherwise a + * protocol error might occur. + * + * \memberof wl_display + */ +WL_EXPORT void +wl_display_upgrade(struct wl_display *display) +{ + struct wl_registry *registry = wl_display_get_registry(display); + struct upgrade_listener listener = { }; + wl_registry_add_listener(registry, &upgrade_registry_listener, &listener); + wl_display_roundtrip(display); + if (listener.wl_display_upgrade) + wl_display_roundtrip(display); + if (listener.wl_fixes) { + wl_fixes_destroy_registry(listener.wl_fixes, registry); + wl_fixes_destroy(listener.wl_fixes); + } + if (listener.wl_display_upgrade) { + uint32_t version = min(listener.version, (uint32_t)wl_display_interface.version); + wl_display_upgrade_upgrade(listener.wl_display_upgrade, version); + display->proxy.version = version; + } +} + /** Get a display context's file descriptor * * \param display The display context object diff --git a/src/wayland-server-core.h b/src/wayland-server-core.h index c8a64772..59f36a7f 100644 --- a/src/wayland-server-core.h +++ b/src/wayland-server-core.h @@ -697,6 +697,9 @@ wl_shm_buffer_create(struct wl_client *client, uint32_t id, int32_t width, int32_t height, int32_t stride, uint32_t format); +int +wl_display_init_upgrade(struct wl_display *display, uint32_t max_version); + void wl_log_set_handler_server(wl_log_func_t handler); diff --git a/src/wayland-server.c b/src/wayland-server.c index 31cc9824..bbd7bc60 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -86,6 +86,7 @@ struct wl_client { struct wl_priv_signal resource_created_signal; void *data; wl_user_data_destroy_func_t data_dtor; + bool has_upgrade; }; struct wl_display { @@ -113,6 +114,7 @@ struct wl_display { struct wl_event_source *term_source; size_t max_buffer_size; + uint32_t max_upgrade_version; }; struct wl_global { @@ -1077,8 +1079,15 @@ registry_bind(struct wl_client *client, global->bind(client, global->data, version, id); } +static void +registry_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + static const struct wl_registry_interface registry_interface = { - registry_bind + .bind = registry_bind, + .release = registry_release, }; static void @@ -1114,7 +1123,7 @@ display_get_registry(struct wl_client *client, struct wl_global *global; registry_resource = - wl_resource_create(client, &wl_registry_interface, 1, id); + wl_resource_create(client, &wl_registry_interface, resource->version, id); if (registry_resource == NULL) { wl_client_post_no_memory(client); return; @@ -2428,6 +2437,59 @@ wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size) wl_connection_set_max_buffer_size(client->connection, max_buffer_size); } +static void +upgrade_display(struct wl_client *client, struct wl_resource *resource, + uint32_t version) +{ + wl_resource_destroy(resource); + // TODO: Check that there are no other objects. + if (version < 1 || version > client->display->max_upgrade_version) { + wl_resource_post_error(resource, + WL_DISPLAY_UPGRADE_ERROR_OUT_OF_BOUNDS, + "version is out of bounds"); + return; + } + client->display_resource->version = (int)version; +} + +static struct wl_display_upgrade_interface upgrade_interface = { + .upgrade = upgrade_display, +}; + +static void +bind_upgrade(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + resource = wl_resource_create(client, &wl_display_upgrade_interface, + (int)version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + if (client->has_upgrade) { + wl_resource_post_error(resource, + WL_DISPLAY_UPGRADE_ERROR_ALREADY_BOUND, + "wl_display_upgrade has already been bound"); + return; + } + client->has_upgrade = true; + wl_display_upgrade_send_max_version(resource, + client->display->max_upgrade_version); + wl_resource_set_implementation(resource, &upgrade_interface, NULL, NULL); +} + +WL_EXPORT int +wl_display_init_upgrade(struct wl_display *display, uint32_t max_version) +{ + if (max_version > (uint32_t)wl_display_upgrade_interface.version) + max_version = wl_display_upgrade_interface.version; + display->max_upgrade_version = max_version; + if (!wl_global_create(display, &wl_display_upgrade_interface, 1, NULL, bind_upgrade)) + return -1; + return 0; +} + /** \cond INTERNAL */ /** Initialize a wl_priv_signal object